diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..f15c6aa7 --- /dev/null +++ b/.github/FUNDING.yml @@ -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"] + diff --git a/.github/ISSUE_TEMPLATE/Custom.md b/.github/ISSUE_TEMPLATE/Custom.md index 90ac4ae1..31d27a81 100644 --- a/.github/ISSUE_TEMPLATE/Custom.md +++ b/.github/ISSUE_TEMPLATE/Custom.md @@ -1,17 +1,28 @@ --- name: 问题描述 -about: 请详细描述你遇到的问题 +about: 提交问题前,请先阅读文档和搜索issue --- -## Aria版本 + +## 版本 +* 框架版本 -## 什么问题 +* 系统版本 +## 错误的url -## 如何复现此问题 +## 错误日志 + + + +## 重现步骤 + +1. +2. +3. +4. -## 控制台日志 diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..4dd85fee --- /dev/null +++ b/.github/stale.yml @@ -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 diff --git a/AppFrame/build.gradle b/AppFrame/build.gradle index 7ff09de7..c6be7353 100644 --- a/AppFrame/build.gradle +++ b/AppFrame/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' android { compileSdkVersion rootProject.ext.compileSdkVersion @@ -13,6 +15,9 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { + debug{ + debuggable true + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -21,6 +26,10 @@ android { dataBinding { enabled = true } + lintOptions { + abortOnError false + } + } dependencies { @@ -29,11 +38,11 @@ dependencies { exclude group: 'com.android.support', module: 'support-annotations' }) 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 'io.reactivex:rxandroid:1.2.0' 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:adapter-rxjava: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-extensions:2.0.0' - annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.0.0' + kapt 'androidx.lifecycle:lifecycle-compiler:2.0.0' } diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/DiskLruCache.java b/AppFrame/src/main/java/com/arialyy/frame/cache/DiskLruCache.java index 48c60fb0..1b1f25a5 100644 --- a/AppFrame/src/main/java/com/arialyy/frame/cache/DiskLruCache.java +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/DiskLruCache.java @@ -245,7 +245,6 @@ public final class DiskLruCache implements Closeable { /** * 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 { File[] files = dir.listFiles(); if (files == null) { diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java b/AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java index 6a6da25d..f5bcdc79 100644 --- a/AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java +++ b/AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java @@ -12,7 +12,7 @@ import javax.crypto.spec.SecretKeySpec; /** * AES加密 * - * @author lyy + * author lyy */ public class AESEncryption { /** diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java b/AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java index 4b405f48..7b05cac8 100644 --- a/AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java +++ b/AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java @@ -92,5 +92,6 @@ public class RegularExpression { /** * 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}"; } diff --git a/Aria/bintray-release.gradle b/Aria/bintray-release.gradle index 0e8b78df..4a861c11 100644 --- a/Aria/bintray-release.gradle +++ b/Aria/bintray-release.gradle @@ -1,10 +1,11 @@ apply plugin: 'com.novoda.bintray-release' publish { - artifactId = 'aria-core' +// artifactId = 'aria-core' +// uploadName = 'AriaApi' + artifactId = 'core' + uploadName = 'Core' userOrg = rootProject.ext.userOrg groupId = rootProject.ext.groupId - // uploadName = rootProject.uploadName - uploadName = 'AriaApi' publishVersion = rootProject.ext.publishVersion desc = rootProject.ext.desc website = rootProject.ext.website diff --git a/Aria/build.gradle b/Aria/build.gradle index ca7841a2..c795d6b6 100644 --- a/Aria/build.gradle +++ b/Aria/build.gradle @@ -7,10 +7,13 @@ android { defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 329 - versionName "3.2.9" + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName } buildTypes { + debug{ + debuggable true + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -20,14 +23,17 @@ android { abortOnError false } } - dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') testImplementation 'junit:junit:4.12' - implementation 'androidx.appcompat:appcompat:1.0.0' - api project(':AriaAnnotations') - api 'com.arialyy.aria:aria-ftp-plug:1.0.4' // 打包时用这个 +// implementation "androidx.appcompat:appcompat:${rootProject.ext.XAppcompatVersion}" + api project(path: ':AriaAnnotations') + 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' \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/Aria.java b/Aria/src/main/java/com/arialyy/aria/core/Aria.java index fb218198..ed27fb18 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/Aria.java +++ b/Aria/src/main/java/com/arialyy/aria/core/Aria.java @@ -24,11 +24,10 @@ import android.app.Service; import android.content.Context; import android.os.Build; 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.upload.UploadReceiver; import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; /** * Created by lyy on 2016/12/1. @@ -132,21 +131,20 @@ import com.arialyy.aria.util.ALog; private static Context convertContext(Object obj) { if (obj instanceof Application) { return (Application) obj; - } else if (obj instanceof Service) { + } + if (obj instanceof Service) { return (Service) obj; - } else if (obj instanceof Activity) { + } + if (obj instanceof Activity) { return (Activity) obj; - } else if (obj instanceof DialogFragment) { - return ((DialogFragment) obj).getContext(); - } else if (obj instanceof android.app.DialogFragment) { - return ((android.app.DialogFragment) obj).getActivity(); - } else if (obj instanceof Fragment) { - return ((Fragment) obj).getContext(); - } else if (obj instanceof android.app.Fragment) { - return ((android.app.Fragment) obj).getActivity(); - } else if (obj instanceof Dialog) { + } + if (CommonUtil.isFragment(obj.getClass())) { + return CommonUtil.getFragmentActivity(obj); + } + if (obj instanceof Dialog) { return ((Dialog) obj).getContext(); - } else if (obj instanceof PopupWindow) { + } + if (obj instanceof PopupWindow) { return ((PopupWindow) obj).getContentView().getContext(); } ALog.e("Aria", "请使用download(this)或upload(this)"); diff --git a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java index 6ace735b..37d740b5 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java @@ -19,34 +19,23 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.Application; -import android.app.Dialog; 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.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.widget.PopupWindow; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; +import android.util.Log; import com.arialyy.aria.core.command.CommandManager; 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.Configuration; import com.arialyy.aria.core.config.DGroupConfig; import com.arialyy.aria.core.config.DownloadConfig; 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.DownloadGroupEntity; import com.arialyy.aria.core.download.DownloadReceiver; import com.arialyy.aria.core.inf.AbsReceiver; import com.arialyy.aria.core.inf.IReceiver; 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.UploadReceiver; 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.AriaCrashHandler; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.DeleteURecord; import com.arialyy.aria.util.RecordUtil; import java.io.File; -import java.io.IOException; -import java.util.ArrayList; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Iterator; -import java.util.List; import java.util.Map; 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 { private static final String TAG = "AriaManager"; 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; private Map mReceivers = new ConcurrentHashMap<>(); - /** - * activity 和其Dialog、Fragment的映射表 - */ - private Map> mSubClass = new ConcurrentHashMap<>(); private static Context APP; - private DownloadConfig mDConfig; - private UploadConfig mUConfig; - private AppConfig mAConfig; - private DGroupConfig mDGConfig; - private Handler mAriaHandler; private DelegateWrapper mDbWrapper; + private AriaConfig mConfig; private AriaManager(Context context) { APP = context.getApplicationContext(); } public static AriaManager getInstance() { - if (INSTANCE == null) { - throw new NullPointerException("请使用AriaManager.init(context)初始化管理器"); - } return INSTANCE; } @@ -118,56 +87,16 @@ import org.xml.sax.SAXException; } private void initData() { + mConfig = AriaConfig.init(APP); initDb(APP); regAppLifeCallback(APP); - initConfig(); initAria(); - amendTaskState(); - regNetCallBack(APP); } public Context getAPP() { 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()); + amendTaskState(); } private void initAria() { - if (mAConfig.getUseAriaCrashHandler()) { + AppConfig appConfig = mConfig.getAConfig(); + if (appConfig.getUseAriaCrashHandler()) { Thread.setDefaultUncaughtExceptionHandler(new AriaCrashHandler()); } - mAConfig.setLogLevel(mAConfig.getLogLevel()); + appConfig.setLogLevel(appConfig.getLogLevel()); 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 getReceiver() { return mReceivers; } @@ -237,7 +157,7 @@ import org.xml.sax.SAXException; * @deprecated 后续版本会删除该api */ @Deprecated public AriaManager setUploadQueueMod(QueueMod mod) { - mUConfig.setQueueMod(mod.tag); + mConfig.getUConfig().setQueueMod(mod.tag); return this; } @@ -253,7 +173,7 @@ import org.xml.sax.SAXException; * @deprecated 后续版本会删除该api */ @Deprecated public AriaManager setDownloadQueueMod(QueueMod mod) { - mDConfig.setQueueMod(mod.tag); + mConfig.getDConfig().setQueueMod(mod.tag); return this; } @@ -267,7 +187,7 @@ import org.xml.sax.SAXException; * */ public DownloadConfig getDownloadConfig() { - return mDConfig; + return mConfig.getDConfig(); } /** @@ -280,14 +200,14 @@ import org.xml.sax.SAXException; * */ public UploadConfig getUploadConfig() { - return mUConfig; + return mConfig.getUConfig(); } /** * 获取APP配置 */ public AppConfig getAppConfig() { - return mAConfig; + return mConfig.getAConfig(); } /** @@ -300,13 +220,14 @@ import org.xml.sax.SAXException; * */ public DGroupConfig getDGroupConfig() { - return mDGConfig; + return mConfig.getDGConfig(); } /** * 处理下载操作 */ DownloadReceiver download(Object obj) { + IReceiver receiver = mReceivers.get(getKey(ReceiverType.DOWNLOAD, obj)); if (receiver == null) { receiver = putReceiver(ReceiverType.DOWNLOAD, obj); @@ -335,101 +256,30 @@ import org.xml.sax.SAXException; public void delRecord(int type, String key, boolean removeFile) { switch (type) { case 1: // 删除普通任务记录 - RecordUtil.delTaskRecord(key, RecordHandler.TYPE_DOWNLOAD, removeFile, true); + RecordUtil.delTaskRecord(key, IRecordHandler.TYPE_DOWNLOAD, removeFile, true); break; case 2: - RecordUtil.delGroupTaskRecord(key, removeFile); + RecordUtil.delGroupTaskRecordByHash(key, removeFile); break; case 3: - RecordUtil.delTaskRecord(key, RecordHandler.TYPE_UPLOAD); + DeleteURecord.getInstance().deleteRecord(key, removeFile, true); break; } } - private IReceiver putReceiver(@ReceiverType String type, Object obj) { + private IReceiver putReceiver(ReceiverType type, Object obj) { final String key = getKey(type, obj); 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) { - AbsReceiver absReceiver; - switch (type) { - 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; + AbsReceiver absReceiver = + type.equals(ReceiverType.DOWNLOAD) ? new DownloadReceiver(obj) : new UploadReceiver(obj); mReceivers.put(key, absReceiver); receiver = absReceiver; } 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 list = mSubClass.get(key); - if (list == null) { - list = new ArrayList<>(); - mSubClass.put(key, list); - } - list.add(createKey(type, sub)); - } - /** * 根据功能类型和控件类型获取对应的key * @@ -438,60 +288,8 @@ import org.xml.sax.SAXException; * @return key的格式为:{@code String.format("%s_%s_%s", obj.getClass().getName(), type, * obj.hashCode());} */ - private String createKey(@ReceiverType String type, Object obj) { - return String.format("%s_%s_%s", obj.getClass().getName(), type, 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()); - } + private String getKey(ReceiverType type, Object obj) { + return String.format("%s_%s_%s", CommonUtil.getTargetName(obj), type.name(), obj.hashCode()); } /** @@ -513,40 +311,47 @@ import org.xml.sax.SAXException; ALog.e(TAG, "target obj is null"); return; } - List temp = new ArrayList<>(); // 移除寄主的receiver for (Iterator> iter = mReceivers.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = iter.next(); String key = entry.getKey(); - if (key.equals(getKey(ReceiverType.DOWNLOAD, obj)) || key.equals( - getKey(ReceiverType.UPLOAD, obj))) { - AbsReceiver receiver = mReceivers.get(key); - List subNames = mSubClass.get(key); - if (subNames != null && !subNames.isEmpty()) { - temp.addAll(subNames); - } - if (receiver != null) { - receiver.destroy(); + AbsReceiver receiver = entry.getValue(); + + if (receiver.isFragment()){ + Method method = CommonUtil.getMethod(receiver.obj.getClass(), "getActivity"); + if (method != null){ + try { + Activity ac = (Activity) method.invoke(receiver.obj); + if (ac == obj){ + receiver.destroy(); + iter.remove(); + continue; + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } } + } + + // 处理内部类的 + String objClsName = obj.getClass().getName(); + if (receiver.isLocalOrAnonymousClass && key.startsWith(objClsName)) { + receiver.destroy(); iter.remove(); + continue; } - } - // 移除寄生的receiver - if (!temp.isEmpty()) { - for (Iterator> iter = mReceivers.entrySet().iterator(); - iter.hasNext(); ) { - Map.Entry entry = iter.next(); - if (temp.contains(entry.getKey())) { - AbsReceiver receiver = mReceivers.get(entry.getKey()); - if (receiver != null) { - receiver.destroy(); - } - iter.remove(); - } + if (key.equals(getKey(ReceiverType.DOWNLOAD, obj)) + || key.equals(getKey(ReceiverType.UPLOAD, obj)) + ) { + receiver.destroy(); + iter.remove(); } } + Log.d(TAG, "debug"); } /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/WidgetLiftManager.java b/Aria/src/main/java/com/arialyy/aria/core/WidgetLiftManager.java index 2dae53cb..763280d2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/WidgetLiftManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/WidgetLiftManager.java @@ -21,7 +21,6 @@ import android.content.DialogInterface; import android.os.Build; import android.os.Message; import android.widget.PopupWindow; -import androidx.fragment.app.DialogFragment; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import java.lang.reflect.Field; @@ -30,33 +29,20 @@ import java.lang.reflect.Field; * Created by lyy on 2017/2/7. * 为组件添加生命周期 */ -final class WidgetLiftManager { +public final class WidgetLiftManager { private final String TAG = "WidgetLiftManager"; /** * 处理DialogFragment事件 - * - * @param dialogFragment {@link android.app.DialogFragment} - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) boolean handleDialogFragmentLift( - android.app.DialogFragment dialogFragment) { - return handleDialogLift(dialogFragment.getDialog()); - } - - /** - * 处理DialogFragment事件 - * - * @param dialogFragment {@link DialogFragment} */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) boolean handleDialogFragmentLift( - DialogFragment dialogFragment) { - return handleDialogLift(dialogFragment.getDialog()); + @TargetApi(Build.VERSION_CODES.HONEYCOMB) public boolean handleDialogFragmentLift(Dialog dialog) { + return handleDialogLift(dialog); } /** * 处理悬浮框取消或dismiss事件 */ - boolean handlePopupWindowLift(PopupWindow popupWindow) { + public boolean handlePopupWindowLift(PopupWindow popupWindow) { try { Field dismissField = CommonUtil.getField(popupWindow.getClass(), "mOnDismissListener"); PopupWindow.OnDismissListener listener = @@ -87,8 +73,16 @@ final class WidgetLiftManager { /** * 处理对话框取消或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 { Field dismissField = CommonUtil.getField(dialog.getClass(), "mDismissMessage"); Message dismissMsg = (Message) dismissField.get(dialog); diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmd.java index 4867c87b..85ee7650 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmd.java @@ -16,7 +16,7 @@ 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; /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmdFactory.java b/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmdFactory.java index 14caef9a..1a7cbf34 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmdFactory.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/AbsCmdFactory.java @@ -15,7 +15,8 @@ */ 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. @@ -25,8 +26,7 @@ public abstract class AbsCmdFactory extends AbsCmd< mTaskWrapper = entity; TAG = CommonUtil.getClassName(this); if (entity instanceof DGTaskWrapper) { - mQueue = DownloadGroupTaskQueue.getInstance(); + mQueue = DGroupTaskQueue.getInstance(); isDownloadCmd = true; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/AbsNormalCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/AbsNormalCmd.java index 10494a6f..7efde45b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/AbsNormalCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/AbsNormalCmd.java @@ -18,14 +18,14 @@ package com.arialyy.aria.core.command; import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.inf.AbsTask; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.task.AbsTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.aria.core.inf.ITask; -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.scheduler.ISchedulers; +import com.arialyy.aria.core.task.ITask; +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.listener.ISchedulers; import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; @@ -54,19 +54,19 @@ public abstract class AbsNormalCmd extends AbsCmd { ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD"); return; } - mQueue = DownloadTaskQueue.getInstance(); + mQueue = DTaskQueue.getInstance(); } else if (taskType == ITask.DOWNLOAD_GROUP) { if (!(entity instanceof DGTaskWrapper)) { ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD_GROUP"); return; } - mQueue = DownloadGroupTaskQueue.getInstance(); + mQueue = DGroupTaskQueue.getInstance(); } else if (taskType == ITask.UPLOAD) { if (!(entity instanceof UTaskWrapper)) { ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_UPLOAD"); return; } - mQueue = UploadTaskQueue.getInstance(); + mQueue = UTaskQueue.getInstance(); } else { ALog.e(TAG, "任务类型错误,任务类型应该为ICM.TASK_TYPE_DOWNLOAD、TASK_TYPE_DOWNLOAD_GROUP、TASK_TYPE_UPLOAD"); return; @@ -157,14 +157,6 @@ public abstract class AbsNormalCmd extends AbsCmd { mQueue.resumeTask(task); } - /** - * 启动指定任务 - * - * @param task 指定任务 - */ - void startTask(AbsTask task) { - mQueue.startTask(task); - } /** * 从队列中获取任务 diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/AddCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/AddCmd.java index 299ba3f8..03046ff8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/AddCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/AddCmd.java @@ -16,8 +16,8 @@ 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.task.AbsTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.util.ALog; diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/CancelAllCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/CancelAllCmd.java index 52cb7de5..f589d984 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/CancelAllCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/CancelAllCmd.java @@ -20,11 +20,11 @@ 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.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.core.manager.TaskWrapperManager; -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.queue.DGroupTaskQueue; +import com.arialyy.aria.core.queue.DTaskQueue; +import com.arialyy.aria.core.queue.UTaskQueue; import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.orm.DbEntity; @@ -104,11 +104,11 @@ final public class CancelAllCmd extends AbsNormalCmd AbsNormalCmd createNormalCmd(T entity, int cmd, + int taskType) { + return NormalCmdFactory.getInstance().createCmd(entity, cmd, taskType); + } + + /** + * 创建任务组命令 + * + * @param childUrl 子任务url + */ + public static AbsGroupCmd createGroupCmd(T entity, int cmd, + String childUrl) { + return GroupCmdFactory.getInstance().createCmd(entity, cmd, childUrl); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/CommandManager.java b/Aria/src/main/java/com/arialyy/aria/core/command/CommandManager.java index 6ca3a813..79c9427f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/CommandManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/CommandManager.java @@ -30,7 +30,7 @@ public class CommandManager { EventMsgUtil.getDefault().register(this); } - public static CommandManager init() { + public static void init() { if (instance == null) { synchronized (CommandManager.class) { if (instance == null) { @@ -38,7 +38,6 @@ public class CommandManager { } } } - return instance; } @Event @@ -51,9 +50,6 @@ public class CommandManager { @Event public void start(StartCmd cmd) { - if (CommonUtil.isFastDoubleClick()) { - return; - } cmd.executeCmd(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStartCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStartCmd.java index 4cd0e163..bb16de55 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStartCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStartCmd.java @@ -15,7 +15,7 @@ */ 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. diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStopCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStopCmd.java index 78b8b63f..7cdcd226 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStopCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/DGSubStopCmd.java @@ -15,7 +15,7 @@ */ 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. diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/GroupCmdFactory.java b/Aria/src/main/java/com/arialyy/aria/core/command/GroupCmdFactory.java index db7b1f31..e1241d65 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/GroupCmdFactory.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/GroupCmdFactory.java @@ -15,7 +15,7 @@ */ 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. 任务组子任务控制命令 @@ -46,13 +46,11 @@ public class GroupCmdFactory { } /** - * @param target 创建任务的对象 * @param wrapper 参数信息 * @param type 命令类型{@link #SUB_TASK_START}、{@link #SUB_TASK_STOP} * @param childUrl 需要控制的子任务url */ - public AbsGroupCmd createCmd(String target, AbsGroupTaskWrapper wrapper, int type, - String childUrl) { + public AbsGroupCmd createCmd(AbsGroupTaskWrapper wrapper, int type, String childUrl) { AbsGroupCmd cmd = null; switch (type) { case SUB_TASK_START: diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/HighestPriorityCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/HighestPriorityCmd.java index 42fe3c20..d2d8b228 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/HighestPriorityCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/HighestPriorityCmd.java @@ -15,10 +15,11 @@ */ package com.arialyy.aria.core.command; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.queue.DownloadTaskQueue; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.queue.DTaskQueue; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.NetUtils; @@ -41,7 +42,7 @@ final class HighestPriorityCmd extends AbsNormalCmd @Override public void executeCmd() { if (!canExeCmd) return; - if (!NetUtils.isConnected(AriaManager.getInstance().getAPP())){ + if (!NetUtils.isConnected(AriaConfig.getInstance().getAPP())){ ALog.e(TAG, "启动任务失败,网络未连接"); return; } @@ -50,7 +51,7 @@ final class HighestPriorityCmd extends AbsNormalCmd task = (DownloadTask) createTask(); } if (task != null) { - ((DownloadTaskQueue) mQueue).setTaskHighestPriority(task); + ((DTaskQueue) mQueue).setTaskHighestPriority(task); } } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/NormalCmdFactory.java b/Aria/src/main/java/com/arialyy/aria/core/command/NormalCmdFactory.java index 767a7fdd..194b7c32 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/NormalCmdFactory.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/NormalCmdFactory.java @@ -16,7 +16,8 @@ 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. @@ -83,8 +84,7 @@ public class NormalCmdFactory extends AbsCmdFactory extends AbsNormalCmd { task = createTask(); } if (task != null) { - task.cancel(TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY); - task.start(TaskSchedulerType.TYPE_START_AND_RESET_STATE); + mQueue.cancelTask(task, TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY); + mQueue.startTask(task, TaskSchedulerType.TYPE_START_AND_RESET_STATE); } } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/ResumeAllCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/ResumeAllCmd.java index f29e79f6..4e81b18a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/ResumeAllCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/ResumeAllCmd.java @@ -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; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.download.DGTaskWrapper; -import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.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.AriaConfig; import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.aria.core.manager.TaskWrapperManager; -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.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.NetUtils; -import java.util.ArrayList; -import java.util.List; /** * Created by AriaL on 2017/6/13. @@ -30,105 +30,17 @@ import java.util.List; * 4.恢复下载的任务规则是,停止时间越晚的任务启动越早,按照DESC来进行排序 */ final class ResumeAllCmd extends AbsNormalCmd { - private List mWaitList = new ArrayList<>(); ResumeAllCmd(T entity, int taskType) { super(entity, taskType); } @Override public void executeCmd() { - if (!NetUtils.isConnected(AriaManager.getInstance().getAPP())) { + if (!NetUtils.isConnected(AriaConfig.getInstance().getAPP())) { ALog.w(TAG, "恢复任务失败,网络未连接"); return; } - if (isDownloadCmd) { - findTaskData(1); - findTaskData(2); - } else { - findTaskData(3); - } - resumeWaitTask(); - } - - /** - * 查找数据库中的所有任务数据 - * - * @param type {@code 1}单任务下载任务;{@code 2}任务组下载任务;{@code 3} 单任务上传任务 - */ - private void findTaskData(int type) { - if (type == 1) { - List 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 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 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 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); - } + new Thread(new ResumeThread(isDownloadCmd, + String.format("state!=%s", IEntity.STATE_COMPLETE))).start(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/ResumeThread.java b/Aria/src/main/java/com/arialyy/aria/core/command/ResumeThread.java new file mode 100644 index 00000000..32f442d6 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/command/ResumeThread.java @@ -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 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 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 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 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 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(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/StartCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/StartCmd.java index 5aec44d8..54fcdec0 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/StartCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/StartCmd.java @@ -16,51 +16,47 @@ 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.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.ITaskWrapper; -import com.arialyy.aria.core.manager.TaskWrapperManager; -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.core.task.AbsTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CommonUtil; 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} */ -final class StartCmd extends AbsNormalCmd { +final public class StartCmd extends AbsNormalCmd { + + private boolean nowStart = false; StartCmd(T entity, int taskType) { super(entity, taskType); } + /** + * 立即执行任务 + * + * @param nowStart true 立即执行任务,无论执行队列是否满了 + */ + public void setNowStart(boolean nowStart) { + this.nowStart = nowStart; + } + @Override public void executeCmd() { if (!canExeCmd) return; - if (!NetUtils.isConnected(AriaManager.getInstance().getAPP())) { + if (!NetUtils.isConnected(AriaConfig.getInstance().getAPP())) { ALog.e(TAG, "启动任务失败,网络未连接"); return; } String mod; int maxTaskNum = mQueue.getMaxTaskNum(); - AriaManager manager = AriaManager.getInstance(); + AriaConfig config = AriaConfig.getInstance(); if (isDownloadCmd) { - mod = manager.getDownloadConfig().getQueueMod(); + mod = config.getDConfig().getQueueMod(); } else { - mod = manager.getUploadConfig().getQueueMod(); + mod = config.getUConfig().getQueueMod(); } AbsTask task = getTask(); @@ -86,13 +82,29 @@ final class StartCmd extends AbsNormalCmd { startTask(); } } else { - sendWaitState(task); + if (nowStart) { + startTask(); + } else { + sendWaitState(task); + } } } } else { //任务没执行并且执行队列中没有该任务,才认为任务没有运行中 if (!mQueue.taskIsRunning(task.getKey())) { - resumeTask(); + if (mod.equals(QueueMod.NOW.getTag())) { + resumeTask(); + } else { + if (mQueue.getCurrentExePoolNum() < maxTaskNum) { + resumeTask(); + } else { + if (nowStart) { + resumeTask(); + } else { + sendWaitState(task); + } + } + } } else { ALog.w(TAG, String.format("任务【%s】已经在运行", task.getTaskName())); } @@ -106,73 +118,7 @@ final class StartCmd extends AbsNormalCmd { * 当缓冲队列为null时,查找数据库中所有等待中的任务 */ private void findAllWaitTask() { - new Thread(new WaitTaskThread()).start(); - } - - private class WaitTaskThread implements Runnable { - - @Override public void run() { - if (isDownloadCmd) { - handleTask(findWaitData(1)); - handleTask(findWaitData(2)); - } else { - handleTask(findWaitData(3)); - } - } - - private List findWaitData(int type) { - List waitList = new ArrayList<>(); - TaskWrapperManager tManager = TaskWrapperManager.getInstance(); - if (type == 1) { // 普通下载任务 - List 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 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 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 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); - } - } + new Thread( + new ResumeThread(isDownloadCmd, String.format("state=%s", IEntity.STATE_WAIT))).start(); } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/StopAllCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/StopAllCmd.java index 81966da4..bd95cbc8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/StopAllCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/StopAllCmd.java @@ -1,6 +1,6 @@ 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. diff --git a/Aria/src/main/java/com/arialyy/aria/core/command/StopCmd.java b/Aria/src/main/java/com/arialyy/aria/core/command/StopCmd.java index 57c04eb4..5032fc83 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/command/StopCmd.java +++ b/Aria/src/main/java/com/arialyy/aria/core/command/StopCmd.java @@ -16,8 +16,8 @@ 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.task.AbsTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.util.ALog; diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsBuilderTarget.java index beb66c9b..a070bf83 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsBuilderTarget.java @@ -15,18 +15,25 @@ */ 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.IStartFeature; import com.arialyy.aria.core.inf.AbsTarget; /** - * 处理第一次下载 + * 处理第一次创建的任务 */ public abstract class AbsBuilderTarget extends AbsTarget implements IStartFeature { private BuilderController mStartController; + /** + * 任务操作前调用 + */ + protected void onPre() { + + } + private synchronized BuilderController getController() { if (mStartController == null) { mStartController = new BuilderController(getTaskWrapper()); @@ -34,23 +41,42 @@ public abstract class AbsBuilderTarget extends 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 public long add() { + onPre(); return getController().add(); } /** * 开始任务 * - * @return 正常启动,返回任务id,否则返回-1 + * @return 创建成功,返回任务id,创建失败,返回-1 */ @Override public long create() { + onPre(); return getController().create(); } @@ -65,6 +91,7 @@ public abstract class AbsBuilderTarget extends */ @Override public long setHighestPriority() { + onPre(); return getController().setHighestPriority(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsNormalTarget.java index bcaf9b10..a05e8d13 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsNormalTarget.java @@ -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.NormalController; 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.IEntity; import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.DeleteDGRecord; import com.arialyy.aria.util.RecordUtil; /** @@ -32,19 +32,38 @@ import com.arialyy.aria.util.RecordUtil; public abstract class AbsNormalTarget extends AbsTarget implements INormalFeature { + /** + * 任务操作前调用 + */ + protected void onPre() { + + } + + /** + * 是否忽略权限检查 + */ + public TARGET ignoreCheckPermissions() { + getController().ignoreCheckPermissions(); + return (TARGET) this; + } + /** * 任务是否在执行 * * @return {@code true} 任务正在执行 */ - public abstract boolean isRunning(); + public boolean isRunning() { + return false; + } /** * 任务是否存在 * * @return {@code true} 任务存在 */ - public abstract boolean taskExists(); + public boolean taskExists() { + return false; + } private NormalController mNormalController; @@ -64,11 +83,10 @@ public abstract class AbsNormalTarget extends Ab cancel(); } else { if (getEntity() instanceof AbsNormalEntity) { - RecordUtil.delTaskRecord((AbsNormalEntity) getEntity(), getTaskWrapper().isRemoveFile()); + RecordUtil.delNormalTaskRecord((AbsNormalEntity) getEntity(), getTaskWrapper().isRemoveFile()); } else if (getEntity() instanceof DownloadGroupEntity) { - RecordUtil.delGroupTaskRecord(((DownloadGroupEntity) getEntity()), - getTaskWrapper().isRemoveFile(), - true); + DeleteDGRecord.getInstance() + .deleteRecord(getEntity(), getTaskWrapper().isRemoveFile(), true); } TaskWrapperManager.getInstance().removeTaskWrapper(getTaskWrapper()); } @@ -139,6 +157,7 @@ public abstract class AbsNormalTarget extends Ab */ @Override public void stop() { + onPre(); getController().stop(); } @@ -147,7 +166,18 @@ public abstract class AbsNormalTarget extends Ab */ @Override 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 extends Ab */ @Override public void cancel() { - getController().cancel(); + cancel(false); } /** @@ -163,17 +193,19 @@ public abstract class AbsNormalTarget extends Ab */ @Override public void reTry() { + onPre(); getController().reTry(); } /** * 删除任务 * - * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 + * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经完成的文件 * {@code false}如果任务已经完成,只删除任务数据库记录, */ @Override public void cancel(boolean removeFile) { + onPre(); getController().cancel(removeFile); } @@ -181,12 +213,14 @@ public abstract class AbsNormalTarget extends Ab * 重新下载 */ @Override - public void reStart() { - getController().reStart(); + public long reStart() { + onPre(); + return getController().reStart(); } @Override public void save() { + onPre(); getController().save(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/BaseDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/BaseDelegate.java deleted file mode 100644 index f1496339..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/BaseDelegate.java +++ /dev/null @@ -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 { - 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 controller(@ControllerType Class clazz) { - return FeatureController.newInstance(clazz, getTaskWrapper()); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java b/Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java new file mode 100644 index 00000000..09301f2d --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/FtpOption.java @@ -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.TimeZone,如:America/Chicago or Asia/Rangoon. + * + * @param serverTimeZoneId 时区 + */ + public void setServerTimeZoneId(String serverTimeZoneId) { + this.serverTimeZoneId = serverTimeZoneId; + } + + /** + *

+ * 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. + *

+ * 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 + * "jan|feb|mar|apr|maí|jún|júl|ágú|sep|okt|nóv|des". + *

+ * + * @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 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 + * Locale.US + * + * @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 + * Locale.US 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 getSupportedLanguageCodes() { + return LANGUAGE_CODE_MAP.keySet(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/HttpOption.java b/Aria/src/main/java/com/arialyy/aria/core/common/HttpOption.java new file mode 100644 index 00000000..a431f301 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/HttpOption.java @@ -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 params; + private Map headers; + private RequestEnum requestEnum = RequestEnum.GET; + private Map 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 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 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 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; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java b/Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java deleted file mode 100644 index 30a4564f..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java +++ /dev/null @@ -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> - extends AbsFileer { - 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 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 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); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java b/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java index 780f262b..dbce4c5f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ProxyHelper.java @@ -16,6 +16,12 @@ package com.arialyy.aria.core.common; 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.Map; import java.util.Set; @@ -69,61 +75,86 @@ public class ProxyHelper { * @return {@link #PROXY_TYPE_DOWNLOAD},如果没有实体对象则返回空的list */ public Set checkProxyType(Class clazz) { - final String className = clazz.getName(); Set result = mProxyCache.get(clazz.getName()); if (result != null) { return result; } - result = new HashSet<>(); - try { - if (getClass().getClassLoader() - .loadClass(className.concat(TaskEnum.DOWNLOAD_GROUP.proxySuffix)) - != null) { - result.add(PROXY_TYPE_DOWNLOAD_GROUP); - } - } catch (ClassNotFoundException e) { - //e.printStackTrace(); + result = checkProxyTypeByInterface(clazz); + if (result != null && !result.isEmpty()) { + mProxyCache.put(clazz.getName(), result); + return result; } - try { - if (getClass().getClassLoader().loadClass(className.concat(TaskEnum.DOWNLOAD.proxySuffix)) - != null) { - result.add(PROXY_TYPE_DOWNLOAD); - } - } catch (ClassNotFoundException e) { - //e.printStackTrace(); + result = checkProxyTypeByProxyClass(clazz); + + if (!result.isEmpty()) { + mProxyCache.put(clazz.getName(), result); } + return result; + } - try { - if (getClass().getClassLoader().loadClass(className.concat(TaskEnum.UPLOAD.proxySuffix)) - != null) { - result.add(PROXY_TYPE_UPLOAD); - } - } catch (ClassNotFoundException e) { - //e.printStackTrace(); + private Set checkProxyTypeByProxyClass(Class clazz) { + final String className = clazz.getName(); + Set 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 (getClass().getClassLoader().loadClass(className.concat(TaskEnum.M3U8_PEER.proxySuffix)) - != null) { - result.add(PROXY_TYPE_M3U8_PEER); - } - } catch (ClassNotFoundException e) { - //e.printStackTrace(); + if (checkProxyExist(className, TaskEnum.UPLOAD.proxySuffix)) { + result.add(PROXY_TYPE_UPLOAD); } + if (checkProxyExist(className, TaskEnum.M3U8_PEER.proxySuffix)) { + result.add(PROXY_TYPE_M3U8_PEER); + } + + if (checkProxyExist(className, TaskEnum.DOWNLOAD_GROUP_SUB.proxySuffix)) { + result.add(PROXY_TYPE_DOWNLOAD_GROUP_SUB); + } + return result; + } + + private Set checkProxyTypeByInterface(Class clazz) { + if (!TaskInternalListenerInterface.class.isAssignableFrom(clazz)) { + return null; + } + Set 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); + } + + 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 { - if (getClass().getClassLoader() - .loadClass(className.concat(TaskEnum.DOWNLOAD_GROUP_SUB.proxySuffix)) - != null) { - result.add(PROXY_TYPE_DOWNLOAD_GROUP_SUB); + if (getClass().getClassLoader().loadClass(clsName) != null) { + return true; + } + if (Class.forName(clsName) != null) { + return true; } } catch (ClassNotFoundException e) { //e.printStackTrace(); } - - if (!result.isEmpty()) { - mProxyCache.put(clazz.getName(), result); - } - return result; + return false; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java b/Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java deleted file mode 100644 index f39f4c59..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java +++ /dev/null @@ -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 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 keys = pro.keySet(); - // 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录... - // 第一步应该是record 和 state去重取正确的线程数 - Set 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; - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java b/Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java new file mode 100644 index 00000000..701d4a56 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/SFtpOption.java @@ -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; + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java b/Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java deleted file mode 100644 index 05aa0d63..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java +++ /dev/null @@ -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 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; - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/controller/BuilderController.java b/Aria/src/main/java/com/arialyy/aria/core/common/controller/BuilderController.java index 78f102f2..586b243d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/controller/BuilderController.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/controller/BuilderController.java @@ -15,10 +15,10 @@ */ 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.event.EventMsgUtil; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; /** * 创建任务时使用的控制器 @@ -30,14 +30,15 @@ public final class BuilderController extends FeatureController implements IStart } /** - * 添加任务 + * 添加任务,只添加任务不进行下载 * * @return 正常添加,返回任务id,否则返回-1 */ public long add() { + setAction(ACTION_ADD); if (checkConfig()) { EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CREATE, + .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CREATE, checkTaskType())); return getEntity().getId(); } @@ -50,20 +51,21 @@ public final class BuilderController extends FeatureController implements IStart * @return 正常启动,返回任务id,否则返回-1 */ public long create() { + setAction(ACTION_CREATE); if (checkConfig()) { EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, + .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, checkTaskType())); return getEntity().getId(); } - return -1; } @Override public long setHighestPriority() { + setAction(ACTION_PRIORITY); if (checkConfig()) { EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_HIGHEST_PRIORITY, + .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_HIGHEST_PRIORITY, checkTaskType())); return getEntity().getId(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/controller/FeatureController.java b/Aria/src/main/java/com/arialyy/aria/core/common/controller/FeatureController.java index 6e385a19..92322853 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/controller/FeatureController.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/controller/FeatureController.java @@ -19,21 +19,21 @@ import android.Manifest; import android.content.pm.PackageManager; import android.os.Handler; 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.CheckDGEntityUtil; import com.arialyy.aria.core.download.CheckFtpDirEntityUtil; 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.AbsTaskWrapper; import com.arialyy.aria.core.inf.ICheckEntityUtil; -import com.arialyy.aria.core.inf.ITask; -import com.arialyy.aria.core.inf.ITaskWrapper; -import com.arialyy.aria.core.scheduler.ISchedulers; +import com.arialyy.aria.core.listener.ISchedulers; 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.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.CommonUtil; import java.lang.reflect.Constructor; @@ -43,9 +43,25 @@ import java.lang.reflect.InvocationTargetException; * 功能控制器 */ 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 AbsTaskWrapper mTaskWrapper; + /** + * 是否忽略权限检查 true 忽略权限检查 + */ + private boolean ignoreCheckPermissions = false; + private int action = ACTION_DEF; FeatureController(AbsTaskWrapper wrapper) { mTaskWrapper = wrapper; @@ -87,6 +103,24 @@ public abstract class FeatureController { return null; } + void setAction(int action) { + this.action = action; + } + + /** + * 是否忽略权限检查 + */ + public void ignoreCheckPermissions() { + this.ignoreCheckPermissions = true; + } + + /** + * 强制执行任务,不管文件路径是否被占用 + */ + public void ignoreFilePathOccupy() { + mTaskWrapper.setIgnoreFilePathOccupy(true); + } + protected AbsTaskWrapper getTaskWrapper() { return mTaskWrapper; } @@ -111,7 +145,7 @@ public abstract class FeatureController { * 如果检查实体失败,将错误回调 */ boolean checkConfig() { - if (!checkPermission()) { + if (!ignoreCheckPermissions && !checkPermission()) { return false; } boolean b = checkEntity(); @@ -134,21 +168,21 @@ public abstract class FeatureController { */ private boolean checkPermission() { - if (AriaManager.getInstance() + if (AriaConfig.getInstance() .getAPP() .checkCallingOrSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ALog.e(TAG, "启动失败,缺少权限:Manifest.permission.WRITE_EXTERNAL_STORAGE"); return false; } - if (AriaManager.getInstance() + if (AriaConfig.getInstance() .getAPP() .checkCallingOrSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) { ALog.e(TAG, "启动失败,缺少权限:Manifest.permission.INTERNET"); return false; } - if (AriaManager.getInstance() + if (AriaConfig.getInstance() .getAPP() .checkCallingOrSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { @@ -162,15 +196,15 @@ public abstract class FeatureController { private boolean checkEntity() { ICheckEntityUtil checkUtil = null; if (mTaskWrapper instanceof DTaskWrapper) { - checkUtil = CheckDEntityUtil.newInstance((DTaskWrapper) mTaskWrapper); + checkUtil = CheckDEntityUtil.newInstance((DTaskWrapper) mTaskWrapper, action); } else if (mTaskWrapper instanceof DGTaskWrapper) { 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) { - checkUtil = CheckDGEntityUtil.newInstance((DGTaskWrapper) mTaskWrapper); + checkUtil = CheckDGEntityUtil.newInstance((DGTaskWrapper) mTaskWrapper, action); } } else if (mTaskWrapper instanceof UTaskWrapper) { - checkUtil = CheckUEntityUtil.newInstance((UTaskWrapper) mTaskWrapper); + checkUtil = CheckUEntityUtil.newInstance((UTaskWrapper) mTaskWrapper, action); } return checkUtil != null && checkUtil.checkEntity(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/controller/INormalFeature.java b/Aria/src/main/java/com/arialyy/aria/core/common/controller/INormalFeature.java index 550e5a50..3808e1ba 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/controller/INormalFeature.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/controller/INormalFeature.java @@ -30,6 +30,14 @@ public interface INormalFeature { */ void resume(); + /** + * 正常来说,当执行队列满时,调用恢复任务接口,只能将任务放到缓存队列中。 + * 如果希望调用恢复接口,马上进入执行队列,需要使用该方法 + * + * @param newStart true 立即将任务恢复到执行队列中 + */ + void resume(boolean newStart); + /** * 删除任务 */ @@ -51,7 +59,7 @@ public interface INormalFeature { /** * 重新下载 */ - void reStart(); + long reStart(); /** * 保存数据 diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/controller/NormalController.java b/Aria/src/main/java/com/arialyy/aria/core/common/controller/NormalController.java index 6cb27d12..3a5a6cd8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/controller/NormalController.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/controller/NormalController.java @@ -16,11 +16,12 @@ package com.arialyy.aria.core.common.controller; 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.StartCmd; 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.CommonUtil; /** * 启动控制器 @@ -37,9 +38,10 @@ public final class NormalController extends FeatureController implements INormal */ @Override public void stop() { + setAction(ACTION_STOP); if (checkConfig()) { EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP, + .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP, checkTaskType())); } } @@ -49,10 +51,24 @@ public final class NormalController extends FeatureController implements INormal */ @Override public void resume() { + resume(false); + } + + /** + * 正常来说,当执行队列满时,调用恢复任务接口,只能将任务放到缓存队列中。 + * 如果希望调用恢复接口,马上进入执行队列,需要使用该方法 + * + * @param newStart true 立即将任务恢复到执行队列中 + */ + @Override public void resume(boolean newStart) { + setAction(ACTION_RESUME); if (checkConfig()) { + StartCmd cmd = + (StartCmd) CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, + checkTaskType()); + cmd.setNowStart(newStart); EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_START, - checkTaskType())); + .post(cmd); } } @@ -61,11 +77,7 @@ public final class NormalController extends FeatureController implements INormal */ @Override public void cancel() { - if (checkConfig()) { - EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CANCEL, - checkTaskType())); - } + cancel(false); } /** @@ -73,13 +85,14 @@ public final class NormalController extends FeatureController implements INormal */ @Override public void reTry() { + setAction(ACTION_RETRY); if (checkConfig()) { int taskType = checkTaskType(); EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP, taskType)); + .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_STOP, taskType)); EventMsgUtil.getDefault() .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 public void cancel(boolean removeFile) { + setAction(ACTION_CANCEL); if (checkConfig()) { CancelCmd cancelCmd = - (CancelCmd) CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CANCEL, + (CancelCmd) CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_CANCEL, checkTaskType()); cancelCmd.removeFile = removeFile; EventMsgUtil.getDefault().post(cancelCmd); @@ -104,15 +118,19 @@ public final class NormalController extends FeatureController implements INormal * 重新下载 */ @Override - public void reStart() { + public long reStart() { + setAction(ACTION_RESTART); if (checkConfig()) { EventMsgUtil.getDefault() - .post(CommonUtil.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_RESTART, + .post(CmdHelper.createNormalCmd(getTaskWrapper(), NormalCmdFactory.TASK_RESTART, checkTaskType())); + return getEntity().getId(); } + return -1; } @Override public void save() { + setAction(ACTION_SAVE); if (!checkConfig()) { ALog.e(TAG, "保存修改失败"); } else { diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java deleted file mode 100644 index a3345e2c..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java +++ /dev/null @@ -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> - 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; - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FTPSDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FTPSDelegate.java deleted file mode 100644 index 3a610e18..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FTPSDelegate.java +++ /dev/null @@ -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 extends BaseDelegate { - - 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 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 setAlias(String keyAlias) { - if (TextUtils.isEmpty(keyAlias)) { - throw new NullPointerException("别名为空"); - } - mUrlEntity.keyAlias = keyAlias; - return this; - } - - /** - * 设置证书密码 - * - * @param storePass 私钥密码 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public FTPSDelegate setStorePass(String storePass) { - if (TextUtils.isEmpty(storePass)) { - throw new NullPointerException("证书密码为空"); - } - mUrlEntity.storePass = storePass; - return this; - } - - /** - * 设置证书路径 - * - * @param storePath 证书路径 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public FTPSDelegate 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 setImplicit(boolean isImplicit) { - mUrlEntity.isImplicit = isImplicit; - return this; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpDelegate.java deleted file mode 100644 index 2c019156..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpDelegate.java +++ /dev/null @@ -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 extends BaseDelegate { - private static final String TAG = "FtpDelegate"; - - public FtpDelegate(TARGET target, AbsTaskWrapper wrapper) { - super(target, wrapper); - } - - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public FtpDelegate charSet(String charSet) { - if (TextUtils.isEmpty(charSet)) { - throw new NullPointerException("字符编码为空"); - } - getTaskWrapper().asFtp().setCharSet(charSet); - return this; - } - - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public FtpDelegate login(String userName, String password) { - return login(userName, password, null); - } - - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public FtpDelegate 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 asFtps() { - return new FTPSDelegate<>(mTarget, mWrapper); - } - - /** - * 配置ftp客户端信息 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public FtpDelegate setFtpClentConfig(FTPClientConfig config) { - getTaskWrapper().asFtp().setClientConfig(config); - return this; - } - - //@Override public TARGET setProxy(Proxy proxy) { - // mTarget.getTaskWrapper().asFtp().setProxy(proxy); - // return mTarget; - //} -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskConfig.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskConfig.java deleted file mode 100644 index c4b5a332..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskConfig.java +++ /dev/null @@ -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 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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/SSLSessionReuseFTPSClient.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/SSLSessionReuseFTPSClient.java deleted file mode 100644 index 63fed6e3..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/SSLSessionReuseFTPSClient.java +++ /dev/null @@ -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"); - } - } - } -} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java deleted file mode 100644 index 10ebeea7..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpDelegate.java +++ /dev/null @@ -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 extends BaseDelegate { - - public HttpDelegate(TARGET target, AbsTaskWrapper wrapper) { - super(target, wrapper); - } - - /** - * 设置请求类型 - * - * @param requestEnum {@link RequestEnum} - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public HttpDelegate setRequestType(RequestEnum requestEnum) { - getTaskWrapper().asHttp().setRequestEnum(requestEnum); - return this; - } - - /** - * 设置http请求参数 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public HttpDelegate setParams(Map params) { - getTaskWrapper().asHttp().setParams(params); - return this; - } - - /** - * 设置http请求参数 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public HttpDelegate setParam(String key, String value) { - if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { - ALog.d(TAG, "key 或value 为空"); - return this; - } - Map 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 setFormFields(Map 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 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 addHeaders(@NonNull Map 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 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 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 keys = taskDelegate.getHeaders().keySet(); - for (String key : keys) { - if (headers.containsKey(key)) { - i++; - } else { - break; - } - } - if (i == taskDelegate.getHeaders().size()) { - int j = 0; - Collection 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 keys = headers.keySet(); - for (String key : keys) { - taskDelegate.getHeaders().put(key, headers.get(key)); - } - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/CheckDEntityUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/CheckDEntityUtil.java index ea81c0e1..e5cd7bdb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/CheckDEntityUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/CheckDEntityUtil.java @@ -16,14 +16,16 @@ package com.arialyy.aria.core.download; import android.text.TextUtils; -import com.arialyy.aria.core.common.RecordHandler; -import com.arialyy.aria.core.download.m3u8.M3U8Entity; +import com.arialyy.aria.core.common.controller.FeatureController; 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.ITaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.util.ALog; 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 java.io.File; @@ -31,15 +33,17 @@ import java.io.File; * 检查下载任务实体 */ public class CheckDEntityUtil implements ICheckEntityUtil { - private final String TAG = "CheckDLoadEntity"; + private final String TAG = CommonUtil.getClassName(getClass()); private DTaskWrapper mWrapper; private DownloadEntity mEntity; + private int action; - public static CheckDEntityUtil newInstance(DTaskWrapper wrapper) { - return new CheckDEntityUtil(wrapper); + public static CheckDEntityUtil newInstance(DTaskWrapper wrapper, int action) { + return new CheckDEntityUtil(wrapper, action); } - private CheckDEntityUtil(DTaskWrapper wrapper) { + private CheckDEntityUtil(DTaskWrapper wrapper, int action) { + this.action = action; mWrapper = wrapper; mEntity = mWrapper.getEntity(); } @@ -47,11 +51,11 @@ public class CheckDEntityUtil implements ICheckEntityUtil { @Override public boolean checkEntity() { if (mWrapper.getErrorEvent() != null) { - ALog.e(TAG, mWrapper.getErrorEvent().errorMsg); + ALog.e(TAG, String.format("任务操作失败,%s", mWrapper.getErrorEvent().errorMsg)); return false; } - boolean b = checkFtps() && checkUrl() && checkFilePath(); + boolean b = checkUrl() && checkFilePath(); if (b) { mEntity.save(); } @@ -64,36 +68,39 @@ public class CheckDEntityUtil implements ICheckEntityUtil { private void handleM3U8() { File file = new File(mWrapper.getTempFilePath()); - // 缓存文件夹格式:问文件夹/.文件名_码率 - String cacheDir = String.format("%s/.%s_%s", file.getParent(), file.getName(), - mWrapper.asM3U8().getBandWidth()); - mWrapper.asM3U8().setCacheDir(cacheDir); + Object bw = mWrapper.getM3U8Params().getParam(IOptionConstant.bandWidth); + int bandWidth = bw == null ? 0 : (int) bw; + String cacheDir = FileUtil.getTsCacheDir(file.getPath(), bandWidth); + + mWrapper.getM3U8Params().setParams(IOptionConstant.cacheDir, cacheDir); M3U8Entity m3U8Entity = mEntity.getM3U8Entity(); + if (m3U8Entity == null) { m3U8Entity = new M3U8Entity(); m3U8Entity.setFilePath(mEntity.getFilePath()); m3U8Entity.setPeerIndex(0); m3U8Entity.setCacheDir(cacheDir); - m3U8Entity.setGenerateIndexFile(mWrapper.asM3U8().isGenerateIndexFileTemp()); m3U8Entity.insert(); } else { - m3U8Entity.setGenerateIndexFile(mWrapper.asM3U8().isGenerateIndexFileTemp()); m3U8Entity.update(); } - if (mWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) { + if (mWrapper.getRequestType() == ITaskWrapper.M3U8_VOD + && action == FeatureController.ACTION_CREATE) { if (mEntity.getFileSize() == 0) { ALog.w(TAG, "由于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()) { ALog.w(TAG, "对于直播来说,每次下载都是一个新文件,所以你需要设置新都文件路径,否则Aria框架将会覆盖已下载的文件"); file.delete(); } } - if (mWrapper.asM3U8().getBandWidthUrlConverter() != null - && mWrapper.asM3U8().getBandWidth() == 0) { + if (action != FeatureController.ACTION_CANCEL + && mWrapper.getM3U8Params().getHandler(IOptionConstant.bandWidthUrlConverter) != null + && bandWidth == 0) { ALog.w(TAG, "你已经设置了码率url转换器,但是没有设置码率,Aria框架将采用第一个获取到的码率"); } } @@ -103,7 +110,12 @@ public class CheckDEntityUtil implements ICheckEntityUtil { if (TextUtils.isEmpty(filePath)) { ALog.e(TAG, "下载失败,文件保存路径为null"); 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)); return false; } @@ -129,19 +141,22 @@ public class CheckDEntityUtil implements ICheckEntityUtil { /** * 检查路径冲突 + * @return true 路径没有冲突 */ 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 (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", filePath)) { - if (!mWrapper.isForceDownload()) { - ALog.e(TAG, String.format("下载失败,保存路径【%s】已经被其它任务占用,请设置其它保存路径", filePath)); - return false; - } else { - ALog.w(TAG, String.format("保存路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath)); - RecordUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD); - } + if (!CheckUtil.checkDPathConflicts(mWrapper.isIgnoreFilePathOccupy(), filePath, + mWrapper.getRequestType())) { + return false; } File newFile = new File(filePath); @@ -149,19 +164,19 @@ public class CheckDEntityUtil implements ICheckEntityUtil { mEntity.setFileName(newFile.getName()); // 如过使用Content-Disposition中的文件名,将不会执行重命名工作 - if (mWrapper.asHttp().isUseServerFileName() - || mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) { + Object usf = mWrapper.getOptionParams().getParam(IOptionConstant.useServerFileName); + if ((usf != null && (boolean) usf) || mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) { return true; } if (!TextUtils.isEmpty(mEntity.getFilePath())) { File oldFile = new File(mEntity.getFilePath()); 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())); } 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())); } } @@ -174,7 +189,7 @@ public class CheckDEntityUtil implements ICheckEntityUtil { if (TextUtils.isEmpty(url)) { ALog.e(TAG, "下载失败,url为null"); return false; - } else if (!CheckUtil.checkUrlNotThrow(url)) { + } else if (!CheckUtil.checkUrl(url)) { ALog.e(TAG, "下载失败,url【" + url + "】错误"); return false; } @@ -188,16 +203,4 @@ public class CheckDEntityUtil implements ICheckEntityUtil { } 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; - } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/CheckDGEntityUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/CheckDGEntityUtil.java index 94d3fc8b..67c6e0a2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/CheckDGEntityUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/CheckDGEntityUtil.java @@ -17,10 +17,13 @@ package com.arialyy.aria.core.download; import android.text.TextUtils; 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.IOptionConstant; import com.arialyy.aria.orm.DbEntity; 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 java.io.File; import java.util.ArrayList; @@ -39,12 +42,17 @@ public class CheckDGEntityUtil implements ICheckEntityUtil { * 是否需要修改路径 */ 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; mEntity = mWrapper.getEntity(); } @@ -55,34 +63,41 @@ public class CheckDGEntityUtil implements ICheckEntityUtil { * @return {@code true} 合法 */ private boolean checkDirPath() { - if (TextUtils.isEmpty(mWrapper.getDirPathTemp())) { + String dirPath = mWrapper.getDirPathTemp(); + + if (TextUtils.isEmpty(dirPath)) { ALog.e(TAG, "文件夹路径不能为null"); 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; + } + if (!dirPath.startsWith("/")) { + ALog.e(TAG, String.format("文件夹路径【%s】错误", dirPath)); return false; } - File file = new File(mWrapper.getDirPathTemp()); + if (file.isFile()) { - ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", mWrapper.getDirPathTemp())); + ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", dirPath)); return false; } - if ((mEntity.getDirPath() == null || !mEntity.getDirPath().equals(mWrapper.getDirPathTemp())) - && DbEntity.checkDataExist(DownloadGroupEntity.class, "dirPath=?", - mWrapper.getDirPathTemp())) { - ALog.e(TAG, String.format("文件夹路径【%s】已被其它任务占用,请重新设置文件夹路径", mWrapper.getDirPathTemp())); + // 检查路径冲突 + if (mWrapper.isNewTask() && !CheckUtil.checkDGPathConflicts(mWrapper.isIgnoreFilePathOccupy(), + dirPath)) { return false; } if (TextUtils.isEmpty(mEntity.getDirPath()) || !mEntity.getDirPath() - .equals(mWrapper.getDirPathTemp())) { + .equals(dirPath)) { if (!file.exists()) { file.mkdirs(); } needModifyPath = true; - mEntity.setDirPath(mWrapper.getDirPathTemp()); - ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", mWrapper.getDirPathTemp())); + mEntity.setDirPath(dirPath); + ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", dirPath)); } 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 public boolean checkEntity() { 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; } @@ -128,14 +176,16 @@ public class CheckDGEntityUtil implements ICheckEntityUtil { return false; } - if (!mWrapper.isUnknownSize() && mEntity.getFileSize() == 0) { + if (action != FeatureController.ACTION_CANCEL + && !mWrapper.isUnknownSize() + && mEntity.getFileSize() == 0) { ALog.e(TAG, "组合任务必须设置文件文件大小,默认需要强制设置文件大小。如果无法获取到总长度,请调用#unknownSize()来标志该组合任务"); return false; } - if (mWrapper.asHttp().getRequestEnum() == RequestEnum.POST) { - for (DTaskWrapper subTask : mWrapper.getSubTaskWrapper()) { - subTask.asHttp().setRequestEnum(RequestEnum.POST); + if (mWrapper.getOptionParams().getParam(IOptionConstant.requestEnum) == RequestEnum.POST) { + for (DTaskWrapper subWrapper : mWrapper.getSubTaskWrapper()) { + subWrapper.getOptionParams().setParams(IOptionConstant.requestEnum, RequestEnum.POST); } } @@ -168,13 +218,12 @@ public class CheckDGEntityUtil implements ICheckEntityUtil { if (!newName.equals(entity.getFileName())) { String oldPath = mEntity.getDirPath() + "/" + entity.getFileName(); String newPath = mEntity.getDirPath() + "/" + newName; - if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=? or isComplete='true'", - newPath)) { - ALog.w(TAG, String.format("更新文件名失败,路径【%s】已存在或文件已下载", newPath)); + if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", newPath)) { + ALog.w(TAG, String.format("更新文件名失败,路径【%s】被其它任务占用", newPath)); return; } - RecordUtil.modifyTaskRecord(oldPath, newPath); + RecordUtil.modifyTaskRecord(oldPath, newPath, mEntity.getTaskType()); entity.setFilePath(newPath); entity.setFileName(newName); } @@ -190,7 +239,7 @@ public class CheckDGEntityUtil implements ICheckEntityUtil { */ private boolean checkUrls() { if (mEntity.getUrls().isEmpty()) { - ALog.e(TAG, "下载失败,子任务下载列表为null"); + ALog.e(TAG, "操作失败,子任务下载列表为null"); return false; } @@ -235,9 +284,6 @@ public class CheckDGEntityUtil implements ICheckEntityUtil { mWrapper.getSubNameTemp().remove(index); } } - - mEntity.setGroupHash(CommonUtil.getMd5Code(mEntity.getUrls())); - return true; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/CheckFtpDirEntityUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/CheckFtpDirEntityUtil.java index 3f82a400..9e479fea 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/CheckFtpDirEntityUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/CheckFtpDirEntityUtil.java @@ -16,21 +16,25 @@ package com.arialyy.aria.core.download; import android.text.TextUtils; +import com.arialyy.aria.core.FtpUrlEntity; 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.CheckUtil; import java.io.File; public class CheckFtpDirEntityUtil implements ICheckEntityUtil { private final String TAG = "CheckFtpDirEntityUtil"; private DGTaskWrapper mWrapper; private DownloadGroupEntity mEntity; + private int action; - public static CheckFtpDirEntityUtil newInstance(DGTaskWrapper wrapper) { - return new CheckFtpDirEntityUtil(wrapper); + public static CheckFtpDirEntityUtil newInstance(DGTaskWrapper wrapper, int action) { + return new CheckFtpDirEntityUtil(wrapper, action); } - private CheckFtpDirEntityUtil(DGTaskWrapper wrapper) { + private CheckFtpDirEntityUtil(DGTaskWrapper wrapper, int action) { + this.action = action; mWrapper = wrapper; mEntity = mWrapper.getEntity(); } @@ -41,33 +45,34 @@ public class CheckFtpDirEntityUtil implements ICheckEntityUtil { * @return {@code true} 合法 */ private boolean checkDirPath() { - if (TextUtils.isEmpty(mWrapper.getDirPathTemp())) { + String dirPath = mWrapper.getDirPathTemp(); + if (TextUtils.isEmpty(dirPath)) { ALog.e(TAG, "文件夹路径不能为null"); 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; } - File file = new File(mWrapper.getDirPathTemp()); + File file = new File(dirPath); if (file.isFile()) { - ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", mWrapper.getDirPathTemp())); + ALog.e(TAG, String.format("路径【%s】是文件,请设置文件夹路径", dirPath)); return false; } - if ((mEntity.getDirPath() == null || !mEntity.getDirPath().equals(mWrapper.getDirPathTemp())) - && DbEntity.checkDataExist(DownloadGroupEntity.class, "dirPath=?", - mWrapper.getDirPathTemp())) { - ALog.e(TAG, String.format("文件夹路径【%s】已被其它任务占用,请重新设置文件夹路径", mWrapper.getDirPathTemp())); + // 检查路径冲突 + if (mWrapper.isNewTask() && !CheckUtil.checkDGPathConflicts(mWrapper.isIgnoreFilePathOccupy(), + dirPath)) { return false; } if (TextUtils.isEmpty(mEntity.getDirPath()) || !mEntity.getDirPath() - .equals(mWrapper.getDirPathTemp())) { + .equals(dirPath)) { if (!file.exists()) { file.mkdirs(); } - mEntity.setDirPath(mWrapper.getDirPathTemp()); - ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", mWrapper.getDirPathTemp())); + mEntity.setDirPath(dirPath); + ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", dirPath)); } return true; } @@ -75,7 +80,7 @@ public class CheckFtpDirEntityUtil implements ICheckEntityUtil { @Override public boolean checkEntity() { if (mWrapper.getErrorEvent() != null) { - ALog.e(TAG, mWrapper.getErrorEvent().errorMsg); + ALog.e(TAG, String.format("任务操作失败,%s", mWrapper.getErrorEvent().errorMsg)); return false; } @@ -83,12 +88,15 @@ public class CheckFtpDirEntityUtil implements ICheckEntityUtil { if (b) { mEntity.save(); } - if (mWrapper.asFtp().getUrlEntity().isFtps) { - if (TextUtils.isEmpty(mWrapper.asFtp().getUrlEntity().storePath)) { + FtpUrlEntity urlEntity = + (FtpUrlEntity) mWrapper.getOptionParams().getParam(IOptionConstant.ftpUrlEntity); + assert urlEntity != null; + if (urlEntity.isFtps) { + if (TextUtils.isEmpty(urlEntity.idEntity.storePath)) { ALog.e(TAG, "证书路径为空"); return false; } - if (TextUtils.isEmpty(mWrapper.asFtp().getUrlEntity().keyAlias)) { + if (TextUtils.isEmpty(urlEntity.idEntity.keyAlias)) { ALog.e(TAG, "证书别名为空"); return false; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTaskListener.java new file mode 100644 index 00000000..262ac178 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTaskListener.java @@ -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 { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java index 3c28d429..7766da1d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadReceiver.java @@ -15,35 +15,39 @@ */ 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.aria.core.AriaConfig; import com.arialyy.aria.core.AriaManager; 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.common.AbsBuilderTarget; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.common.ProxyHelper; -import com.arialyy.aria.core.download.group.FtpDirBuilderTarget; -import com.arialyy.aria.core.download.group.FtpDirNormalTarget; -import com.arialyy.aria.core.download.group.GroupBuilderTarget; -import com.arialyy.aria.core.download.group.GroupNormalTarget; -import com.arialyy.aria.core.download.group.GroupTargetFactory; -import com.arialyy.aria.core.download.normal.FtpBuilderTarget; -import com.arialyy.aria.core.download.normal.FtpNormalTarget; -import com.arialyy.aria.core.download.normal.HttpBuilderTarget; -import com.arialyy.aria.core.download.normal.HttpNormalTarget; -import com.arialyy.aria.core.download.normal.DNormalTargetFactory; +import com.arialyy.aria.core.download.target.DTargetFactory; +import com.arialyy.aria.core.download.target.FtpBuilderTarget; +import com.arialyy.aria.core.download.target.FtpDirBuilderTarget; +import com.arialyy.aria.core.download.target.FtpDirNormalTarget; +import com.arialyy.aria.core.download.target.FtpNormalTarget; +import com.arialyy.aria.core.download.target.GroupBuilderTarget; +import com.arialyy.aria.core.download.target.GroupNormalTarget; +import com.arialyy.aria.core.download.target.HttpBuilderTarget; +import com.arialyy.aria.core.download.target.HttpNormalTarget; 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.ITask; 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.task.ITask; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.ComponentUtil; import com.arialyy.aria.util.DbDataHelper; import java.util.ArrayList; import java.util.List; @@ -54,7 +58,10 @@ import java.util.Set; * 下载功能接收器 */ public class DownloadReceiver extends AbsReceiver { - private final String TAG = "DownloadReceiver"; + + public DownloadReceiver(Object obj) { + super(obj); + } /** * 设置最大下载速度,单位:kb @@ -64,7 +71,7 @@ public class DownloadReceiver extends AbsReceiver { */ @Deprecated public DownloadReceiver setMaxSpeed(int maxSpeed) { - AriaManager.getInstance().getDownloadConfig().setMaxSpeed(maxSpeed); + AriaConfig.getInstance().getDConfig().setMaxSpeed(maxSpeed); return this; } @@ -73,11 +80,10 @@ public class DownloadReceiver extends AbsReceiver { * * @param url 下载地址 */ - @CheckResult - public HttpBuilderTarget load(@NonNull String url) { - CheckUtil.checkUrlInvalidThrow(url); - return DNormalTargetFactory.getInstance() - .generateBuilderTarget(HttpBuilderTarget.class, url, targetName); + public HttpBuilderTarget load(String url) { + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP); + return DTargetFactory.getInstance() + .generateBuilderTarget(HttpBuilderTarget.class, url); } /** @@ -86,11 +92,10 @@ public class DownloadReceiver extends AbsReceiver { * @param taskId 任务id,可从{@link AbsBuilderTarget#create()}、{@link AbsBuilderTarget#add()}、{@link * AbsEntity#getId()}读取任务id */ - @CheckResult public HttpNormalTarget load(long taskId) { - CheckUtil.checkTaskId(taskId); - return DNormalTargetFactory.getInstance() - .generateNormalTarget(HttpNormalTarget.class, taskId, targetName); + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP); + return DTargetFactory.getInstance() + .generateNormalTarget(HttpNormalTarget.class, taskId); } /** @@ -98,33 +103,32 @@ public class DownloadReceiver extends AbsReceiver { * * @param urls 组合任务只任务列被,如果任务组的中的下载地址改变了,则任务从新的一个任务组 */ - @CheckResult public GroupBuilderTarget loadGroup(List urls) { - CheckUtil.checkDownloadUrls(urls); - return GroupTargetFactory.getInstance().generateGroupBuilderTarget(urls, targetName); + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP); + return DTargetFactory.getInstance().generateGroupBuilderTarget(urls); } /** * 加载组合任务,用于任务停止、删除等操作 * - * @param taskId 任务id,可从{@link AbsBuilderTarget#create()}、{@link AbsBuilderTarget#add()}、{@link - * * AbsEntity#getId()}读取任务id + * @param taskId 任务id,可从{@link AbsBuilderTarget#create}、{@link AbsBuilderTarget#add}、{@link + * AbsEntity#getId}读取任务id */ - @CheckResult + public GroupNormalTarget loadGroup(long taskId) { - CheckUtil.checkTaskId(taskId); - return GroupTargetFactory.getInstance() - .generateNormalTarget(GroupNormalTarget.class, taskId, targetName); + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP); + return DTargetFactory.getInstance() + .generateNormalTarget(GroupNormalTarget.class, taskId); } /** * 加载ftp单任务下载地址,用于任务第一次下载,如果需要控制任务停止或删除等操作,请使用{@link #loadFtp(long)} */ - @CheckResult - public FtpBuilderTarget loadFtp(@NonNull String url) { - CheckUtil.checkUrlInvalidThrow(url); - return DNormalTargetFactory.getInstance() - .generateBuilderTarget(FtpBuilderTarget.class, url, targetName); + + public FtpBuilderTarget loadFtp(String url) { + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP); + return DTargetFactory.getInstance() + .generateBuilderTarget(FtpBuilderTarget.class, url); } /** @@ -133,20 +137,20 @@ public class DownloadReceiver extends AbsReceiver { * @param taskId 任务id,可从{@link AbsBuilderTarget#create()}、{@link AbsBuilderTarget#add()}、{@link * AbsEntity#getId()}读取任务id */ - @CheckResult + public FtpNormalTarget loadFtp(long taskId) { - CheckUtil.checkTaskId(taskId); - return DNormalTargetFactory.getInstance() - .generateNormalTarget(FtpNormalTarget.class, taskId, targetName); + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP); + return DTargetFactory.getInstance() + .generateNormalTarget(FtpNormalTarget.class, taskId); } /** * 加载ftp文件夹下载地址,用于任务第一次下载,如果需要控制任务停止或删除等操作,请使用{@link #loadFtpDir(long)} */ - @CheckResult - public FtpDirBuilderTarget loadFtpDir(@NonNull String dirUrl) { - CheckUtil.checkUrlInvalidThrow(dirUrl); - return GroupTargetFactory.getInstance().generateDirBuilderTarget(dirUrl, targetName); + + public FtpDirBuilderTarget loadFtpDir(String dirUrl) { + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP); + return DTargetFactory.getInstance().generateDirBuilderTarget(dirUrl); } /** @@ -155,26 +159,38 @@ public class DownloadReceiver extends AbsReceiver { * @param taskId 任务id,可从{@link AbsBuilderTarget#create()}、{@link AbsBuilderTarget#add()}、{@link * AbsEntity#getId()}读取任务id */ - @CheckResult + public FtpDirNormalTarget loadFtpDir(long taskId) { - CheckUtil.checkTaskId(taskId); - return GroupTargetFactory.getInstance() - .generateNormalTarget(FtpDirNormalTarget.class, taskId, targetName); + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP); + return DTargetFactory.getInstance() + .generateNormalTarget(FtpDirNormalTarget.class, taskId); } /** * 将当前类注册到Aria */ public void register() { - if (TextUtils.isEmpty(targetName)) { - ALog.e(TAG, "download register target null"); + if (obj == null) { + ALog.e(TAG, String.format("register【%s】观察者为空", getTargetName())); return; } - Object obj = OBJ_MAP.get(getKey()); - if (obj == null) { - ALog.e(TAG, String.format("【%s】观察者为空", targetName)); + if (obj instanceof TaskInternalListenerInterface){ + ProxyHelper.getInstance().checkProxyType(obj.getClass()); + 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; } + Set set = ProxyHelper.getInstance().checkProxyType(obj.getClass()); if (set != null && !set.isEmpty()) { for (Integer type : set) { @@ -189,7 +205,7 @@ public class DownloadReceiver extends AbsReceiver { } } } 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 module类中销毁 */ @Override public void unRegister() { - if (needRmListener) { + if (isNeedRmListener()) { unRegisterListener(); } - AriaManager.getInstance().removeReceiver(OBJ_MAP.get(getKey())); + AriaManager.getInstance().removeReceiver(obj); } - @Override public String getType() { + @Override public ReceiverType getType() { return ReceiverType.DOWNLOAD; } @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) { - ALog.e(TAG, String.format("【%s】观察者为空", targetName)); + ALog.e(TAG, String.format("unRegister【%s】观察者为空", getTargetName())); return; } Set set = ProxyHelper.getInstance().mProxyCache.get(obj.getClass().getName()); @@ -230,7 +241,9 @@ public class DownloadReceiver extends AbsReceiver { for (Integer integer : set) { if (integer == ProxyHelper.PROXY_TYPE_DOWNLOAD) { TaskSchedulers.getInstance().unRegister(obj); - } else if (integer == ProxyHelper.PROXY_TYPE_DOWNLOAD_GROUP) { + continue; + } + if (integer == ProxyHelper.PROXY_TYPE_DOWNLOAD_GROUP) { TaskSchedulers.getInstance().unRegister(obj); } } @@ -243,7 +256,10 @@ public class DownloadReceiver extends AbsReceiver { * @param taskId 任务实体唯一id */ 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)); } @@ -254,7 +270,9 @@ public class DownloadReceiver extends AbsReceiver { * @return 如果url错误或查找不到数据,则返回null */ public DownloadEntity getFirstDownloadEntity(String downloadUrl) { - CheckUtil.checkUrl(downloadUrl); + if (!CheckUtil.checkUrl(downloadUrl)) { + return null; + } return DbEntity.findFirst(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl); } @@ -264,7 +282,9 @@ public class DownloadReceiver extends AbsReceiver { * @return 如果url错误或查找不到数据,则返回null */ public List getDownloadEntity(String downloadUrl) { - CheckUtil.checkUrl(downloadUrl); + if (!CheckUtil.checkUrl(downloadUrl)) { + return null; + } return DbEntity.findDatas(DownloadEntity.class, "url=? and isGroupChild='false'", downloadUrl); } @@ -275,7 +295,9 @@ public class DownloadReceiver extends AbsReceiver { * @return 如果实体不存在,返回null */ public DownloadGroupEntity getGroupEntity(long taskId) { - CheckUtil.checkTaskId(taskId); + if (taskId < 0) { + ALog.e(TAG, "任务Id错误"); + } return DbDataHelper.getDGEntity(taskId); } @@ -286,8 +308,10 @@ public class DownloadReceiver extends AbsReceiver { * @return 如果实体不存在,返回null */ public DownloadGroupEntity getGroupEntity(List urls) { - CheckUtil.checkDownloadUrls(urls); - return DbDataHelper.getDGEntity(CommonUtil.getMd5Code(urls)); + if (CheckUtil.checkDownloadUrlsIsEmpty(urls)) { + return null; + } + return DbDataHelper.getDGEntityByHash(CommonUtil.getMd5Code(urls)); } /** @@ -297,8 +321,28 @@ public class DownloadReceiver extends AbsReceiver { * @return 如果实体不存在,返回null */ public DownloadGroupEntity getFtpDirEntity(String url) { - CheckUtil.checkUrl(url); - return DbDataHelper.getDGEntity(url); + if (!CheckUtil.checkUrl(url)) { + return null; + } + return DbDataHelper.getDGEntityByHash(url); + } + + /** + * 获取执行中的任务 + * + * @return 没有执行中的任务,返回null + */ + public List getDRunningTask() { + return DTaskQueue.getInstance().getRunningTask(DownloadEntity.class); + } + + /** + * 获取执行中的任务 + * + * @return 没有执行中的任务,返回null + */ + public List getDGRunningTask() { + return DGroupTaskQueue.getInstance().getRunningTask(DownloadGroupEntity.class); } /** @@ -396,15 +440,7 @@ public class DownloadReceiver extends AbsReceiver { * @return 如果没有任务组列表,则返回null */ public List getGroupTaskList() { - List wrappers = DbEntity.findRelationData(DGEntityWrapper.class); - if (wrappers == null || wrappers.isEmpty()) { - return null; - } - List entities = new ArrayList<>(); - for (DGEntityWrapper wrapper : wrappers) { - entities.add(wrapper.groupEntity); - } - return entities; + return getGroupTaskList(1, 10); } /** @@ -471,7 +507,7 @@ public class DownloadReceiver extends AbsReceiver { public void removeAllTask(boolean removeFile) { final AriaManager ariaManager = AriaManager.getInstance(); CancelAllCmd cancelCmd = - (CancelAllCmd) CommonUtil.createNormalCmd(new DTaskWrapper(null), + (CancelAllCmd) CmdHelper.createNormalCmd(new DTaskWrapper(null), NormalCmdFactory.TASK_CANCEL_ALL, ITask.DOWNLOAD); cancelCmd.removeFile = removeFile; EventMsgUtil.getDefault().post(cancelCmd); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskListener.java new file mode 100644 index 00000000..84418149 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTaskListener.java @@ -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 { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java deleted file mode 100644 index 3fb04c6c..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java +++ /dev/null @@ -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 { - 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 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); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java deleted file mode 100644 index a3f81b8c..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpFileInfoThread.java +++ /dev/null @@ -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 { - 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)); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java deleted file mode 100644 index ad28bb41..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java deleted file mode 100644 index a0335b02..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java +++ /dev/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 mExeLoader = new WeakHashMap<>(); - private Map 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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/ChildDLoadListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/ChildDLoadListener.java deleted file mode 100644 index 4cd0ec03..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/ChildDLoadListener.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/DGroupUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/DGroupUtil.java deleted file mode 100644 index 272583ef..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/DGroupUtil.java +++ /dev/null @@ -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 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()); - } -} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java deleted file mode 100644 index 85935c0b..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java +++ /dev/null @@ -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()); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirInfoThread.java deleted file mode 100644 index d9e54dac..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirInfoThread.java +++ /dev/null @@ -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 { - - 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()); - } - 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); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupTargetFactory.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupTargetFactory.java deleted file mode 100644 index 73ab3772..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupTargetFactory.java +++ /dev/null @@ -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 generateNormalTarget(Class 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 urls, String targetName) { - return new GroupBuilderTarget(urls, targetName); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/SimpleSchedulers.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/SimpleSchedulers.java deleted file mode 100644 index 59633f6d..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/SimpleSchedulers.java +++ /dev/null @@ -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、子任务失败次数大于等于配置的重试次数,才能认为子任务停止 - * 2、stopNum + failNum + completeNum + cacheNum == subSize,则认为组合任务停止 - * 3、failNum == 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、所有的子任务已经停止,则认为组合任务停止 - * 2、completeNum + 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, "没有下一子任务"); - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/SubDLoadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/SubDLoadUtil.java deleted file mode 100644 index ee9713b8..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/SubDLoadUtil.java +++ /dev/null @@ -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())); - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java deleted file mode 100644 index beea90f8..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java +++ /dev/null @@ -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 extends BaseDelegate { - 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 generateIndexFile() { - mTaskWrapper.asM3U8().setGenerateIndexFileTemp(true); - return this; - } - - /** - * 是否合并ts文件,默认合并ts - * - * @param merge {@code true}合并所有ts文件为一个 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public M3U8Delegate merge(boolean merge) { - mTaskWrapper.asM3U8().setMergeFile(merge); - return this; - } - - /** - * 如果你希望使用自行处理ts文件的合并,可以使用{@link ITsMergeHandler}处理ts文件的合并 - * 需要注意的是:只有{@link #merge(boolean)}设置合并ts文件,该方法才会生效 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public M3U8Delegate 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 setTsUrlConvert(IVodTsUrlConverter converter) { - mTaskWrapper.asM3U8().setVodUrlConverter(converter); - return this; - } - - /** - * 选择需要下载的码率,默认下载的码率 - * - * @param bandWidth 指定的码率 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public M3U8Delegate 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 setBandWidthUrlConverter(IBandWidthUrlConverter converter) { - mTaskWrapper.asM3U8().setBandWidthUrlConverter(converter); - return this; - } - - /** - * 处理点播文件的下载参数 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public M3U8VodDelegate asVod() { - return new M3U8VodDelegate<>(mTarget, mTaskWrapper); - } - - /** - * 处理直播类的下载 - */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public M3U8LiveDelegate asLive() { - return new M3U8LiveDelegate<>(mTarget, mTaskWrapper); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveLoader.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveLoader.java deleted file mode 100644 index 7986096c..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveLoader.java +++ /dev/null @@ -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 mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM); - private LiveStateManager mManager; - private ReentrantLock LOCK = new ReentrantLock(); - private Condition mCondition = LOCK.newCondition(); - private LinkedBlockingQueue 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 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 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; - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveOption.java similarity index 52% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java rename to Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveOption.java index edf7cf36..c8a3b8f3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveOption.java @@ -15,48 +15,46 @@ */ 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; +import com.arialyy.aria.core.processor.ILiveTsUrlConverter; import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CheckUtil; /** * m3u8直播参数设置 */ -public class M3U8LiveDelegate extends BaseDelegate { +public class M3U8LiveOption extends M3U8Option { - M3U8LiveDelegate(TARGET target, AbsTaskWrapper wrapper) { - super(target, wrapper); - getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE); + private ILiveTsUrlConverter liveTsUrlConverter; + private long liveUpdateInterval; + + public M3U8LiveOption() { + super(); } /** * M3U8 ts 文件url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的 * 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 * - * @param converter {@link ILiveTsUrlConverter} + * @param liveTsUrlConverter {@link ILiveTsUrlConverter} */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public M3U8LiveDelegate setLiveTsUrlConvert(ILiveTsUrlConverter converter) { - ((DTaskWrapper) getTaskWrapper()).asM3U8().setLiveTsUrlConverter(converter); + public M3U8LiveOption setLiveTsUrlConvert(ILiveTsUrlConverter liveTsUrlConverter) { + CheckUtil.checkMemberClass(liveTsUrlConverter.getClass()); + this.liveTsUrlConverter = liveTsUrlConverter; return this; } /** * 设置直播的m3u8文件更新间隔,默认10000微秒。 * - * @param interval 更新间隔,单位微秒 + * @param liveUpdateInterval 更新间隔,单位微秒 */ - @CheckResult(suggest = Suggest.TO_CONTROLLER) - public M3U8LiveDelegate setM3U8FileUpdateInterval(long interval) { - if (interval <= 1) { + public M3U8LiveOption setM3U8FileUpdateInterval(long liveUpdateInterval) { + if (liveUpdateInterval <= 1) { ALog.e(TAG, "间隔时间错误"); return this; } - ((DTaskWrapper) getTaskWrapper()).asM3U8().setLiveUpdateInterval(interval); + + this.liveUpdateInterval = liveUpdateInterval; return this; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveUtil.java deleted file mode 100644 index 45fa8f5b..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveUtil.java +++ /dev/null @@ -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 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); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Option.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Option.java new file mode 100644 index 00000000..7bc57a10 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Option.java @@ -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 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; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodDelegate.java deleted file mode 100644 index c69da6d4..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodDelegate.java +++ /dev/null @@ -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 extends BaseDelegate { - 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 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 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 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)); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodOption.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodOption.java new file mode 100644 index 00000000..563224c7 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodOption.java @@ -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 { + 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; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodUtil.java deleted file mode 100644 index 6bb06896..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodUtil.java +++ /dev/null @@ -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 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) info.obj)); - } else { - mUrls.addAll( - converter.convert(mWrapper.asM3U8().getBandWidthUrl(), (List) info.obj)); - } - } else { - mUrls.addAll((Collection) 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(); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupConfigHandler.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/AbsGroupConfigHandler.java similarity index 80% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupConfigHandler.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/AbsGroupConfigHandler.java index 30a26e2f..3eec7170 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupConfigHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/AbsGroupConfigHandler.java @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * 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 com.arialyy.aria.core.common.AbsNormalTarget; import com.arialyy.aria.core.download.DGTaskWrapper; 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.inf.AbsTarget; import com.arialyy.aria.core.inf.IConfigHandler; import com.arialyy.aria.core.manager.SubTaskManager; 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.util.CommonUtil; @@ -44,9 +44,15 @@ abstract class AbsGroupConfigHandler implements IConfi TAG = CommonUtil.getClassName(getClass()); mTarget = target; 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); if (getEntity() != null) { getTaskWrapper().setDirPathTemp(getEntity().getDirPath()); @@ -58,10 +64,9 @@ abstract class AbsGroupConfigHandler implements IConfi * * @return 子任务管理器 */ - @CheckResult SubTaskManager getSubTaskManager() { if (mSubTaskManager == null) { - mSubTaskManager = new SubTaskManager(mTarget.getTargetName(), getTaskWrapper()); + mSubTaskManager = new SubTaskManager(getTaskWrapper()); } return mSubTaskManager; } @@ -77,11 +82,10 @@ abstract class AbsGroupConfigHandler implements IConfi } @Override public boolean isRunning() { - DownloadGroupTask task = DownloadGroupTaskQueue.getInstance().getTask(getEntity().getKey()); + DownloadGroupTask task = DGroupTaskQueue.getInstance().getTask(getEntity().getKey()); return task != null && task.isRunning(); } - @CheckResult TARGET setDirPath(String dirPath) { mWrapper.setDirPathTemp(dirPath); return mTarget; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/normal/DNormalConfigHandler.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/DNormalConfigHandler.java similarity index 78% rename from Aria/src/main/java/com/arialyy/aria/core/download/normal/DNormalConfigHandler.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/DNormalConfigHandler.java index 2c86fc08..fc610f31 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/normal/DNormalConfigHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/DNormalConfigHandler.java @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.normal; +package com.arialyy.aria.core.download.target; import android.text.TextUtils; +import com.arialyy.aria.core.common.AbsNormalTarget; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.event.ErrorEvent; import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.IConfigHandler; 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.util.ALog; @@ -40,26 +41,30 @@ class DNormalConfigHandler implements IConfigHandler { /** * @param taskId 第一次下载,taskId为-1 */ - DNormalConfigHandler(TARGET target, long taskId, String targetName) { + DNormalConfigHandler(TARGET target, long taskId) { 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); - 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); if (mEntity != null) { getWrapper().setTempFilePath(mEntity.getFilePath()); } } - public TARGET updateUrl(String newUrl) { + TARGET updateUrl(String newUrl) { if (TextUtils.isEmpty(newUrl)) { ALog.e(TAG, "url更新失败,newUrl为null"); return mTarget; @@ -84,15 +89,16 @@ class DNormalConfigHandler implements IConfigHandler { } @Override public boolean isRunning() { - return DownloadTaskQueue.getInstance().taskIsRunning(mEntity.getKey()); + return DTaskQueue.getInstance().taskIsRunning(mEntity.getKey()); } void setForceDownload(boolean forceDownload) { - getWrapper().setForceDownload(forceDownload); + getWrapper().setIgnoreFilePathOccupy(forceDownload); } void setUrl(String url) { mEntity.setUrl(url); + mWrapper.setTempUrl(url); } String getUrl() { diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/normal/DNormalTargetFactory.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/DTargetFactory.java similarity index 55% rename from Aria/src/main/java/com/arialyy/aria/core/download/normal/DNormalTargetFactory.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/DTargetFactory.java index 31655cb3..8e25cbac 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/normal/DNormalTargetFactory.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/DTargetFactory.java @@ -13,29 +13,30 @@ * See the License for the specific language governing permissions and * 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.AbsNormalTarget; +import java.util.List; /** - * @Author aria - * @Date 2019-09-05 + * @author aria + * 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) { - synchronized (DNormalTargetFactory.class) { + synchronized (DTargetFactory.class) { if (INSTANCE == null) { - INSTANCE = new DNormalTargetFactory(); + INSTANCE = new DTargetFactory(); } } } @@ -43,27 +44,37 @@ public class DNormalTargetFactory { return INSTANCE; } - public T generateNormalTarget(Class clazz, long taskId, - String targetName) { + public T generateNormalTarget(Class clazz, long taskId) { T target = null; if (clazz == HttpNormalTarget.class) { - target = (T) new HttpNormalTarget(taskId, targetName); + target = (T) new HttpNormalTarget(taskId); } 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; } - public T generateBuilderTarget(Class clazz, String url, - String targetName) { + public T generateBuilderTarget(Class clazz, String url) { T target = null; if (clazz == HttpBuilderTarget.class) { - target = (T) new HttpBuilderTarget(url, targetName); + target = (T) new HttpBuilderTarget(url); } else if (clazz == FtpBuilderTarget.class) { - target = (T) new FtpBuilderTarget(url, targetName); + target = (T) new FtpBuilderTarget(url); } return target; } + + public FtpDirBuilderTarget generateDirBuilderTarget(String url) { + return new FtpDirBuilderTarget(url); + } + + public GroupBuilderTarget generateGroupBuilderTarget(List urls) { + return new GroupBuilderTarget(urls); + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/normal/FtpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java similarity index 62% rename from Aria/src/main/java/com/arialyy/aria/core/download/normal/FtpBuilderTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java index 6f904f73..476c5db1 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/normal/FtpBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpBuilderTarget.java @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.ftp.FtpDelegate; +import com.arialyy.aria.core.common.FtpOption; +import com.arialyy.aria.core.common.SFtpOption; 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; /** @@ -30,19 +28,38 @@ import com.arialyy.aria.util.CommonUtil; public class FtpBuilderTarget extends AbsBuilderTarget { private DNormalConfigHandler mConfigHandler; - FtpBuilderTarget(String url, String targetName) { - mConfigHandler = new DNormalConfigHandler<>(this, -1, targetName); + FtpBuilderTarget(String url) { + mConfigHandler = new DNormalConfigHandler<>(this, -1); mConfigHandler.setUrl(url); - getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(url)); getTaskWrapper().setRequestType(ITaskWrapper.D_FTP); + getEntity().setTaskType(ITaskWrapper.D_FTP); + getTaskWrapper().setNewTask(true); } /** * 设置登陆、字符串编码、ftps等参数 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpDelegate option() { - return new FtpDelegate<>(this, getTaskWrapper()); + public FtpBuilderTarget option(FtpOption option) { + if (option == null) { + 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 { * 1、如果保存路径是该文件的保存路径,如:/mnt/sdcard/file.zip,则使用路径中的文件名file.zip * 2、如果保存路径是文件夹路径,如:/mnt/sdcard/,则使用FTP服务器该文件的文件名 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpBuilderTarget setFilePath(@NonNull String filePath) { + public FtpBuilderTarget setFilePath(String filePath) { int lastIndex = mConfigHandler.getUrl().lastIndexOf("/"); getEntity().setFileName(mConfigHandler.getUrl().substring(lastIndex + 1)); mConfigHandler.setTempFilePath(filePath); @@ -66,9 +82,10 @@ public class FtpBuilderTarget extends AbsBuilderTarget { * * @param filePath 路径必须为文件路径,不能为文件夹路径 * @param forceDownload {@code true}强制下载,不考虑文件路径是否被占用 + * @deprecated 使用 {@link #ignoreFilePathOccupy()} */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpBuilderTarget setFilePath(@NonNull String filePath, boolean forceDownload) { + @Deprecated + public FtpBuilderTarget setFilePath(String filePath, boolean forceDownload) { mConfigHandler.setTempFilePath(filePath); mConfigHandler.setForceDownload(forceDownload); return this; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirBuilderTarget.java similarity index 77% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirBuilderTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirBuilderTarget.java index c581a363..efd29d3a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirBuilderTarget.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.ftp.FtpDelegate; +import com.arialyy.aria.core.common.FtpOption; import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.manager.SubTaskManager; import com.arialyy.aria.util.CommonUtil; @@ -29,12 +27,13 @@ import com.arialyy.aria.util.CommonUtil; */ public class FtpDirBuilderTarget extends AbsBuilderTarget { private FtpDirConfigHandler mConfigHandler; + private String url; - FtpDirBuilderTarget(String url, String targetName) { - setTargetName(targetName); + FtpDirBuilderTarget(String url) { + this.url = url; mConfigHandler = new FtpDirConfigHandler<>(this, -1); getEntity().setGroupHash(url); - getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(url)); + getTaskWrapper().setNewTask(true); } /** @@ -56,7 +55,6 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget { * * @param dirPath 任务组保存文件夹路径 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpDirBuilderTarget setDirPath(String dirPath) { return mConfigHandler.setDirPath(dirPath); } @@ -64,7 +62,6 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget { /** * 设置任务组别名 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpDirBuilderTarget setGroupAlias(String alias) { mConfigHandler.setGroupAlias(alias); return this; @@ -73,9 +70,13 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget { /** * 设置登陆、字符串编码、ftps等参数 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpDelegate option() { - return new FtpDelegate<>(this, getTaskWrapper()); + public FtpDirBuilderTarget option(FtpOption option) { + if (option == null) { + throw new NullPointerException("ftp 任务配置为空"); + } + option.setUrlEntity(CommonUtil.getFtpUrlInfo(url)); + getTaskWrapper().getOptionParams().setParams(option); + return this; } @Override public DownloadGroupEntity getEntity() { @@ -87,7 +88,6 @@ public class FtpDirBuilderTarget extends AbsBuilderTarget { * * @return 子任务管理器 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public SubTaskManager getSubTaskManager() { return mConfigHandler.getSubTaskManager(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirConfigHandler.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirConfigHandler.java similarity index 77% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirConfigHandler.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirConfigHandler.java index ef75325d..3595874f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirConfigHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirConfigHandler.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * 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.inf.AbsTarget; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.ITaskWrapper; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.CommonUtil; import java.util.List; /** @@ -33,11 +35,12 @@ class FtpDirConfigHandler extends AbsGroupConfigHandle } private void init() { - getTaskWrapper().setRequestType(AbsTaskWrapper.D_FTP_DIR); + getTaskWrapper().setRequestType(ITaskWrapper.D_FTP_DIR); List wrappers = getTaskWrapper().getSubTaskWrapper(); if (!wrappers.isEmpty()) { for (DTaskWrapper subWrapper : wrappers) { subWrapper.setRequestType(ITaskWrapper.D_FTP); + subWrapper.getEntity().setTaskType(ITaskWrapper.D_FTP); } } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirNormalTarget.java similarity index 76% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirNormalTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirNormalTarget.java index 45bd433b..b79d158b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpDirNormalTarget.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.ftp.FtpDelegate; +import com.arialyy.aria.core.common.FtpOption; 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.util.CommonUtil; @@ -29,11 +28,11 @@ import com.arialyy.aria.util.CommonUtil; */ public class FtpDirNormalTarget extends AbsNormalTarget { private FtpDirConfigHandler mConfigHandler; + private FtpOption option; - FtpDirNormalTarget(long taskId, String targetName) { - setTargetName(targetName); + FtpDirNormalTarget(long taskId) { mConfigHandler = new FtpDirConfigHandler<>(this, taskId); - getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getKey())); + getTaskWrapper().setNewTask(false); } @Override public boolean isRunning() { @@ -63,7 +62,6 @@ public class FtpDirNormalTarget extends AbsNormalTarget { * * @param dirPath 任务组保存文件夹路径 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpDirNormalTarget modifyDirPath(String dirPath) { return mConfigHandler.setDirPath(dirPath); } @@ -71,9 +69,12 @@ public class FtpDirNormalTarget extends AbsNormalTarget { /** * 设置登陆、字符串编码、ftps等参数 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpDelegate option() { - return new FtpDelegate<>(this, getTaskWrapper()); + public FtpDirNormalTarget option(FtpOption option) { + if (option == null) { + throw new NullPointerException("ftp 任务配置为空"); + } + this.option = option; + return this; } @Override public DownloadGroupEntity getEntity() { @@ -85,8 +86,16 @@ public class FtpDirNormalTarget extends AbsNormalTarget { * * @return 子任务管理器 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public SubTaskManager 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); + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/normal/FtpNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java similarity index 63% rename from Aria/src/main/java/com/arialyy/aria/core/download/normal/FtpNormalTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java index 10aa07c8..68b13de0 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/normal/FtpNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/FtpNormalTarget.java @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.ftp.FtpDelegate; +import com.arialyy.aria.core.common.FtpOption; +import com.arialyy.aria.core.common.SFtpOption; 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; /** @@ -31,18 +29,36 @@ import com.arialyy.aria.util.CommonUtil; public class FtpNormalTarget extends AbsNormalTarget { private DNormalConfigHandler mConfigHandler; - FtpNormalTarget(long taskId, String targetName) { - mConfigHandler = new DNormalConfigHandler<>(this, taskId, targetName); - getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getUrl())); + FtpNormalTarget(long taskId) { + mConfigHandler = new DNormalConfigHandler<>(this, taskId); getTaskWrapper().setRequestType(ITaskWrapper.D_FTP); + getTaskWrapper().setNewTask(false); } /** * 设置登陆、字符串编码、ftps等参数 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpDelegate option() { - return new FtpDelegate<>(this, getTaskWrapper()); + public FtpNormalTarget option(FtpOption option) { + if (option == null) { + 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 { * 1、如果保存路径是该文件的保存路径,如:/mnt/sdcard/file.zip,则使用路径中的文件名file.zip * 2、如果保存路径是文件夹路径,如:/mnt/sdcard/,则使用FTP服务器该文件的文件名 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpNormalTarget modifyFilePath(@NonNull String filePath) { + public FtpNormalTarget modifyFilePath(String filePath) { int lastIndex = mConfigHandler.getUrl().lastIndexOf("/"); getEntity().setFileName(mConfigHandler.getUrl().substring(lastIndex + 1)); mConfigHandler.setTempFilePath(filePath); @@ -62,7 +77,6 @@ public class FtpNormalTarget extends AbsNormalTarget { /** * 更新下载地址 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public FtpNormalTarget updateUrl(String newUrl) { return mConfigHandler.updateUrl(newUrl); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/GroupBuilderTarget.java similarity index 76% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/GroupBuilderTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/GroupBuilderTarget.java index c9dd1724..79896fae 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/GroupBuilderTarget.java @@ -13,16 +13,14 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.http.HttpDelegate; +import com.arialyy.aria.core.common.HttpOption; +import com.arialyy.aria.core.download.AbsGroupTaskWrapper; 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.wrapper.ITaskWrapper; import com.arialyy.aria.util.ALog; import java.util.List; @@ -33,19 +31,22 @@ import java.util.List; public class GroupBuilderTarget extends AbsBuilderTarget { private HttpGroupConfigHandler mConfigHandler; - GroupBuilderTarget(List urls, String targetName) { - setTargetName(targetName); + GroupBuilderTarget(List urls) { mConfigHandler = new HttpGroupConfigHandler<>(this, -1); - getTaskWrapper().setRequestType(ITaskWrapper.DG_HTTP); mConfigHandler.setGroupUrl(urls); + getTaskWrapper().setRequestType(ITaskWrapper.DG_HTTP); + getTaskWrapper().setNewTask(true); } /** * 设置http请求参数,header等信息 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpDelegate option() { - return new HttpDelegate<>(this, getTaskWrapper()); + public GroupBuilderTarget option(HttpOption option) { + if (option == null) { + throw new NullPointerException("任务配置为空"); + } + getTaskWrapper().getOptionParams().setParams(option); + return this; } /** @@ -56,7 +57,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget { * * @param fileSize 任务组总大小 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupBuilderTarget setFileSize(long fileSize) { if (fileSize <= 0) { ALog.e(TAG, "文件大小不能小于 0"); @@ -75,7 +75,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget { * 2、如果你的知道组合任务的总长度,请使用{@link #setFileSize(long)}设置组合任务的长度。 * 3、由于网络或其它原因的存在,这种方式获取的组合任务大小有可能是不准确的。 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupBuilderTarget unknownSize() { ((DGTaskWrapper) getTaskWrapper()).setUnknownSize(true); return this; @@ -86,7 +85,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget { * * @return 子任务管理器 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public SubTaskManager getSubTaskManager() { return mConfigHandler.getSubTaskManager(); } @@ -96,7 +94,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget { * * @deprecated {@link #setSubFileName(List)} 请使用该api */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) @Deprecated public GroupBuilderTarget setSubTaskFileName(List subTaskFileName) { return setSubFileName(subTaskFileName); } @@ -104,12 +101,19 @@ public class GroupBuilderTarget extends AbsBuilderTarget { /** * 设置任务组别名 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupBuilderTarget setGroupAlias(String alias) { mConfigHandler.setGroupAlias(alias); return this; } + /** + * {@code true} 忽略任务冲突,不考虑组任务hash冲突的情况 + */ + public GroupBuilderTarget ignoreTaskOccupy() { + ((AbsGroupTaskWrapper) getTaskWrapper()).setIgnoreTaskOccupy(true); + return this; + } + /** * 设置任务组的文件夹路径,在Aria中,任务组的所有子任务都会下载到以任务组组名的文件夹中。 * 如:groupDirPath = "/mnt/sdcard/download/group_test" @@ -129,7 +133,6 @@ public class GroupBuilderTarget extends AbsBuilderTarget { * * @param dirPath 任务组保存文件夹路径 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupBuilderTarget setDirPath(String dirPath) { return mConfigHandler.setDirPath(dirPath); } @@ -137,21 +140,7 @@ public class GroupBuilderTarget extends AbsBuilderTarget { /** * 设置子任务文件名,该方法必须在{@link #setDirPath(String)}之后调用,否则不生效 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupBuilderTarget setSubFileName(List 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; - } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/GroupNormalTarget.java similarity index 54% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/GroupNormalTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/GroupNormalTarget.java index 4ba0cbed..ad6a57ea 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/GroupNormalTarget.java @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.http.HttpDelegate; -import com.arialyy.aria.core.inf.ITaskWrapper; +import com.arialyy.aria.core.common.HttpOption; +import com.arialyy.aria.core.download.AbsGroupTaskWrapper; +import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.manager.SubTaskManager; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.ALog; import java.util.List; /** @@ -30,18 +31,21 @@ import java.util.List; public class GroupNormalTarget extends AbsNormalTarget { private HttpGroupConfigHandler mConfigHandler; - GroupNormalTarget(long taskId, String targetName) { - setTargetName(targetName); + GroupNormalTarget(long taskId) { mConfigHandler = new HttpGroupConfigHandler<>(this, taskId); getTaskWrapper().setRequestType(ITaskWrapper.DG_HTTP); + getTaskWrapper().setNewTask(false); } /** * 设置http请求参数,header等信息 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpDelegate option() { - return new HttpDelegate<>(this, getTaskWrapper()); + public GroupNormalTarget option(HttpOption option) { + if (option == null) { + throw new NullPointerException("任务配置为空"); + } + getTaskWrapper().getOptionParams().setParams(option); + return this; } /** @@ -49,7 +53,6 @@ public class GroupNormalTarget extends AbsNormalTarget { * * @return 子任务管理器 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public SubTaskManager getSubTaskManager() { return mConfigHandler.getSubTaskManager(); } @@ -57,7 +60,6 @@ public class GroupNormalTarget extends AbsNormalTarget { /** * 设置任务组别名 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupNormalTarget setGroupAlias(String alias) { mConfigHandler.setGroupAlias(alias); return this; @@ -68,11 +70,18 @@ public class GroupNormalTarget extends AbsNormalTarget { * * @param urls 新的组合任务下载地址列表 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupNormalTarget updateUrls(List urls) { return mConfigHandler.updateUrls(urls); } + /** + * {@code true} 忽略任务冲突,不考虑组任务hash冲突的情况 + */ + public GroupNormalTarget ignoreTaskOccupy() { + ((AbsGroupTaskWrapper) getTaskWrapper()).setIgnoreTaskOccupy(true); + return this; + } + /** * 更新任务组的文件夹路径,在Aria中,任务组的所有子任务都会下载到以任务组组名的文件夹中。 * 如:groupDirPath = "/mnt/sdcard/download/group_test" @@ -92,7 +101,6 @@ public class GroupNormalTarget extends AbsNormalTarget { * * @param dirPath 任务组保存文件夹路径 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupNormalTarget modifyDirPath(String dirPath) { return mConfigHandler.setDirPath(dirPath); } @@ -100,11 +108,41 @@ public class GroupNormalTarget extends AbsNormalTarget { /** * 更新子任务文件名,该方法必须在{@link #modifyDirPath(String)}之后调用,否则不生效 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public GroupNormalTarget modifySubFileName(List 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() { return mConfigHandler.isRunning(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/normal/HttpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/HttpBuilderTarget.java similarity index 50% rename from Aria/src/main/java/com/arialyy/aria/core/download/normal/HttpBuilderTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/HttpBuilderTarget.java index 7f68a5a7..10d07f42 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/normal/HttpBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/HttpBuilderTarget.java @@ -13,48 +13,56 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.http.HttpDelegate; -import com.arialyy.aria.core.download.m3u8.M3U8Delegate; -import com.arialyy.aria.core.inf.IHttpFileLenAdapter; -import com.arialyy.aria.core.inf.ITaskWrapper; +import com.arialyy.aria.core.common.HttpOption; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.m3u8.M3U8LiveOption; +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 { private DNormalConfigHandler mConfigHandler; - HttpBuilderTarget(String url, String targetName) { - mConfigHandler = new DNormalConfigHandler<>(this, -1, targetName); - getTaskWrapper().setRequestType(ITaskWrapper.D_HTTP); + HttpBuilderTarget(String url) { + mConfigHandler = new DNormalConfigHandler<>(this, -1); mConfigHandler.setUrl(url); + getTaskWrapper().setRequestType(ITaskWrapper.D_HTTP); + getTaskWrapper().setNewTask(true); + ((DownloadEntity)getEntity()).setTaskType(ITaskWrapper.D_HTTP); } - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public M3U8Delegate asM3U8() { - return new M3U8Delegate<>(this, getTaskWrapper()); + public HttpBuilderTarget m3u8VodOption(M3U8VodOption m3U8VodOption) { + if (m3U8VodOption == null) { + throw new NullPointerException("m3u8任务设置为空"); + } + getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_VOD); + getTaskWrapper().getEntity().setFileSize(m3U8VodOption.getFileSize()); + ((DTaskWrapper) getTaskWrapper()).getM3U8Params().setParams(m3U8VodOption); + return this; } - /** - * 设置http请求参数,header等信息 - */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpDelegate option() { - return new HttpDelegate<>(this, getTaskWrapper()); + public HttpBuilderTarget m3u8LiveOption(M3U8LiveOption m3U8LiveOption) { + if (m3U8LiveOption == null) { + throw new NullPointerException("m3u8任务设置为空"); + } + getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE); + ((DTaskWrapper) getTaskWrapper()).getM3U8Params().setParams(m3U8LiveOption); + return this; } /** - * 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment;filename=***} - * 如果获取不到服务器文件名,则使用用户设置的文件名 - * - * @param use {@code true} 使用 + * 设置http请求参数,header等信息 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpBuilderTarget useServerFileName(boolean use) { - getTaskWrapper().asHttp().setUseServerFileName(use); + public HttpBuilderTarget option(HttpOption option) { + if (option == null) { + throw new NullPointerException("任务配置为空"); + } + getTaskWrapper().getOptionParams().setParams(option); return this; } @@ -65,8 +73,7 @@ public class HttpBuilderTarget extends AbsBuilderTarget { * * @param filePath 路径必须为文件路径,不能为文件夹路径 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpBuilderTarget setFilePath(@NonNull String filePath) { + public HttpBuilderTarget setFilePath(String filePath) { mConfigHandler.setTempFilePath(filePath); return this; } @@ -78,23 +85,12 @@ public class HttpBuilderTarget extends AbsBuilderTarget { * * @param filePath 路径必须为文件路径,不能为文件夹路径 * @param forceDownload {@code true}强制下载,不考虑文件路径是否被占用 + * @deprecated 使用 {@link #ignoreFilePathOccupy()} */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpBuilderTarget setFilePath(@NonNull String filePath, boolean forceDownload) { + @Deprecated + public HttpBuilderTarget setFilePath(String filePath, boolean forceDownload) { mConfigHandler.setTempFilePath(filePath); mConfigHandler.setForceDownload(forceDownload); 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; - } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/HttpGroupConfigHandler.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/HttpGroupConfigHandler.java similarity index 96% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/HttpGroupConfigHandler.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/HttpGroupConfigHandler.java index b5a3c914..77249633 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/HttpGroupConfigHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/HttpGroupConfigHandler.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * 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.DownloadEntity; import com.arialyy.aria.core.inf.AbsTarget; +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; @@ -59,6 +59,7 @@ class HttpGroupConfigHandler extends AbsGroupConfigHan List subEntities = DbDataHelper.createHttpSubTask(groupHash, mUrls); List wrappers = new ArrayList<>(); for (DownloadEntity subEntity : subEntities) { + subEntity.setTaskType(ITaskWrapper.D_HTTP); wrappers.add(new DTaskWrapper(subEntity)); } getEntity().setUrls(urls); @@ -69,7 +70,6 @@ class HttpGroupConfigHandler extends AbsGroupConfigHan /** * 设置子任务文件名,该方法必须在{@link #setDirPath(String)}之后调用,否则不生效 */ - @CheckResult TARGET setSubFileName(List subTaskFileName) { if (subTaskFileName == null || subTaskFileName.isEmpty()) { ALog.w(TAG, "修改子任务的文件名失败:列表为null"); @@ -90,7 +90,6 @@ class HttpGroupConfigHandler extends AbsGroupConfigHan * * @param urls 新的组合任务下载地址列表 */ - @CheckResult TARGET updateUrls(List urls) { if (urls == null || urls.isEmpty()) { throw new NullPointerException("下载地址列表为空"); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/normal/HttpNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/HttpNormalTarget.java similarity index 55% rename from Aria/src/main/java/com/arialyy/aria/core/download/normal/HttpNormalTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/download/target/HttpNormalTarget.java index 36cf7421..53eb4b2c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/normal/HttpNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/HttpNormalTarget.java @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * 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.Suggest; -import com.arialyy.aria.core.common.http.HttpDelegate; +import com.arialyy.aria.core.common.HttpOption; +import com.arialyy.aria.core.download.DTaskWrapper; 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. @@ -29,22 +30,44 @@ import com.arialyy.aria.core.download.m3u8.M3U8Delegate; public class HttpNormalTarget extends AbsNormalTarget { private DNormalConfigHandler mConfigHandler; - HttpNormalTarget(long taskId, String targetName) { - mConfigHandler = new DNormalConfigHandler<>(this, taskId, targetName); + HttpNormalTarget(long taskId) { + mConfigHandler = new DNormalConfigHandler<>(this, taskId); getTaskWrapper().setRequestType(getTaskWrapper().getEntity().getTaskType()); + getTaskWrapper().setNewTask(false); } - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public M3U8Delegate asM3U8() { - return new M3U8Delegate<>(this, getTaskWrapper()); + public M3U8NormalTarget m3u8VodOption(M3U8VodOption m3U8VodOption) { + if (m3U8VodOption == null) { + 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等信息 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpDelegate option() { - return new HttpDelegate<>(this, getTaskWrapper()); + public HttpNormalTarget option(HttpOption option) { + if (option == null) { + throw new NullPointerException("任务配置为空"); + } + getTaskWrapper().getOptionParams().setParams(option); + return this; } /** @@ -52,7 +75,6 @@ public class HttpNormalTarget extends AbsNormalTarget { * 如:原文件路径 /mnt/sdcard/test.zip * 如果需要将test.zip改为game.zip,只需要重新设置文件路径为:/mnt/sdcard/game.zip */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public HttpNormalTarget modifyFilePath(String filePath) { mConfigHandler.setTempFilePath(filePath); return this; @@ -68,7 +90,6 @@ public class HttpNormalTarget extends AbsNormalTarget { /** * 更新下载地址 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public HttpNormalTarget updateUrl(String newUrl) { return mConfigHandler.updateUrl(newUrl); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java new file mode 100644 index 00000000..3e847729 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/M3U8NormalTarget.java @@ -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(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)); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/target/TcpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/target/TcpBuilderTarget.java new file mode 100644 index 00000000..81f6f9a6 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/target/TcpBuilderTarget.java @@ -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 { + + 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; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpDelegate.java new file mode 100644 index 00000000..abbf5af5 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpDelegate.java @@ -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; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpTaskConfig.java b/Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpTaskConfig.java new file mode 100644 index 00000000..326ec56b --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/tcp/TcpTaskConfig.java @@ -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; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalTask.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalTask.java deleted file mode 100644 index d3fcb898..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalTask.java +++ /dev/null @@ -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 - extends AbsTask { - - /** - * 最高优先级命令,最高优先级命令有以下属性 - * 1、在下载队列中,有且只有一个最高优先级任务 - * 2、最高优先级任务会一直存在,直到用户手动暂停或任务完成 - * 3、任务调度器不会暂停最高优先级任务 - * 4、用户手动暂停或任务完成后,第二次重新执行该任务,该命令将失效 - * 5、如果下载队列中已经满了,则会停止队尾的任务 - * 6、把任务设置为最高优先级任务后,将自动执行任务,不需要重新调用start()启动任务 - */ - public void setHighestPriority(boolean isHighestPriority) { - isHeighestTask = isHighestPriority; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsReceiver.java index 5d8fefed..f18ac3bb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsReceiver.java @@ -16,31 +16,119 @@ package com.arialyy.aria.core.inf; -import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; -import com.arialyy.aria.core.queue.DownloadTaskQueue; -import com.arialyy.aria.core.queue.UploadTaskQueue; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import android.app.Dialog; +import android.util.Log; +import android.widget.PopupWindow; +import com.arialyy.aria.core.WidgetLiftManager; +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.util.CommonUtil; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; /** * Created by AriaL on 2017/6/27. * 接收器 */ public abstract class AbsReceiver implements IReceiver { + protected String TAG = getClass().getSimpleName(); + /** - * 观察者对象map - * key 由 {@link #getKey(IReceiver)}指定 + * 观察者对象 */ - public static final Map OBJ_MAP = new ConcurrentHashMap<>(); + public Object obj; + /** * 观察者对象类的完整名称 */ - public String targetName; + private String targetName; + /** * 当dialog、dialogFragment、popupwindow已经被用户使用了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,生成规则: @@ -69,14 +157,7 @@ public abstract class AbsReceiver implements IReceiver { * 移除观察者对象 */ private void removeObj() { - for (Iterator> iter = OBJ_MAP.entrySet().iterator(); - iter.hasNext(); ) { - Map.Entry entry = iter.next(); - String key = entry.getKey(); - if (key.equals(getKey())) { - iter.remove(); - } - } + obj = null; } @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(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java index 806a39fe..3c9281cb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java @@ -16,10 +16,10 @@ package com.arialyy.aria.core.inf; import android.text.TextUtils; -import androidx.annotation.CheckResult; -import com.arialyy.aria.core.common.Suggest; -import com.arialyy.aria.core.common.controller.NormalController; +import com.arialyy.aria.core.common.AbsEntity; 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.CommonUtil; @@ -31,7 +31,6 @@ public abstract class AbsTarget { protected String TAG; private AbsEntity mEntity; private AbsTaskWrapper mTaskWrapper; - private String mTargetName; protected AbsTarget() { TAG = CommonUtil.getClassName(this); @@ -46,14 +45,6 @@ public abstract class AbsTarget { return mEntity; } - public String getTargetName() { - return mTargetName; - } - - public void setTargetName(String mTargetName) { - this.mTargetName = mTargetName; - } - /** * 获取任务实体 */ @@ -69,7 +60,6 @@ public abstract class AbsTarget { * * @param str 扩展数据 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public TARGET setExtendField(String str) { if (TextUtils.isEmpty(str)) return (TARGET) this; if (TextUtils.isEmpty(mEntity.getStr()) || !mEntity.getStr().equals(str)) { @@ -84,10 +74,10 @@ public abstract class AbsTarget { /** * 重置状态,将任务状态设置为未开始状态 * 注意:如果在后续方法调用链中没有调用 {@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()}才能将修改保存到数据库 */ - @CheckResult(suggest = "after use #create()、#stop()、#cancel()、#resume()、#save()?") public TARGET resetState() { getTaskWrapper().getEntity().setState(IEntity.STATE_WAIT); getTaskWrapper().setRefreshInfo(true); diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IConfigHandler.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IConfigHandler.java index 445b23f5..98ede7ef 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IConfigHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IConfigHandler.java @@ -15,6 +15,8 @@ */ package com.arialyy.aria.core.inf; +import com.arialyy.aria.core.common.AbsEntity; + /** * Created by lyy on 2019/4/5. * 普通任务配置处理 diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IReceiver.java index a4fb92b1..1ca11e75 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/IReceiver.java @@ -49,5 +49,11 @@ public interface IReceiver { * * @return {@link ReceiverType} */ - @ReceiverType String getType(); + ReceiverType getType(); + + /** + * 判断是否是fragment,如果是fragment,在activity销毁时,需要将其从receiver中移除 + */ + boolean isFragment(); + } diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/ReceiverType.java b/Aria/src/main/java/com/arialyy/aria/core/inf/ReceiverType.java index cf4aa2be..87456d05 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/ReceiverType.java +++ b/Aria/src/main/java/com/arialyy/aria/core/inf/ReceiverType.java @@ -15,19 +15,18 @@ */ package com.arialyy.aria.core.inf; -import androidx.annotation.StringDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * {@link AbsReceiver}类型 */ -@StringDef({ - ReceiverType.DOWNLOAD, - ReceiverType.UPLOAD, -}) -@Retention(RetentionPolicy.SOURCE) -public @interface ReceiverType { - String DOWNLOAD = "download"; - String UPLOAD = "upload"; +public enum ReceiverType { + DOWNLOAD(1, "download"), + UPLOAD(2, "upload"); + + String name; + int type; + + ReceiverType(int type, String name) { + this.type = type; + this.name = name; + } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/manager/DGTaskWrapperFactory.java b/Aria/src/main/java/com/arialyy/aria/core/manager/DGTaskWrapperFactory.java index efca22d4..e877052b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/manager/DGTaskWrapperFactory.java +++ b/Aria/src/main/java/com/arialyy/aria/core/manager/DGTaskWrapperFactory.java @@ -19,6 +19,7 @@ import com.arialyy.aria.core.download.DGEntityWrapper; import com.arialyy.aria.core.download.DGTaskWrapper; import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.DbDataHelper; import java.util.List; @@ -26,7 +27,7 @@ import java.util.List; * Created by Aria.Lao on 2017/11/1. 组合任务wrapper */ class DGTaskWrapperFactory implements IGroupWrapperFactory { - private static final String TAG = "DTaskWrapperFactory"; + private final String TAG = CommonUtil.getClassName(this); private static volatile DGTaskWrapperFactory INSTANCE = null; private DGTaskWrapperFactory() { @@ -42,14 +43,17 @@ class DGTaskWrapperFactory implements IGroupWrapperFactory mCachePool; BaseExecutePool mExecutePool; @@ -64,7 +70,33 @@ public abstract class AbsTaskQueue List getRunningTask(Class type) { + List exeTask = mExecutePool.getAllTask(); + List cacheTask = mCachePool.getAllTask(); + List entities = new ArrayList<>(); + if (exeTask != null && !exeTask.isEmpty()) { + for (TASK task : exeTask) { + entities.add((T) task.getTaskWrapper().getEntity()); + } + } + if (cacheTask != null && !cacheTask.isEmpty()) { + for (TASK task : cacheTask) { + entities.add((T) task.getTaskWrapper().getEntity()); + } + } + return entities.isEmpty() ? null : entities; + } + @Override public boolean taskIsRunning(String key) { + if (TextUtils.isEmpty(key)) { + ALog.w(TAG, "key为空,无法确认任务是否执行"); + return false; + } TASK task = mExecutePool.getTask(key); if (task == null && ThreadTaskManager.getInstance().taskIsRunning(key)) { ThreadTaskManager.getInstance().removeTaskThread(key); @@ -82,6 +114,10 @@ public abstract class AbsTaskQueue= getMaxTaskNum()) { task.getTaskWrapper().getEntity().setState(IEntity.STATE_WAIT); mCachePool.putTaskToFirst(task); @@ -95,8 +131,7 @@ public abstract class AbsTaskQueue= 1) { for (int i = 0; i < diff; i++) { TASK nextTask = getNextTask(); @@ -194,6 +234,7 @@ public abstract class AbsTaskQueue { - private static volatile DownloadGroupTaskQueue INSTANCE = null; + private static volatile DGroupTaskQueue INSTANCE = null; - private final String TAG = "DownloadGroupTaskQueue"; + private final String TAG = CommonUtil.getClassName(this); - public static DownloadGroupTaskQueue getInstance() { + public static DGroupTaskQueue getInstance() { if (INSTANCE == null) { - synchronized (DownloadGroupTaskQueue.class) { - INSTANCE = new DownloadGroupTaskQueue(); + synchronized (DGroupTaskQueue.class) { + INSTANCE = new DGroupTaskQueue(); + EventMsgUtil.getDefault().register(INSTANCE); } } return INSTANCE; } - private DownloadGroupTaskQueue() { + private DGroupTaskQueue() { } @Override int getQueueType() { return TYPE_DG_QUEUE; } + @Event + public void maxTaskNum(DGMaxNumEvent event) { + setMaxTaskNum(event.maxNum); + } + @Override public int getMaxTaskNum() { - return AriaManager.getInstance().getDGroupConfig().getMaxTaskNum(); + return AriaConfig.getInstance().getDGConfig().getMaxTaskNum(); } @Override public DownloadGroupTask createTask(DGTaskWrapper wrapper) { @@ -65,6 +75,6 @@ public class DownloadGroupTaskQueue } @Override public int getOldMaxNum() { - return AriaManager.getInstance().getDGroupConfig().oldMaxTaskNum; + return AriaConfig.getInstance().getDGConfig().oldMaxTaskNum; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java b/Aria/src/main/java/com/arialyy/aria/core/queue/DTaskQueue.java similarity index 57% rename from Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java rename to Aria/src/main/java/com/arialyy/aria/core/queue/DTaskQueue.java index 8b7d7589..499a37dc 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/DownloadTaskQueue.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/DTaskQueue.java @@ -16,42 +16,52 @@ package com.arialyy.aria.core.queue; -import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.download.DownloadTask; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.event.DMaxNumEvent; +import com.arialyy.aria.core.event.Event; +import com.arialyy.aria.core.event.EventMsgUtil; import com.arialyy.aria.core.inf.TaskSchedulerType; import com.arialyy.aria.core.scheduler.TaskSchedulers; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.ALog; import java.util.LinkedHashSet; -import java.util.Map; +import java.util.List; import java.util.Set; /** * Created by lyy on 2016/8/17. * 下载任务队列 */ -public class DownloadTaskQueue extends AbsTaskQueue { +public class DTaskQueue extends AbsTaskQueue { private static final String TAG = "DownloadTaskQueue"; - private static volatile DownloadTaskQueue INSTANCE = null; + private static volatile DTaskQueue INSTANCE = null; - public static DownloadTaskQueue getInstance() { + public static DTaskQueue getInstance() { if (INSTANCE == null) { - synchronized (DownloadTaskQueue.class) { - INSTANCE = new DownloadTaskQueue(); + synchronized (DTaskQueue.class) { + INSTANCE = new DTaskQueue(); + EventMsgUtil.getDefault().register(INSTANCE); } } return INSTANCE; } - private DownloadTaskQueue() { + private DTaskQueue() { } @Override int getQueueType() { return TYPE_D_QUEUE; } + @Event + public void maxTaskNum(DMaxNumEvent event) { + setMaxTaskNum(event.maxNum); + } + @Override public int getOldMaxNum() { - return AriaManager.getInstance().getDownloadConfig().oldMaxTaskNum; + return AriaConfig.getInstance().getDConfig().oldMaxTaskNum; } /** @@ -59,11 +69,10 @@ public class DownloadTaskQueue extends AbsTaskQueue */ public void setTaskHighestPriority(DownloadTask task) { task.setHighestPriority(true); - Map exeTasks = mExecutePool.getAllTask(); + //Map exeTasks = mExecutePool.getAllTask(); + List exeTasks = mExecutePool.getAllTask(); if (exeTasks != null && !exeTasks.isEmpty()) { - Set keys = exeTasks.keySet(); - for (String key : keys) { - DownloadTask temp = exeTasks.get(key); + for (DownloadTask temp : exeTasks) { if (temp != null && temp.isRunning() && temp.isHighestPriorityTask() && !temp.getKey() .equals(task.getKey())) { ALog.e(TAG, "设置最高优先级任务失败,失败原因【任务中已经有最高优先级任务,请等待上一个最高优先级任务完成,或手动暂停该任务】"); @@ -71,28 +80,28 @@ public class DownloadTaskQueue extends AbsTaskQueue return; } } - } - int maxSize = AriaManager.getInstance().getDownloadConfig().getMaxTaskNum(); - int currentSize = mExecutePool.size(); - if (currentSize == 0 || currentSize < maxSize) { - startTask(task); - } else { - Set tempTasks = new LinkedHashSet<>(); - for (int i = 0; i < maxSize; i++) { - DownloadTask oldTsk = mExecutePool.pollTask(); - if (oldTsk != null && oldTsk.isRunning()) { - if (i == maxSize - 1) { - oldTsk.stop(TaskSchedulerType.TYPE_STOP_AND_WAIT); - mCachePool.putTaskToFirst(oldTsk); - break; + int maxSize = AriaConfig.getInstance().getDConfig().getMaxTaskNum(); + int currentSize = mExecutePool.size(); + if (currentSize == 0 || currentSize < maxSize) { + startTask(task); + } else { + Set tempTasks = new LinkedHashSet<>(); + for (int i = 0; i < maxSize; i++) { + DownloadTask oldTsk = mExecutePool.pollTask(); + if (oldTsk != null && oldTsk.isRunning()) { + if (i == maxSize - 1) { + oldTsk.stop(TaskSchedulerType.TYPE_STOP_AND_WAIT); + mCachePool.putTaskToFirst(oldTsk); + break; + } + tempTasks.add(oldTsk); } - tempTasks.add(oldTsk); } - } - startTask(task); + startTask(task); - for (DownloadTask temp : tempTasks) { - mExecutePool.putTask(temp); + for (DownloadTask temp : tempTasks) { + mExecutePool.putTask(temp); + } } } } @@ -117,6 +126,6 @@ public class DownloadTaskQueue extends AbsTaskQueue } @Override public int getMaxTaskNum() { - return AriaManager.getInstance().getDownloadConfig().getMaxTaskNum(); + return AriaConfig.getInstance().getDConfig().getMaxTaskNum(); } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java b/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java index b669150b..0ef328b5 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/ITaskQueue.java @@ -17,14 +17,14 @@ package com.arialyy.aria.core.queue; import com.arialyy.aria.core.download.DGTaskWrapper; -import com.arialyy.aria.core.download.DownloadGroupTask; -import com.arialyy.aria.core.download.DownloadTask; import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.inf.AbsTask; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.ITask; -import com.arialyy.aria.core.upload.UploadTask; +import com.arialyy.aria.core.inf.TaskSchedulerType; +import com.arialyy.aria.core.task.DownloadGroupTask; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.core.task.ITask; +import com.arialyy.aria.core.task.UploadTask; import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; /** * Created by lyy on 2016/8/16. 任务功能接口 @@ -66,6 +66,13 @@ public interface ITaskQueue { +public class UTaskQueue extends AbsTaskQueue { private static final String TAG = "UploadTaskQueue"; - private static volatile UploadTaskQueue INSTANCE = null; + private static volatile UTaskQueue INSTANCE = null; - public static UploadTaskQueue getInstance() { + public static UTaskQueue getInstance() { if (INSTANCE == null) { - synchronized (UploadTaskQueue.class) { - INSTANCE = new UploadTaskQueue(); + synchronized (UTaskQueue.class) { + INSTANCE = new UTaskQueue(); + EventMsgUtil.getDefault().register(INSTANCE); } } return INSTANCE; } - private UploadTaskQueue() { + private UTaskQueue() { + } + + @Event + public void maxTaskNum(UMaxNumEvent event){ + setMaxTaskNum(event.maxNum); } @Override int getQueueType() { @@ -46,11 +56,11 @@ public class UploadTaskQueue extends AbsTaskQueue { } @Override public int getOldMaxNum() { - return AriaManager.getInstance().getUploadConfig().oldMaxTaskNum; + return AriaConfig.getInstance().getUConfig().oldMaxTaskNum; } @Override public int getMaxTaskNum() { - return AriaManager.getInstance().getUploadConfig().getMaxTaskNum(); + return AriaConfig.getInstance().getUConfig().getMaxTaskNum(); } @Override public UploadTask createTask(UTaskWrapper wrapper) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseCachePool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseCachePool.java index 5a5a96de..89a442fc 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseCachePool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseCachePool.java @@ -17,68 +17,46 @@ package com.arialyy.aria.core.queue.pool; import android.text.TextUtils; -import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.task.AbsTask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.LinkedBlockingDeque; /** * Created by lyy on 2016/8/14. 任务缓存池,所有下载任务最先缓存在这个池中 */ public class BaseCachePool implements IPool { - private static final String TAG = "BaseCachePool"; + private final String TAG = CommonUtil.getClassName(this); private static final int MAX_NUM = Integer.MAX_VALUE; //最大下载任务数 - private static final long TIME_OUT = 1000; private static final Object LOCK = new Object(); - private Map mCacheMap; - private LinkedBlockingQueue mCacheQueue; + private Deque mCacheQueue; BaseCachePool() { - mCacheQueue = new LinkedBlockingQueue<>(MAX_NUM); - mCacheMap = new ConcurrentHashMap<>(); + mCacheQueue = new LinkedBlockingDeque<>(MAX_NUM); } /** * 获取被缓存的任务 */ - public Map getAllTask() { - return mCacheMap; + public List getAllTask() { + return new ArrayList<>(mCacheQueue); } /** * 清除所有缓存的任务 */ public void clear() { - for (String key : mCacheMap.keySet()) { - TASK task = mCacheMap.get(key); - mCacheQueue.remove(task); - mCacheMap.remove(key); - } + mCacheQueue.clear(); } /** * 将任务放在队首 */ public boolean putTaskToFirst(TASK task) { - if (mCacheQueue.isEmpty()) { - return putTask(task); - } else { - Set temps = new LinkedHashSet<>(); - temps.add(task); - for (int i = 0, len = size(); i < len; i++) { - TASK temp = pollTask(); - temps.add(temp); - } - for (TASK t : temps) { - putTask(t); - } - return true; - } + return mCacheQueue.offerFirst(task); } @Override public boolean putTask(TASK task) { @@ -87,16 +65,12 @@ public class BaseCachePool implements IPool { ALog.e(TAG, "任务不能为空!!"); return false; } - String key = task.getKey(); if (mCacheQueue.contains(task)) { ALog.w(TAG, "任务【" + task.getTaskName() + "】进入缓存队列失败,原因:已经在缓存队列中"); return false; } else { boolean s = mCacheQueue.offer(task); ALog.d(TAG, "任务【" + task.getTaskName() + "】进入缓存队列" + (s ? "成功" : "失败")); - if (s) { - mCacheMap.put(CommonUtil.keyToHashKey(key), task); - } return s; } } @@ -104,19 +78,8 @@ public class BaseCachePool implements IPool { @Override public TASK pollTask() { synchronized (LOCK) { - try { - TASK task; - task = mCacheQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); - if (task != null) { - String url = task.getKey(); - mCacheMap.remove(CommonUtil.keyToHashKey(url)); - } - return task; - } catch (InterruptedException e) { - e.printStackTrace(); - } + return mCacheQueue.pollFirst(); } - return null; } @Override public TASK getTask(String key) { @@ -125,12 +88,17 @@ public class BaseCachePool implements IPool { ALog.e(TAG, "key 为null"); return null; } - return mCacheMap.get(CommonUtil.keyToHashKey(key)); + for (TASK task : mCacheQueue) { + if (task.getKey().equals(key)) { + return task; + } + } } + return null; } @Override public boolean taskExits(String key) { - return mCacheMap.containsKey(CommonUtil.keyToHashKey(key)); + return getTask(key) != null; } @Override public boolean removeTask(TASK task) { @@ -139,8 +107,6 @@ public class BaseCachePool implements IPool { ALog.e(TAG, "任务不能为空"); return false; } else { - String key = CommonUtil.keyToHashKey(task.getKey()); - mCacheMap.remove(key); return mCacheQueue.remove(task); } } @@ -152,10 +118,7 @@ public class BaseCachePool implements IPool { ALog.e(TAG, "请传入有效的下载链接"); return false; } - String temp = CommonUtil.keyToHashKey(key); - TASK task = mCacheMap.get(temp); - mCacheMap.remove(temp); - return mCacheQueue.remove(task); + return mCacheQueue.remove(getTask(key)); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseExecutePool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseExecutePool.java index 03fe9e49..533105ee 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseExecutePool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/BaseExecutePool.java @@ -18,29 +18,26 @@ package com.arialyy.aria.core.queue.pool; import android.text.TextUtils; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.task.AbsTask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; -import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.LinkedBlockingDeque; /** * Created by lyy on 2016/8/15. 任务执行池,所有当前下载任务都该任务池中,默认下载大小为2 */ public class BaseExecutePool implements IPool { - private final String TAG = "BaseExecutePool"; + private final String TAG = CommonUtil.getClassName(this); private static final Object LOCK = new Object(); - final long TIME_OUT = 1000; - ArrayBlockingQueue mExecuteQueue; - Map mExecuteMap; + Deque mExecuteQueue; int mSize; BaseExecutePool() { mSize = getMaxSize(); - mExecuteQueue = new ArrayBlockingQueue<>(mSize); - mExecuteMap = new ConcurrentHashMap<>(); + mExecuteQueue = new LinkedBlockingDeque<>(mSize); } /** @@ -55,8 +52,8 @@ public class BaseExecutePool implements IPool { /** * 获取所有正在执行的任务 */ - public Map getAllTask() { - return mExecuteMap; + public List getAllTask() { + return new ArrayList<>(mExecuteQueue); } @Override public boolean putTask(TASK task) { @@ -88,17 +85,13 @@ public class BaseExecutePool implements IPool { */ public void setMaxNum(int maxNum) { synchronized (LOCK) { - try { - ArrayBlockingQueue temp = new ArrayBlockingQueue<>(maxNum); - TASK task; - while ((task = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS)) != null) { - temp.offer(task); - } - mExecuteQueue = temp; - mSize = maxNum; - } catch (InterruptedException e) { - e.printStackTrace(); + Deque temp = new LinkedBlockingDeque<>(maxNum); + TASK task; + while ((task = mExecuteQueue.poll()) != null) { + temp.offer(task); } + mExecuteQueue = temp; + mSize = maxNum; } } @@ -109,12 +102,8 @@ public class BaseExecutePool implements IPool { */ boolean putNewTask(TASK newTask) { synchronized (LOCK) { - String url = newTask.getKey(); boolean s = mExecuteQueue.offer(newTask); ALog.d(TAG, "任务【" + newTask.getTaskName() + "】进入执行队列" + (s ? "成功" : "失败")); - if (s) { - mExecuteMap.put(CommonUtil.keyToHashKey(url), newTask); - } return s; } } @@ -124,37 +113,19 @@ public class BaseExecutePool implements IPool { */ boolean pollFirstTask() { synchronized (LOCK) { - try { - TASK oldTask = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); - if (oldTask == null) { - ALog.w(TAG, "移除任务失败,原因:任务为null"); - return false; - } - oldTask.stop(); - String key = CommonUtil.keyToHashKey(oldTask.getKey()); - mExecuteMap.remove(key); - } catch (InterruptedException e) { - e.printStackTrace(); + TASK oldTask = mExecuteQueue.pollFirst(); + if (oldTask == null) { + ALog.w(TAG, "移除任务失败,原因:任务为null"); return false; } + oldTask.stop(); return true; } } @Override public TASK pollTask() { synchronized (LOCK) { - try { - TASK task; - task = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); - if (task != null) { - String url = task.getKey(); - mExecuteMap.remove(CommonUtil.keyToHashKey(url)); - } - return task; - } catch (InterruptedException e) { - e.printStackTrace(); - } - return null; + return mExecuteQueue.poll(); } } @@ -164,12 +135,18 @@ public class BaseExecutePool implements IPool { ALog.e(TAG, "key为null"); return null; } - return mExecuteMap.get(CommonUtil.keyToHashKey(key)); + for (TASK task : mExecuteQueue) { + if (task.getKey().equals(key)) { + return task; + } + } + + return null; } } @Override public boolean taskExits(String key) { - return mExecuteMap.containsKey(CommonUtil.keyToHashKey(key)); + return getTask(key) != null; } @Override public boolean removeTask(TASK task) { @@ -189,16 +166,8 @@ public class BaseExecutePool implements IPool { ALog.e(TAG, "key 为null"); return false; } - String convertKey = CommonUtil.keyToHashKey(key); - TASK task = mExecuteMap.get(convertKey); - final int oldQueueSize = mExecuteQueue.size(); - boolean isSuccess = mExecuteQueue.remove(task); - final int newQueueSize = mExecuteQueue.size(); - if (isSuccess && newQueueSize != oldQueueSize) { - mExecuteMap.remove(convertKey); - return true; - } - return false; + + return mExecuteQueue.remove(getTask(key)); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DGLoadExecutePool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DGLoadExecutePool.java index f9e64b0a..ee3ea11e 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DGLoadExecutePool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DGLoadExecutePool.java @@ -15,17 +15,19 @@ */ package com.arialyy.aria.core.queue.pool; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.task.AbsTask; +import com.arialyy.aria.util.CommonUtil; /** * Created by AriaL on 2017/6/29. * 单个下载任务的执行池 */ class DGLoadExecutePool extends DLoadExecutePool { - private final String TAG = "DGLoadExecutePool"; + private final String TAG = CommonUtil.getClassName(this); @Override protected int getMaxSize() { - return AriaManager.getInstance().getDGroupConfig().getMaxTaskNum(); + return AriaConfig.getInstance().getDGConfig().getMaxTaskNum(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DLoadExecutePool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DLoadExecutePool.java index f43c2bb2..bfa8afd3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DLoadExecutePool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/DLoadExecutePool.java @@ -15,12 +15,9 @@ */ package com.arialyy.aria.core.queue.pool; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.task.AbsTask; import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CommonUtil; -import java.util.Set; -import java.util.concurrent.TimeUnit; /** * Created by AriaL on 2017/6/29. @@ -30,7 +27,7 @@ class DLoadExecutePool extends BaseExecutePool { private final String TAG = "DownloadExecutePool"; @Override protected int getMaxSize() { - return AriaManager.getInstance().getDownloadConfig().getMaxTaskNum(); + return AriaConfig.getInstance().getDConfig().getMaxTaskNum(); } @Override public boolean putTask(TASK task) { @@ -45,9 +42,10 @@ class DLoadExecutePool extends BaseExecutePool { return false; } else { if (mExecuteQueue.size() >= mSize) { - Set keys = mExecuteMap.keySet(); - for (String key : keys) { - if (mExecuteMap.get(key).isHighestPriorityTask()) return false; + for (TASK temp : mExecuteQueue) { + if (temp.isHighestPriorityTask()) { + return false; + } } if (pollFirstTask()) { return putNewTask(task); @@ -61,22 +59,15 @@ class DLoadExecutePool extends BaseExecutePool { } @Override boolean pollFirstTask() { - try { - TASK oldTask = mExecuteQueue.poll(TIME_OUT, TimeUnit.MICROSECONDS); - if (oldTask == null) { - ALog.w(TAG, "移除任务失败,错误原因:任务为null"); - return false; - } - if (oldTask.isHighestPriorityTask()) { - return false; - } - oldTask.stop(); - String key = CommonUtil.keyToHashKey(oldTask.getKey()); - mExecuteMap.remove(key); - } catch (InterruptedException e) { - e.printStackTrace(); + TASK oldTask = mExecuteQueue.pollFirst(); + if (oldTask == null) { + ALog.w(TAG, "移除任务失败,错误原因:任务为null"); + return false; + } + if (oldTask.isHighestPriorityTask()) { return false; } + oldTask.stop(); return true; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/IPool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/IPool.java index 313520cb..d016c81f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/IPool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/IPool.java @@ -16,8 +16,8 @@ package com.arialyy.aria.core.queue.pool; -import com.arialyy.aria.core.inf.AbsEntity; -import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.task.AbsTask; /** * Created by lyy on 2016/8/14. 任务池 diff --git a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/UploadExecutePool.java b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/UploadExecutePool.java index 7988fc5b..c8af2622 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/queue/pool/UploadExecutePool.java +++ b/Aria/src/main/java/com/arialyy/aria/core/queue/pool/UploadExecutePool.java @@ -15,14 +15,14 @@ */ package com.arialyy.aria.core.queue.pool; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.inf.AbsTask; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.task.AbsTask; /** * Created by Aria.Lao on 2017/7/17. */ public class UploadExecutePool extends BaseExecutePool { @Override protected int getMaxSize() { - return AriaManager.getInstance().getUploadConfig().getMaxTaskNum(); + return AriaConfig.getInstance().getUConfig().getMaxTaskNum(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptM3U8PeerTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptM3U8PeerTaskListener.java new file mode 100644 index 00000000..1e856ae2 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptM3U8PeerTaskListener.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.scheduler; + +/** + * Created by Aria.Lao on 2019/6/26. + * m3u8切片事件回调类 + */ +public class AptM3U8PeerTaskListener implements M3U8PeerTaskListener, ISchedulerListener{ + + @Override public void onPeerStart(final String m3u8Url, final String peerPath, final int peerIndex) { + } + + @Override public void onPeerComplete(final String m3u8Url, final String peerPath, final int peerIndex) { + } + + @Override public void onPeerFail(final String m3u8Url, final String peerPath, final int peerIndex) { + } + + @Override public void setListener(Object obj) { + + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptNormalTaskListener.java similarity index 65% rename from Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListener.java rename to Aria/src/main/java/com/arialyy/aria/core/scheduler/AptNormalTaskListener.java index 08b1a8bf..4095a95b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptNormalTaskListener.java @@ -15,21 +15,21 @@ */ package com.arialyy.aria.core.scheduler; -import com.arialyy.aria.core.download.DownloadGroupTask; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.inf.ITask; -import com.arialyy.aria.core.upload.UploadTask; +import com.arialyy.aria.core.task.DownloadGroupTask; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.core.task.ITask; +import com.arialyy.aria.core.task.UploadTask; /** * Created by Aria.Lao on 2017/6/7. * 普通任务事件{@link DownloadTask}、{@link UploadTask}、{@link DownloadGroupTask}回调类 */ -public class NormalTaskListener implements ISchedulerListener { +public class AptNormalTaskListener implements NormalTaskListenerInterface, ISchedulerListener { /** * 队列已经满了,继续创建任务,将会回调该方法 */ - public void onWait(TASK task) { + @Override public void onWait(TASK task) { } @@ -37,42 +37,42 @@ public class NormalTaskListener implements ISchedulerListene * 预处理,有时有些地址链接比较慢,这时可以先在这个地方出来一些界面上的UI,如按钮的状态。 * 在这个回调中,任务是获取不到文件大小,下载速度等参数 */ - public void onPre(TASK task) { + @Override public void onPre(TASK task) { } /** * 任务预加载完成 */ - public void onTaskPre(TASK task) { + @Override public void onTaskPre(TASK task) { } /** * 任务恢复下载 */ - public void onTaskResume(TASK task) { + @Override public void onTaskResume(TASK task) { } /** * 任务开始 */ - public void onTaskStart(TASK task) { + @Override public void onTaskStart(TASK task) { } /** * 任务停止 */ - public void onTaskStop(TASK task) { + @Override public void onTaskStop(TASK task) { } /** * 任务取消 */ - public void onTaskCancel(TASK task) { + @Override public void onTaskCancel(TASK task) { } @@ -88,25 +88,25 @@ public class NormalTaskListener implements ISchedulerListene /** * 任务失败 */ - public void onTaskFail(TASK task, Exception e) { + @Override public void onTaskFail(TASK task, Exception e) { } /** * 任务完成 */ - public void onTaskComplete(TASK task) { + @Override public void onTaskComplete(TASK task) { } /** * 任务执行中 */ - public void onTaskRunning(TASK task) { + @Override public void onTaskRunning(TASK task) { } - public void onNoSupportBreakPoint(TASK task) { + @Override public void onNoSupportBreakPoint(TASK task) { } diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptSubTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptSubTaskListener.java new file mode 100644 index 00000000..a8b2c75d --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/AptSubTaskListener.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.scheduler; + +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.task.ITask; + +/** + * Created by Aria.Lao on 2019/6/26. + * 子任务事件回调类 + */ +public class AptSubTaskListener + implements SubTaskListener, ISchedulerListener { + + @Override public void onNoSupportBreakPoint(TASK task) { + + } + + @Override public void onSubTaskPre(TASK task, SUB_ENTITY subTask) { + + } + + @Override public void onSubTaskStart(TASK task, SUB_ENTITY subTask) { + + } + + @Override public void onSubTaskStop(TASK task, SUB_ENTITY subTask) { + + } + + @Override public void onSubTaskCancel(TASK task, SUB_ENTITY subTask) { + + } + + @Override public void onSubTaskComplete(TASK task, SUB_ENTITY subTask) { + + } + + @Deprecated + public void onSubTaskFail(TASK task, SUB_ENTITY subTask) { + + } + + @Override public void onSubTaskFail(TASK task, SUB_ENTITY subTask, Exception e) { + + } + + @Override public void onSubTaskRunning(TASK task, SUB_ENTITY subTask) { + + } + + @Override public void setListener(Object obj) { + + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/FailureTaskHandler.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/FailureTaskHandler.java index 18c352f2..a578d951 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/FailureTaskHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/FailureTaskHandler.java @@ -15,11 +15,11 @@ */ package com.arialyy.aria.core.scheduler; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.inf.AbsEntity; -import com.arialyy.aria.core.inf.ITask; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.queue.ITaskQueue; +import com.arialyy.aria.core.task.ITask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import java.util.ArrayList; @@ -42,7 +42,6 @@ class FailureTaskHandler { private TaskSchedulers mSchedulers; private final ReentrantLock LOCK = new ReentrantLock(); private Condition mCondition = LOCK.newCondition(); - private AriaManager mAriaManager; static FailureTaskHandler init(TaskSchedulers schedulers) { if (INSTANCE == null) { @@ -57,7 +56,6 @@ class FailureTaskHandler { private FailureTaskHandler(TaskSchedulers schedulers) { mSchedulers = schedulers; - mAriaManager = AriaManager.getInstance(); new Thread(new Runnable() { @Override public void run() { while (true) { @@ -82,35 +80,46 @@ class FailureTaskHandler { } private void retryTask(final TASK task) { - long interval = task.getTaskWrapper().getConfig().getReTryInterval(); - final int num = task.getTaskWrapper().getConfig().getReTryNum(); - final ITaskQueue queue = mSchedulers.getQueue(task); - mAriaManager.getAriaHandler().postDelayed(new Runnable() { - @Override public void run() { - AbsEntity entity = task.getTaskWrapper().getEntity(); - if (entity.getFailNum() <= num) { - ALog.d(TAG, String.format("任务【%s】开始重试", task.getTaskName())); - queue.reTryStart(task); - } else { - queue.removeTaskFormQueue(task.getKey()); - mSchedulers.startNextTask(queue, task.getSchedulerType()); - TaskWrapperManager.getInstance().removeTaskWrapper(task.getTaskWrapper()); - } - mExeQueue.remove(task); - int index = mHashList.indexOf(task.hashCode()); - if (index != -1) { - mHashList.remove(index); - } - if (LOCK.isLocked()) { - try { - LOCK.lock(); - mCondition.signalAll(); - } finally { - LOCK.unlock(); + final ITaskQueue queue = mSchedulers.getQueue(task.getTaskType()); + if (task.isNeedRetry()){ + long interval = task.getTaskWrapper().getConfig().getReTryInterval(); + final int num = task.getTaskWrapper().getConfig().getReTryNum(); + AriaConfig.getInstance().getAriaHandler().postDelayed(new Runnable() { + @Override public void run() { + AbsEntity entity = task.getTaskWrapper().getEntity(); + if (entity.getFailNum() <= num) { + ALog.d(TAG, String.format("任务【%s】开始重试", task.getTaskName())); + queue.reTryStart(task); + } else { + queue.removeTaskFormQueue(task.getKey()); + mSchedulers.startNextTask(queue, task.getSchedulerType()); + TaskWrapperManager.getInstance().removeTaskWrapper(task.getTaskWrapper()); } + next(task); } + }, interval); + }else { + queue.removeTaskFormQueue(task.getKey()); + mSchedulers.startNextTask(queue, task.getSchedulerType()); + TaskWrapperManager.getInstance().removeTaskWrapper(task.getTaskWrapper()); + next(task); + } + } + + private void next(TASK task){ + mExeQueue.remove(task); + int index = mHashList.indexOf(task.hashCode()); + if (index != -1) { + mHashList.remove(index); + } + if (LOCK.isLocked()) { + try { + LOCK.lock(); + mCondition.signalAll(); + } finally { + LOCK.unlock(); } - }, interval); + } } void offer(TASK task) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java index 3df14066..a7a100de 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/M3U8PeerTaskListener.java @@ -19,18 +19,11 @@ package com.arialyy.aria.core.scheduler; * Created by Aria.Lao on 2019/6/26. * m3u8切片事件回调类 */ -public class M3U8PeerTaskListener implements ISchedulerListener { +public interface M3U8PeerTaskListener extends TaskInternalListenerInterface { - public void onPeerStart(final String m3u8Url, final String peerPath, final int peerIndex) { - } + public void onPeerStart(final String m3u8Url, final String peerPath, final int peerIndex); - public void onPeerComplete(final String m3u8Url, final String peerPath, final int peerIndex) { - } + public void onPeerComplete(final String m3u8Url, final String peerPath, final int peerIndex); - public void onPeerFail(final String m3u8Url, final String peerPath, final int peerIndex) { - } - - @Override public void setListener(Object obj) { - - } + public void onPeerFail(final String m3u8Url, final String peerPath, final int peerIndex); } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListenerInterface.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListenerInterface.java new file mode 100644 index 00000000..29cfb36b --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/NormalTaskListenerInterface.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.scheduler; + +import com.arialyy.aria.core.task.DownloadGroupTask; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.core.task.ITask; +import com.arialyy.aria.core.task.UploadTask; + +/** + * Created by Aria.Lao on 2017/6/7. + * 普通任务事件{@link DownloadTask}、{@link UploadTask}、{@link DownloadGroupTask}回调类 + */ +public interface NormalTaskListenerInterface extends + TaskInternalListenerInterface { + + /** + * 队列已经满了,继续创建任务,将会回调该方法 + */ + public void onWait(TASK task); + + /** + * 预处理,有时有些地址链接比较慢,这时可以先在这个地方出来一些界面上的UI,如按钮的状态。 + * 在这个回调中,任务是获取不到文件大小,下载速度等参数 + */ + public void onPre(TASK task); + + /** + * 任务预加载完成 + */ + public void onTaskPre(TASK task); + + /** + * 任务恢复下载 + */ + public void onTaskResume(TASK task); + + /** + * 任务开始 + */ + public void onTaskStart(TASK task); + + /** + * 任务停止 + */ + public void onTaskStop(TASK task); + + /** + * 任务取消 + */ + public void onTaskCancel(TASK task); + + /** + * 任务失败 + */ + public void onTaskFail(TASK task, Exception e); + + /** + * 任务完成 + */ + public void onTaskComplete(TASK task); + + /** + * 任务执行中 + */ + public void onTaskRunning(TASK task); + + public void onNoSupportBreakPoint(TASK task); +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java index d29fea62..a9ff4195 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/SubTaskListener.java @@ -15,54 +15,29 @@ */ package com.arialyy.aria.core.scheduler; -import com.arialyy.aria.core.inf.AbsNormalEntity; -import com.arialyy.aria.core.inf.ITask; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.task.ITask; /** * Created by Aria.Lao on 2019/6/26. * 子任务事件回调类 */ -public class SubTaskListener - implements ISchedulerListener { +public interface SubTaskListener extends + TaskInternalListenerInterface { - public void onNoSupportBreakPoint(TASK task) { + public void onNoSupportBreakPoint(TASK task); - } + public void onSubTaskPre(TASK task, SUB_ENTITY subTask); - public void onSubTaskPre(TASK task, SUB_ENTITY subTask) { + public void onSubTaskStart(TASK task, SUB_ENTITY subTask); - } + public void onSubTaskStop(TASK task, SUB_ENTITY subTask); - public void onSubTaskStart(TASK task, SUB_ENTITY subTask) { + public void onSubTaskCancel(TASK task, SUB_ENTITY subTask); - } + public void onSubTaskComplete(TASK task, SUB_ENTITY subTask); - public void onSubTaskStop(TASK task, SUB_ENTITY subTask) { + public void onSubTaskFail(TASK task, SUB_ENTITY subTask, Exception e); - } - - public void onSubTaskCancel(TASK task, SUB_ENTITY subTask) { - - } - - public void onSubTaskComplete(TASK task, SUB_ENTITY subTask) { - - } - - @Deprecated - public void onSubTaskFail(TASK task, SUB_ENTITY subTask) { - - } - - public void onSubTaskFail(TASK task, SUB_ENTITY subTask, Exception e) { - - } - - public void onSubTaskRunning(TASK task, SUB_ENTITY subTask) { - - } - - @Override public void setListener(Object obj) { - - } + public void onSubTaskRunning(TASK task, SUB_ENTITY subTask); } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskInternalListenerInterface.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskInternalListenerInterface.java new file mode 100644 index 00000000..c4d73d59 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskInternalListenerInterface.java @@ -0,0 +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.scheduler; + +/** + * 直接实现监听器回调接口的基类,不对外部直接开放,仅作为内部监听器的父接口使用 + * + * @author ChenFei(chenfei0928 @ gmail.com) + * Date: 2020-07-07 15:18 + */ +public interface TaskInternalListenerInterface { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java b/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java index 33f8c7eb..62dd3e26 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java +++ b/Aria/src/main/java/com/arialyy/aria/core/scheduler/TaskSchedulers.java @@ -19,23 +19,25 @@ import android.content.Intent; import android.os.Bundle; import android.os.Message; import com.arialyy.annotations.TaskEnum; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.download.DownloadGroupTask; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.inf.AbsEntity; -import com.arialyy.aria.core.inf.AbsNormalEntity; -import com.arialyy.aria.core.inf.AbsTask; -import com.arialyy.aria.core.inf.GroupSendParams; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.group.GroupSendParams; import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.aria.core.inf.ITask; import com.arialyy.aria.core.inf.TaskSchedulerType; +import com.arialyy.aria.core.listener.ISchedulers; import com.arialyy.aria.core.manager.TaskWrapperManager; -import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; -import com.arialyy.aria.core.queue.DownloadTaskQueue; +import com.arialyy.aria.core.queue.DGroupTaskQueue; +import com.arialyy.aria.core.queue.DTaskQueue; import com.arialyy.aria.core.queue.ITaskQueue; -import com.arialyy.aria.core.queue.UploadTaskQueue; -import com.arialyy.aria.core.upload.UploadTask; +import com.arialyy.aria.core.queue.UTaskQueue; +import com.arialyy.aria.core.task.AbsTask; +import com.arialyy.aria.core.task.DownloadGroupTask; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.core.task.ITask; +import com.arialyy.aria.core.task.UploadTask; import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.NetUtils; import java.util.Iterator; import java.util.Map; @@ -46,16 +48,16 @@ import java.util.concurrent.ConcurrentHashMap; * Created by lyy on 2017/6/4. 事件调度器,用于处理任务状态的调度 */ public class TaskSchedulers implements ISchedulers { - private final String TAG = "AbsSchedulers"; + private final String TAG = CommonUtil.getClassName(getClass()); private static volatile TaskSchedulers INSTANCE; private static FailureTaskHandler mFailureTaskHandler; - private Map> mObservers = new ConcurrentHashMap<>(); - private AriaManager mAriaManager; + private Map> mObservers = new ConcurrentHashMap<>(); + private AriaConfig mAriaConfig; private TaskSchedulers() { - mAriaManager = AriaManager.getInstance(); + mAriaConfig = AriaConfig.getInstance(); } public static TaskSchedulers getInstance() { @@ -73,18 +75,17 @@ public class TaskSchedulers implements ISchedulers { /** * 通过任务类型获取任务队列 * - * @param curTask 当前接收到的的任务 + * @param taskType 任务类型 */ - ITaskQueue getQueue(TASK curTask) { - int taskType = curTask.getTaskType(); + ITaskQueue getQueue(int taskType) { if (taskType == ITask.DOWNLOAD) { - return DownloadTaskQueue.getInstance(); + return DTaskQueue.getInstance(); } if (taskType == ITask.DOWNLOAD_GROUP) { - return DownloadGroupTaskQueue.getInstance(); + return DGroupTaskQueue.getInstance(); } if (taskType == ITask.UPLOAD) { - return UploadTaskQueue.getInstance(); + return UTaskQueue.getInstance(); } throw new NullPointerException("任务类型错误,type = " + taskType); } @@ -97,15 +98,19 @@ public class TaskSchedulers implements ISchedulers { */ public void register(Object obj, TaskEnum taskEnum) { String targetName = obj.getClass().getName(); - Map listeners = mObservers.get(getKey(obj)); + Map listeners = mObservers.get(getKey(obj)); if (listeners == null) { listeners = new ConcurrentHashMap<>(); mObservers.put(getKey(obj), listeners); } - String proxyClassName = targetName + taskEnum.proxySuffix; if (!hasProxyListener(listeners, taskEnum)) { + if (obj instanceof TaskInternalListenerInterface) { + listeners.put(taskEnum, obj); + return; + } + String proxyClassName = targetName + taskEnum.proxySuffix; ISchedulerListener listener = createListener(proxyClassName); if (listener != null) { listener.setListener(obj); @@ -122,7 +127,7 @@ public class TaskSchedulers implements ISchedulers { * @param taskEnum 代理类类型 * @return true,已注册代理类,false,没有注册代理类 */ - private boolean hasProxyListener(Map listeners, TaskEnum taskEnum) { + private boolean hasProxyListener(Map listeners, TaskEnum taskEnum) { return !listeners.isEmpty() && listeners.get(taskEnum) != null; } @@ -135,9 +140,9 @@ public class TaskSchedulers implements ISchedulers { if (!mObservers.containsKey(getKey(obj))) { return; } - for (Iterator>> iter = + for (Iterator>> iter = mObservers.entrySet().iterator(); iter.hasNext(); ) { - Map.Entry> entry = iter.next(); + Map.Entry> entry = iter.next(); if (entry.getKey().equals(getKey(obj))) { iter.remove(); @@ -170,6 +175,11 @@ public class TaskSchedulers implements ISchedulers { } @Override public boolean handleMessage(Message msg) { + if (msg.what == CHECK_FAIL) { + handlePreFailTask(msg.arg1); + return true; + } + if (msg.arg1 == IS_SUB_TASK) { return handleSubEvent(msg); } @@ -179,11 +189,11 @@ public class TaskSchedulers implements ISchedulers { } TASK task = (TASK) msg.obj; - if (task == null && msg.what != ISchedulers.CHECK_FAIL) { + if (task == null) { ALog.e(TAG, "请传入下载任务"); return true; } - handleNormalEvent(task, msg.what, msg.arg1); + handleNormalEvent(task, msg.what); return true; } @@ -195,7 +205,7 @@ public class TaskSchedulers implements ISchedulers { if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } @@ -225,11 +235,12 @@ public class TaskSchedulers implements ISchedulers { } } - boolean canSend = mAriaManager.getAppConfig().isUseBroadcast(); + boolean canSend = mAriaConfig.getAConfig().isUseBroadcast(); if (canSend) { Intent intent = new Intent(ISchedulers.ARIA_TASK_INFO_ACTION); + intent.setPackage(mAriaConfig.getAPP().getPackageName()); intent.putExtras(data); - mAriaManager.getAPP().sendBroadcast(intent); + mAriaConfig.getAPP().sendBroadcast(intent); } return true; @@ -243,7 +254,7 @@ public class TaskSchedulers implements ISchedulers { if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } @@ -279,9 +290,9 @@ public class TaskSchedulers implements ISchedulers { } } - boolean canSend = mAriaManager.getAppConfig().isUseBroadcast(); + boolean canSend = mAriaConfig.getAConfig().isUseBroadcast(); if (canSend) { - mAriaManager.getAPP().sendBroadcast( + mAriaConfig.getAPP().sendBroadcast( createData(msg.what, ITask.DOWNLOAD_GROUP_SUB, params.entity)); } @@ -291,8 +302,8 @@ public class TaskSchedulers implements ISchedulers { /** * 处理普通任务和任务组的事件 */ - private void handleNormalEvent(TASK task, int what, int arg1) { - ITaskQueue queue = getQueue(task); + private void handleNormalEvent(TASK task, int what) { + ITaskQueue queue = getQueue(task.getTaskType()); switch (what) { case STOP: if (task.getState() == IEntity.STATE_WAIT) { @@ -323,9 +334,6 @@ public class TaskSchedulers implements ISchedulers { case FAIL: handleFailTask(queue, task); break; - case CHECK_FAIL: - handlePreFailTask(queue, arg1); - break; } if (what == FAIL || what == CHECK_FAIL) { @@ -342,35 +350,41 @@ public class TaskSchedulers implements ISchedulers { normalTaskCallback(what, task); } - private void handlePreFailTask(ITaskQueue queue, int taskType) { - startNextTask(queue, TaskSchedulerType.TYPE_DEFAULT); + /** + * 处理what为{@link #CHECK_FAIL}信息错误的任务 + * + * @param taskType 任务类型 + */ + private void handlePreFailTask(int taskType) { + startNextTask(getQueue(taskType), TaskSchedulerType.TYPE_DEFAULT); // 发送广播 - boolean canSend = mAriaManager.getAppConfig().isUseBroadcast(); + boolean canSend = mAriaConfig.getAConfig().isUseBroadcast(); if (canSend) { Intent intent = new Intent(ISchedulers.ARIA_TASK_INFO_ACTION); + intent.setPackage(mAriaConfig.getAPP().getPackageName()); Bundle b = new Bundle(); b.putInt(ISchedulers.TASK_TYPE, taskType); b.putInt(ISchedulers.TASK_STATE, ISchedulers.FAIL); - mAriaManager.getAPP().sendBroadcast(intent); + mAriaConfig.getAPP().sendBroadcast(intent); } // 处理回调 if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } - NormalTaskListener listener = null; + NormalTaskListenerInterface listener = null; if (mObservers.get(key) != null) { if (taskType == ITask.DOWNLOAD) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD); } else if (taskType == ITask.DOWNLOAD_GROUP) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD_GROUP); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD_GROUP); } else if (taskType == ITask.DOWNLOAD_GROUP) { - listener = (NormalTaskListener) listeners.get(TaskEnum.UPLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.UPLOAD); } } if (listener != null) { @@ -390,18 +404,18 @@ public class TaskSchedulers implements ISchedulers { if (mObservers.size() > 0) { Set keys = mObservers.keySet(); for (String key : keys) { - Map listeners = mObservers.get(key); + Map listeners = mObservers.get(key); if (listeners == null || listeners.isEmpty()) { continue; } - NormalTaskListener listener = null; + NormalTaskListenerInterface listener = null; if (mObservers.get(key) != null) { if (task instanceof DownloadTask) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD); } else if (task instanceof DownloadGroupTask) { - listener = (NormalTaskListener) listeners.get(TaskEnum.DOWNLOAD_GROUP); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.DOWNLOAD_GROUP); } else if (task instanceof UploadTask) { - listener = (NormalTaskListener) listeners.get(TaskEnum.UPLOAD); + listener = (NormalTaskListenerInterface) listeners.get(TaskEnum.UPLOAD); } } if (listener != null) { @@ -411,7 +425,7 @@ public class TaskSchedulers implements ISchedulers { } } - private void normalTaskCallback(int state, TASK task, NormalTaskListener listener) { + private void normalTaskCallback(int state, TASK task, NormalTaskListenerInterface listener) { if (listener != null) { if (task == null && state != ISchedulers.CHECK_FAIL) { ALog.e(TAG, "TASK 为null,回调失败"); @@ -463,16 +477,16 @@ public class TaskSchedulers implements ISchedulers { * 发送普通任务的广播 */ private void sendNormalBroadcast(int state, TASK task) { - boolean canSend = mAriaManager.getAppConfig().isUseBroadcast(); + boolean canSend = mAriaConfig.getAConfig().isUseBroadcast(); if (!canSend) { return; } int type = task.getTaskType(); if (type == ITask.DOWNLOAD || type == ITask.DOWNLOAD_GROUP) { - mAriaManager.getAPP().sendBroadcast( + mAriaConfig.getAPP().sendBroadcast( createData(state, type, task.getTaskWrapper().getEntity())); } else if (type == ITask.UPLOAD) { - mAriaManager.getAPP().sendBroadcast( + mAriaConfig.getAPP().sendBroadcast( createData(state, type, task.getTaskWrapper().getEntity())); } else { ALog.w(TAG, "发送广播失败,没有对应的任务"); @@ -488,6 +502,7 @@ public class TaskSchedulers implements ISchedulers { */ private Intent createData(int taskState, int taskType, AbsEntity entity) { Intent intent = new Intent(ISchedulers.ARIA_TASK_INFO_ACTION); + intent.setPackage(mAriaConfig.getAPP().getPackageName()); Bundle b = new Bundle(); b.putInt(ISchedulers.TASK_TYPE, taskType); b.putInt(ISchedulers.TASK_STATE, taskState); @@ -512,9 +527,9 @@ public class TaskSchedulers implements ISchedulers { } int num = task.getTaskWrapper().getConfig().getReTryNum(); - boolean isNotNetRetry = mAriaManager.getAppConfig().isNotNetRetry(); + boolean isNotNetRetry = mAriaConfig.getAConfig().isNotNetRetry(); - if ((!NetUtils.isConnected(mAriaManager.getAPP()) && !isNotNetRetry) + if ((!NetUtils.isConnected(mAriaConfig.getAPP()) && !isNotNetRetry) || task.getTaskWrapper().getEntity().getFailNum() > num) { queue.removeTaskFormQueue(task.getKey()); startNextTask(queue, task.getSchedulerType()); diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/CheckUEntityUtil.java b/Aria/src/main/java/com/arialyy/aria/core/upload/CheckUEntityUtil.java index b92446bb..ea6c81f2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/CheckUEntityUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/CheckUEntityUtil.java @@ -17,7 +17,6 @@ package com.arialyy.aria.core.upload; import android.text.TextUtils; import com.arialyy.aria.core.inf.ICheckEntityUtil; -import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CheckUtil; import java.io.File; @@ -26,12 +25,14 @@ public class CheckUEntityUtil implements ICheckEntityUtil { private final String TAG = "CheckUEntityUtil"; private UTaskWrapper mWrapper; private UploadEntity mEntity; + private int action; - public static CheckUEntityUtil newInstance(UTaskWrapper wrapper) { - return new CheckUEntityUtil(wrapper); + public static CheckUEntityUtil newInstance(UTaskWrapper wrapper, int action) { + return new CheckUEntityUtil(wrapper, action); } - private CheckUEntityUtil(UTaskWrapper wrapper) { + private CheckUEntityUtil(UTaskWrapper wrapper, int action) { + this.action = action; mWrapper = wrapper; mEntity = mWrapper.getEntity(); } @@ -39,11 +40,11 @@ public class CheckUEntityUtil implements ICheckEntityUtil { @Override public boolean checkEntity() { if (mWrapper.getErrorEvent() != null) { - ALog.e(TAG, mWrapper.getErrorEvent().errorMsg); + ALog.e(TAG, String.format("任务操作失败,%s", mWrapper.getErrorEvent().errorMsg)); return false; } - boolean b = checkFtps() && checkUrl() && checkFilePath(); + boolean b = checkUrl() && checkFilePath(); if (b) { mEntity.save(); } @@ -59,6 +60,12 @@ public class CheckUEntityUtil implements ICheckEntityUtil { ALog.e(TAG, "上传失败,文件路径【" + filePath + "】不合法"); return false; } + // 任务是新任务,并且路径冲突就不会继续执行 + if (mWrapper.isNewTask() + && !CheckUtil.checkUPathConflicts(mWrapper.isIgnoreFilePathOccupy(), filePath, + mWrapper.getRequestType())) { + return false; + } File file = new File(mEntity.getFilePath()); if (!file.exists()) { @@ -69,6 +76,7 @@ public class CheckUEntityUtil implements ICheckEntityUtil { ALog.e(TAG, "上传失败,文件【" + filePath + "】不能是文件夹"); return false; } + return true; } @@ -78,7 +86,7 @@ public class CheckUEntityUtil implements ICheckEntityUtil { if (TextUtils.isEmpty(url)) { ALog.e(TAG, "上传失败,url为null"); return false; - } else if (!CheckUtil.checkUrlNotThrow(url)) { + } else if (!CheckUtil.checkUrl(url)) { ALog.e(TAG, "上传失败,url【" + url + "】错误"); return false; } @@ -90,16 +98,4 @@ public class CheckUEntityUtil implements ICheckEntityUtil { mEntity.setUrl(url); return true; } - - private boolean checkFtps() { - //if (mWrapper.getRequestType() == ITaskWrapper.U_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; - } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java deleted file mode 100644 index c37416c2..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadListener.java +++ /dev/null @@ -1,56 +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.upload; - -import com.arialyy.aria.core.inf.IUploadListener; -import com.arialyy.aria.exception.BaseException; - -/** - * Created by lyy on 2017/2/23. - */ -class UploadListener implements IUploadListener { - @Override public void onPre() { - - } - - @Override public void onStart(long startLocation) { - - } - - @Override public void onResume(long resumeLocation) { - - } - - @Override public void onStop(long stopLocation) { - - } - - @Override public void onProgress(long currentLocation) { - - } - - @Override public void onCancel() { - - } - - @Override public void onComplete() { - - } - - @Override public void onFail(boolean needRetry, BaseException e) { - - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java index 99383582..cb299fb7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadReceiver.java @@ -16,29 +16,31 @@ package com.arialyy.aria.core.upload; import android.text.TextUtils; -import androidx.annotation.CheckResult; -import androidx.annotation.NonNull; import com.arialyy.annotations.TaskEnum; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.AriaManager; 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.common.AbsBuilderTarget; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.common.ProxyHelper; 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.ITask; import com.arialyy.aria.core.inf.ReceiverType; +import com.arialyy.aria.core.queue.UTaskQueue; +import com.arialyy.aria.core.scheduler.TaskInternalListenerInterface; import com.arialyy.aria.core.scheduler.TaskSchedulers; -import com.arialyy.aria.core.upload.normal.FtpBuilderTarget; -import com.arialyy.aria.core.upload.normal.FtpNormalTarget; -import com.arialyy.aria.core.upload.normal.HttpBuilderTarget; -import com.arialyy.aria.core.upload.normal.HttpNormalTarget; -import com.arialyy.aria.core.upload.normal.UNormalTargetFactory; +import com.arialyy.aria.core.task.ITask; +import com.arialyy.aria.core.upload.target.FtpBuilderTarget; +import com.arialyy.aria.core.upload.target.FtpNormalTarget; +import com.arialyy.aria.core.upload.target.HttpBuilderTarget; +import com.arialyy.aria.core.upload.target.HttpNormalTarget; +import com.arialyy.aria.core.upload.target.UTargetFactory; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CheckUtil; -import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.ComponentUtil; import java.util.List; import java.util.Set; @@ -47,7 +49,9 @@ import java.util.Set; * 上传功能接收器 */ public class UploadReceiver extends AbsReceiver { - private static final String TAG = "UploadReceiver"; + public UploadReceiver(Object obj) { + super(obj); + } /** * 设置最大上传速度,单位:kb @@ -57,7 +61,7 @@ public class UploadReceiver extends AbsReceiver { */ @Deprecated public UploadReceiver setMaxSpeed(int maxSpeed) { - AriaManager.getInstance().getUploadConfig().setMaxSpeed(maxSpeed); + AriaConfig.getInstance().getUConfig().setMaxSpeed(maxSpeed); return this; } @@ -66,11 +70,12 @@ public class UploadReceiver extends AbsReceiver { * * @param filePath 文件路径 */ - @CheckResult - public HttpBuilderTarget load(@NonNull String filePath) { - CheckUtil.checkUploadPath(filePath); - return UNormalTargetFactory.getInstance() - .generateBuilderTarget(HttpBuilderTarget.class, filePath, targetName); + + public HttpBuilderTarget load(String filePath) { + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP); + CheckUtil.checkUploadPathIsEmpty(filePath); + return UTargetFactory.getInstance() + .generateBuilderTarget(HttpBuilderTarget.class, filePath); } /** @@ -79,11 +84,11 @@ public class UploadReceiver extends AbsReceiver { * @param taskId 任务id,可从{@link AbsBuilderTarget#create()}、{@link AbsBuilderTarget#add()}、{@link * AbsEntity#getId()}读取任务id */ - @CheckResult + public HttpNormalTarget load(long taskId) { - CheckUtil.checkTaskId(taskId); - return UNormalTargetFactory.getInstance() - .generateNormalTarget(HttpNormalTarget.class, taskId, targetName); + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_HTTP); + return UTargetFactory.getInstance() + .generateNormalTarget(HttpNormalTarget.class, taskId); } /** @@ -91,11 +96,12 @@ public class UploadReceiver extends AbsReceiver { * * @param filePath 文件路径 */ - @CheckResult - public FtpBuilderTarget loadFtp(@NonNull String filePath) { - CheckUtil.checkUploadPath(filePath); - return UNormalTargetFactory.getInstance() - .generateBuilderTarget(FtpBuilderTarget.class, filePath, targetName); + + public FtpBuilderTarget loadFtp(String filePath) { + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP); + CheckUtil.checkUploadPathIsEmpty(filePath); + return UTargetFactory.getInstance() + .generateBuilderTarget(FtpBuilderTarget.class, filePath); } /** @@ -104,11 +110,11 @@ public class UploadReceiver extends AbsReceiver { * @param taskId 任务id,可从{@link AbsBuilderTarget#create()}、{@link AbsBuilderTarget#add()}、{@link * AbsEntity#getId()}读取任务id */ - @CheckResult + public FtpNormalTarget loadFtp(long taskId) { - CheckUtil.checkTaskId(taskId); - return UNormalTargetFactory.getInstance() - .generateNormalTarget(FtpNormalTarget.class, taskId, targetName); + ComponentUtil.getInstance().checkComponentExist(ComponentUtil.COMPONENT_TYPE_FTP); + return UTargetFactory.getInstance() + .generateNormalTarget(FtpNormalTarget.class, taskId); } /** @@ -228,6 +234,15 @@ public class UploadReceiver extends AbsReceiver { ITask.UPLOAD)); } + /** + * 获取执行中的任务 + * + * @return 没有执行中的任务,返回null + */ + public List getURunningTask() { + return UTaskQueue.getInstance().getRunningTask(UploadEntity.class); + } + /** * 删除所有任务 * @@ -237,7 +252,7 @@ public class UploadReceiver extends AbsReceiver { public void removeAllTask(boolean removeFile) { final AriaManager am = AriaManager.getInstance(); CancelAllCmd cancelCmd = - (CancelAllCmd) CommonUtil.createNormalCmd(new UTaskWrapper(null), + (CancelAllCmd) CmdHelper.createNormalCmd(new UTaskWrapper(null), NormalCmdFactory.TASK_CANCEL_ALL, ITask.UPLOAD); cancelCmd.removeFile = removeFile; @@ -252,15 +267,18 @@ public class UploadReceiver extends AbsReceiver { * 将当前类注册到Aria */ public void register() { - if (TextUtils.isEmpty(targetName)) { - ALog.e(TAG, "upload register target null"); + if (obj == null) { + ALog.e(TAG, String.format("【%s】观察者为空", getTargetName())); return; } - Object obj = OBJ_MAP.get(getKey()); - if (obj == null) { - ALog.e(TAG, String.format("【%s】观察者为空", targetName)); + if (obj instanceof TaskInternalListenerInterface){ + ProxyHelper.getInstance().checkProxyType(obj.getClass()); + if (obj instanceof UploadTaskListener){ + TaskSchedulers.getInstance().register(obj, TaskEnum.UPLOAD); + } return; } + Set set = ProxyHelper.getInstance().checkProxyType(obj.getClass()); if (set != null && !set.isEmpty()) { for (Integer type : set) { @@ -269,7 +287,7 @@ public class UploadReceiver extends AbsReceiver { } } } else { - ALog.i(TAG, "没有Aria的注解方法"); + ALog.e(TAG, "没有Aria的注解方法,详情见:https://aria.laoyuyu.me/aria_doc/other/annotaion_invalid.html"); } } @@ -278,24 +296,19 @@ public class UploadReceiver extends AbsReceiver { * 如果是Dialog或popupwindow,需要你在撤销界面时调用该方法 */ @Override public void unRegister() { - if (needRmListener) { + if (isNeedRmListener()) { unRegisterListener(); } - AriaManager.getInstance().removeReceiver(OBJ_MAP.get(getKey())); + AriaManager.getInstance().removeReceiver(obj); } - @Override public String getType() { + @Override public ReceiverType getType() { return ReceiverType.UPLOAD; } @Override protected void unRegisterListener() { - if (TextUtils.isEmpty(targetName)) { - ALog.e(TAG, "upload unRegisterListener target null"); - return; - } - Object obj = OBJ_MAP.get(getKey()); if (obj == null) { - ALog.e(TAG, String.format("【%s】观察者为空", targetName)); + ALog.e(TAG, String.format("【%s】观察者为空", getTargetName())); return; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTaskListener.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTaskListener.java new file mode 100644 index 00000000..ec734377 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/UploadTaskListener.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.upload; + +import com.arialyy.aria.core.scheduler.NormalTaskListenerInterface; +import com.arialyy.aria.core.task.UploadTask; + +/** + * 上传任务接口 + * + * @author ChenFei(chenfei0928 @ gmail.com) + * Date: 2020-07-07 13:23 + */ +public interface UploadTaskListener extends NormalTaskListenerInterface { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/FtpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/normal/FtpBuilderTarget.java deleted file mode 100644 index a823b4da..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/FtpBuilderTarget.java +++ /dev/null @@ -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.upload.normal; - -import androidx.annotation.CheckResult; -import androidx.annotation.NonNull; -import com.arialyy.aria.core.common.AbsBuilderTarget; -import com.arialyy.aria.core.common.Suggest; -import com.arialyy.aria.core.common.ftp.FtpDelegate; -import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor; -import com.arialyy.aria.core.inf.AbsTaskWrapper; - -/** - * Created by Aria.Lao on 2017/7/27. - * ftp单任务上传 - */ -public class FtpBuilderTarget extends AbsBuilderTarget { - private UNormalConfigHandler mConfigHandler; - - FtpBuilderTarget(String filePath, String targetName) { - mConfigHandler = new UNormalConfigHandler<>(this, -1, targetName); - mConfigHandler.setFilePath(filePath); - getTaskWrapper().setRequestType(AbsTaskWrapper.U_FTP); - } - - /** - * 设置上传路径 - * - * @param tempUrl 上传路径 - */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpBuilderTarget setUploadUrl(String tempUrl) { - mConfigHandler.setTempUrl(tempUrl); - return this; - } - - /** - * FTP文件上传拦截器,如果远端已有同名文件,可使用该拦截器控制覆盖文件或修改该文件上传到服务器端端的文件名 - */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpBuilderTarget setUploadInterceptor(@NonNull IFtpUploadInterceptor uploadInterceptor) { - return mConfigHandler.setUploadInterceptor(uploadInterceptor); - } - - /** - * 设置登陆、字符串编码、ftps等参数 - */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpDelegate option() { - return new FtpDelegate<>(this, getTaskWrapper()); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java new file mode 100644 index 00000000..4c7ee63e --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpBuilderTarget.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.upload.target; + +import com.arialyy.aria.core.common.AbsBuilderTarget; +import com.arialyy.aria.core.common.FtpOption; +import com.arialyy.aria.core.common.SFtpOption; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.CommonUtil; + +/** + * Created by Aria.Lao on 2017/7/27. + * ftp单任务上传 + */ +public class FtpBuilderTarget extends AbsBuilderTarget { + private UNormalConfigHandler mConfigHandler; + private String url; + + FtpBuilderTarget(String filePath) { + mConfigHandler = new UNormalConfigHandler<>(this, -1); + mConfigHandler.setFilePath(filePath); + getTaskWrapper().setRequestType(ITaskWrapper.U_FTP); + ((UploadEntity)getEntity()).setTaskType(ITaskWrapper.U_FTP); + getTaskWrapper().setNewTask(true); + } + + /** + * 设置上传路径 + * + * @param tempUrl 上传路径 + */ + public FtpBuilderTarget setUploadUrl(String tempUrl) { + url = tempUrl; + mConfigHandler.setTempUrl(tempUrl); + return this; + } + + /** + * 如果文件路径被其它任务占用,删除其它任务 + * + * @deprecated 使用 {@link #ignoreFilePathOccupy()} + */ + @Deprecated + public FtpBuilderTarget forceUpload() { + getTaskWrapper().setIgnoreFilePathOccupy(true); + return this; + } + + /** + * 设置登陆、字符串编码、ftps等参数 + */ + public FtpBuilderTarget option(FtpOption option) { + if (option == null) { + throw new NullPointerException("ftp 任务配置为空"); + } + option.setUrlEntity(CommonUtil.getFtpUrlInfo(url)); + getTaskWrapper().getOptionParams().setParams(option); + return this; + } + + /** + * 设置登陆、字符串编码、sftp等参数 + */ + public FtpBuilderTarget sftpOption(SFtpOption option) { + if (option == null) { + throw new NullPointerException("ftp 任务配置为空"); + } + option.setUrlEntity(CommonUtil.getFtpUrlInfo(url)); + getTaskWrapper().getOptionParams().setParams(option); + ((UploadEntity)getEntity()).setTaskType(ITaskWrapper.U_SFTP); + getTaskWrapper().setRequestType(ITaskWrapper.U_SFTP); + return this; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/FtpNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java similarity index 53% rename from Aria/src/main/java/com/arialyy/aria/core/upload/normal/FtpNormalTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java index ba6e8128..8597665c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/FtpNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/FtpNormalTarget.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.normal; +package com.arialyy.aria.core.upload.target; -import androidx.annotation.CheckResult; import com.arialyy.aria.core.common.AbsNormalTarget; -import com.arialyy.aria.core.common.Suggest; -import com.arialyy.aria.core.common.ftp.FtpDelegate; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.common.FtpOption; +import com.arialyy.aria.core.common.SFtpOption; import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.util.CommonUtil; /** @@ -30,18 +29,36 @@ import com.arialyy.aria.util.CommonUtil; public class FtpNormalTarget extends AbsNormalTarget { private UNormalConfigHandler mConfigHandler; - FtpNormalTarget(long taskId, String targetName) { - mConfigHandler = new UNormalConfigHandler<>(this, taskId, targetName); - getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(getEntity().getUrl())); - getTaskWrapper().setRequestType(AbsTaskWrapper.U_FTP); + FtpNormalTarget(long taskId) { + mConfigHandler = new UNormalConfigHandler<>(this, taskId); + getTaskWrapper().setRequestType(ITaskWrapper.U_FTP); + getTaskWrapper().setNewTask(false); } /** * 设置登陆、字符串编码、ftps等参数 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public FtpDelegate option() { - return new FtpDelegate<>(this, getTaskWrapper()); + public FtpNormalTarget option(FtpOption option) { + if (option == null) { + 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(getEntity().getUrl())); + getTaskWrapper().getOptionParams().setParams(option); + (getEntity()).setTaskType(ITaskWrapper.U_SFTP); + getTaskWrapper().setRequestType(ITaskWrapper.U_SFTP); + return this; } @Override public UploadEntity getEntity() { diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/HttpBuilderTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/HttpBuilderTarget.java similarity index 57% rename from Aria/src/main/java/com/arialyy/aria/core/upload/normal/HttpBuilderTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/upload/target/HttpBuilderTarget.java index 1badcbaa..4940a4c8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/HttpBuilderTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/HttpBuilderTarget.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.normal; +package com.arialyy.aria.core.upload.target; -import androidx.annotation.CheckResult; import com.arialyy.aria.core.common.AbsBuilderTarget; -import com.arialyy.aria.core.common.Suggest; -import com.arialyy.aria.core.common.http.HttpDelegate; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.common.HttpOption; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; /** * Created by lyy on 2017/2/28. @@ -28,13 +27,14 @@ import com.arialyy.aria.core.inf.AbsTaskWrapper; public class HttpBuilderTarget extends AbsBuilderTarget { private UNormalConfigHandler mConfigHandler; - HttpBuilderTarget(String filePath, String targetName) { - - mConfigHandler = new UNormalConfigHandler<>(this, -1, targetName); + HttpBuilderTarget(String filePath) { + mConfigHandler = new UNormalConfigHandler<>(this, -1); mConfigHandler.setFilePath(filePath); //http暂时不支持断点上传 getTaskWrapper().setSupportBP(false); - getTaskWrapper().setRequestType(AbsTaskWrapper.U_HTTP); + getTaskWrapper().setRequestType(ITaskWrapper.U_HTTP); + ((UploadEntity) getEntity()).setTaskType(ITaskWrapper.U_HTTP); + getTaskWrapper().setNewTask(true); } /** @@ -42,7 +42,6 @@ public class HttpBuilderTarget extends AbsBuilderTarget { * * @param tempUrl 上传路径 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public HttpBuilderTarget setUploadUrl(String tempUrl) { mConfigHandler.setTempUrl(tempUrl); return this; @@ -51,8 +50,23 @@ public class HttpBuilderTarget extends AbsBuilderTarget { /** * 设置http请求参数,header等信息 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpDelegate option() { - return new HttpDelegate<>(this, getTaskWrapper()); + public HttpBuilderTarget option(HttpOption option) { + + if (option == null) { + throw new NullPointerException("任务配置为空"); + } + getTaskWrapper().getOptionParams().setParams(option); + return this; + } + + /** + * 如果文件路径被其它任务占用,删除其它任务 + * + * @deprecated 使用 {@link #ignoreFilePathOccupy()} + */ + @Deprecated + public HttpBuilderTarget forceUpload() { + getTaskWrapper().setIgnoreFilePathOccupy(true); + return this; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/HttpNormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/HttpNormalTarget.java similarity index 69% rename from Aria/src/main/java/com/arialyy/aria/core/upload/normal/HttpNormalTarget.java rename to Aria/src/main/java/com/arialyy/aria/core/upload/target/HttpNormalTarget.java index c83e8b46..af61eb87 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/HttpNormalTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/HttpNormalTarget.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.normal; +package com.arialyy.aria.core.upload.target; -import androidx.annotation.CheckResult; import com.arialyy.aria.core.common.AbsNormalTarget; -import com.arialyy.aria.core.common.Suggest; -import com.arialyy.aria.core.common.http.HttpDelegate; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.common.HttpOption; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.util.ALog; /** * Created by lyy on 2017/2/28. @@ -28,10 +27,11 @@ import com.arialyy.aria.core.inf.AbsTaskWrapper; public class HttpNormalTarget extends AbsNormalTarget { private UNormalConfigHandler mConfigHandler; - HttpNormalTarget(long taskId, String targetName) { - mConfigHandler = new UNormalConfigHandler<>(this, taskId, targetName); + HttpNormalTarget(long taskId) { + mConfigHandler = new UNormalConfigHandler<>(this, taskId); getTaskWrapper().setSupportBP(false); getTaskWrapper().setRequestType(AbsTaskWrapper.U_HTTP); + getTaskWrapper().setNewTask(false); } /** @@ -39,7 +39,6 @@ public class HttpNormalTarget extends AbsNormalTarget { * * @param tempUrl 上传路径 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) public HttpNormalTarget setUploadUrl(String tempUrl) { mConfigHandler.setTempUrl(tempUrl); return this; @@ -48,9 +47,16 @@ public class HttpNormalTarget extends AbsNormalTarget { /** * 设置http请求参数,header等信息 */ - @CheckResult(suggest = Suggest.TASK_CONTROLLER) - public HttpDelegate option() { - return new HttpDelegate<>(this, getTaskWrapper()); + public HttpNormalTarget option(HttpOption option) { + if (option == null) { + throw new NullPointerException("任务配置为空"); + } + getTaskWrapper().getOptionParams().setParams(option); + return this; + } + + @Override public void resume() { + ALog.e(TAG, "http上传任务没有恢复功能"); } @Override public boolean isRunning() { diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/UNormalConfigHandler.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/UNormalConfigHandler.java similarity index 65% rename from Aria/src/main/java/com/arialyy/aria/core/upload/normal/UNormalConfigHandler.java rename to Aria/src/main/java/com/arialyy/aria/core/upload/target/UNormalConfigHandler.java index 90144aa9..310c8703 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/UNormalConfigHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/UNormalConfigHandler.java @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.normal; +package com.arialyy.aria.core.upload.target; -import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.AbsNormalTarget; +import com.arialyy.aria.core.common.ErrorCode; import com.arialyy.aria.core.event.ErrorEvent; -import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.IConfigHandler; import com.arialyy.aria.core.manager.TaskWrapperManager; -import com.arialyy.aria.core.queue.UploadTaskQueue; +import com.arialyy.aria.core.queue.UTaskQueue; +import com.arialyy.aria.core.task.UploadTask; import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.core.upload.UploadTask; import com.arialyy.aria.orm.DbEntity; -import com.arialyy.aria.util.CommonUtil; import java.io.File; /** @@ -34,23 +34,27 @@ import java.io.File; * 普通上传任务通用功能处理 */ class UNormalConfigHandler implements IConfigHandler { - private String TAG = "UNormalDelegate"; private UploadEntity mEntity; private TARGET mTarget; private UTaskWrapper mWrapper; - UNormalConfigHandler(TARGET target, long taskId, String targetName) { + UNormalConfigHandler(TARGET target, long taskId) { mTarget = target; - initTarget(taskId, targetName); + initTarget(taskId); } - private void initTarget(long taskId, String targetName) { + private void initTarget(long taskId) { mWrapper = TaskWrapperManager.getInstance().getNormalTaskWrapper(UTaskWrapper.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); mTarget.setTaskWrapper(mWrapper); getTaskWrapper().setTempUrl(mEntity.getUrl()); } @@ -62,14 +66,6 @@ class UNormalConfigHandler implements IConfigHandler { mEntity.setFileSize(file.length()); } - TARGET setUploadInterceptor(IFtpUploadInterceptor uploadInterceptor) { - if (uploadInterceptor == null) { - throw new NullPointerException("ftp拦截器为空"); - } - getTaskWrapper().asFtp().setUploadInterceptor(uploadInterceptor); - return mTarget; - } - @Override public AbsEntity getEntity() { return mEntity; } @@ -79,13 +75,12 @@ class UNormalConfigHandler implements IConfigHandler { } @Override public boolean isRunning() { - UploadTask task = UploadTaskQueue.getInstance().getTask(mEntity.getKey()); + UploadTask task = UTaskQueue.getInstance().getTask(mEntity.getKey()); return task != null && task.isRunning(); } void setTempUrl(String tempUrl) { getTaskWrapper().setTempUrl(tempUrl); - getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(tempUrl)); } private UTaskWrapper getTaskWrapper() { diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/UNormalTargetFactory.java b/Aria/src/main/java/com/arialyy/aria/core/upload/target/UTargetFactory.java similarity index 64% rename from Aria/src/main/java/com/arialyy/aria/core/upload/normal/UNormalTargetFactory.java rename to Aria/src/main/java/com/arialyy/aria/core/upload/target/UTargetFactory.java index bdd87e7a..64088ac9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/normal/UNormalTargetFactory.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/target/UTargetFactory.java @@ -13,29 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.normal; +package com.arialyy.aria.core.upload.target; import com.arialyy.aria.core.common.AbsBuilderTarget; import com.arialyy.aria.core.common.AbsNormalTarget; /** - * @Author aria - * @Date 2019-09-05 + * @author aria + * Date: 2019-09-05 */ -public class UNormalTargetFactory { +public class UTargetFactory { - public static volatile UNormalTargetFactory INSTANCE; + public static volatile UTargetFactory INSTANCE; - private UNormalTargetFactory() { + private UTargetFactory() { } - public static UNormalTargetFactory getInstance() { + public static UTargetFactory getInstance() { if (INSTANCE == null) { - synchronized (UNormalTargetFactory.class) { + synchronized (UTargetFactory.class) { if (INSTANCE == null) { - INSTANCE = new UNormalTargetFactory(); + INSTANCE = new UTargetFactory(); } } } @@ -43,25 +43,23 @@ public class UNormalTargetFactory { return INSTANCE; } - public T generateNormalTarget(Class clazz, long taskId, - String targetName) { + public T generateNormalTarget(Class clazz, long taskId) { T target = null; if (clazz == HttpNormalTarget.class) { - target = (T) new HttpNormalTarget(taskId, targetName); + target = (T) new HttpNormalTarget(taskId); } else if (clazz == FtpNormalTarget.class) { - target = (T) new FtpNormalTarget(taskId, targetName); + target = (T) new FtpNormalTarget(taskId); } return target; } - public T generateBuilderTarget(Class clazz, String url, - String targetName) { + public T generateBuilderTarget(Class clazz, String url) { T target = null; if (clazz == HttpBuilderTarget.class) { - target = (T) new HttpBuilderTarget(url, targetName); + target = (T) new HttpBuilderTarget(url); } else if (clazz == FtpBuilderTarget.class) { - target = (T) new FtpBuilderTarget(url, targetName); + target = (T) new FtpBuilderTarget(url); } return target; diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java deleted file mode 100644 index a41e3f9a..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java +++ /dev/null @@ -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.upload.uploader; - -import android.text.TextUtils; -import aria.apache.commons.net.ftp.FTPClient; -import aria.apache.commons.net.ftp.FTPReply; -import aria.apache.commons.net.ftp.OnFtpInputStreamListener; -import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask; -import com.arialyy.aria.core.common.ftp.FtpTaskConfig; -import com.arialyy.aria.core.config.UploadConfig; -import com.arialyy.aria.core.upload.UTaskWrapper; -import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.exception.AriaIOException; -import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.BufferedRandomAccessFile; -import com.arialyy.aria.util.CommonUtil; -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -/** - * Created by Aria.Lao on 2017/7/28. D_FTP 单线程上传任务,需要FTP 服务器给用户打开append和write的权限 - */ -class FtpThreadTask extends AbsFtpThreadTask { - private final String TAG = "FtpThreadTask"; - private String dir, remotePath; - - FtpThreadTask(SubThreadConfig config) { - super(config); - } - - @Override public int getMaxSpeed() { - return getTaskConfig().getMaxSpeed(); - } - - @Override protected UploadConfig getTaskConfig() { - return getTaskWrapper().getConfig(); - } - - @Override public FtpThreadTask call() throws Exception { - super.call(); - FTPClient client = null; - BufferedRandomAccessFile file = null; - try { - ALog.d(TAG, - String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getFileName(), - mRecord.threadId, mRecord.startLocation, mRecord.endLocation)); - client = createClient(); - if (client == null) { - return this; - } - initPath(); - client.makeDirectory(dir); - client.changeWorkingDirectory(dir); - client.setRestartOffset(mRecord.startLocation); - int reply = client.getReplyCode(); - if (!FTPReply.isPositivePreliminary(reply) && reply != FTPReply.FILE_ACTION_OK) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, - String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, - client.getReplyString(), getEntity().getFilePath()))); - client.disconnect(); - return this; - } - - file = - new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize()); - if (mRecord.startLocation != 0) { - //file.skipBytes((int) getConfig().START_LOCATION); - file.seek(mRecord.startLocation); - } - boolean complete = upload(client, file); - if (!complete || isBreak()) { - return this; - } - ALog.i(TAG, String.format("任务【%s】线程__%s__上传完毕", getFileName(), mRecord.threadId)); - writeConfig(true, mRecord.endLocation); - sendCompleteMsg(); - } catch (IOException e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(), - getConfig().url))); - } catch (Exception e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, null, e)); - } finally { - try { - if (file != null) { - file.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - closeClient(client); - onThreadComplete(); - } - return this; - } - - private void initPath() throws UnsupportedEncodingException { - FtpTaskConfig delegate = getTaskWrapper().asFtp(); - dir = CommonUtil.convertFtpChar(charSet, delegate.getUrlEntity().remotePath); - - String fileName = - TextUtils.isEmpty(delegate.getNewFileName()) ? CommonUtil.convertFtpChar(charSet, - getEntity().getFileName()) - : CommonUtil.convertFtpChar(charSet, delegate.getNewFileName()); - - remotePath = - CommonUtil.convertFtpChar(charSet, - String.format("%s/%s", delegate.getUrlEntity().remotePath, fileName)); - } - - /** - * 上传 - * - * @return {@code true}上传成功、{@code false} 上传失败 - */ - private boolean upload(final FTPClient client, final BufferedRandomAccessFile bis) - throws IOException { - - try { - ALog.d(TAG, String.format("remotePath: %s", remotePath)); - client.storeFile(remotePath, new FtpFISAdapter(bis), new OnFtpInputStreamListener() { - boolean isStoped = false; - - @Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred, - int bytesTransferred, long streamSize) { - try { - if (isBreak() && !isStoped) { - isStoped = true; - client.abor(); - } - if (mSpeedBandUtil != null) { - mSpeedBandUtil.limitNextBytes(bytesTransferred); - } - progress(bytesTransferred); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - } catch (IOException e) { - String msg = String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", client.getReplyCode(), - client.getReplyString(), getEntity().getFilePath()); - if (client.isConnected()) { - client.disconnect(); - } - if (e.getMessage().contains("AriaIOException caught while copying")) { - e.printStackTrace(); - } else { - fail(mChildCurrentLocation, - new AriaIOException(TAG, msg, e)); - } - return false; - } - - int reply = client.getReplyCode(); - if (!FTPReply.isPositiveCompletion(reply)) { - if (reply != FTPReply.TRANSFER_ABORTED) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, - String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, - client.getReplyString(), getEntity().getFilePath())), false); - } - if (client.isConnected()) { - client.disconnect(); - } - return false; - } - return true; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java deleted file mode 100644 index 528da2d4..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java +++ /dev/null @@ -1,116 +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.upload.uploader; - -import com.arialyy.aria.core.common.CompleteInfo; -import com.arialyy.aria.core.common.IUtil; -import com.arialyy.aria.core.common.OnFileInfoCallback; -import com.arialyy.aria.core.inf.AbsEntity; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.IUploadListener; -import com.arialyy.aria.core.upload.UTaskWrapper; -import com.arialyy.aria.exception.BaseException; -import com.arialyy.aria.util.CheckUtil; - -/** - * Created by lyy on 2017/2/9. - * 简单的文件上传工具 - */ -public class SimpleUploadUtil implements IUtil, Runnable { - private static final String TAG = "SimpleUploadUtil"; - - private UTaskWrapper mTaskWrapper; - private IUploadListener mListener; - private Uploader mUploader; - private boolean isStop = false, isCancel = false; - - public SimpleUploadUtil(UTaskWrapper taskWrapper, IUploadListener listener) { - mTaskWrapper = taskWrapper; - CheckUtil.checkTaskEntity(taskWrapper); - if (listener == null) { - throw new IllegalArgumentException("上传监听不能为空"); - } - mListener = listener; - mUploader = new Uploader(mListener, taskWrapper); - } - - @Override public void run() { - mListener.onPre(); - switch (mTaskWrapper.getRequestType()) { - case AbsTaskWrapper.U_FTP: - FtpFileInfoThread infoThread = - new FtpFileInfoThread(mTaskWrapper, new OnFileInfoCallback() { - @Override public void onComplete(String url, CompleteInfo info) { - if (info.code == FtpFileInfoThread.CODE_COMPLETE) { - mListener.onComplete(); - } else { - mUploader.start(); - } - } - - @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { - failUpload(e, needRetry); - } - }); - new Thread(infoThread).start(); - break; - case AbsTaskWrapper.U_HTTP: - mUploader.start(); - break; - } - } - - private void failUpload(BaseException e, boolean needRetry) { - if (isStop || isCancel) { - return; - } - mListener.onFail(needRetry, e); - mUploader.onDestroy(); - } - - @Override public String getKey() { - return mTaskWrapper.getKey(); - } - - @Override public long getFileSize() { - return mUploader.getFileSize(); - } - - @Override public long getCurrentLocation() { - return mUploader.getCurrentLocation(); - } - - @Override public boolean isRunning() { - return mUploader.isRunning(); - } - - @Override public void cancel() { - isCancel = true; - mUploader.cancel(); - } - - @Override public void stop() { - isStop = true; - mUploader.stop(); - } - - @Override public void start() { - if (isStop || isCancel) { - return; - } - new Thread(this).start(); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java deleted file mode 100644 index 32858fce..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java +++ /dev/null @@ -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.upload.uploader; - -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.common.AbsThreadTask; -import com.arialyy.aria.core.common.NormalFileer; -import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.event.Event; -import com.arialyy.aria.core.event.SpeedEvent; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.IUploadListener; -import com.arialyy.aria.core.upload.UTaskWrapper; -import com.arialyy.aria.core.upload.UploadEntity; -import java.io.File; - -/** - * Created by Aria.Lao on 2017/7/27. - * 文件上传器 - */ -class Uploader extends NormalFileer { - - Uploader(IUploadListener listener, UTaskWrapper taskEntity) { - super(listener, taskEntity); - mTempFile = new File(mEntity.getFilePath()); - setUpdateInterval( - AriaManager.getInstance().getUploadConfig().getUpdateInterval()); - } - - @Override protected boolean handleNewTask() { - return true; - } - - @Override protected AbsThreadTask selectThreadTask(SubThreadConfig config) { - switch (mTaskWrapper.getRequestType()) { - case AbsTaskWrapper.U_FTP: - return new FtpThreadTask(config); - case AbsTaskWrapper.U_HTTP: - return new HttpThreadTask(config); - } - return null; - } - - @Event - public void setMaxSpeed(SpeedEvent event) { - setMaxSpeed(event.speed); - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/AbsDelegate.java b/Aria/src/main/java/com/arialyy/aria/orm/AbsDelegate.java deleted file mode 100644 index 72f19d55..00000000 --- a/Aria/src/main/java/com/arialyy/aria/orm/AbsDelegate.java +++ /dev/null @@ -1,99 +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.orm; - -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CommonUtil; -import java.lang.reflect.Field; -import java.net.URLEncoder; - -/** - * Created by laoyuyu on 2018/3/22. - */ -abstract class AbsDelegate { - static final String TAG = "AbsDelegate"; - - /** - * URL编码字符串 - * - * @param str 原始字符串 - * @return 编码后的字符串 - */ - String encodeStr(String str) { - str = str.replaceAll("\\\\+", "%2B"); - return URLEncoder.encode(str); - } - - /** - * 检查list参数是否合法,list只能是{@code List} - * - * @return {@code true} 合法 - */ - boolean checkList(Field list) { - Class t = CommonUtil.getListParamType(list); - if (t != null && t == String.class) { - return true; - } else { - ALog.d(TAG, "map参数错误,支持List的参数字段"); - return false; - } - } - - /** - * 检查map参数是否合法,map只能是{@code Map} - * - * @return {@code true} 合法 - */ - boolean checkMap(Field map) { - Class[] ts = CommonUtil.getMapParamType(map); - if (ts != null - && ts[0] != null - && ts[1] != null - && ts[0] == String.class - && ts[1] == String.class) { - return true; - } else { - ALog.d(TAG, "map参数错误,支持Map的参数字段"); - return false; - } - } - - void closeCursor(Cursor cursor) { - synchronized (AbsDelegate.class) { - if (cursor != null && !cursor.isClosed()) { - try { - cursor.close(); - } catch (android.database.SQLException e) { - e.printStackTrace(); - } - } - } - } - - /** - * 检查数据库是否关闭,已经关闭的话,打开数据库 - * - * @return 返回数据库 - */ - SQLiteDatabase checkDb(SQLiteDatabase db) { - if (db == null || !db.isOpen()) { - db = SqlHelper.getInstance().getDb(); - } - return db; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DelegateCommon.java b/Aria/src/main/java/com/arialyy/aria/orm/DelegateCommon.java deleted file mode 100644 index 3408a8af..00000000 --- a/Aria/src/main/java/com/arialyy/aria/orm/DelegateCommon.java +++ /dev/null @@ -1,225 +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.orm; - -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.text.TextUtils; -import com.arialyy.aria.orm.annotation.Default; -import com.arialyy.aria.orm.annotation.Foreign; -import com.arialyy.aria.orm.annotation.Primary; -import com.arialyy.aria.util.CheckUtil; -import com.arialyy.aria.util.CommonUtil; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Created by laoyuyu on 2018/3/22. - * 通用委托,创建表,检查字段 - */ -class DelegateCommon extends AbsDelegate { - private DelegateCommon() { - } - - /** - * 删除指定的表 - */ - void dropTable(SQLiteDatabase db, String tableName) { - db = checkDb(db); - String deleteSQL = String.format("DROP TABLE IF EXISTS %s", tableName); - //db.beginTransaction(); - db.execSQL(deleteSQL); - //db.setTransactionSuccessful(); - //db.endTransaction(); - } - - /** - * 清空表数据 - */ - void clean(SQLiteDatabase db, Class clazz) { - db = checkDb(db); - String tableName = CommonUtil.getClassName(clazz); - if (tableExists(db, clazz)) { - String sql = "DELETE FROM " + tableName; - db.execSQL(sql); - } - } - - /** - * 查找表是否存在 - * - * @param clazz 数据库实体 - * @return true,该数据库实体对应的表存在;false,不存在 - */ - boolean tableExists(SQLiteDatabase db, Class clazz) { - return tableExists(db, CommonUtil.getClassName(clazz)); - } - - /** - * 查找表是否存在 - * - * @param tableName 表名 - * @return true,该数据库实体对应的表存在;false,不存在 - */ - boolean tableExists(SQLiteDatabase db, String tableName) { - db = checkDb(db); - Cursor cursor = null; - try { - String sql = - String.format("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='%s'", - tableName); - cursor = db.rawQuery(sql, null); - if (cursor != null && cursor.moveToNext()) { - int count = cursor.getInt(0); - if (count > 0) { - return true; - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - closeCursor(cursor); - } - return false; - } - - /** - * 检查某个字段的值是否存在 - * - * @param expression 字段和值"url=xxx" - * @return {@code true}该字段的对应的value已存在 - */ - boolean checkDataExist(SQLiteDatabase db, Class clazz, String... expression) { - db = checkDb(db); - if (!CheckUtil.checkSqlExpression(expression)) { - return false; - } - String sql = String.format("SELECT rowid, * FROM %s WHERE %s ", CommonUtil.getClassName(clazz), - expression[0]); - sql = sql.replace("?", "%s"); - Object[] params = new String[expression.length - 1]; - for (int i = 0, len = params.length; i < len; i++) { - params[i] = String.format("'%s'", encodeStr(expression[i + 1])); - } - sql = String.format(sql, params); - Cursor cursor = db.rawQuery(sql, null); - final boolean isExist = cursor.getCount() > 0; - closeCursor(cursor); - return isExist; - } - - /** - * 创建表 - * - * @param clazz 数据库实体 - */ - void createTable(SQLiteDatabase db, Class clazz) { - db = checkDb(db); - List fields = CommonUtil.getAllFields(clazz); - if (fields != null && fields.size() > 0) { - //外键Map,在Sqlite3中foreign修饰的字段必须放在最后 - final List foreignArray = new ArrayList<>(); - StringBuilder sb = new StringBuilder(); - sb.append("CREATE TABLE IF NOT EXISTS ") - .append(CommonUtil.getClassName(clazz)) - .append(" ("); - for (Field field : fields) { - field.setAccessible(true); - if (SqlUtil.isIgnore(field)) { - continue; - } - Class type = field.getType(); - sb.append(field.getName()); - if (type == String.class || type.isEnum()) { - sb.append(" VARCHAR"); - } else if (type == int.class || type == Integer.class) { - sb.append(" INTEGER"); - } else if (type == float.class || type == Float.class) { - sb.append(" FLOAT"); - } else if (type == double.class || type == Double.class) { - sb.append(" DOUBLE"); - } else if (type == long.class || type == Long.class) { - sb.append(" BIGINT"); - } else if (type == boolean.class || type == Boolean.class) { - sb.append(" BOOLEAN"); - } else if (type == java.util.Date.class || type == java.sql.Date.class) { - sb.append(" DATA"); - } else if (type == byte.class || type == Byte.class) { - sb.append(" BLOB"); - } else if (type == Map.class || type == List.class) { - sb.append(" TEXT"); - } else { - continue; - } - if (SqlUtil.isPrimary(field)) { - Primary pk = field.getAnnotation(Primary.class); - sb.append(" PRIMARY KEY"); - if (pk.autoincrement() && (type == int.class || type == Integer.class)) { - sb.append(" AUTOINCREMENT"); - } - } - - if (SqlUtil.isForeign(field)) { - foreignArray.add(field); - } - - if (SqlUtil.isNoNull(field)) { - sb.append(" NOT NULL"); - } - - if (SqlUtil.isDefault(field)) { - Default d = field.getAnnotation(Default.class); - if (!TextUtils.isEmpty(d.value())) { - sb.append(" DEFAULT ").append("'").append(d.value()).append("'"); - } - } - - if (SqlUtil.isUnique(field)) { - sb.append(" UNIQUE"); - } - - sb.append(","); - } - - for (Field field : foreignArray) { - Foreign foreign = field.getAnnotation(Foreign.class); - sb.append("FOREIGN KEY (") - .append(field.getName()) - .append(") REFERENCES ") - .append(CommonUtil.getClassName(foreign.parent())) - .append("(") - .append(foreign.column()) - .append(")"); - ActionPolicy update = foreign.onUpdate(); - ActionPolicy delete = foreign.onDelete(); - if (update != ActionPolicy.NO_ACTION) { - sb.append(" ON UPDATE ").append(update.function); - } - - if (delete != ActionPolicy.NO_ACTION) { - sb.append(" ON DELETE ").append(update.function); - } - sb.append(","); - } - - String str = sb.toString(); - str = str.substring(0, str.length() - 1) + ");"; - db.execSQL(str); - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DelegateFind.java b/Aria/src/main/java/com/arialyy/aria/orm/DelegateFind.java deleted file mode 100644 index a008628f..00000000 --- a/Aria/src/main/java/com/arialyy/aria/orm/DelegateFind.java +++ /dev/null @@ -1,586 +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.orm; - -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.text.TextUtils; -import android.util.SparseArray; -import com.arialyy.aria.orm.annotation.Many; -import com.arialyy.aria.orm.annotation.One; -import com.arialyy.aria.orm.annotation.Wrapper; -import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CheckUtil; -import com.arialyy.aria.util.CommonUtil; -import java.lang.reflect.Field; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; - -/** - * Created by laoyuyu on 2018/3/22. - * 查询数据 - */ -class DelegateFind extends AbsDelegate { - private final String PARENT_COLUMN_ALIAS = "p"; - private final String CHILD_COLUMN_ALIAS = "c"; - - private DelegateFind() { - } - - /** - * 获取{@link One}和{@link Many}注解的字段 - * - * @return 返回[OneField, ManyField] ,如果注解依赖错误返回null - */ - private Field[] getOneAndManyField(Class clazz) { - Field[] om = new Field[2]; - Field[] fields = clazz.getDeclaredFields(); - Field one = null, many = null; - boolean hasOne = false, hasMany = false; - for (Field field : fields) { - if (SqlUtil.isOne(field)) { - if (hasOne) { - ALog.w(TAG, "查询数据失败,实体中有多个@One 注解"); - return null; - } - hasOne = true; - one = field; - } - if (SqlUtil.isMany(field)) { - if (hasMany) { - ALog.w(TAG, "查询数据失败,实体中有多个@Many 注解"); - return null; - } - if (!field.getType().isAssignableFrom(List.class)) { - ALog.w(TAG, "查询数据失败,@Many 注解的类型不是List"); - return null; - } - hasMany = true; - many = field; - } - } - - if (one == null || many == null) { - ALog.w(TAG, "查询数据失败,实体中没有@One或@Many注解"); - return null; - } - - if (many.getType() != List.class) { - ALog.w(TAG, "查询数据失败,@Many注解的字段必须是List"); - return null; - } - om[0] = one; - om[1] = many; - return om; - } - - /** - * 查找一对多的关联数据 - * 如果查找不到数据或实体没有被{@link Wrapper}注解,将返回null - * 如果实体中没有{@link One}或{@link Many}注解,将返回null - * 如果实体中有多个{@link One}或{@link Many}注解,将返回nul - * {@link One} 的注解对象必须是{@link DbEntity},{@link Many}的注解对象必须是List,并且List中的类型必须是{@link DbEntity} - */ - List findRelationData(SQLiteDatabase db, Class clazz, - String... expression) { - return exeRelationSql(db, clazz, -1, -1, expression); - } - - /** - * 查找一对多的关联数据 - * 如果查找不到数据或实体没有被{@link Wrapper}注解,将返回null - * 如果实体中没有{@link One}或{@link Many}注解,将返回null - * 如果实体中有多个{@link One}或{@link Many}注解,将返回nul - * {@link One} 的注解对象必须是{@link DbEntity},{@link Many}的注解对象必须是List,并且List中的类型必须是{@link DbEntity} - */ - List findRelationData(SQLiteDatabase db, Class clazz, - int page, int num, String... expression) { - if (page < 1 || num < 1) { - return null; - } - return exeRelationSql(db, clazz, page, num, expression); - } - - /** - * 执行关联查询,如果不需要分页,page和num传-1 - * - * @param page 当前页 - * @param num 一页的数量 - */ - private List exeRelationSql(SQLiteDatabase db, Class wrapperClazz, - int page, int num, String... expression) { - db = checkDb(db); - if (SqlUtil.isWrapper(wrapperClazz)) { - Field[] om = getOneAndManyField(wrapperClazz); - if (om == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - Field one = om[0], many = om[1]; - try { - Many m = many.getAnnotation(Many.class); - Class parentClazz = Class.forName(one.getType().getName()); - Class childClazz = Class.forName(CommonUtil.getListParamType(many).getName()); - final String pTableName = parentClazz.getSimpleName(); - final String cTableName = childClazz.getSimpleName(); - List pColumn = SqlUtil.getAllNotIgnoreField(parentClazz); - List cColumn = SqlUtil.getAllNotIgnoreField(childClazz); - StringBuilder pSb = new StringBuilder(); - StringBuilder cSb = new StringBuilder(); - - if (pColumn != null) { - pSb.append(pTableName.concat(".rowid AS ").concat(PARENT_COLUMN_ALIAS).concat("rowid,")); - for (Field f : pColumn) { - String temp = PARENT_COLUMN_ALIAS.concat(f.getName()); - pSb.append(pTableName.concat(".").concat(f.getName())) - .append(" AS ") - .append(temp) - .append(","); - } - } - - if (cColumn != null) { - pSb.append(cTableName.concat(".rowid AS ").concat(CHILD_COLUMN_ALIAS).concat("rowid,")); - for (Field f : cColumn) { - String temp = CHILD_COLUMN_ALIAS.concat(f.getName()); - cSb.append(cTableName.concat(".").concat(f.getName())) - .append(" AS ") - .append(temp) - .append(","); - } - } - - String pColumnAlia = pSb.toString(); - String cColumnAlia = cSb.toString(); - if (!TextUtils.isEmpty(pColumnAlia)) { - pColumnAlia = pColumnAlia.substring(0, pColumnAlia.length() - 1); - } - - if (!TextUtils.isEmpty(cColumnAlia)) { - cColumnAlia = cColumnAlia.substring(0, cColumnAlia.length() - 1); - } - - sb.append("SELECT "); - - if (!TextUtils.isEmpty(pColumnAlia)) { - sb.append(pColumnAlia).append(","); - } - if (!TextUtils.isEmpty(cColumnAlia)) { - sb.append(cColumnAlia); - } - if (TextUtils.isEmpty(pColumnAlia) && TextUtils.isEmpty(cColumnAlia)) { - sb.append(" * "); - } - - sb.append(" FROM ") - .append(pTableName) - .append(" INNER JOIN ") - .append(cTableName) - .append(" ON ") - .append(pTableName.concat(".").concat(m.parentColumn())) - .append(" = ") - .append(cTableName.concat(".").concat(m.entityColumn())); - String sql; - if (expression != null && expression.length > 0) { - if (!CheckUtil.checkSqlExpression(expression)) { - return null; - } - sb.append(" WHERE ").append(expression[0]).append(" "); - sql = sb.toString(); - sql = sql.replace("?", "%s"); - Object[] params = new String[expression.length - 1]; - for (int i = 0, len = params.length; i < len; i++) { - params[i] = String.format("'%s'", encodeStr(expression[i + 1])); - } - sql = String.format(sql, params); - } else { - sql = sb.toString(); - } - if (page != -1 && num != -1) { - sql = sql.concat(String.format(" LIMIT %s,%s", (page - 1) * num, num)); - } - Cursor cursor = db.rawQuery(sql, null); - List data = - newInstanceEntity(wrapperClazz, parentClazz, childClazz, cursor, pColumn, cColumn); - closeCursor(cursor); - return data; - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - } else { - ALog.e(TAG, "查询数据失败,实体类没有使用@Wrapper 注解"); - return null; - } - return null; - } - - /** - * 创建关联查询的数据 - * - * @param pColumn 父表的所有字段 - * @param cColumn 字表的所有字段 - */ - private synchronized List newInstanceEntity( - Class wrapperClazz, Class

parentClazz, - Class childClazz, - Cursor cursor, - List pColumn, List cColumn) { - List wrappers = new ArrayList<>(); - SparseArray> childs = new SparseArray<>(); // 所有子表数据 - SparseArray parents = new SparseArray<>(); // 所有父表数据 - - try { - while (cursor.moveToNext()) { - int pRowId = cursor.getInt(cursor.getColumnIndex(PARENT_COLUMN_ALIAS.concat("rowid"))); - if (childs.get(pRowId) == null) { - childs.put(pRowId, new ArrayList()); - parents.put(pRowId, createParent(pRowId, parentClazz, pColumn, cursor)); - } - childs.get(pRowId).add(createChild(childClazz, cColumn, cursor)); - } - - List wFields = SqlUtil.getAllNotIgnoreField(wrapperClazz); - if (wFields == null || wFields.isEmpty()) { - return null; - } - for (int i = 0; i < parents.size(); i++) { - int pRowId = parents.keyAt(i); - T wrapper = wrapperClazz.newInstance(); - boolean isPSet = false, isCSet = false; // 保证One 或 Many 只设置一次 - for (Field f : wFields) { - if (!isPSet && f.getAnnotation(One.class) != null) { - f.set(wrapper, parents.get(pRowId)); - isPSet = true; - } - if (!isCSet && f.getAnnotation(Many.class) != null) { - f.set(wrapper, childs.get(pRowId)); - isCSet = true; - } - } - wrapper.handleConvert(); //处理下转换 - wrappers.add(wrapper); - } - } catch (Exception e) { - e.printStackTrace(); - } - - return wrappers; - } - - /** - * 创建子对象 - */ - private T createChild(Class childClazz, List cColumn, - Cursor cursor) - throws InstantiationException, IllegalAccessException { - T child = childClazz.newInstance(); - child.rowID = cursor.getInt(cursor.getColumnIndex(CHILD_COLUMN_ALIAS.concat("rowid"))); - for (Field field : cColumn) { - field.setAccessible(true); - int columnIndex = cursor.getColumnIndex(CHILD_COLUMN_ALIAS.concat(field.getName())); - setFieldValue(field.getType(), field, columnIndex, cursor, child); - } - return child; - } - - /** - * 创建父对象 - */ - private T createParent(int rowId, Class parentClazz, List pColumn, - Cursor cursor) - throws InstantiationException, IllegalAccessException { - T parent = parentClazz.newInstance(); - parent.rowID = rowId; - for (Field field : pColumn) { - field.setAccessible(true); - int columnIndex = cursor.getColumnIndex(PARENT_COLUMN_ALIAS.concat(field.getName())); - setFieldValue(field.getType(), field, columnIndex, cursor, parent); - } - return parent; - } - - /** - * 条件查寻数据 - */ - List findData(SQLiteDatabase db, Class clazz, String... expression) { - db = checkDb(db); - if (!CheckUtil.checkSqlExpression(expression)) { - return null; - } - String sql = String.format("SELECT rowid, * FROM %s WHERE %s", CommonUtil.getClassName(clazz), - expression[0]); - String[] params = new String[expression.length - 1]; - try { - // 处理系统出现的问题:https://github.com/AriaLyy/Aria/issues/450 - System.arraycopy(expression, 1, params, 0, params.length); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - - return exeNormalDataSql(db, clazz, sql, params); - } - - /** - * 获取分页数据 - */ - List findData(SQLiteDatabase db, Class clazz, int page, int num, - String... expression) { - if (page < 1 || num < 1) { - return null; - } - db = checkDb(db); - if (!CheckUtil.checkSqlExpression(expression)) { - return null; - } - String sql = String.format("SELECT rowid, * FROM %s WHERE %s LIMIT %s,%s", - CommonUtil.getClassName(clazz), - expression[0], (page - 1) * num, num); - - String[] params = new String[expression.length - 1]; - try { - // 处理系统出现的问题:https://github.com/AriaLyy/Aria/issues/450 - System.arraycopy(expression, 1, params, 0, params.length); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - - return exeNormalDataSql(db, clazz, sql, params); - } - - /** - * 模糊查寻数据 - */ - List findDataByFuzzy(SQLiteDatabase db, Class clazz, - String conditions) { - db = checkDb(db); - if (TextUtils.isEmpty(conditions)) { - throw new IllegalArgumentException("sql语句表达式不能为null或\"\""); - } - if (!conditions.toUpperCase().contains("LIKE")) { - throw new IllegalArgumentException("sql语句表达式未包含LIEK"); - } - String sql = String.format("SELECT rowid, * FROM %s, WHERE %s", CommonUtil.getClassName(clazz), - conditions); - return exeNormalDataSql(db, clazz, sql, null); - } - - /** - * 分页、模糊搜索数据 - */ - List findDataByFuzzy(SQLiteDatabase db, Class clazz, - int page, int num, String conditions) { - if (page < 1 || num < 1) { - return null; - } - db = checkDb(db); - if (TextUtils.isEmpty(conditions)) { - throw new IllegalArgumentException("sql语句表达式不能为null或\"\""); - } - if (!conditions.toUpperCase().contains("LIKE")) { - throw new IllegalArgumentException("sql语句表达式未包含LIEK"); - } - String sql = String.format("SELECT rowid, * FROM %s WHERE %s LIMIT %s,%s", - CommonUtil.getClassName(clazz), conditions, (page - 1) * num, num); - return exeNormalDataSql(db, clazz, sql, null); - } - - /** - * 查找表的所有数据 - */ - List findAllData(SQLiteDatabase db, Class clazz) { - db = checkDb(db); - String sql = String.format("SELECT rowid, * FROM %s", CommonUtil.getClassName(clazz)); - return exeNormalDataSql(db, clazz, sql, null); - } - - /** - * 执行查询普通数据的sql语句,并创建对象 - * - * @param sql sql 查询语句 - * @param selectionArgs 查询参数,如何sql语句中查询条件含有'?'则该参数不能为空 - */ - private List exeNormalDataSql(SQLiteDatabase db, Class clazz, - String sql, String[] selectionArgs) { - String[] temp = new String[selectionArgs.length]; - int i = 0; - for (String arg : selectionArgs) { - temp[i] = encodeStr(arg); - i++; - } - Cursor cursor = db.rawQuery(sql, temp); - List data = cursor.getCount() > 0 ? newInstanceEntity(clazz, cursor) : null; - closeCursor(cursor); - return data; - } - - /** - * 根据数据游标创建一个具体的对象 - */ - private synchronized List newInstanceEntity(Class clazz, - Cursor cursor) { - List fields = CommonUtil.getAllFields(clazz); - List entitys = new ArrayList<>(); - if (fields != null && fields.size() > 0) { - try { - while (cursor.moveToNext()) { - T entity = clazz.newInstance(); - String primaryName = ""; - for (Field field : fields) { - field.setAccessible(true); - if (SqlUtil.isIgnore(field)) { - continue; - } - - Class type = field.getType(); - if (SqlUtil.isPrimary(field) && (type == int.class || type == Integer.class)) { - primaryName = field.getName(); - } - - int column = cursor.getColumnIndex(field.getName()); - if (column == -1) continue; - setFieldValue(type, field, column, cursor, entity); - } - //当设置了主键,而且主键的类型为integer时,查询RowID等于主键 - entity.rowID = cursor.getInt( - cursor.getColumnIndex(TextUtils.isEmpty(primaryName) ? "rowid" : primaryName)); - //mDataCache.put(getCacheKey(entity), entity); - entitys.add(entity); - } - closeCursor(cursor); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - return entitys; - } - - /** - * 设置字段的值 - * - * @throws IllegalAccessException - */ - private void setFieldValue(Class type, Field field, int columnIndex, Cursor cursor, - DbEntity entity) - throws IllegalAccessException { - if (cursor == null || cursor.isClosed()) { - ALog.e(TAG, "cursor没有初始化"); - return; - } - if (type == String.class) { - String temp = cursor.getString(columnIndex); - if (!TextUtils.isEmpty(temp)) { - field.set(entity, URLDecoder.decode(temp)); - } - } else if (type == int.class || type == Integer.class) { - field.setInt(entity, cursor.getInt(columnIndex)); - } else if (type == float.class || type == Float.class) { - field.setFloat(entity, cursor.getFloat(columnIndex)); - } else if (type == double.class || type == Double.class) { - field.setDouble(entity, cursor.getDouble(columnIndex)); - } else if (type == long.class || type == Long.class) { - field.setLong(entity, cursor.getLong(columnIndex)); - } else if (type == boolean.class || type == Boolean.class) { - String temp = cursor.getString(columnIndex); - if (TextUtils.isEmpty(temp)) { - field.setBoolean(entity, false); - } else { - field.setBoolean(entity, !temp.equalsIgnoreCase("false")); - } - } else if (type == java.util.Date.class || type == java.sql.Date.class) { - field.set(entity, new Date(URLDecoder.decode(cursor.getString(columnIndex)))); - } else if (type == byte[].class) { - field.set(entity, cursor.getBlob(columnIndex)); - } else if (type == Map.class) { - String temp = cursor.getString(columnIndex); - if (!TextUtils.isEmpty(temp)) { - field.set(entity, SqlUtil.str2Map(URLDecoder.decode(temp))); - } - } else if (type == List.class) { - String value = cursor.getString(columnIndex); - if (!TextUtils.isEmpty(value)) { - field.set(entity, SqlUtil.str2List(URLDecoder.decode(value), field)); - } - } - } - - /** - * 获取所在行Id - */ - int[] getRowId(SQLiteDatabase db, Class clazz) { - db = checkDb(db); - Cursor cursor = db.rawQuery("SELECT rowid, * FROM " + CommonUtil.getClassName(clazz), null); - int[] ids = new int[cursor.getCount()]; - int i = 0; - while (cursor.moveToNext()) { - ids[i] = cursor.getInt(cursor.getColumnIndex("rowid")); - i++; - } - cursor.close(); - return ids; - } - - /** - * 获取行Id - */ - int getRowId(SQLiteDatabase db, Class clazz, Object[] wheres, Object[] values) { - db = checkDb(db); - if (wheres.length <= 0 || values.length <= 0) { - ALog.e(TAG, "请输入删除条件"); - return -1; - } else if (wheres.length != values.length) { - ALog.e(TAG, "groupHash 和 vaule 长度不相等"); - return -1; - } - StringBuilder sb = new StringBuilder(); - sb.append("SELECT rowid FROM ").append(CommonUtil.getClassName(clazz)).append(" WHERE "); - int i = 0; - for (Object where : wheres) { - sb.append(where).append("=").append("'").append(values[i]).append("'"); - sb.append(i >= wheres.length - 1 ? "" : ","); - i++; - } - Cursor c = db.rawQuery(sb.toString(), null); - int id = c.getColumnIndex("rowid"); - c.close(); - return id; - } - - /** - * 通过rowId判断数据是否存在 - */ - boolean itemExist(SQLiteDatabase db, Class clazz, long rowId) { - return itemExist(db, CommonUtil.getClassName(clazz), rowId); - } - - /** - * 通过rowId判断数据是否存在 - */ - boolean itemExist(SQLiteDatabase db, String tableName, long rowId) { - db = checkDb(db); - String sql = "SELECT rowid FROM " + tableName + " WHERE rowid=" + rowId; - Cursor cursor = db.rawQuery(sql, null); - boolean isExist = cursor.getCount() > 0; - cursor.close(); - return isExist; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/SqlUtil.java b/Aria/src/main/java/com/arialyy/aria/orm/SqlUtil.java deleted file mode 100644 index 5e9116a7..00000000 --- a/Aria/src/main/java/com/arialyy/aria/orm/SqlUtil.java +++ /dev/null @@ -1,273 +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.orm; - -import android.text.TextUtils; -import com.arialyy.aria.orm.annotation.Default; -import com.arialyy.aria.orm.annotation.Foreign; -import com.arialyy.aria.orm.annotation.Ignore; -import com.arialyy.aria.orm.annotation.Many; -import com.arialyy.aria.orm.annotation.NoNull; -import com.arialyy.aria.orm.annotation.One; -import com.arialyy.aria.orm.annotation.Primary; -import com.arialyy.aria.orm.annotation.Unique; -import com.arialyy.aria.orm.annotation.Wrapper; -import com.arialyy.aria.util.CommonUtil; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Created by lyy on 2017/7/24. - * sql工具 - */ -final class SqlUtil { - - /** - * 获取主键字段名 - */ - static String getPrimaryName(Class clazz) { - List fields = CommonUtil.getAllFields(clazz); - String column; - if (fields != null && !fields.isEmpty()) { - - for (Field field : fields) { - field.setAccessible(true); - if (isPrimary(field)) { - column = field.getName(); - return column; - } - } - } - return null; - } - - /** - * 获取类中所有不被忽略的字段 - */ - static List getAllNotIgnoreField(Class clazz) { - List fields = CommonUtil.getAllFields(clazz); - List temp = new ArrayList<>(); - if (fields != null && fields.size() > 0) { - for (Field f : fields) { - f.setAccessible(true); - if (!isIgnore(f)) { - temp.add(f); - } - } - return temp; - } else { - return null; - } - } - - /** - * 列表数据转字符串 - * - * @param field list反射字段 - */ - static String list2Str(DbEntity dbEntity, Field field) throws IllegalAccessException { - List list = (List) field.get(dbEntity); - if (list == null || list.isEmpty()) return ""; - StringBuilder sb = new StringBuilder(); - for (Object aList : list) { - sb.append(aList).append("$$"); - } - return sb.toString(); - } - - /** - * 字符串转列表 - * - * @param str 数据库中的字段 - * @return 如果str为null,则返回null - */ - static List str2List(String str, Field field) { - if (TextUtils.isEmpty(str)) return null; - String[] datas = str.split("\\$\\$"); - List list = new ArrayList(); - Class clazz = CommonUtil.getListParamType(field); - if (clazz != null) { - String type = clazz.getName(); - for (String data : datas) { - list.add(checkData(type, data)); - } - } - - return list; - } - - /** - * 字符串转Map,只支持 - *

-   *   {@code Map}
-   * 
- */ - static Map str2Map(String str) { - Map map = new HashMap<>(); - if (TextUtils.isEmpty(str)) { - return map; - } - boolean isDecode = false; - if (str.endsWith("_&_decode_&_")) { - isDecode = true; - str = str.substring(0, str.length() - 12); - } - String[] element = str.split(","); - for (String data : element) { - String[] s = data.split("\\$"); - if (isDecode) { - map.put(CommonUtil.decryptBASE64(s[0]), CommonUtil.decryptBASE64(s[1])); - } else { - map.put(s[0], s[1]); - } - } - return map; - } - - /** - * Map转字符串,只支持 - *
-   *   {@code Map}
-   * 
- */ - static String map2Str(Map map) { - StringBuilder sb = new StringBuilder(); - Set keys = map.keySet(); - for (String key : keys) { - sb.append(CommonUtil.encryptBASE64(key)) - .append("$") - .append(CommonUtil.encryptBASE64(map.get(key))) - .append(","); - } - String str = sb.toString(); - str = TextUtils.isEmpty(str) ? str : str.substring(0, str.length() - 1); - //3.3.10版本之前没有decode,需要加标志 - if (map.size() != 0) { - str += "_&_decode_&_"; - } - return str; - } - - /** - * shadow$_klass_、shadow$_monitor_、{@link Ignore}、rowID、{@link Field#isSynthetic()}、{@link - * Modifier#isFinal(int)}、{@link Modifier#isStatic(int)}将被忽略 - * - * @return true 忽略该字段 - */ - static boolean isIgnore(Field field) { - // field.isSynthetic(), 使用as热启动App时,AS会自动给你的class添加change字段 - Ignore ignore = field.getAnnotation(Ignore.class); - int modifiers = field.getModifiers(); - String fieldName = field.getName(); - return (ignore != null && ignore.value()) || fieldName.equals("rowID") || fieldName.equals( - "shadow$_klass_") || fieldName.equals("shadow$_monitor_") || field.isSynthetic() || Modifier - .isStatic(modifiers) || Modifier.isFinal(modifiers); - } - - /** - * 判断是否是Wrapper注解 - * - * @return {@code true} 是 - */ - static boolean isWrapper(Class clazz) { - Wrapper w = (Wrapper) clazz.getAnnotation(Wrapper.class); - return w != null; - } - - /** - * 判断是否一对多注解 - */ - static boolean isMany(Field field) { - Many oneToMany = field.getAnnotation(Many.class); - return oneToMany != null; - } - - /** - * 判断是否是一对一注解 - */ - static boolean isOne(Field field) { - One oneToOne = field.getAnnotation(One.class); - return oneToOne != null; - } - - /** - * 判断是否是主键约束 - * - * @return {@code true}主键约束 - */ - static boolean isPrimary(Field field) { - Primary pk = field.getAnnotation(Primary.class); - return pk != null; - } - - /** - * 判断是否是外键约束 - * - * @return {@code true}外键约束 - */ - static boolean isForeign(Field field) { - Foreign fk = field.getAnnotation(Foreign.class); - return fk != null; - } - - /** - * 判断是否是非空约束 - * - * @return {@code true}为非空约束 - */ - static boolean isNoNull(Field field) { - NoNull nn = field.getAnnotation(NoNull.class); - return nn != null; - } - - /** - * 判断是否是default - * - * @return {@code true}为default - */ - static boolean isDefault(Field field) { - Default nn = field.getAnnotation(Default.class); - return nn != null; - } - - /** - * 判断是否是Unique - * - * @return {@code true}为Unique - */ - static boolean isUnique(Field field) { - Unique nn = field.getAnnotation(Unique.class); - return nn != null; - } - - private static Object checkData(String type, String data) { - if (type.equalsIgnoreCase("java.lang.String")) { - return data; - } else if (type.equalsIgnoreCase("int") || type.equals("java.lang.Integer")) { - return Integer.parseInt(data); - } else if (type.equalsIgnoreCase("double") || type.equals("java.lang.Double")) { - return Double.parseDouble(data); - } else if (type.equalsIgnoreCase("float") || type.equals("java.lang.Float")) { - return Float.parseFloat(data); - } - return null; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java deleted file mode 100644 index ef291be5..00000000 --- a/Aria/src/main/java/com/arialyy/aria/util/CheckUtil.java +++ /dev/null @@ -1,287 +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.util; - -import android.text.TextUtils; -import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.upload.UTaskWrapper; -import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.exception.ParamException; -import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Created by Lyy on 2016/9/23. - * 检查帮助类 - */ -public class CheckUtil { - private static final String TAG = "CheckUtil"; - - /** - * 检查ftp上传路径,如果ftp上传路径为空,抛出空指针异常 - * 如果ftp上传路径不是以"ftp"或"sftp",抛出参数异常 - * - * @param ftpUrl ftp上传路径 - */ - public static void checkFtpUploadUrl(String ftpUrl) { - if (TextUtils.isEmpty(ftpUrl)) { - throw new ParamException("ftp上传路径为空"); - } else if (!ftpUrl.startsWith("ftp") || !ftpUrl.startsWith("sftp")) { - throw new ParamException("ftp上传路径无效"); - } - } - - /** - * 判空 - */ - public static void checkNull(Object obj) { - if (obj == null) throw new IllegalArgumentException("不能传入空对象"); - } - - /** - * 检查分页数据,需要查询的页数,从1开始,如果page小于1 或 num 小于1,则抛出{@link NullPointerException} - * - * @param page 从1 开始 - * @param num 每页数量 - */ - public static void checkPageParams(int page, int num) { - if (page < 1 || num < 1) throw new NullPointerException("page和num不能小于1"); - } - - /** - * 检查sql的expression是否合法 - * - * @return false 不合法 - */ - public static boolean checkSqlExpression(String... expression) { - if (expression.length == 0) { - ALog.e(TAG, "sql语句表达式不能为null"); - return false; - } - if (expression.length == 1) { - ALog.e(TAG, String.format("表达式需要写入参数,参数信息:%s", Arrays.toString(expression))); - return false; - } - String where = expression[0]; - if (!where.contains("?")) { - ALog.e(TAG, String.format("请在where语句的'='后编写?,参数信息:%s", Arrays.toString(expression))); - return false; - } - Pattern pattern = Pattern.compile("\\?"); - Matcher matcher = pattern.matcher(where); - int count = 0; - while (matcher.find()) { - count++; - } - if (count < expression.length - 1) { - ALog.e(TAG, String.format("条件语句的?个数不能小于参数个数,参数信息:%s", Arrays.toString(expression))); - return false; - } - if (count > expression.length - 1) { - ALog.e(TAG, String.format("条件语句的?个数不能大于参数个数, 参数信息:%s", Arrays.toString(expression))); - return false; - } - return true; - } - - /** - * 检查下载实体 - */ - public static void checkDownloadEntity(DownloadEntity entity) { - checkUrlInvalidThrow(entity.getUrl()); - entity.setUrl(entity.getUrl()); - checkPath(entity.getDownloadPath()); - } - - /** - * 检测下载链接是否为null - */ - public static void checkPath(String path) { - if (TextUtils.isEmpty(path)) { - throw new IllegalArgumentException("保存路径不能为null"); - } - } - - /** - * 检测url是否合法,如果url不合法,将抛异常 - */ - public static void checkTaskId(long taskId) { - if (taskId < 0) { - throw new IllegalArgumentException("任务id不能小于0"); - } - } - - /** - * 检测url是否合法,如果url不合法,将抛异常 - */ - public static void checkUrlInvalidThrow(String url) { - if (TextUtils.isEmpty(url)) { - throw new IllegalArgumentException("url不能为null"); - } else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) { - throw new IllegalArgumentException("url错误"); - } - int index = url.indexOf("://"); - if (index == -1) { - throw new IllegalArgumentException("url不合法"); - } - } - - /** - * 检测url是否合法,如果url不合法,将抛出{@link IllegalArgumentException}异常 - */ - public static void checkUrl(String url) { - if (TextUtils.isEmpty(url)) { - throw new NullPointerException("url为空"); - } else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) { - throw new IllegalArgumentException(String.format("url【%s】错误", url)); - } - int index = url.indexOf("://"); - if (index == -1) { - throw new IllegalArgumentException(String.format("url【%s】不合法", url)); - } - } - - /** - * 检测url是否合法 - * - * @return {@code true} 合法,{@code false} 非法 - */ - public static boolean checkUrlNotThrow(String url) { - if (TextUtils.isEmpty(url)) { - ALog.e(TAG, "url不能为null"); - return false; - } else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) { - ALog.e(TAG, "url【" + url + "】错误"); - return false; - } - int index = url.indexOf("://"); - if (index == -1) { - ALog.e(TAG, "url【" + url + "】不合法"); - } - return true; - } - - /** - * 检测下载链接组是否为null - */ - public static void checkDownloadUrls(List urls) { - if (urls == null || urls.isEmpty()) { - throw new IllegalArgumentException("链接组不能为null"); - } - } - - /** - * 检查下载任务组保存路径 - */ - public static void checkDownloadPaths(List paths) { - if (paths == null || paths.isEmpty()) { - throw new IllegalArgumentException("链接保存路径不能为null"); - } - } - - /** - * 检测上传地址是否为null - */ - public static void checkUploadPath(String uploadPath) { - if (TextUtils.isEmpty(uploadPath)) { - throw new IllegalArgumentException("上传地址不能为null"); - } - File file = new File(uploadPath); - if (!file.exists()) { - throw new IllegalArgumentException("上传文件不存在"); - } - } - - /** - * 检查任务实体 - */ - public static void checkTaskEntity(AbsTaskWrapper entity) { - if (entity instanceof DTaskWrapper) { - checkDownloadTaskEntity(((DTaskWrapper) entity).getEntity()); - } else if (entity instanceof UTaskWrapper) { - checkUploadTaskEntity(((UTaskWrapper) entity).getEntity()); - } - } - - /** - * 检查命令实体 - * - * @param checkType 删除命令和停止命令不需要检查下载链接和保存路径 - * @return {@code false}实体无效 - */ - public static boolean checkCmdEntity(AbsTaskWrapper entity, boolean checkType) { - boolean b = false; - if (entity instanceof DTaskWrapper) { - DownloadEntity entity1 = ((DTaskWrapper) entity).getEntity(); - if (entity1 == null) { - ALog.e(TAG, "下载实体不能为空"); - } else if (checkType && TextUtils.isEmpty(entity1.getUrl())) { - ALog.e(TAG, "下载链接不能为空"); - } else if (checkType && TextUtils.isEmpty(entity1.getDownloadPath())) { - ALog.e(TAG, "保存路径不能为空"); - } else { - b = true; - } - } else if (entity instanceof UTaskWrapper) { - UploadEntity entity1 = ((UTaskWrapper) entity).getEntity(); - if (entity1 == null) { - ALog.e(TAG, "上传实体不能为空"); - } else if (TextUtils.isEmpty(entity1.getFilePath())) { - ALog.e(TAG, "上传文件路径不能为空"); - } else { - b = true; - } - } - return b; - } - - /** - * 检查上传实体是否合法 - */ - private static void checkUploadTaskEntity(UploadEntity entity) { - if (entity == null) { - throw new NullPointerException("上传实体不能为空"); - } else if (TextUtils.isEmpty(entity.getFilePath())) { - throw new IllegalArgumentException("上传文件路径不能为空"); - } else if (TextUtils.isEmpty(entity.getFileName())) { - throw new IllegalArgumentException("上传文件名不能为空"); - } - } - - /** - * 检测下载实体是否合法 - * 合法(true) - * - * @param entity 下载实体 - */ - private static void checkDownloadTaskEntity(DownloadEntity entity) { - if (entity == null) { - throw new NullPointerException("下载实体不能为空"); - } else if (TextUtils.isEmpty(entity.getUrl())) { - throw new IllegalArgumentException("下载链接不能为空"); - } else if (TextUtils.isEmpty(entity.getFileName())) { - throw new NullPointerException("文件名不能为null"); - } else if (TextUtils.isEmpty(entity.getDownloadPath())) { - throw new NullPointerException("文件保存路径不能为null"); - } - } -} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/util/RecordUtil.java b/Aria/src/main/java/com/arialyy/aria/util/RecordUtil.java deleted file mode 100644 index 5f83d08c..00000000 --- a/Aria/src/main/java/com/arialyy/aria/util/RecordUtil.java +++ /dev/null @@ -1,389 +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.util; - -import android.text.TextUtils; -import com.arialyy.aria.core.common.RecordHandler; -import com.arialyy.aria.core.common.RecordWrapper; -import com.arialyy.aria.core.common.TaskRecord; -import com.arialyy.aria.core.common.ThreadRecord; -import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadGroupEntity; -import com.arialyy.aria.core.download.m3u8.M3U8Entity; -import com.arialyy.aria.core.inf.AbsEntity; -import com.arialyy.aria.core.inf.AbsNormalEntity; -import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.orm.DbEntity; -import java.io.File; -import java.util.List; - -/** - * 任务记录处理工具 - */ -public class RecordUtil { - private static final String TAG = "RecordUtil"; - - /** - * 删除任务组记录 - * - * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; - * {@code false} 如果任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 - */ - public static void delGroupTaskRecord(String groupHash, boolean removeFile) { - if (TextUtils.isEmpty(groupHash)) { - ALog.e(TAG, "删除下载任务组记录失败,groupHash为null"); - return; - } - DownloadGroupEntity groupEntity = DbDataHelper.getDGEntity(groupHash); - - delGroupTaskRecord(groupEntity, removeFile, true); - } - - /** - * 删除任务组记录 - * - * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; - * {@code false} 如果任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 - */ - public static void delGroupTaskRecord(DownloadGroupEntity groupEntity, boolean removeFile, - boolean removeEntity) { - if (groupEntity == null) { - ALog.e(TAG, "删除下载任务组记录失败,任务组实体为null"); - return; - } - List records = - DbEntity.findRelationData(RecordWrapper.class, "dGroupHash=?", groupEntity.getGroupHash()); - - if (records == null || records.isEmpty()) { - ALog.w(TAG, "组任务记录删除失败,记录为null"); - } else { - for (RecordWrapper record : records) { - if (record == null || record.taskRecord == null) { - continue; - } - // 删除分块文件 - if (record.taskRecord.isBlock) { - for (int i = 0, len = record.taskRecord.threadNum; i < len; i++) { - File partFile = - new File(String.format(RecordHandler.SUB_PATH, record.taskRecord.filePath, i)); - if (partFile.exists()) { - partFile.delete(); - } - } - } - DbEntity.deleteData(ThreadRecord.class, "taskKey=?", record.taskRecord.filePath); - record.taskRecord.deleteData(); - } - } - - List subs = groupEntity.getSubEntities(); - if (subs != null) { - for (DownloadEntity sub : subs) { - File file = new File(sub.getFilePath()); - if (file.exists() && (removeFile || !sub.isComplete())) { - file.delete(); - } - } - } - - // 删除文件夹 - if (!TextUtils.isEmpty(groupEntity.getDirPath())) { - File dir = new File(groupEntity.getDirPath()); - if (dir.exists() && (removeFile || !groupEntity.isComplete())) { - dir.delete(); - } - } - if (removeEntity) { - DbEntity.deleteData(DownloadEntity.class, "groupHash=?", groupEntity.getGroupHash()); - DbEntity.deleteData(DownloadGroupEntity.class, "groupHash=?", groupEntity.getGroupHash()); - } - } - - /** - * 删除任务记录,默认删除文件 - * - * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; - * {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 - * 如果是上传任务,无论任务是否完成,都只删除记录 - */ - public static void delTaskRecord(AbsNormalEntity entity, boolean removeFile) { - if (entity == null) return; - String filePath; - int type; - if (entity instanceof DownloadEntity) { - type = RecordHandler.TYPE_DOWNLOAD; - filePath = ((DownloadEntity) entity).getDownloadPath(); - } else if (entity instanceof UploadEntity) { - type = RecordHandler.TYPE_UPLOAD; - filePath = ((UploadEntity) entity).getFilePath(); - } else { - ALog.w(TAG, "删除记录失败,未知类型"); - return; - } - File targetFile = new File(filePath); - TaskRecord record = getTaskRecord(filePath); - if (record == null) { - if (removeFile) { - removeTargetFile(targetFile); - } - removeRecord(filePath); - removeEntity(type, filePath); - return; - } - - /* - * 处理任务未完成的情况 - */ - if (!entity.isComplete()) { - if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { // 删除ts分片文件 - removeTsCache(targetFile, record.bandWidth); - } else if (record.isBlock) { // 删除分块文件 - removeBlockFile(record); - } - } else if (removeFile) { // 处理任务完成情况 - if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { - removeTsCache(targetFile, record.bandWidth); - } - removeTargetFile(targetFile); - } - - // 成功与否都将删除记录 - removeRecord(filePath); - } - - /** - * 删除任务记录,默认删除文件 - * - * @param filePath 文件路径 - * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; - * {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 - * 如果是上传任务,无论任务是否完成,都只删除记录 - * @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link RecordHandler#TYPE_UPLOAD} - * 上传任务的记录 - * @param removeEntity {@code true} 删除任务实体, - */ - public static void delTaskRecord(String filePath, int type, boolean removeFile, - boolean removeEntity) { - if (TextUtils.isEmpty(filePath)) { - throw new NullPointerException("删除记录失败,文件路径为空"); - } - if (!filePath.startsWith("/")) { - throw new IllegalArgumentException(String.format("文件路径错误,filePath:%s", filePath)); - } - if (type != RecordHandler.TYPE_DOWNLOAD && type != RecordHandler.TYPE_UPLOAD) { - throw new IllegalArgumentException("任务记录类型错误"); - } - - AbsEntity entity; - if (type == RecordHandler.TYPE_DOWNLOAD) { - entity = DbEntity.findFirst(DownloadEntity.class, "downloadPath=?", filePath); - } else { - entity = DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath); - } - File targetFile = new File(filePath); - TaskRecord record = getTaskRecord(filePath); - if (entity == null || record == null) { - if (removeFile) { - removeTargetFile(targetFile); - } - removeRecord(filePath); - removeEntity(type, filePath); - return; - } - - /* - * 处理任务未完成的情况 - */ - if (!entity.isComplete()) { - if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { // 删除ts分片文件 - removeTsCache(targetFile, record.bandWidth); - } else if (record.isBlock) { // 删除分块文件 - removeBlockFile(record); - } - } else if (removeFile) { // 处理任务完成情况 - if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { - removeTsCache(targetFile, record.bandWidth); - } - removeTargetFile(targetFile); - } - - // 成功与否都将删除记录 - removeRecord(filePath); - - if (removeEntity) { - removeEntity(type, filePath); - } - } - - private static void removeTargetFile(File targetFile) { - if (targetFile.exists()) { - targetFile.delete(); - } - } - - private static void removeRecord(String filePath) { - ALog.i(TAG, "删除任务记录"); - DbEntity.deleteData(ThreadRecord.class, "taskKey=?", filePath); - DbEntity.deleteData(TaskRecord.class, "filePath=?", filePath); - // 处理m3u8实体的删除 - DbEntity.deleteData(M3U8Entity.class, "filePath=?", filePath); - } - - /** - * 删除实体 - * - * @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link RecordHandler#TYPE_UPLOAD} - * 上传任务的记录 - */ - private static void removeEntity(int type, String filePath) { - if (type == RecordHandler.TYPE_DOWNLOAD) { - DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath); - } else { - DbEntity.deleteData(UploadEntity.class, "filePath=?", filePath); - } - } - - /** - * 根据文件路径获取任务记录 - */ - private static TaskRecord getTaskRecord(String filePath) { - List recordWrapper = - DbEntity.findRelationData(RecordWrapper.class, "filePath=?", filePath); - if (recordWrapper == null - || recordWrapper.isEmpty() - || recordWrapper.get(0) == null - || recordWrapper.get(0).taskRecord == null) { - return null; - } else { - return recordWrapper.get(0).taskRecord; - } - } - - /** - * 删除多线程分块下载的分块文件 - */ - private static void removeBlockFile(TaskRecord record) { - for (int i = 0, len = record.threadNum; i < len; i++) { - File partFile = new File(String.format(RecordHandler.SUB_PATH, record.filePath, i)); - if (partFile.exists()) { - partFile.delete(); - } - } - } - - /** - * 删除ts文件 - */ - private static void removeTsCache(File targetFile, long bandWidth) { - - String cacheDir = null; - if (!targetFile.isDirectory()) { - cacheDir = - String.format("%s/.%s_%s", targetFile.getParent(), targetFile.getName(), bandWidth); - } - - if (!TextUtils.isEmpty(cacheDir)) { - File cacheDirF = new File(cacheDir); - if (!cacheDirF.exists()) { - return; - } - File[] files = cacheDirF.listFiles(); - for (File f : files) { - if (f.exists()) { - f.delete(); - } - } - File cDir = new File(cacheDir); - if (cDir.exists()) { - cDir.delete(); - } - } - } - - /** - * 删除任务记录,默认删除文件,删除任务实体 - * - * @param filePath 文件路径 - * @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link RecordHandler#TYPE_UPLOAD} - * 上传任务的记录 - */ - public static void delTaskRecord(String filePath, int type) { - delTaskRecord(filePath, type, false, true); - } - - /** - * 修改任务路径,修改文件路径和任务记录信息。如果是分块任务,则修改分块文件的路径。 - * - * @param oldPath 旧的文件路径 - * @param newPath 新的文件路径 - */ - public static void modifyTaskRecord(String oldPath, String newPath) { - if (oldPath.equals(newPath)) { - ALog.w(TAG, "修改任务记录失败,新文件路径和旧文件路径一致"); - return; - } - TaskRecord record = DbDataHelper.getTaskRecord(oldPath); - if (record == null) { - if (new File(oldPath).exists()) { - ALog.w(TAG, "修改任务记录失败,文件【" + oldPath + "】对应的任务记录不存在"); - } - return; - } - if (!record.isBlock) { - File oldFile = new File(oldPath); - if (oldFile.exists()) { - oldFile.renameTo(new File(newPath)); - } - } - - record.filePath = newPath; - record.update(); - // 修改线程记录 - if (record.threadRecords != null && !record.threadRecords.isEmpty()) { - for (ThreadRecord tr : record.threadRecords) { - tr.taskKey = newPath; - File blockFile = new File(String.format(RecordHandler.SUB_PATH, oldPath, tr.threadId)); - if (blockFile.exists()) { - blockFile.renameTo(new File(String.format(RecordHandler.SUB_PATH, newPath, tr.threadId))); - } - } - DbEntity.updateManyData(record.threadRecords); - } - } - - /** - * 检查分块任务是否存在 - * - * @param filePath 文件保存路径 - * @return {@code true} 分块文件存在 - */ - public static boolean blockTaskExists(String filePath) { - return new File(String.format(RecordHandler.SUB_PATH, filePath, 0)).exists(); - } - - /** - * 获取分块文件的快大小 - * - * @param fileLen 文件总长度 - * @param blockId 分块id - * @param blockNum 分块数量 - * @return 分块长度 - */ - public static long getBlockLen(long fileLen, int blockId, int blockNum) { - final long averageLen = fileLen / blockNum; - return blockId == blockNum - 1 ? (fileLen - blockId * averageLen) : averageLen; - } -} diff --git a/AriaAnnotations/bintray-release.gradle b/AriaAnnotations/bintray-release.gradle index 0e8fef08..91049599 100644 --- a/AriaAnnotations/bintray-release.gradle +++ b/AriaAnnotations/bintray-release.gradle @@ -1,10 +1,11 @@ apply plugin: 'com.novoda.bintray-release' publish { - artifactId = 'aria-annotations' +// artifactId = 'aria-annotations' +// uploadName = 'AriaAnnotations' + artifactId = 'annotations' + uploadName = 'Annotations' userOrg = rootProject.ext.userOrg groupId = rootProject.ext.groupId - // uploadName = rootProject.uploadName - uploadName = 'AriaAnnotations' publishVersion = rootProject.ext.publishVersion desc = rootProject.ext.desc website = rootProject.ext.website diff --git a/AriaAnnotations/build.gradle b/AriaAnnotations/build.gradle index a9f2139b..a77f0adb 100644 --- a/AriaAnnotations/build.gradle +++ b/AriaAnnotations/build.gradle @@ -11,4 +11,9 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } -apply from: 'bintray-release.gradle' \ No newline at end of file +//apply from: 'bintray-release.gradle' + +ext{ + PUBLISH_ARTIFACT_ID = 'annotations' +} +apply from: '../gradle/mavenCentral-release.gradle' \ No newline at end of file diff --git a/AriaAnnotations/src/main/java/com/arialyy/annotations/TaskEnum.java b/AriaAnnotations/src/main/java/com/arialyy/annotations/TaskEnum.java index 5723fad1..20c6f630 100644 --- a/AriaAnnotations/src/main/java/com/arialyy/annotations/TaskEnum.java +++ b/AriaAnnotations/src/main/java/com/arialyy/annotations/TaskEnum.java @@ -20,16 +20,16 @@ package com.arialyy.annotations; * 任务类型枚举 */ public enum TaskEnum { - DOWNLOAD("com.arialyy.aria.core.download", "DownloadTask", "$$DownloadListenerProxy", - "NormalTaskListener"), - DOWNLOAD_GROUP("com.arialyy.aria.core.download", "DownloadGroupTask", - "$$DownloadGroupListenerProxy", "NormalTaskListener"), - DOWNLOAD_GROUP_SUB("com.arialyy.aria.core.download", "DownloadGroupTask", - "$$DGSubListenerProxy", "SubTaskListener"), - UPLOAD("com.arialyy.aria.core.upload", "UploadTask", "$$UploadListenerProxy", - "NormalTaskListener"), - M3U8_PEER("com.arialyy.aria.core.download", "DownloadTask", "$$M3U8PeerListenerProxy", - "M3U8PeerTaskListener"); + DOWNLOAD("com.arialyy.aria.core.task", "DownloadTask", "$$DownloadListenerProxy", + "AptNormalTaskListener"), + DOWNLOAD_GROUP("com.arialyy.aria.core.task", "DownloadGroupTask", + "$$DownloadGroupListenerProxy", "AptNormalTaskListener"), + DOWNLOAD_GROUP_SUB("com.arialyy.aria.core.task", "DownloadGroupTask", + "$$DGSubListenerProxy", "AptSubTaskListener"), + UPLOAD("com.arialyy.aria.core.task", "UploadTask", "$$UploadListenerProxy", + "AptNormalTaskListener"), + M3U8_PEER("com.arialyy.aria.core.task", "DownloadTask", "$$M3U8PeerListenerProxy", + "AptM3U8PeerTaskListener"); public String pkg, className, proxySuffix, proxySuperClass; diff --git a/AriaCompiler/bintray-release.gradle b/AriaCompiler/bintray-release.gradle index b833f212..839a216f 100644 --- a/AriaCompiler/bintray-release.gradle +++ b/AriaCompiler/bintray-release.gradle @@ -1,10 +1,11 @@ apply plugin: 'com.novoda.bintray-release' publish { - artifactId = 'aria-compiler' +// artifactId = 'aria-compiler' +// uploadName = 'AriaCompiler' + artifactId = 'compiler' + uploadName = 'Compiler' userOrg = rootProject.ext.userOrg groupId = rootProject.ext.groupId - // uploadName = rootProject.uploadName - uploadName = 'AriaCompiler' publishVersion = rootProject.ext.publishVersion desc = rootProject.ext.desc website = rootProject.ext.website diff --git a/AriaCompiler/build.gradle b/AriaCompiler/build.gradle index 167d083a..7ad5069d 100644 --- a/AriaCompiler/build.gradle +++ b/AriaCompiler/build.gradle @@ -8,10 +8,15 @@ targetCompatibility = JavaVersion.VERSION_1_7 dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.google.auto:auto-common:0.6' - implementation 'com.google.auto.service:auto-service:1.0-rc2' + implementation 'com.google.auto:auto-common:0.10' + implementation 'com.google.auto.service:auto-service:1.0-rc6' + annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' implementation 'com.squareup:javapoet:1.9.0' implementation project(':AriaAnnotations') } -apply from: 'bintray-release.gradle' \ No newline at end of file +//apply from: 'bintray-release.gradle' +ext{ + PUBLISH_ARTIFACT_ID = 'compiler' +} +apply from: '../gradle/mavenCentral-release.gradle' \ No newline at end of file diff --git a/AriaCompiler/src/main/java/com/arialyy/compiler/EntityInfo.java b/AriaCompiler/src/main/java/com/arialyy/compiler/EntityInfo.java index 27f35ced..20742573 100644 --- a/AriaCompiler/src/main/java/com/arialyy/compiler/EntityInfo.java +++ b/AriaCompiler/src/main/java/com/arialyy/compiler/EntityInfo.java @@ -20,7 +20,7 @@ package com.arialyy.compiler; * 实体信息 */ enum EntityInfo { - NORMAL("com.arialyy.aria.core.inf", "AbsNormalEntity"), + NORMAL("com.arialyy.aria.core.common", "AbsNormalEntity"), DOWNLOAD("com.arialyy.aria.core.download", "DownloadEntity"), UPLOAD("com.arialyy.aria.core.upload", "UploadEntity"); String pkg, className; diff --git a/AriaFtpPlug/build.gradle b/AriaFtpPlug/build.gradle deleted file mode 100644 index 71876c9d..00000000 --- a/AriaFtpPlug/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply plugin: 'java' - -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" -} - -sourceCompatibility = JavaVersion.VERSION_1_7 -targetCompatibility = JavaVersion.VERSION_1_7 - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) -} - -//apply from: 'bintray-release.gradle' \ No newline at end of file diff --git a/DEV_LOG.md b/DEV_LOG.md index a5ede232..27354c0c 100644 --- a/DEV_LOG.md +++ b/DEV_LOG.md @@ -1,12 +1,148 @@ ## 开发日志 - + v_3.7 + + v_3.8.16 (2021/4/18) + - 修复单线程下载时,文件已经完成,重复下载回调下载失败的问题 + - 修复一个重新下载文件时,同名路径文件没有被被删除的问题 + - fix bug https://github.com/AriaLyy/Aria/issues/807 + - fix bug https://github.com/AriaLyy/Aria/issues/811 + - fix bug https://github.com/AriaLyy/Aria/issues/851 + - 修复组合任务在获取子任务信息的过程中,手动停止或删除,没有回调的问题 + + v_3.8.15 (2020/11/9) + - 修复不支持断点的连接下载失败问题,https://github.com/AriaLyy/Aria/issues/771 + - 修复iv不存在时,索引文件异常的问题,https://github.com/AriaLyy/Aria/issues/780 + - fix bug https://github.com/AriaLyy/Aria/issues/799 + - fix bug https://github.com/AriaLyy/Aria/issues/798 + + v_3.8.14 (2020/9/23) + - 修复spi机制的兼容问题,https://github.com/AriaLyy/Aria/issues/743 + - 增加路径不可以写的判断,下载时,如果路径不可写,将执行失败回调 https://github.com/AriaLyy/Aria/issues/750 + + v_3.8.12 (2020/8/15) + - 修复一个正则表达式导致的文件名保存号异常问题 https://github.com/AriaLyy/Aria/issues/715 + - 修复一个匿名内部类中的内存溢出的问题 https://github.com/AriaLyy/Aria/issues/705 + - 修复同一个url地址的任务提示路径冲突的问题 + - 修复m3u8任务地址错误时,无法删除实体的问题 https://github.com/AriaLyy/Aria/issues/712 + - 修复m3u8gzip的问题,https://github.com/AriaLyy/Aria/issues/639 + - 修复http表单上传,本地md5和上传的服务的的文件md5不一致的问题 https://github.com/AriaLyy/Aria/issues/730 + - 修复0kb的文件不可下载的问题 https://github.com/AriaLyy/Aria/issues/711 + - 修复加密的m3u8下载,并且使用索引模式时,key的uri没有使用双引号的问题 https://github.com/AriaLyy/Aria/issues/731 + - 修复删除m3u8索引文件后,下载完成回调无法触发的问题 + - 修复删除加密的m3u8文件时,key没有被删除的问题 https://github.com/AriaLyy/Aria/issues/735#issuecomment-673958845 + - 增加m3u8密钥下载地址转换器增加ts列表的url地址 https://github.com/AriaLyy/Aria/issues/718 + - 增加现在http文件下载将使用HEAD请求获取文件大小,配置文件增加 。慎用,并不是所有服务器都支持head请求 + - 增加允许不使用apt直接通过实现监听器来回调下载进度更新,该功能由[chenfei0928](https://github.com/chenfei0928)提交,感谢他的pr。如果注解不生效,只需要实现`DownloadListener`接口便可 + - 增加m3u8使用`ignoreFailureTs`后将不会自动重试失败的切片 https://github.com/AriaLyy/Aria/issues/662 + + v_3.8.10 (2020/6/26) + - fix bug https://github.com/AriaLyy/Aria/issues/703 + - fix bug https://github.com/AriaLyy/Aria/issues/702 + - fix bug https://github.com/AriaLyy/Aria/issues/695 + + v_3.8.9 (2020/6/14) + - fix bug https://github.com/AriaLyy/Aria/issues/688 + - fix bug https://github.com/AriaLyy/Aria/issues/690 + - m3u8任务增加`setUseDefConvert()`方法,用于处理默认的m3u8任务 + + v_3.8.8 (2020/6/7) + - 修复设置了cancel(false),文件还是被删除的问题 https://github.com/AriaLyy/Aria/issues/686 + - 修复错误url的下载任务,无法删除的问题 https://github.com/AriaLyy/Aria/issues/684 + + v_3.8.7 (2020/5/25) + - 修复组合任务单个子任务失败后,重新恢复组合任务,组合任务状态变为完成的问题 + - 修复40x错误,会继续重试并且无法重试成功的问题 https://github.com/AriaLyy/Aria/issues/619 + - 修复wait模式下,resume(true)无效问题 + - 修复now模式下的一些问题 https://github.com/AriaLyy/Aria/issues/620 + - 修复组任务,其中一个子任务在获取文件长度失败后,重新恢复组合任务,组合任务状态变为完成的问题 https://github.com/AriaLyy/Aria/issues/628 + - 修复组任务中,其中一个子任务是30x地址,导致调度器无法出现该子任务状态的问题 + - 增加组任务groupHash冲突检查 https://github.com/AriaLyy/Aria/issues/635 + - 修复task.cancel(false)还是把本地文件删除的问题 https://github.com/AriaLyy/Aria/issues/646 + - fix bug https://github.com/AriaLyy/Aria/issues/670 + - fix bug https://github.com/AriaLyy/Aria/issues/664 + + v_3.8.6 (2020/2/17) + - fix bug https://github.com/AriaLyy/Aria/issues/608 + - fix bug https://github.com/AriaLyy/Aria/issues/579#issuecomment-586665035 + - fix bug https://github.com/AriaLyy/Aria/issues/610 + - fix bug https://github.com/AriaLyy/Aria/issues/614 + - 增加文件名适配器(感谢小伙伴[DaveBoy](https://github.com/DaveBoy)的PR) + - 优化异常提示 + + v_3.8.5 (2020/1/18) + - fix bug https://github.com/AriaLyy/Aria/issues/599 + - 增加密钥url转换器的参数 https://github.com/AriaLyy/Aria/issues/603 + - 增加sftp,文件上传、下载功能,[sftp下载](https://aria.laoyuyu.me/aria_doc/download/sftp_normal.html),[sftp上传](https://aria.laoyuyu.me/aria_doc/upload/sftp_normal.html) + - 使用零拷贝技术,优化了合并分块的效率 + + v_3.8.3 (2020/1/9) + - fix bug https://github.com/AriaLyy/Aria/issues/573 + - android P适配 https://github.com/AriaLyy/Aria/issues/581 + - 添加ftp服务器标志 https://github.com/AriaLyy/Aria/issues/580 + - 重构loader模块,让loader模块的代码更加清晰,去除一些不必要的线程创建 + - 修复ftp上传完成后,删除服务器端的文件,无法重新下载的问题 + - 增加获取执行中的任务api,详情见:https://aria.laoyuyu.me/aria_doc/api/task_list.html + - 增加获取剩余时间的api,详情见:https://aria.laoyuyu.me/aria_doc/start/task_explain.html + - fix bug https://github.com/AriaLyy/Aria/issues/595 + + v_3.8.1 (2019/12/22) + - 修复一个表创建失败的问题 https://github.com/AriaLyy/Aria/issues/570 + - 修复一个非分块模式下导致下载失败的问题 https://github.com/AriaLyy/Aria/issues/571 + - 修复一个服务器端无法创建socket连接,却没有返回码导致客户端卡住的问题 https://github.com/AriaLyy/Aria/issues/569 + - 修复文件删除后,组合任务没有重新下载的问题 https://github.com/AriaLyy/Aria/issues/574 + - 优化缓存队列和执行队列 + + v_3.8 (2019/12/17) + - 移除androidx和support的依赖,现在无论是哪个版本的appcompat包都可以使用本框架 + - 修复一个在xml中使用fragment导致的内存泄漏问题 + - m3u8协议的key信息增加了`keyFormat`,`keyFormatVersion`字段 + - m3u8增加了`ignoreFailureTs`方法,忽略下载失败的ts切片 + - 修复在dialogFragment的`onCreateDialog()`注册导致的注解不生效问题 + - 修复组合任务初始化失败时,无法删除的问题 + - 修复`reStart()`后,无法停止的问题 + - ftp增加主动模式,开启主动模式:https://aria.laoyuyu.me/aria_doc/api/ftp_params.html + - 修复ftp服务器无法响应`abor`命令导致的无法停止上传的问题 https://github.com/AriaLyy/Aria/issues/564 + - 修复ftp上传时,服务器有长度为0的文件导致上传失败的问题 + - 修复下载任务和上传任务的文件路径是同一个时,导致的记录混乱问题 + - 优化提示 + + v_3.7.10 (2019/12/3) + - fix bug https://github.com/AriaLyy/Aria/issues/543#issuecomment-559733124 + - fix bug https://github.com/AriaLyy/Aria/issues/542 + - fix bug https://github.com/AriaLyy/Azria/issues/547 + - 修复下载失败时,中断重试无效的问题 + - 增加忽略权限检查的api,`ignoreCheckPermissions()` + - 增加通用的的忽略文件路径被占用的api,`isIgnoreFilePathOccupy()` + + v_3.7.9 (2019/11/28) + - fix bug https://github.com/AriaLyy/Aria/issues/537 + + v_3.7.8 (2019/11/28) + - fix bug https://github.com/AriaLyy/Aria/issues/526 + - fix bug https://github.com/AriaLyy/Aria/issues/533 + - fix bug https://github.com/AriaLyy/Aria/issues/535 + - 修复ftp无法完成下载的问题 + - 修复一个非分块模式下,调用`updateUrl(newUrl)`后无法恢复下载的问题 + - 增加立即恢复任务的接口,正常来说,当执行队列满时,调用恢复任务接口,只能将任务放到缓存队列中。如果希望调用恢复接口,马上进入执行队列,需要调用`resume(true)`这个重载方法。 + - 增加M3U8加密密钥的下载地址转换器 https://github.com/AriaLyy/Aria/issues/522 + + v_3.7.7 (2019/11/20) + - 修复ftp无法完成下载的问题 + - 修复一个http下载崩溃的问题 + + v_3.7.6 (2019/11/19) + - fix bug https://github.com/AriaLyy/Aria/issues/505 + - fix bug https://github.com/AriaLyy/Aria/issues/516 + - fix bug https://github.com/AriaLyy/Aria/issues/515 + - 增加强制上传的api`forceUpload()` + - 修复for循环上传文件出现的问题 + - 移除创建任务的500ms间隔限制 + - 修复多线程读写时可能出现的`database is locked`的问题 + + v_3.7.5 (2019/11/10) + - fix bug https://github.com/AriaLyy/Aria/issues/500 + - fix bug https://github.com/AriaLyy/Aria/issues/508 + - fix bug https://github.com/AriaLyy/Aria/issues/503 + - 修复m3u8创建索引不成功的问题 + + v_3.7.4 (2019/11/2) + - 修复一个class被莫名改变的问题 + - 修复非分块模式下导致的一个下载失败问题 + - fix bug https://github.com/AriaLyy/Aria/issues/493 + + v_3.7.3 (2019/10/31) + - fix bug https://github.com/AriaLyy/Aria/issues/495 + - fix bug https://github.com/AriaLyy/Aria/issues/496 + + v_3.7.2 (2019/10/28) - fix bug https://github.com/AriaLyy/Aria/issues/450 - fix bug https://github.com/AriaLyy/Aria/issues/466 - fix bug https://github.com/AriaLyy/Aria/issues/454 - fix bug https://github.com/AriaLyy/Aria/issues/467 - fix bug https://github.com/AriaLyy/Aria/issues/459 + - fix bug https://github.com/AriaLyy/Aria/issues/487 + - fix bug https://github.com/AriaLyy/Aria/issues/483 + - fix bug https://github.com/AriaLyy/Aria/issues/482 + - fix bug https://github.com/AriaLyy/Aria/issues/473 - 移除隐藏api的反射 https://github.com/AriaLyy/Aria/issues/456 - - 新增ftp免证书登陆功能h ttps://github.com/AriaLyy/Aria/issues/455 + - 新增ftp免证书登陆功能 https://github.com/AriaLyy/Aria/issues/455 - 适配androidX - 修复组合任务,恢复下载,会出现进度显示为0的问题 - m3u8点播下载新增创建ts索引功能 diff --git a/ENGLISH_README.md b/ENGLISH_README.md index 345c118a..4043578b 100644 --- a/ENGLISH_README.md +++ b/ENGLISH_README.md @@ -1,291 +1,172 @@ # Aria -![图标](https://github.com/AriaLyy/DownloadUtil/blob/master/app/src/main/res/mipmap-hdpi/ic_launcher.png)
- -## [中文文档](https://github.com/AriaLyy/Aria/blob/master/CHINESE_README.md) - - -Aria project is from the moment taht the work encountered in a file download management needs adn i was tortured at the time of the pain.
-Since then i have a idea which is to program a simple and easy to use,stable and efficient download framework. -Aria experienced 1.0 to 3.0 development, be more and more close to the original set by the target. - -Aria has the following characteristics: - + simple and convenient - - can be used in Activity, Service, Fragment, Dialog, popupWindow, Notification and other components - - support the task of automatic scheduling, the user does not need to care about the state of the task switch logic - - [Through the Aria event, it is easy to get the download status of the current download task](#status) - - [a code plus can get the current download speed](#interface) - - [a code can be dynamically set the maximum number of downloads](#parameters) - - [code to achieve speed limit](#interface) - - [It is easy to modify the number of download threads by modifying the configuration file](https://github.com/AriaLyy/Aria/blob/master/app/src/main/assets/aria_config.xml) - - [priority to download a task](#interface) - +![图标](https://github.com/AriaLyy/DownloadUtil/blob/master/img/ic_launcher.png)
+## [ENGLISH DOC](https://github.com/AriaLyy/Aria/blob/master/ENGLISH_README.md)
+## [中文文档](https://aria.laoyuyu.me/aria_doc) +The Aria project originated from the need for file download management encountered in work. At that time, the pain of being downloaded was unhappy. Since then, a simple and easy-to-use, stable and efficient download framework has emerged. Aria has experienced 1.0 to 3.0 development. , It is getting closer and closer to the goal set at the beginning. + +Aria has the following characteristics: + + Simple and convenient + - Can be used in Activity, Service, Fragment, Dialog, popupWindow, Notification and other components + - Support HTTP\FTP resumable download, multi-task automatic scheduling + - Support multi-file package download, multi-file sharing the same progress (eg: video + cover + subtitles) + - Support downloading FTP folder + - Support HTTP form upload + - Support file FTP resumable upload + - Support FTPS resumable upload, [see](https://aria.laoyuyu.me/aria_doc/api/ftp_params.html#%E4%BA%8C%E3%80%81ftps) + - Support SFTP resumable upload, [sftp download](https://aria.laoyuyu.me/aria_doc/download/sftp_normal.html), [sftp upload](https://aria.laoyuyu.me/aria_doc/upload /sftp_normal.html) + Support https address download - It is easy to set the CA certificate information in the configuration file - + Support 300,301,302 redirect download link download - + Support upload operation + + Support [Multi-threaded download in blocks](https://aria.laoyuyu.me/aria_doc/start/config.html), which can more effectively play the machine IO performance + + Support 300, 301, 302 redirect download link download + + Support file download of m3u8 and hls protocol [m3u8 download](https://aria.laoyuyu.me/aria_doc/download/m3u8.html) + + Support m3u8 download support, [click to view details](https://aria.laoyuyu.me/aria_doc/download/m3u8_vod.html) + + The download support file length increases dynamically, and the file download initialization will no longer occupy too much memory space, see [Dynamic Length Configuration](https://aria.laoyuyu.me/aria_doc/start/config.html#%E4% B8%8B%E8%BD%BD%E5%8A%A8%E6%80%81%E6%96%87%E4%BB%B6%E8%AF%B4%E6%98%8E) -How do we to use Aria? -* [download](#Using) -* [upload](#Upload) s +[How to use Aria?] (#use) + +If you think Aria is helpful to you, your star and issues will be my greatest support. Of course, you are also very welcome to PR, [PR Method](https://www.zhihu.com/question/21682976/answer /79489643)`^_^` + +## Example +* Multitask download + +![Multitask download](https://github.com/AriaLyy/DownloadUtil/blob/master/img/download_img.gif) + +* Speed limit + +![网速下载限制](https://github.com/AriaLyy/DownloadUtil/blob/master/img/max_speed.gif) + +* Multi-file package download + + + +* m3u8 download + +![m3u8点播文件边下边看](https://github.com/AriaLyy/Aria/blob/master/img/m3u8VodDownload.gif) + +## Lib +[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/AriaLyy/Aria/blob/master/LICENSE) +[![Core](https://img.shields.io/badge/Core-3.8.10-blue)](https://github.com/AriaLyy/Aria) +[![Compiler](https://img.shields.io/badge/Compiler-3.8.10-blue)](https://github.com/AriaLyy/Aria) +[![FtpComponent](https://img.shields.io/badge/FtpComponent-3.8.10-orange)](https://github.com/AriaLyy/Aria) +[![FtpComponent](https://img.shields.io/badge/SFtpComponent-3.8.10-orange)](https://github.com/AriaLyy/Aria) +[![M3U8Component](https://img.shields.io/badge/M3U8Component-3.8.10-orange)](https://github.com/AriaLyy/Aria) -If you feel that Aria is helpful to you, your star and issues will be the greatest support for me.`^_^` -## Download -[![Download](https://api.bintray.com/packages/arialyy/maven/AriaApi/images/download.svg)](https://bintray.com/arialyy/maven/AriaApi/_latestVersion) -[![Download](https://api.bintray.com/packages/arialyy/maven/AriaCompiler/images/download.svg)](https://bintray.com/arialyy/maven/AriaCompiler/_latestVersion) ```java -compile 'com.arialyy.aria:aria-core:3.2.0' -annotationProcessor 'com.arialyy.aria:aria-compiler:3.2.0' +implementation 'com.arialyy.aria:core:3.8.10' +annotationProcessor 'com.arialyy.aria:compiler:3.8.10' +implementation 'com.arialyy.aria:ftpComponent:3.8.10' # If you need to use ftp, please add this component +implementation 'com.arialyy.aria:sftpComponent:3.8.10' # If you need to use sftp, please add this component +implementation 'com.arialyy.aria:m3u8Component:3.8.10' # If you need to use the m3u8 download function, please add this component ``` -## For example -![Multi-task download](https://github.com/AriaLyy/DownloadUtil/blob/master/img/download_img.gif) -![download speed limit](https://github.com/AriaLyy/DownloadUtil/blob/master/img/max_speed.gif) +If you are using Kotlin, please use the official method provided by Kotlin to configure apt, [kotlin kapt official configuration portal](https://www.kotlincn.net/docs/reference/kapt.html) -## performance -![Performance display](https://github.com/AriaLyy/DownloadUtil/blob/master/img/performance.png) +__⚠️Note: When the version below 3.5.4 is upgraded, the [configuration file] needs to be updated(https://aria.laoyuyu.me/aria_doc/start/config.html)!!__ + +__⚠️Note: Version 3.8 and above have been adapted to AndroidX and support libraries can be used *** -## Using -Since Aria involves the operation of files and networks, you need to add the following permissions to the manifest file. +## Use +Because Aria involves file and network operations, you need to add the following permissions to the manifest file. If you want to use Aria in a system above 6.0, you need to dynamically apply for file system read and write permissions to the Android system, [How to use Android system permissions](https://developer.android.com/training/permissions/index.html?hl=zh-cn) ```xml + + ``` -## Use Aria to download -* Add a task (do not download), when other download tasks are completed, will automatically download the waiting task +## Use Aria +### Basic use +The example is a single-task download, only a very simple code is needed to realize the download function. +* Create task ```java - Aria.download(this) - .load(DOWNLOAD_URL) - .setDownloadPath(DOWNLOAD_PATH) //file save path - .add(); + long taskId = Aria.download(this) + .load(DOWNLOAD_URL) //Read download address + .setFilePath(DOWNLOAD_PATH) //Set the full path where the file is saved + .create(); //Create and start download ``` - -* download - +* Stop\Resume task ```java Aria.download(this) - .load(DOWNLOAD_URL) //load download url - .setDownloadPath(DOWNLOAD_PATH) //file save path - .start(); //start download - ``` -* Pause + .load(taskId) //task id + .stop(); // stop task + //.resume(); // resume task - ```java - Aria.download(this).load(DOWNLOAD_URL).pause(); - ``` -* Resume download - - ```java - Aria.download(this).load(DOWNLOAD_URL).resume(); - ``` -* Cancel download - - ```java - Aria.download(this).load(DOWNLOAD_URL).cancel(); ``` -### status -If you want to read the download progress or download the information, then you need to create an event class and register the event class into the Aria manager in the onResume (Activity, Fragment) or constructor (Dialog, PopupWindow). +### Obtaining task status +Based on the consideration of decoupling, the download function of Aria is separated from the state acquisition, and the state acquisition will not be integrated into the chain code, but Aria provides another simpler and more flexible solution. +Through annotations, you can easily get all the status of the task. 1. Register the object to Aria - - `Aria.download(this).register();` or `Aria.upload(this).register();` - ```java - @Override - protected void onCreate(Bundle savedInstanceState) { +```java +protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Aria.download(this).register(); - } - ``` - -2. Use`@Download` or `@Upload` to annotate your function
- **note:** - - Annotation is done by using `Apt`, so you do not need to worry that this will affect your machine's performance - - The annotated method**can not be modified by private** - - The annotated method**can have only one argument, and the parameter type must be either`DownloadTask` or `UploadTask`** - - Method name can be any string - -3. In addition to using annotation methods in widget (Activity, Fragment, Dialog, Popupwindow), you can also use annotation functions in components such as Service, Notification, and so on. - - ```java - @Download.onPre(DOWNLOAD_URL) - protected void onPre(DownloadTask task) {} - - @Download.onTaskStart - void taskStart(DownloadTask task) {} - - @Download.onTaskRunning - protected void running(DownloadTask task) {} - - @Download.onTaskResume - void taskResume(DownloadTask task) {} - - @Download.onTaskStop - void taskStop(DownloadTask task) {} - - @Download.onTaskCancel - void taskCancel(DownloadTask task) {} - - @Download.onTaskFail - void taskFail(DownloadTask task) {} - - @Download.onTaskComplete - void taskComplete(DownloadTask task) {} - - @Download.onNoSupportBreakPoint - public void onNoSupportBreakPoint(DownloadTask task) {} - - ``` -4. If you want to set up a listener for a single task, or for some specific task.
- **Adding a download address for a task in an annotation means that only the task triggers the annotated method.** - - ```java - @Download.onTaskRunning({ - "https://test.xx.apk", - "http://test.xx2.apk" - }) void taskRunning(DownloadTask task) { - mAdapter.setProgress(task.getDownloadEntity()); - } - ``` -In the above example,only the download address is`https://test.xx.apk` and `http://test.xx2.apk`will trigger the`taskRunning(DownloadTask task)`method。 - -### parameters -#### [Configuration file setting parameters](https://github.com/AriaLyy/Aria/blob/master/app/src/main/assets/aria_config.xml) -#### Set the parameters in the code -In addition to the file mode to modify the Aria parameter, the same, you can also modify the code in the Aria parameters
-Get the configuration file directly through`Aria.get(this).getDownloadConfig()`or`Aria.get(this).getUploadConfig()`
-and then modify the parameters: -```java -// 修改最大下载数,调用完成后,立即生效 -// 如当前下载任务数是4,修改完成后,当前任务数会被Aria自动调度任务数 -Aria.get(this).getDownloadConfig().setMaxTaskNum(3); +} ``` -### interface -* Stop all tasks - - ```java - Aria.download(this).stopAllTask(); - ``` - -* Restore all stopped tasks - - ```java -Aria.download(this).resumeAllTask(); - ``` - -* Delete all tasks +2. Obtain task execution status through annotations + **Note: ** + - Annotation back is implemented using Apt, so you don’t need to worry about this affecting the performance of your machine + - Annotated methods **cannot be modified by private** + - The annotated method ** can only have one parameter, and the parameter type must be `DownloadTask` or `UploadTask` or `DownloadGroupTask`** + - Method name can be any string - ```java - Aria.download(this).removeAllTask(); - ``` - -* Maximum download speed limit - ```java - //单位为 kb - Aria.download(this).setMaxSpeed(speed); - ``` - -* Get download speed for current tasks
-Speed parameters a bit special,need to [download the event support](#status) -``` java -@Override public void onTaskRunning(DownloadTask task) { - //If you turn on the speed unit conversion configuration, you can get the download speed with units in the following ways, such as: 1 mb/s - String convertSpeed = task.getConvertSpeed(); - //If you have your own unit format, you can get the original byte length by the following method - long speed = task.getSpeed(); +```java +//Process the status of the task execution here, such as the refresh of the progress bar +@Download.onTaskRunning protected void running(DownloadTask task) { + if(task.getKey().eques(url)){ + .... + You can judge whether it is the callback of the specified task by url + } + int p = task.getPercent(); //Task progress percentage + String speed = task.getConvertSpeed(); //Download speed after unit conversion, unit conversion needs to be opened in the configuration file + String speed1 = task.getSpeed(); //Original byte length speed } -``` -* Get the downloaded file size, the current progress percentage
-Likewise, you can also get the downloaded file size in the DownloadTask object -``` -@Override public void onTaskRunning(DownloadTask task) { -  //Get the file size - long fileSize = task.getFileSize(); - //Get the file size after conversion - String fileSize1 = task.getConvertFileSize(); - //The current percentage of progress - int percent = task.getPercent(); +@Download.onTaskComplete void taskComplete(DownloadTask task) { + //Process the status of task completion here } ``` -* Set the high priority task
- If you want to give priority to download a task, you can -``` java -Aria.download(this).load(DOWNLOAD_URL).setDownloadPath(PATH).setHighestPriority(); -``` - -* Set the extension field
- Sometimes, you may want to store some of your own data when you download it
-**TIP**: If you have more data, or the data is more complex, you can first convert the data to JSON, and then save it to Aria's download entity -```java -Aria.download(this).load(DOWNLOAD_URL).setExtendField(str) -``` - -## Upload - - * Add a task (add only, do not upload) +### [Document address](https://aria.laoyuyu.me/aria_doc/) - ```java - Aria.upload(this) - .load(filePath) //file path - .setUploadUrl(uploadUrl) // upload the path - .setAttachment(fileKey) //The server reads the file's key - .add(); - ``` - * Upload +### Version Log + + v_3.8.10 (2020/6/26) + - fix bug https://github.com/AriaLyy/Aria/issues/703 + - fix bug https://github.com/AriaLyy/Aria/issues/702 + - fix bug https://github.com/AriaLyy/Aria/issues/695 - ```java - Aria.upload(this) - .load(filePath) //file path - .setUploadUrl(uploadUrl) //upload the path - .setAttachment(fileKey) //The server reads the file's key - .start(); - ``` - * cancel upload +[More Version Log](https://github.com/AriaLyy/Aria/blob/master/DEV_LOG.md) - ```java - Aria.upload(this).load(filePath).cancel(); - ``` - -## Confused configuration +## Confusion configuration ``` -dontwarn com.arialyy.aria.** -keep class com.arialyy.aria.**{*;} -keep class **$$DownloadListenerProxy{ *; } -keep class **$$UploadListenerProxy{ *; } +-keep class **$$DownloadGroupListenerProxy{ *; } +-keep class **$$DGSubListenerProxy{ *; } -keepclasseswithmembernames class * { @Download.* ; @Upload.* ; + @DownloadGroup.* ; } ``` -## others - Have any questions that can give me a message in the[issues](https://github.com/AriaLyy/Aria/issues)。 +## Other + If you have any questions, you can leave me a feedback at [issues](https://github.com/AriaLyy/Aria/issues).
+ Before submitting an issue, I hope you have checked [wiki](https://aria.laoyuyu.me/aria_doc/) or searched [issues](https://github.com/AriaLyy/Aria/issues).
-*** +## Donate + https://paypal.me/arialyy -## 后续版本开发规划 -* ~~http、scoket断点上传~~ -* ~~实现上传队列调度功能~~ - - -## Development log - + v_3.1.9 Repair the stopAll queue without task when the problem of collapse, increase the function for a single task monitor - + v_3.1.7 repair some files can not download the bug, increase the apt annotation method, the incident is more simple - + v_3.1.6 When the task is canceled ontaskCancel callback twice - + v_3.1.5 Optimize the code structure, increase the priority download task function. - + v_3.1.4 Repair the fast switching, pause, and restore functions, the probability of re-download problems, add onPre () callback, onPre () used to request the interface before the implementation of interface UI update operation. - + v_3.1.0 Add the Aria configuration file to optimize the code - + v_3.0.3 Repair the pause after deleting the task, flashing the problem, add the api to delete the record - + v_3.0.2 supports 30x redirect link download - + v_3.0.0 add upload task support to fix some bugs that have been discovered +*** License ------- diff --git a/AriaFtpPlug/.gitignore b/FtpComponent/.gitignore similarity index 100% rename from AriaFtpPlug/.gitignore rename to FtpComponent/.gitignore diff --git a/FtpComponent/bintray-release.gradle b/FtpComponent/bintray-release.gradle new file mode 100644 index 00000000..8f9782b7 --- /dev/null +++ b/FtpComponent/bintray-release.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.novoda.bintray-release' +publish { +// artifactId = 'aria-compiler' +// uploadName = 'AriaCompiler' + artifactId = 'ftpComponent' + uploadName = 'FtpComponent' + userOrg = rootProject.ext.userOrg + groupId = rootProject.ext.groupId + publishVersion = rootProject.ext.publishVersion + desc = rootProject.ext.desc + website = rootProject.ext.website + licences = rootProject.ext.licences +} \ No newline at end of file diff --git a/FtpComponent/build.gradle b/FtpComponent/build.gradle new file mode 100644 index 00000000..701738be --- /dev/null +++ b/FtpComponent/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + debug{ + debuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation project(path: ':PublicComponent') +} + +//apply from: 'bintray-release.gradle' +ext{ + PUBLISH_ARTIFACT_ID = 'ftp' +} +apply from: '../gradle/mavenCentral-release.gradle' \ No newline at end of file diff --git a/FtpComponent/consumer-rules.pro b/FtpComponent/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/AriaFtpPlug/proguard-rules.pro b/FtpComponent/proguard-rules.pro similarity index 70% rename from AriaFtpPlug/proguard-rules.pro rename to FtpComponent/proguard-rules.pro index 36df18ca..f1b42451 100644 --- a/AriaFtpPlug/proguard-rules.pro +++ b/FtpComponent/proguard-rules.pro @@ -1,14 +1,10 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in E:\sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# Add any project specific keep options here: - # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: diff --git a/FtpComponent/src/main/AndroidManifest.xml b/FtpComponent/src/main/AndroidManifest.xml new file mode 100644 index 00000000..25aac7fe --- /dev/null +++ b/FtpComponent/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/DatagramSocketClient.java b/FtpComponent/src/main/java/aria/apache/commons/net/DatagramSocketClient.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/DatagramSocketClient.java rename to FtpComponent/src/main/java/aria/apache/commons/net/DatagramSocketClient.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/DatagramSocketFactory.java b/FtpComponent/src/main/java/aria/apache/commons/net/DatagramSocketFactory.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/DatagramSocketFactory.java rename to FtpComponent/src/main/java/aria/apache/commons/net/DatagramSocketFactory.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/DefaultDatagramSocketFactory.java b/FtpComponent/src/main/java/aria/apache/commons/net/DefaultDatagramSocketFactory.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/DefaultDatagramSocketFactory.java rename to FtpComponent/src/main/java/aria/apache/commons/net/DefaultDatagramSocketFactory.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/DefaultSocketFactory.java b/FtpComponent/src/main/java/aria/apache/commons/net/DefaultSocketFactory.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/DefaultSocketFactory.java rename to FtpComponent/src/main/java/aria/apache/commons/net/DefaultSocketFactory.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/MalformedServerReplyException.java b/FtpComponent/src/main/java/aria/apache/commons/net/MalformedServerReplyException.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/MalformedServerReplyException.java rename to FtpComponent/src/main/java/aria/apache/commons/net/MalformedServerReplyException.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/PrintCommandListener.java b/FtpComponent/src/main/java/aria/apache/commons/net/PrintCommandListener.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/PrintCommandListener.java rename to FtpComponent/src/main/java/aria/apache/commons/net/PrintCommandListener.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ProtocolCommandEvent.java b/FtpComponent/src/main/java/aria/apache/commons/net/ProtocolCommandEvent.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ProtocolCommandEvent.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ProtocolCommandEvent.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ProtocolCommandListener.java b/FtpComponent/src/main/java/aria/apache/commons/net/ProtocolCommandListener.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ProtocolCommandListener.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ProtocolCommandListener.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ProtocolCommandSupport.java b/FtpComponent/src/main/java/aria/apache/commons/net/ProtocolCommandSupport.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ProtocolCommandSupport.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ProtocolCommandSupport.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/SocketClient.java b/FtpComponent/src/main/java/aria/apache/commons/net/SocketClient.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/SocketClient.java rename to FtpComponent/src/main/java/aria/apache/commons/net/SocketClient.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/Configurable.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/Configurable.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/Configurable.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/Configurable.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTP.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTP.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTP.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTP.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPClient.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPClient.java similarity index 99% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPClient.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPClient.java index 4bcf98dc..5b85dcc7 100644 --- a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPClient.java +++ b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPClient.java @@ -16,8 +16,13 @@ */ package aria.apache.commons.net.ftp; +import aria.apache.commons.net.MalformedServerReplyException; import aria.apache.commons.net.SocketClient; +import aria.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory; +import aria.apache.commons.net.ftp.parser.FTPFileEntryParserFactory; +import aria.apache.commons.net.ftp.parser.MLSxEntryParser; import aria.apache.commons.net.ftp.parser.ParserInitializationException; +import aria.apache.commons.net.io.CRLFLineReader; import aria.apache.commons.net.io.CopyStreamAdapter; import aria.apache.commons.net.io.CopyStreamEvent; import aria.apache.commons.net.io.CopyStreamException; @@ -53,12 +58,6 @@ import java.util.Properties; import java.util.Random; import java.util.Set; -import aria.apache.commons.net.MalformedServerReplyException; -import aria.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory; -import aria.apache.commons.net.ftp.parser.FTPFileEntryParserFactory; -import aria.apache.commons.net.ftp.parser.MLSxEntryParser; -import aria.apache.commons.net.io.CRLFLineReader; - /** * FTPClient encapsulates all the functionality necessary to store and * retrieve files from an FTP server. This class takes care of all @@ -2263,8 +2262,8 @@ public class FTPClient extends FTP implements Configurable { } /* - * Create the feature map if not already created. - */ + * Create the feature map if not already created. + */ private boolean initFeatureMap() throws IOException { if (__featuresMap == null) { // Don't create map here, because next line may throw exception @@ -2375,10 +2374,10 @@ public class FTPClient extends FTP implements Configurable { boolean success = FTPReply.isPositiveCompletion(sendCommand(FTPCmd.MLST, pathname)); if (success) { String reply = getReplyStrings()[1]; - /* check the response makes sense. - * Must have space before fact(s) and between fact(s) and filename - * Fact(s) can be absent, so at least 3 chars are needed. - */ + /* check the response makes sense. + * Must have space before fact(s) and between fact(s) and filename + * Fact(s) can be absent, so at least 3 chars are needed. + */ if (reply.length() < 3 || reply.charAt(0) != ' ') { throw new MalformedServerReplyException("Invalid server reply (MLST): '" + reply + "'"); } @@ -3663,7 +3662,6 @@ public class FTPClient extends FTP implements Configurable { private long time = System.currentTimeMillis(); private int notAcked; private OnFtpInputStreamListener listener; - private long lTime = time; CSL(FTPClient parent, long idleTime, int maxWait) throws SocketException { this(parent, idleTime, maxWait, null); @@ -3697,12 +3695,9 @@ public class FTPClient extends FTP implements Configurable { time = now; } - //if (now - lTime > 100) { if (listener != null) { listener.onFtpInputStream(parent, totalBytesTransferred, bytesTransferred, streamSize); } - //lTime = now; - //} } void cleanUp() throws IOException { diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPClientConfig.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPClientConfig.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPClientConfig.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPClientConfig.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPCmd.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPCmd.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPCmd.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPCmd.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPCommand.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPCommand.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPCommand.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPCommand.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPConnectionClosedException.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPConnectionClosedException.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPConnectionClosedException.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPConnectionClosedException.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFile.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFile.java similarity index 99% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFile.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFile.java index 5edcc119..3a9dd625 100644 --- a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFile.java +++ b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFile.java @@ -55,7 +55,7 @@ public class FTPFile implements Serializable { /** A constant indicating file/directory write permission. ***/ public static final int WRITE_PERMISSION = 1; /** - * A constant indicating file execute permission or directory listing + * A constant indicating file accept permission or directory listing * permission. ***/ public static final int EXECUTE_PERMISSION = 2; diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParserImpl.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParserImpl.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParserImpl.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileEntryParserImpl.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileFilter.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileFilter.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileFilter.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileFilter.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileFilters.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileFilters.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPFileFilters.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPFileFilters.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPHTTPClient.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPHTTPClient.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPHTTPClient.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPHTTPClient.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPListParseEngine.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPListParseEngine.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPListParseEngine.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPListParseEngine.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPReply.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPReply.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPReply.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPReply.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSClient.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSClient.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSClient.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSClient.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSCommand.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSCommand.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSCommand.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSCommand.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSServerSocketFactory.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSServerSocketFactory.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSServerSocketFactory.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSServerSocketFactory.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSSocketFactory.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSSocketFactory.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSSocketFactory.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSSocketFactory.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSTrustManager.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSTrustManager.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/FTPSTrustManager.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/FTPSTrustManager.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/OnFtpInputStreamListener.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/OnFtpInputStreamListener.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/OnFtpInputStreamListener.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/OnFtpInputStreamListener.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/package-info.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/package-info.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/package-info.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/package-info.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/CompositeFileEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/CompositeFileEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/CompositeFileEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/CompositeFileEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/MLSxEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/MLSxEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/MLSxEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/MLSxEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/MVSFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/MVSFTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/MVSFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/MVSFTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/NTFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/NTFTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/NTFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/NTFTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java similarity index 99% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java index 7585ebbe..212198b3 100644 --- a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java +++ b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java @@ -95,7 +95,7 @@ public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { * Netware file permissions are in the following format: RWCEAFMS, and are explained as follows: *
    *
  • S - Supervisor; All rights. - *
  • R - Read; Right to open and read or execute. + *
  • R - Read; Right to open and read or accept. *
  • W - Write; Right to open and modify. *
  • C - Create; Right to create; when assigned to a file, allows a deleted file to be * recovered. diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/OS2FTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/OS2FTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/OS2FTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/OS2FTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/OS400FTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/OS400FTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/OS400FTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/OS400FTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/ParserInitializationException.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/ParserInitializationException.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/ParserInitializationException.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/ParserInitializationException.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/UnixFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/UnixFTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/UnixFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/UnixFTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/VMSFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/VMSFTPEntryParser.java similarity index 99% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/VMSFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/VMSFTPEntryParser.java index b2578cd0..6c23179c 100644 --- a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/VMSFTPEntryParser.java +++ b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/VMSFTPEntryParser.java @@ -164,7 +164,7 @@ public class VMSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { //Set file permission. //VMS has (SYSTEM,OWNER,GROUP,WORLD) users that can contain - //R (read) W (write) E (execute) D (delete) + //R (read) W (write) E (accept) D (delete) //iterate for OWNER GROUP WORLD permissions for (int access = 0; access < 3; access++) { diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/package-info.java b/FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/package-info.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/ftp/parser/package-info.java rename to FtpComponent/src/main/java/aria/apache/commons/net/ftp/parser/package-info.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CRLFLineReader.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/CRLFLineReader.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CRLFLineReader.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/CRLFLineReader.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamAdapter.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamAdapter.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamAdapter.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamAdapter.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamEvent.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamEvent.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamEvent.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamEvent.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamException.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamException.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamException.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamException.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamListener.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamListener.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/CopyStreamListener.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/CopyStreamListener.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageReader.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageReader.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageReader.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageReader.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageWriter.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageWriter.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageWriter.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/DotTerminatedMessageWriter.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/FromNetASCIIInputStream.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/FromNetASCIIInputStream.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/FromNetASCIIInputStream.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/FromNetASCIIInputStream.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/FromNetASCIIOutputStream.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/FromNetASCIIOutputStream.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/FromNetASCIIOutputStream.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/FromNetASCIIOutputStream.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/SocketInputStream.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/SocketInputStream.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/SocketInputStream.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/SocketInputStream.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/SocketOutputStream.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/SocketOutputStream.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/SocketOutputStream.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/SocketOutputStream.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/ToNetASCIIInputStream.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/ToNetASCIIInputStream.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/ToNetASCIIInputStream.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/ToNetASCIIInputStream.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/ToNetASCIIOutputStream.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/ToNetASCIIOutputStream.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/ToNetASCIIOutputStream.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/ToNetASCIIOutputStream.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/Util.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/Util.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/Util.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/Util.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/io/package-info.java b/FtpComponent/src/main/java/aria/apache/commons/net/io/package-info.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/io/package-info.java rename to FtpComponent/src/main/java/aria/apache/commons/net/io/package-info.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/Base64.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/Base64.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/Base64.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/Base64.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/Charsets.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/Charsets.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/Charsets.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/Charsets.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/KeyManagerUtils.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/KeyManagerUtils.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/KeyManagerUtils.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/KeyManagerUtils.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/ListenerList.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/ListenerList.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/ListenerList.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/ListenerList.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/SSLContextUtils.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/SSLContextUtils.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/SSLContextUtils.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/SSLContextUtils.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/SSLSocketUtils.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/SSLSocketUtils.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/SSLSocketUtils.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/SSLSocketUtils.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/SubnetUtils.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/SubnetUtils.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/SubnetUtils.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/SubnetUtils.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/TrustManagerUtils.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/TrustManagerUtils.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/TrustManagerUtils.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/TrustManagerUtils.java diff --git a/AriaFtpPlug/src/main/java/aria/apache/commons/net/util/package-info.java b/FtpComponent/src/main/java/aria/apache/commons/net/util/package-info.java similarity index 100% rename from AriaFtpPlug/src/main/java/aria/apache/commons/net/util/package-info.java rename to FtpComponent/src/main/java/aria/apache/commons/net/util/package-info.java diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/AbsFtpInfoTask.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/AbsFtpInfoTask.java new file mode 100644 index 00000000..35af8b43 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/AbsFtpInfoTask.java @@ -0,0 +1,381 @@ +/* + * 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.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.AriaConfig; +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.common.FtpConnectionMode; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaFTPException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CheckUtil; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.SSLContextUtil; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.UUID; +import javax.net.ssl.SSLContext; + +/** + * Created by Aria.Lao on 2017/7/25. 获取ftp文件夹信息 + */ +public abstract class AbsFtpInfoTask> + implements Runnable, IInfoTask { + + protected final String TAG = CommonUtil.getClassName(getClass()); + protected ENTITY mEntity; + protected TASK_WRAPPER mTaskWrapper; + protected FtpTaskOption mTaskOption; + private int mConnectTimeOut; + protected long mSize = 0; + protected String charSet = "UTF-8"; + private Callback callback; + private boolean isStop = false, isCancel = false; + + public AbsFtpInfoTask(TASK_WRAPPER taskWrapper) { + mTaskWrapper = taskWrapper; + mEntity = taskWrapper.getEntity(); + mTaskOption = (FtpTaskOption) taskWrapper.getTaskOption(); + mConnectTimeOut = AriaConfig.getInstance().getDConfig().getConnectTimeOut(); + } + + /** + * 获取请求的远程文件路径 + * + * @return 远程文件路径 + */ + protected abstract String getRemotePath(); + + /** + * 处理ftp列表信息 + * + * @param client ftp 客户端对象 + * @param files remotePath 对应的文件列表 + * @param convertedRemotePath 已转换的可被服务器识别的remotePath + */ + protected abstract void handelFileInfo(FTPClient client, FTPFile[] files, + String convertedRemotePath) throws IOException; + + @Override public void stop() { + isStop = true; + } + + @Override public void cancel() { + isCancel = true; + } + + @Override public void setCallback(Callback callback) { + this.callback = callback; + } + + @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】失败", mTaskOption.getUrlEntity().url)); + return; + } + String convertedRemotePath = CommonUtil.convertFtpChar(charSet, getRemotePath()); + FTPFile[] files = client.listFiles(convertedRemotePath); + handelFileInfo(client, files, convertedRemotePath); + } catch (IOException e) { + e.printStackTrace(); + handleFail(client, "FTP错误信息", e, true); + } catch (InterruptedException e) { + e.printStackTrace(); + handleFail(client, "FTP错误信息", e, true); + } finally { + closeClient(client); + } + } + + /** + * 处理拦截 + * + * @param ftpFiles remotePath路径下的所有文件 + * @return {@code false} 拦截器处理完成任务,任务将不再执行,{@code true} 拦截器处理任务完成任务,任务继续执行 + */ + protected boolean onInterceptor(FTPClient client, FTPFile[] ftpFiles) { + return true; + } + + protected void onPreComplete(int code) { + + } + + /** + * 创建FTP客户端 + */ + private FTPClient createFtpClient() throws IOException, InterruptedException { + FTPClient client = null; + final FtpUrlEntity urlEntity = mTaskOption.getUrlEntity(); + if (CheckUtil.checkIp(urlEntity.hostName)) { + client = newInstanceClient(urlEntity); + client.setConnectTimeout(mConnectTimeOut); // 连接10s超时 + InetAddress ip = InetAddress.getByName(urlEntity.hostName); + + client = connect(client, new InetAddress[] { ip }, 0, Integer.parseInt(urlEntity.port)); + mTaskOption.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) { + handleFail(client, String.format("链接失败, url: %s", mTaskOption.getUrlEntity().url), null, + true); + 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) { + e.printStackTrace(); + ALog.e(TAG, String.format("登录失败,错误码为:%s, msg:%s", client.getReplyCode(), + client.getReplyString())); + return null; + } + } + + if (!loginSuccess) { + handleFail(client, "登录失败", null, false); + client.disconnect(); + return null; + } + + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + handleFail(client, String.format("无法连接到ftp服务器,filePath: %s, url: %s", mEntity.getKey(), + mTaskOption.getUrlEntity().url), null, true); + client.disconnect(); + 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(mTaskOption.getCharSet())) { + charSet = mTaskOption.getCharSet(); + } + } + client.setControlEncoding(charSet); + client.setDataTimeout(10 * 1000); + if (mTaskOption.getConnMode() == FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY) { + client.enterLocalActiveMode(); + if (mTaskOption.getMinPort() != 0 && mTaskOption.getMaxPort() != 0) { + client.setActivePortRange(mTaskOption.getMinPort(), mTaskOption.getMaxPort()); + } + if (!TextUtils.isEmpty(mTaskOption.getActiveExternalIPAddress())) { + client.setActiveExternalIPAddress(mTaskOption.getActiveExternalIPAddress()); + } + } else { + client.enterLocalPassiveMode(); + } + client.setFileType(FTP.BINARY_FILE_TYPE); + + 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.idEntity.keyAlias, urlEntity.idEntity.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 (mTaskOption.getClientConfig() != null) { + clientConfig = mTaskOption.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); + mTaskOption.getUrlEntity().validAddr = ips[index]; + + FtpUrlEntity urlEntity = mTaskOption.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(mTaskOption.getUrlEntity()), ips, index + 1, port); + } + } + + /** + * 遍历FTP服务器上对应文件或文件夹大小 + * + * @throws IOException 字符串编码转换错误 + */ + protected 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(); + handleFile(client, 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(FTPClient client, String remotePath, FTPFile ftpFile) { + } + + protected void handleFail(FTPClient client, String msg, Exception e, boolean needRetry) { + if (isStop || isCancel) { + return; + } + if (callback != null) { + if (client == null) { + msg = "创建ftp客户端失败"; + needRetry = false; + } else { + msg = String.format("%s, code: %s, msg: %s", msg, client.getReplyCode(), + client.getReplyString()); + needRetry = needRetry && !CheckUtil.ftpIsBadRequest(client.getReplyCode()); + } + + callback.onFail(mEntity, new AriaFTPException(msg), needRetry); + } + } + + protected void onSucceed(CompleteInfo info){ + callback.onSucceed(mEntity.getKey(), info); + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } + + /** + * 获取可用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; + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/BaseFtpThreadTaskAdapter.java similarity index 74% rename from Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java rename to FtpComponent/src/main/java/com/arialyy/aria/ftp/BaseFtpThreadTaskAdapter.java index 0020ecde..34b6fe4c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/BaseFtpThreadTaskAdapter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common.ftp; +package com.arialyy.aria.ftp; import android.text.TextUtils; import aria.apache.commons.net.ftp.FTP; @@ -21,11 +21,11 @@ import aria.apache.commons.net.ftp.FTPClient; import aria.apache.commons.net.ftp.FTPClientConfig; import aria.apache.commons.net.ftp.FTPReply; import aria.apache.commons.net.ftp.FTPSClient; -import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.common.FtpConnectionMode; import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.inf.AbsNormalEntity; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.exception.AriaIOException; +import com.arialyy.aria.core.task.AbsThreadTaskAdapter; +import com.arialyy.aria.exception.AriaFTPException; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.SSLContextUtil; import java.io.IOException; @@ -34,15 +34,17 @@ import java.net.UnknownHostException; import javax.net.ssl.SSLContext; /** - * Created by lyy on 2017/9/26. FTP单任务父类 + * @author lyy + * Date: 2019-09-18 */ -public abstract class AbsFtpThreadTask> - extends AbsThreadTask { - private final String TAG = "AbsFtpThreadTask"; +public abstract class BaseFtpThreadTaskAdapter extends AbsThreadTaskAdapter { + + protected FtpTaskOption mTaskOption; protected String charSet; - protected AbsFtpThreadTask(SubThreadConfig config) { + protected BaseFtpThreadTaskAdapter(SubThreadConfig config) { super(config); + mTaskOption = (FtpTaskOption) getTaskWrapper().getTaskOption(); } protected void closeClient(FTPClient client) { @@ -61,7 +63,7 @@ public abstract class AbsFtpThreadTask uploadInterceptor; + + /** + * 上传到服务器文件的新文件名{@link FtpInterceptHandler#getNewFileName()} + */ + private String newFileName; + + /** + * client配置信息 + */ + private FTPClientConfig clientConfig; + + /** + * 连接模式,默认为被动模式 + * {@link FtpConnectionMode} + */ + private int connMode; + + /** + * 主动模式下的端口范围 + */ + private int minPort, maxPort; + + /** + * 主动模式下,对外ip(可被Ftp服务器访问的ip) + */ + 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 = FTPClientConfig.SYST_UNIX; + //---------------- ftp client 配置信息 end + + public String getActiveExternalIPAddress() { + return activeExternalIPAddress; + } + + public void setActiveExternalIPAddress(String activeExternalIPAddress) { + this.activeExternalIPAddress = activeExternalIPAddress; + } + + public int getMinPort() { + return minPort; + } + + public void setMinPort(int minPort) { + this.minPort = minPort; + } + + public int getMaxPort() { + return maxPort; + } + + public void setMaxPort(int maxPort) { + this.maxPort = maxPort; + } + + public int getConnMode() { + return connMode; + } + + public void setConnMode(int connMode) { + this.connMode = connMode; + } + + public FTPClientConfig getClientConfig() { + if (clientConfig == null) { + clientConfig = new FTPClientConfig(systemKey); + if (defaultDateFormatStr != null) { + clientConfig.setDefaultDateFormatStr(defaultDateFormatStr); + } + if (recentDateFormatStr != null) { + clientConfig.setRecentDateFormatStr(recentDateFormatStr); + } + if (serverLanguageCode != null) { + clientConfig.setServerLanguageCode(serverLanguageCode); + } + if (shortMonthNames != null) { + clientConfig.setShortMonthNames(shortMonthNames); + } + if (serverTimeZoneId != null) { + clientConfig.setServerTimeZoneId(serverTimeZoneId); + } + } + 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 == null ? null : 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 TextUtils.isEmpty(charSet) ? "UTF-8" : charSet; + } + + public void setCharSet(String charSet) { + this.charSet = charSet; + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDFileInfoTask.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDFileInfoTask.java new file mode 100644 index 00000000..5fa15bd4 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDFileInfoTask.java @@ -0,0 +1,117 @@ +/* + * 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.ftp.download; + +import aria.apache.commons.net.ftp.FTPClient; +import aria.apache.commons.net.ftp.FTPFile; +import aria.apache.commons.net.ftp.FTPReply; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.exception.AriaFTPException; +import com.arialyy.aria.ftp.AbsFtpInfoTask; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.io.IOException; + +/** + * Created by Aria.Lao on 2017/7/25. + * 获取ftp文件信息 + */ +final class FtpDFileInfoTask extends AbsFtpInfoTask { + + FtpDFileInfoTask(DTaskWrapper taskEntity) { + super(taskEntity); + } + + @Override protected void handleFile(FTPClient client, String remotePath, FTPFile ftpFile) { + super.handleFile(client, remotePath, ftpFile); + if (!FileUtil.checkMemorySpace(mEntity.getFilePath(), ftpFile.getSize())) { + handleFail(client, "内存空间不足", new AriaFTPException( + String.format("获取ftp文件信息失败,内存空间不足, filePath: %s", mEntity.getFilePath())), + false); + } + } + + @Override + protected void handelFileInfo(FTPClient client, FTPFile[] files, String convertedRemotePath) + throws IOException { + boolean isExist = files.length != 0; + if (!isExist) { + int i = convertedRemotePath.lastIndexOf(File.separator); + FTPFile[] files1; + if (i == -1) { + files1 = client.listFiles(); + } else { + files1 = client.listFiles(convertedRemotePath.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); + + handleFail(client, + String.format("文件不存在,url: %s, remotePath:%s", mTaskOption.getUrlEntity().url, + getRemotePath()), null, 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)) { + closeClient(client); + handleFail(client, "获取文件信息错误,url: " + mTaskOption.getUrlEntity().url, null, true); + return; + } + mTaskWrapper.setCode(reply); + if (mSize != 0) { + mEntity.setFileSize(mSize); + } + onPreComplete(reply); + mEntity.update(); + } + + @Override protected String getRemotePath() { + return mTaskOption.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); + onSucceed(new CompleteInfo(code, mTaskWrapper)); + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGInfoTask.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGInfoTask.java new file mode 100644 index 00000000..9cd669a6 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGInfoTask.java @@ -0,0 +1,241 @@ +/* + * 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.ftp.download; + +import android.net.Uri; +import android.text.TextUtils; +import aria.apache.commons.net.ftp.FTPClient; +import aria.apache.commons.net.ftp.FTPFile; +import aria.apache.commons.net.ftp.FTPReply; +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.common.CompleteInfo; +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.wrapper.AbsTaskWrapper; +import com.arialyy.aria.ftp.AbsFtpInfoTask; +import com.arialyy.aria.ftp.FtpTaskOption; +import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.DeleteDGRecord; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; + +/** + * Created by Aria.Lao on 2017/7/25. 获取ftp文件夹信息 + */ +final class FtpDGInfoTask extends AbsFtpInfoTask { + + FtpDGInfoTask(DGTaskWrapper wrapper) { + super(wrapper); + } + + @Override public void run() { + if (mTaskWrapper.getEntity().getFileSize() > 1 && checkSubOption()) { + onSucceed(new CompleteInfo(200, mTaskWrapper)); + } else { + super.run(); + } + } + + @Override + protected void handelFileInfo(FTPClient client, FTPFile[] files, String convertedRemotePath) + throws IOException { + boolean isExist = files.length != 0; + if (!isExist) { + int i = convertedRemotePath.lastIndexOf(File.separator); + FTPFile[] files1; + if (i == -1) { + files1 = client.listFiles(); + } else { + files1 = client.listFiles(convertedRemotePath.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); + + handleFail(client, + String.format("文件不存在,url: %s, remotePath:%s", mTaskOption.getUrlEntity().url, + getRemotePath()), null, 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)) { + closeClient(client); + handleFail(client, "获取文件信息错误,url: " + mTaskOption.getUrlEntity().url, null, true); + return; + } + mTaskWrapper.setCode(reply); + if (mSize != 0) { + mEntity.setFileSize(mSize); + } + onPreComplete(reply); + mEntity.update(); + } + + /** + * 检查子任务的任务设置 + * + * @return true 子任务任务设置成功,false 子任务任务设置失败 + */ + private boolean checkSubOption() { + for (DTaskWrapper wrapper : mTaskWrapper.getSubTaskWrapper()) { + if (wrapper.getTaskOption() == null) { + return false; + } + } + return true; + } + + @Override protected String getRemotePath() { + return mTaskOption.getUrlEntity().remotePath; + } + + @Override protected void handleFile(FTPClient client, String remotePath, FTPFile ftpFile) { + super.handleFile(client, remotePath, ftpFile); + addEntity(remotePath, ftpFile); + } + + @Override protected void onPreComplete(int code) { + super.onPreComplete(code); + mEntity.setFileSize(mSize); + for (DTaskWrapper wrapper : mTaskWrapper.getSubTaskWrapper()) { + cloneInfo(wrapper); + } + onSucceed(new CompleteInfo(code, mTaskWrapper)); + } + + private void cloneInfo(DTaskWrapper subWrapper) { + FtpTaskOption option = (FtpTaskOption) mTaskWrapper.getTaskOption(); + FtpUrlEntity urlEntity = option.getUrlEntity().clone(); + Uri uri = Uri.parse(subWrapper.getEntity().getUrl()); + String remotePath = uri.getPath(); + urlEntity.remotePath = TextUtils.isEmpty(remotePath) ? "/" : remotePath; + + FtpTaskOption subOption = new FtpTaskOption(); + subOption.setUrlEntity(urlEntity); + subOption.setCharSet(option.getCharSet()); + subOption.setProxy(option.getProxy()); + subOption.setClientConfig(option.getClientConfig()); + subOption.setNewFileName(option.getNewFileName()); + subOption.setProxy(option.getProxy()); + subOption.setUploadInterceptor(option.getUploadInterceptor()); + + subWrapper.setTaskOption(subOption); + } + + /** + * FTP文件夹的子任务实体 在这生成 + */ + private void addEntity(String remotePath, FTPFile ftpFile) { + final FtpUrlEntity urlEntity = mTaskOption.getUrlEntity().clone(); + String url = + urlEntity.scheme + "://" + urlEntity.hostName + ":" + urlEntity.port + "/" + remotePath; + if (checkEntityExist(url)) { + ALog.w(TAG, "子任务已存在,取消子任务的添加,url = " + url); + return; + } + DownloadEntity entity = new DownloadEntity(); + entity.setUrl(url); + 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(mTaskOption.getCharSet()))); + entity.setGroupHash(mEntity.getGroupHash()); + entity.setGroupChild(true); + entity.setConvertFileSize(CommonUtil.formatFileSize(ftpFile.getSize())); + entity.setFileSize(ftpFile.getSize()); + + if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", entity.getFilePath())) { + DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", entity.getFilePath()); + } + 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()); + } + mEntity.getSubEntities().add(entity); + mTaskWrapper.getSubTaskWrapper().add(subWrapper); + } + + private void cloneInfo(DTaskWrapper subWrapper, FtpUrlEntity urlEntity) { + FtpTaskOption subOption = new FtpTaskOption(); + subOption.setUrlEntity(urlEntity); + subOption.setCharSet(mTaskOption.getCharSet()); + subOption.setProxy(mTaskOption.getProxy()); + subOption.setClientConfig(mTaskOption.getClientConfig()); + subOption.setNewFileName(mTaskOption.getNewFileName()); + subOption.setProxy(mTaskOption.getProxy()); + subOption.setUploadInterceptor(mTaskOption.getUploadInterceptor()); + + subWrapper.setTaskOption(subOption); + } + + /** + * 检查子任务是否已经存在,如果子任务存在,取消添加操作 + * + * @param key url + * @return true 子任务已存在,false 子任务不存在 + */ + private boolean checkEntityExist(String key) { + for (DTaskWrapper wrapper : mTaskWrapper.getSubTaskWrapper()) { + if (wrapper.getKey().equals(key)) { + return true; + } + } + return false; + } + + @Override + protected void handleFail(FTPClient client, String msg, Exception e, boolean needRetry) { + super.handleFail(client, msg, e, needRetry); + DeleteDGRecord.getInstance().deleteRecord(mTaskWrapper.getEntity(), true, true); + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGLoader.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGLoader.java new file mode 100644 index 00000000..fcf259cd --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGLoader.java @@ -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.ftp.download; + +import android.os.Looper; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.group.AbsGroupLoader; +import com.arialyy.aria.core.group.AbsSubDLoadUtil; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.ALog; + +/** + * ftp 组合任务加载器 + */ +final class FtpDGLoader extends AbsGroupLoader { + FtpDGLoader(AbsTaskWrapper groupWrapper, IEventListener listener) { + super(groupWrapper, listener); + } + + @Override protected void handlerTask(Looper looper) { + if (isBreak()) { + return; + } + mInfoTask.run(); + } + + @Override + protected AbsSubDLoadUtil createSubLoader(DTaskWrapper wrapper, boolean needGetFileInfo) { + FtpSubDLoaderUtil subUtil = new FtpSubDLoaderUtil(getScheduler(), needGetFileInfo, getKey()); + subUtil.setParams(wrapper, null); + return subUtil; + } + + /** + * 启动子任务下载 + */ + private void startSub() { + if (isBreak()) { + return; + } + onPostStart(); + + // ftp需要获取完成只任务信息才更新只任务数量 + getState().setSubSize(getWrapper().getSubTaskWrapper().size()); + + for (DTaskWrapper wrapper : getWrapper().getSubTaskWrapper()) { + if (wrapper.getState() != IEntity.STATE_COMPLETE) { + startSubLoader(createSubLoader(wrapper, false)); + } + } + } + + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = infoTask; + infoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + if (info.code >= 200 && info.code < 300) { + startSub(); + } else { + ALog.e(TAG, "获取任务信息失败,code:" + info.code); + getListener().onFail(false, null); + } + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + //getListener().onFail(needRetry, e); + fail(e, needRetry); + } + }); + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGLoaderUtil.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGLoaderUtil.java new file mode 100644 index 00000000..b11ec04e --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDGLoaderUtil.java @@ -0,0 +1,45 @@ +/* + * 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.ftp.download; + +import com.arialyy.aria.core.download.DGTaskWrapper; +import com.arialyy.aria.core.group.AbsGroupLoader; +import com.arialyy.aria.core.group.AbsGroupLoaderUtil; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.ftp.FtpTaskOption; + +/** + * Created by Aria.Lao on 2017/7/27. + * ftp文件夹下载工具 + */ +public final class FtpDGLoaderUtil extends AbsGroupLoaderUtil { + + @Override protected AbsGroupLoader getLoader() { + if (mLoader == null) { + ((AbsTaskWrapper) getTaskWrapper()).generateTaskOption(FtpTaskOption.class); + mLoader = new FtpDGLoader((AbsTaskWrapper) getTaskWrapper(), getListener()); + } + return mLoader; + } + + @Override protected LoaderStructure buildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new FtpDGInfoTask((DGTaskWrapper) getTaskWrapper())); + structure.accept(getLoader()); + return structure; + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoaderUtil.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoaderUtil.java new file mode 100644 index 00000000..e63ebd41 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDLoaderUtil.java @@ -0,0 +1,50 @@ +/* + * 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.ftp.download; + +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalLoader; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.NormalThreadStateManager; +import com.arialyy.aria.ftp.FtpTaskOption; + +/** + * @author lyy + * Date: 2019-09-19 + */ +public final class FtpDLoaderUtil extends AbsNormalLoaderUtil { + + @Override public AbsNormalLoader getLoader() { + if (mLoader == null){ + getTaskWrapper().generateTaskOption(FtpTaskOption.class); + mLoader = new NormalLoader(getTaskWrapper(), getListener()); + } + return mLoader ; + } + + public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new FtpDRecordHandler((DTaskWrapper) getTaskWrapper())) + .addComponent(new NormalThreadStateManager(getListener())) + .addComponent(new FtpDFileInfoTask((DTaskWrapper) getTaskWrapper())) + .addComponent(new NormalTTBuilder(getTaskWrapper(), new FtpDTTBuilderAdapter())); + structure.accept(getLoader()); + return structure; + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java new file mode 100644 index 00000000..43ae5d23 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDRecordHandler.java @@ -0,0 +1,92 @@ +/* + * 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.ftp.download; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.common.RecordHelper; +import com.arialyy.aria.core.config.Configuration; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.RecordUtil; +import java.util.ArrayList; + +/** + * @author lyy + * Date: 2019-09-19 + */ +final class FtpDRecordHandler extends RecordHandler { + + FtpDRecordHandler(DTaskWrapper wrapper) { + super(wrapper); + } + + @Override public void handlerTaskRecord(TaskRecord record) { + RecordHelper helper = new RecordHelper(getWrapper(), record); + if (record.threadNum == 1) { + helper.handleSingleThreadRecord(); + } else { + if (getWrapper().isSupportBP()) { + if (record.isBlock) { + helper.handleBlockRecord(); + } else { + helper.handleMultiRecord(); + } + } + } + } + + @Override + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + ThreadRecord tr; + tr = new ThreadRecord(); + tr.taskKey = record.filePath; + tr.threadId = threadId; + tr.startLocation = startL; + tr.isComplete = false; + tr.threadType = record.taskType; + //最后一个线程的结束位置即为文件的总长度 + if (threadId == (record.threadNum - 1)) { + endL = getFileSize(); + } + tr.endLocation = endL; + tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + record.isBlock = Configuration.getInstance().downloadCfg.isUseBlock(); + record.taskType = ITaskWrapper.D_FTP; + record.isGroupRecord = false; + + return record; + } + + @Override public int initTaskThreadNum() { + int threadNum = Configuration.getInstance().downloadCfg.getThreadNum(); + return getFileSize() <= IRecordHandler.SUB_LEN + || threadNum == 1 + ? 1 + : threadNum; + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDTTBuilderAdapter.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDTTBuilderAdapter.java new file mode 100644 index 00000000..e34a3e0a --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDTTBuilderAdapter.java @@ -0,0 +1,51 @@ +/* + * 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.ftp.download; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.task.IThreadTaskAdapter; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.FileUtil; +import java.io.File; + +final class FtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter { + + @Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) { + return new FtpDThreadTaskAdapter(config); + } + + @Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) { + if (!record.isBlock) { + if (getTempFile().exists()) { + FileUtil.deleteFile(getTempFile()); + } + //CommonUtil.createFile(mTempFile.getPath()); + } else { + for (int i = 0; i < totalThreadNum; i++) { + File blockFile = + new File(String.format(IRecordHandler.SUB_PATH, getTempFile().getPath(), i)); + if (blockFile.exists()) { + ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i)); + FileUtil.deleteFile(blockFile); + } + } + } + return true; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDThreadTaskAdapter.java similarity index 56% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java rename to FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDThreadTaskAdapter.java index a67e9e10..104c41c5 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpDThreadTaskAdapter.java @@ -13,17 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; +package com.arialyy.aria.ftp.download; import aria.apache.commons.net.ftp.FTPClient; import aria.apache.commons.net.ftp.FTPReply; import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask; -import com.arialyy.aria.core.config.DownloadConfig; -import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.exception.AriaIOException; -import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.exception.AriaFTPException; +import com.arialyy.aria.ftp.BaseFtpThreadTaskAdapter; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.CommonUtil; @@ -36,70 +32,66 @@ import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; /** - * Created by Aria.Lao on 2017/7/24. Ftp下载任务 + * @author lyy + * Date: 2019-09-18 */ -class FtpThreadTask extends AbsFtpThreadTask { - private final String TAG = "FtpThreadTask"; +final class FtpDThreadTaskAdapter extends BaseFtpThreadTaskAdapter { - FtpThreadTask(SubThreadConfig config) { + FtpDThreadTaskAdapter(SubThreadConfig config) { super(config); } - @Override public FtpThreadTask call() throws Exception { - super.call(); - if (mRecord.isComplete) { + @Override protected void handlerThreadTask() { + if (getThreadRecord().isComplete) { handleComplete(); - return this; + return; } FTPClient client = null; InputStream is = null; try { ALog.d(TAG, - String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(), - mRecord.threadId, mRecord.startLocation, mRecord.endLocation)); + String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getTaskWrapper().getKey(), + getThreadRecord().threadId, getThreadRecord().startLocation, + getThreadRecord().endLocation)); client = createClient(); if (client == null) { - fail(mChildCurrentLocation, new TaskException(TAG, "ftp client 创建失败")); - return this; + fail(new AriaFTPException("ftp client 创建失败"), false); + return; } - if (mRecord.startLocation > 0) { - client.setRestartOffset(mRecord.startLocation); + if (getThreadRecord().startLocation > 0) { + client.setRestartOffset(getThreadRecord().startLocation); } //发送第二次指令时,还需要再做一次判断 int reply = client.getReplyCode(); if (!FTPReply.isPositivePreliminary(reply) && reply != FTPReply.COMMAND_OK) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, - String.format("获取文件信息错误,错误码为:%s,msg:%s", reply, client.getReplyString()))); + fail(new AriaFTPException( + String.format("获取文件信息错误,错误码为:%s,msg:%s", reply, client.getReplyString())), false); client.disconnect(); - return this; + return; } String remotePath = - CommonUtil.convertFtpChar(charSet, getTaskWrapper().asFtp().getUrlEntity().remotePath); + CommonUtil.convertFtpChar(charSet, mTaskOption.getUrlEntity().remotePath); ALog.i(TAG, String.format("remotePath【%s】", remotePath)); is = client.retrieveFileStream(remotePath); reply = client.getReplyCode(); if (!FTPReply.isPositivePreliminary(reply)) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, - String.format("获取流失败,错误码为:%s,msg:%s", reply, client.getReplyString()))); + fail(new AriaFTPException( + String.format("获取流失败,错误码为:%s,msg:%s", reply, client.getReplyString())), true); client.disconnect(); - return this; + return; } - if (getConfig().isOpenDynamicFile) { + if (getThreadConfig().isBlock) { readDynamicFile(is); } else { readNormal(is); handleComplete(); } } catch (IOException e) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); + fail(new AriaFTPException(String.format("下载失败【%s】", getThreadConfig().url), e), true); } catch (Exception e) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); + fail(new AriaFTPException(String.format("下载失败【%s】", getThreadConfig().url), e), false); } finally { try { if (is != null) { @@ -109,24 +101,20 @@ class FtpThreadTask extends AbsFtpThreadTask { e.printStackTrace(); } closeClient(client); - onThreadComplete(); } - return this; } /** * 处理线程完成的情况 */ private void handleComplete() { - if (isBreak()) { + if (getThreadTask().isBreak()) { return; } - if (!checkBlock()) { + if (!getThreadTask().checkBlock()) { return; } - ALog.i(TAG, String.format("任务【%s】线程__%s__下载完毕", getFileName(), mRecord.threadId)); - writeConfig(true, mRecord.endLocation); - sendCompleteMsg(); + complete(); } /** @@ -138,19 +126,19 @@ class FtpThreadTask extends AbsFtpThreadTask { ReadableByteChannel fic = null; try { int len; - fos = new FileOutputStream(getConfig().tempFile, true); + fos = new FileOutputStream(getThreadConfig().tempFile, true); foc = fos.getChannel(); fic = Channels.newChannel(is); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); - while (isLive() && (len = fic.read(bf)) != -1) { - if (isBreak()) { + while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) { + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= mRecord.endLocation) { - len = (int) (mRecord.endLocation - mChildCurrentLocation); + if (getRangeProgress() + len >= getThreadRecord().endLocation) { + len = (int) (getThreadRecord().endLocation - getRangeProgress()); bf.flip(); fos.write(bf.array(), 0, len); bf.compact(); @@ -165,8 +153,7 @@ class FtpThreadTask extends AbsFtpThreadTask { } handleComplete(); } catch (IOException e) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); + fail(new AriaFTPException(String.format("下载失败【%s】", getThreadConfig().url), e), true); } finally { try { if (fos != null) { @@ -191,19 +178,22 @@ class FtpThreadTask extends AbsFtpThreadTask { BufferedRandomAccessFile file = null; try { file = - new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize()); - file.seek(mRecord.startLocation); + new BufferedRandomAccessFile(getThreadConfig().tempFile, "rwd", + getTaskConfig().getBuffSize()); + if (getThreadRecord().startLocation > 0) { + file.seek(getThreadRecord().startLocation); + } byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; - while (isLive() && (len = is.read(buffer)) != -1) { - if (isBreak()) { + while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) { + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= mRecord.endLocation) { - len = (int) (mRecord.endLocation - mChildCurrentLocation); + if (getRangeProgress() + len >= getThreadRecord().endLocation) { + len = (int) (getThreadRecord().endLocation - getRangeProgress()); file.write(buffer, 0, len); progress(len); break; @@ -213,8 +203,7 @@ class FtpThreadTask extends AbsFtpThreadTask { } } } catch (IOException e) { - fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); + fail(new AriaFTPException(String.format("下载失败【%s】", getThreadConfig().url), e), true); } finally { try { if (file != null) { @@ -225,12 +214,4 @@ class FtpThreadTask extends AbsFtpThreadTask { } } } - - @Override public int getMaxSpeed() { - return getTaskConfig().getMaxSpeed(); - } - - @Override protected DownloadConfig getTaskConfig() { - return getTaskWrapper().getConfig(); - } } diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpSubDLoaderUtil.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpSubDLoaderUtil.java new file mode 100644 index 00000000..63fb8a77 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/download/FtpSubDLoaderUtil.java @@ -0,0 +1,58 @@ +/* + * 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.ftp.download; + +import android.os.Handler; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.group.AbsSubDLoadUtil; +import com.arialyy.aria.core.group.SubRecordHandler; +import com.arialyy.aria.core.loader.GroupSubThreadStateManager; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.SubLoader; + +/** + * @author lyy + * Date: 2019-09-28 + */ +final class FtpSubDLoaderUtil extends AbsSubDLoadUtil { + /** + * @param schedulers 调度器 + * @param needGetInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息 + */ + FtpSubDLoaderUtil(Handler schedulers, boolean needGetInfo, String parentKey) { + super(schedulers, needGetInfo, parentKey); + } + + @Override protected SubLoader getLoader() { + if (mDLoader == null) { + mDLoader = new SubLoader(getWrapper(), getSchedulers()); + mDLoader.setNeedGetInfo(isNeedGetInfo()); + mDLoader.setParentKey(getParentKey()); + } + return mDLoader; + } + + @Override protected LoaderStructure buildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new SubRecordHandler(getWrapper())) + .addComponent(new GroupSubThreadStateManager(getSchedulers(),getKey())) + .addComponent(new NormalTTBuilder(getWrapper(), new FtpDTTBuilderAdapter())) + .addComponent(new FtpDFileInfoTask(getWrapper())); + structure.accept(getLoader()); + return structure; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFISAdapter.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpFISAdapter.java similarity index 82% rename from Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFISAdapter.java rename to FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpFISAdapter.java index c85a0308..0bab6cf7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFISAdapter.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpFISAdapter.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.uploader; +package com.arialyy.aria.ftp.upload; -import androidx.annotation.NonNull; import com.arialyy.aria.util.BufferedRandomAccessFile; import java.io.IOException; import java.io.InputStream; @@ -34,12 +33,12 @@ final class FtpFISAdapter extends InputStream { void onProgressCallback(byte[] buffer, int byteOffset, int byteCount) throws IOException; } - FtpFISAdapter(@NonNull BufferedRandomAccessFile is, @NonNull ProgressCallback callback) { + FtpFISAdapter(BufferedRandomAccessFile is, ProgressCallback callback) { mIs = is; mCallback = callback; } - FtpFISAdapter(@NonNull BufferedRandomAccessFile is) { + FtpFISAdapter(BufferedRandomAccessFile is) { mIs = is; } @@ -51,7 +50,7 @@ final class FtpFISAdapter extends InputStream { return mIs.read(); } - @Override public int read(@NonNull byte[] buffer) throws IOException { + @Override public int read(byte[] buffer) throws IOException { count = mIs.read(buffer); if (mCallback != null) { mCallback.onProgressCallback(buffer, 0, count); @@ -59,7 +58,7 @@ final class FtpFISAdapter extends InputStream { return count; } - @Override public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) + @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { count = mIs.read(buffer, byteOffset, byteCount); if (mCallback != null) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUFileInfoTask.java similarity index 54% rename from Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java rename to FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUFileInfoTask.java index 3be4fbb1..b4066157 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUFileInfoTask.java @@ -13,23 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.uploader; +package com.arialyy.aria.ftp.upload; import android.text.TextUtils; import aria.apache.commons.net.ftp.FTPClient; import aria.apache.commons.net.ftp.FTPFile; -import com.arialyy.aria.core.common.ftp.AbsFtpInfoThread; +import aria.apache.commons.net.ftp.FTPReply; import com.arialyy.aria.core.common.CompleteInfo; -import com.arialyy.aria.core.common.OnFileInfoCallback; -import com.arialyy.aria.core.common.TaskRecord; -import com.arialyy.aria.core.common.ThreadRecord; -import com.arialyy.aria.core.common.ftp.FtpInterceptHandler; -import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor; +import com.arialyy.aria.core.processor.FtpInterceptHandler; +import com.arialyy.aria.core.processor.IFtpUploadInterceptor; import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.ftp.AbsFtpInfoTask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; -import com.arialyy.aria.util.DbDataHelper; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -38,34 +35,54 @@ import java.util.List; * Created by Aria.Lao on 2017/9/26. * 单任务上传远程服务器文件信息 */ -class FtpFileInfoThread extends AbsFtpInfoThread { - private static final String TAG = "FtpUploadFileInfoThread"; +final class FtpUFileInfoTask extends AbsFtpInfoTask { static final int CODE_COMPLETE = 0xab1; private boolean isComplete = false; private String remotePath; - /** - * true 使用拦截器,false 不使用拦截器 - */ - private boolean useInterceptor = false; + private FTPFile ftpFile; - FtpFileInfoThread(UTaskWrapper taskEntity, OnFileInfoCallback callback) { - super(taskEntity, callback); + FtpUFileInfoTask(UTaskWrapper taskEntity) { + super(taskEntity); } @Override protected String getRemotePath() { return remotePath == null ? - mTaskWrapper.asFtp().getUrlEntity().remotePath + "/" + mEntity.getFileName() : remotePath; + mTaskOption.getUrlEntity().remotePath + "/" + mEntity.getFileName() : remotePath; + } + + @Override + protected void handelFileInfo(FTPClient client, FTPFile[] files, String convertedRemotePath) + throws IOException { + // 处理拦截功能 + if (!onInterceptor(client, files)) { + closeClient(client); + ALog.d(TAG, "拦截器处理完成任务"); + return; + } + + handleFile(client, getRemotePath(), files.length == 0 ? null : files[0]); + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + //服务器上没有该文件路径,表示该任务为新的上传任务 + mTaskWrapper.setNewTask(true); + } + mTaskWrapper.setCode(reply); + onPreComplete(reply); + mEntity.update(); } @Override protected boolean onInterceptor(FTPClient client, FTPFile[] ftpFiles) { // 旧任务将不做处理,否则断点续传上传将失效 - if (!mTaskWrapper.isNewTask()) { - return true; - } + //if (!mTaskWrapper.isNewTask()) { + // ALog.d(TAG, "任务是旧任务,忽略该拦截器"); + // return true; + //} try { - IFtpUploadInterceptor interceptor = mTaskWrapper.asFtp().getUploadInterceptor(); + IFtpUploadInterceptor interceptor = mTaskOption.getUploadInterceptor(); if (interceptor != null) { - useInterceptor = true; + /* + * true 使用拦截器,false 不使用拦截器 + */ List files = new ArrayList<>(); for (FTPFile ftpFile : ftpFiles) { if (ftpFile.isDirectory()) { @@ -77,7 +94,7 @@ class FtpFileInfoThread extends AbsFtpInfoThread { FtpInterceptHandler interceptHandler = interceptor.onIntercept(mEntity, files); /* - 处理远端有同名文件的情况 + * 处理远端有同名文件的情况 */ if (files.contains(mEntity.getFileName())) { if (interceptHandler.isCoverServerFile()) { @@ -89,10 +106,11 @@ class FtpFileInfoThread extends AbsFtpInfoThread { } else if (!TextUtils.isEmpty(interceptHandler.getNewFileName())) { ALog.i(TAG, String.format("远端已拥有同名文件,将修改remotePath,原文件名:%s,新文件名:%s", mEntity.getFileName(), interceptHandler.getNewFileName())); - remotePath = mTaskWrapper.asFtp().getUrlEntity().remotePath + remotePath = mTaskOption.getUrlEntity().remotePath + "/" + interceptHandler.getNewFileName(); - mTaskWrapper.asFtp().setNewFileName(interceptHandler.getNewFileName()); + mTaskOption.setNewFileName(interceptHandler.getNewFileName()); + closeClient(client); run(); return false; @@ -115,50 +133,18 @@ class FtpFileInfoThread extends AbsFtpInfoThread { * @param remotePath ftp服务器文件夹路径 * @param ftpFile ftp服务器上对应的文件 */ - @Override protected void handleFile(String remotePath, FTPFile ftpFile) { - super.handleFile(remotePath, ftpFile); - if (ftpFile != null && !useInterceptor) { - //远程文件已完成 - if (ftpFile.getSize() == mEntity.getFileSize()) { - isComplete = true; - ALog.d(TAG, "FTP服务器上已存在该文件【" + ftpFile.getName() + "】"); - } else { - ALog.w(TAG, "FTP服务器已存在未完成的文件【" - + ftpFile.getName() - + ",size: " - + ftpFile.getSize() - + "】" - + "尝试从位置:" - + (ftpFile.getSize() - 1) - + "开始上传"); - mTaskWrapper.setNewTask(false); - - // 修改记录 - TaskRecord record = DbDataHelper.getTaskRecord(mTaskWrapper.getKey()); - if (record == null) { - record = new TaskRecord(); - record.fileName = mEntity.getFileName(); - record.filePath = mTaskWrapper.getKey(); - record.threadRecords = new ArrayList<>(); - } - ThreadRecord threadRecord; - if (record.threadRecords == null || record.threadRecords.isEmpty()) { - threadRecord = new ThreadRecord(); - threadRecord.taskKey = record.filePath; - } else { - threadRecord = record.threadRecords.get(0); - } - //修改本地保存的停止地址为服务器上对应文件的大小 - threadRecord.startLocation = ftpFile.getSize() - 1; - record.save(); - threadRecord.save(); - } + @Override protected void handleFile(FTPClient client,String remotePath, FTPFile ftpFile) { + super.handleFile(client, remotePath, ftpFile); + this.ftpFile = ftpFile; + if (ftpFile != null && ftpFile.getSize() == mEntity.getFileSize()) { + isComplete = true; } } @Override protected void onPreComplete(int code) { super.onPreComplete(code); - mCallback.onComplete(mEntity.getKey(), - new CompleteInfo(isComplete ? CODE_COMPLETE : code, mTaskWrapper)); + CompleteInfo info = new CompleteInfo(isComplete ? CODE_COMPLETE : code, mTaskWrapper); + info.obj = ftpFile; + onSucceed(info); } } diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java new file mode 100644 index 00000000..5b17677c --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoader.java @@ -0,0 +1,94 @@ +/* + * 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.ftp.upload; + +import android.os.Handler; +import aria.apache.commons.net.ftp.FTPFile; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.loader.NormalLoader; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.exception.AriaException; + +final class FtpULoader extends NormalLoader { + private FTPFile ftpFile; + + FtpULoader(UTaskWrapper wrapper, IEventListener listener) { + super(wrapper, listener); + } + + @Override + protected void startThreadTask() { + if (isBreak()) { + return; + } + // 检查记录 + ((FtpURecordHandler) mRecordHandler).setFtpFile(ftpFile); + if (mRecordHandler.checkTaskCompleted()) { + mRecord.deleteData(); + isComplete = true; + getListener().onComplete(); + return; + } + mRecord = mRecordHandler.getRecord(getFileSize()); + + // 初始化线程状态管理器 + mStateManager.setLooper(mRecord, getLooper()); + getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord, + new Handler(getLooper(), mStateManager.getHandlerCallback()))); + mStateManager.updateCurrentProgress(getEntity().getCurrentProgress()); + + if (mStateManager.getCurrentProgress() > 0) { + getListener().onResume(mStateManager.getCurrentProgress()); + } else { + getListener().onStart(mStateManager.getCurrentProgress()); + } + + startTimer(); + + // 启动线程任务 + for (IThreadTask threadTask : getTaskList()) { + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask); + } + } + + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = infoTask; + infoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + if (info.code == FtpUFileInfoTask.CODE_COMPLETE) { + getListener().onComplete(); + } else { + ftpFile = (FTPFile) info.obj; + startThreadTask(); + } + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + getListener().onFail(needRetry, e); + } + }); + } + + @Override public void addComponent(IRecordHandler recordHandler) { + mRecordHandler = recordHandler; + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java new file mode 100644 index 00000000..3a54d64b --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpULoaderUtil.java @@ -0,0 +1,63 @@ +/* + * 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.ftp.upload; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.NormalThreadStateManager; +import com.arialyy.aria.core.loader.UploadThreadStateManager; +import com.arialyy.aria.core.task.IThreadTaskAdapter; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.ftp.FtpTaskOption; + +/** + * @author lyy + * Date: 2019-09-19 + */ +public final class FtpULoaderUtil extends AbsNormalLoaderUtil { + + @Override public AbsNormalLoader getLoader() { + if (mLoader == null) { + getTaskWrapper().generateTaskOption(FtpTaskOption.class); + mLoader = new FtpULoader((UTaskWrapper) getTaskWrapper(), getListener()); + } + return mLoader; + } + + @Override public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new FtpURecordHandler((UTaskWrapper) getTaskWrapper())) +// .addComponent(new NormalThreadStateManager(getListener())) + .addComponent(new UploadThreadStateManager(getListener())) + .addComponent(new FtpUFileInfoTask((UTaskWrapper) getTaskWrapper())) + .addComponent(new NormalTTBuilder(getTaskWrapper(), new AbsNormalTTBuilderAdapter() { + @Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) { + return new FtpUThreadTaskAdapter(config); + } + + @Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) { + return true; + } + })); + structure.accept(getLoader()); + return structure; + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java new file mode 100644 index 00000000..6d123c84 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpURecordHandler.java @@ -0,0 +1,113 @@ +/* + * 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.ftp.upload; + +import aria.apache.commons.net.ftp.FTPFile; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.RecordUtil; +import java.util.ArrayList; + +/** + * 上传任务记录处理器 + */ +final class FtpURecordHandler extends RecordHandler { + private FTPFile ftpFile; + + FtpURecordHandler(UTaskWrapper wrapper) { + super(wrapper); + } + + void setFtpFile(FTPFile ftpFile) { + this.ftpFile = ftpFile; + } + + @Override public void handlerTaskRecord(TaskRecord record) { + if (record.threadRecords == null || record.threadRecords.isEmpty()) { + record.threadRecords = new ArrayList<>(); + record.threadRecords.add( + createThreadRecord(record, 0, ftpFile == null ? 0 : ftpFile.getSize(), getFileSize())); + } + + if (ftpFile != null) { + //远程文件已完成 + if (ftpFile.getSize() == getFileSize()) { + record.threadRecords.get(0).isComplete = true; + ALog.d(TAG, "FTP服务器上已存在该文件【" + ftpFile.getName() + "】"); + } else if (ftpFile.getSize() == 0) { + getWrapper().setNewTask(true); + ALog.d(TAG, "FTP服务器上已存在该文件【" + ftpFile.getName() + "】,但文件长度为0,重新上传该文件"); + } else { + ALog.w(TAG, "FTP服务器已存在未完成的文件【" + + ftpFile.getName() + + ",size: " + + ftpFile.getSize() + + "】" + + "尝试从位置:" + + (ftpFile.getSize() - 1) + + "开始上传"); + getWrapper().setNewTask(false); + + // 修改记录 + ThreadRecord threadRecord = record.threadRecords.get(0); + //修改本地保存的停止地址为服务器上对应文件的大小 + threadRecord.startLocation = ftpFile.getSize() - 1; + } + } else { + ALog.d(TAG, "FTP服务器上不存在该文件"); + getWrapper().setNewTask(true); + ThreadRecord tr = record.threadRecords.get(0); + tr.startLocation = 0; + tr.endLocation = getFileSize(); + tr.isComplete = false; + } + } + + @Override + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + ThreadRecord tr; + tr = new ThreadRecord(); + tr.taskKey = record.filePath; + tr.threadId = threadId; + tr.startLocation = startL; + tr.isComplete = false; + tr.threadType = record.taskType; + tr.endLocation = getFileSize(); + tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + record.isBlock = false; + record.taskType = ITaskWrapper.U_FTP; + record.isGroupRecord = getEntity().isGroupChild(); + + return record; + } + + @Override public int initTaskThreadNum() { + return 1; + } +} diff --git a/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUThreadTaskAdapter.java b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUThreadTaskAdapter.java new file mode 100644 index 00000000..5d30d4a8 --- /dev/null +++ b/FtpComponent/src/main/java/com/arialyy/aria/ftp/upload/FtpUThreadTaskAdapter.java @@ -0,0 +1,220 @@ +/* + * 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.ftp.upload; + +import android.text.TextUtils; +import aria.apache.commons.net.ftp.FTPClient; +import aria.apache.commons.net.ftp.FTPReply; +import aria.apache.commons.net.ftp.OnFtpInputStreamListener; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.exception.AriaFTPException; +import com.arialyy.aria.ftp.BaseFtpThreadTaskAdapter; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import com.arialyy.aria.util.CommonUtil; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Created by Aria.Lao on 2017/7/28. D_FTP 单线程上传任务,需要FTP 服务器给用户打开append和write的权限 + */ +final class FtpUThreadTaskAdapter extends BaseFtpThreadTaskAdapter { + private String dir, remotePath; + private boolean storeSuccess = false; + private ScheduledThreadPoolExecutor timer; + private FTPClient client = null; + private boolean isTimeOut = true; + private FtpFISAdapter fa; + + FtpUThreadTaskAdapter(SubThreadConfig config) { + super(config); + } + + @Override protected void handlerThreadTask() { + BufferedRandomAccessFile file = null; + try { + ALog.d(TAG, + String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getEntity().getKey(), + getThreadRecord().threadId, getThreadRecord().startLocation, + getThreadRecord().endLocation)); + client = createClient(); + if (client == null) { + return; + } + initPath(); + boolean b = client.makeDirectory(dir); + if (!b) { + ALog.w(TAG, String.format("创建目录失败,错误码为:%s, msg:%s", client.getReplyCode(), + client.getReplyString())); + } + client.changeWorkingDirectory(dir); + client.setRestartOffset(getThreadRecord().startLocation); + int reply = client.getReplyCode(); + if (!FTPReply.isPositivePreliminary(reply) && reply != FTPReply.FILE_ACTION_OK) { + fail(new AriaFTPException( + String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, + client.getReplyString(), getEntity().getFilePath())), false); + client.disconnect(); + return; + } + + file = + new BufferedRandomAccessFile(getThreadConfig().tempFile, "rwd", + getTaskConfig().getBuffSize()); + if (getThreadRecord().startLocation > 0) { + file.seek(getThreadRecord().startLocation); + } + boolean complete = upload(file); + if (getThreadTask().isBreak()) { + return; + } + if (!complete) { + fail(new AriaFTPException("ftp文件上传失败"), false); + return; + } + ALog.i(TAG, + String.format("任务【%s】线程__%s__上传完毕", getEntity().getKey(), getThreadRecord().threadId)); + complete(); + } catch (IOException e) { + e.printStackTrace(); + fail(new AriaFTPException( + String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(), + getThreadConfig().url)), true); + } catch (Exception e) { + fail(new AriaFTPException(null, e), false); + } finally { + try { + if (file != null) { + file.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + closeClient(client); + closeTimer(); + } + } + + private UploadEntity getEntity() { + return (UploadEntity) getTaskWrapper().getEntity(); + } + + private void initPath() throws UnsupportedEncodingException { + dir = CommonUtil.convertFtpChar(charSet, mTaskOption.getUrlEntity().remotePath); + + String fileName = TextUtils.isEmpty(mTaskOption.getNewFileName()) ? getEntity().getFileName() + : mTaskOption.getNewFileName(); + + remotePath = + CommonUtil.convertFtpChar(charSet, + String.format("%s/%s", mTaskOption.getUrlEntity().remotePath, fileName)); + } + + /** + * 启动监听定时器,当网络断开时,如果该任务的FTP服务器的传输线程没有断开,当客户端重新连接时,客户端将无法发送数据到服务端 + * 每隔10s检查一次。 + */ + private void startTimer() { + timer = new ScheduledThreadPoolExecutor(1); + timer.scheduleWithFixedDelay(new Runnable() { + @Override public void run() { + try { + if (isTimeOut) { + fail(new AriaFTPException("socket连接失败,该问题一般出现于网络断开,客户端重新连接," + + "但是服务器端无法创建socket缺没有返回错误码的情况。"), false); + if (fa != null) { + fa.close(); + } + closeTimer(); + } + isTimeOut = true; + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 10, 10, TimeUnit.SECONDS); + } + + private void closeTimer() { + ALog.d(TAG, "closeTimer"); + if (timer != null && !timer.isShutdown()) { + timer.shutdown(); + } + } + + /** + * 上传 + * + * @return {@code true}上传成功、{@code false} 上传失败 + */ + private boolean upload(final BufferedRandomAccessFile bis) + throws IOException { + fa = new FtpFISAdapter(bis); + storeSuccess = false; + startTimer(); + try { + ALog.d(TAG, String.format("remotePath: %s", remotePath)); + storeSuccess = client.storeFile(remotePath, fa, new OnFtpInputStreamListener() { + boolean isStoped = false; + + @Override public void onFtpInputStream(FTPClient client, long totalBytesTransferred, + int bytesTransferred, long streamSize) { + try { + isTimeOut = false; + if (getThreadTask().isBreak() && !isStoped) { + isStoped = true; + client.abor(); + return; + } + if (mSpeedBandUtil != null) { + mSpeedBandUtil.limitNextBytes(bytesTransferred); + } + progress(bytesTransferred); + } catch (IOException e) { + e.printStackTrace(); + storeSuccess = false; + } + } + }); + } catch (IOException e) { + String msg = String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", client.getReply(), + client.getReplyString(), getEntity().getFilePath()); + e.printStackTrace(); + if (e.getMessage().contains("AriaIOException caught while copying")) { + e.printStackTrace(); + } else { + fail(new AriaFTPException(msg, e), !storeSuccess); + } + return false; + } finally { + fa.close(); + } + int reply = client.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + if (reply != FTPReply.TRANSFER_ABORTED) { + fail(new AriaFTPException( + String.format("文件上传错误,错误码为:%s, msg:%s, filePath: %s", reply, client.getReplyString(), + getEntity().getFilePath())), false); + } + closeClient(client); + return false; + } + return storeSuccess; + } +} diff --git a/FtpComponent/src/main/resources/META-INF/MANIFEST.MF b/FtpComponent/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/FtpComponent/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/FtpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil b/FtpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil new file mode 100644 index 00000000..dc09cdf1 --- /dev/null +++ b/FtpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil @@ -0,0 +1,3 @@ +com.arialyy.aria.ftp.download.FtpDLoaderUtil +com.arialyy.aria.ftp.download.FtpDGLoaderUtil +com.arialyy.aria.ftp.upload.FtpULoaderUtil \ No newline at end of file diff --git a/HttpComponent/.gitignore b/HttpComponent/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/HttpComponent/.gitignore @@ -0,0 +1 @@ +/build diff --git a/HttpComponent/bintray-release.gradle b/HttpComponent/bintray-release.gradle new file mode 100644 index 00000000..b222b779 --- /dev/null +++ b/HttpComponent/bintray-release.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.novoda.bintray-release' +publish { +// artifactId = 'aria-compiler' +// uploadName = 'AriaCompiler' + artifactId = 'httpComponent' + uploadName = 'HttpComponent' + userOrg = rootProject.ext.userOrg + groupId = rootProject.ext.groupId + publishVersion = rootProject.ext.publishVersion + desc = rootProject.ext.desc + website = rootProject.ext.website + licences = rootProject.ext.licences +} \ No newline at end of file diff --git a/HttpComponent/build.gradle b/HttpComponent/build.gradle new file mode 100644 index 00000000..d9e3222d --- /dev/null +++ b/HttpComponent/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + debug{ + debuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation project(path: ':PublicComponent') +} + +//apply from: 'bintray-release.gradle' +ext{ + PUBLISH_ARTIFACT_ID = 'http' +} +apply from: '../gradle/mavenCentral-release.gradle' \ No newline at end of file diff --git a/HttpComponent/consumer-rules.pro b/HttpComponent/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/HttpComponent/proguard-rules.pro b/HttpComponent/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/HttpComponent/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/HttpComponent/src/main/AndroidManifest.xml b/HttpComponent/src/main/AndroidManifest.xml new file mode 100644 index 00000000..bb0d2bdf --- /dev/null +++ b/HttpComponent/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/BaseHttpThreadTaskAdapter.java b/HttpComponent/src/main/java/com/arialyy/aria/http/BaseHttpThreadTaskAdapter.java new file mode 100644 index 00000000..d7e62ef4 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/BaseHttpThreadTaskAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http; + +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.task.AbsThreadTaskAdapter; + +/** + * @author lyy + * Date: 2019-09-22 + */ +public abstract class BaseHttpThreadTaskAdapter extends AbsThreadTaskAdapter { + protected HttpTaskOption mTaskOption; + + protected BaseHttpThreadTaskAdapter(SubThreadConfig config) { + super(config); + mTaskOption = (HttpTaskOption) getTaskWrapper().getTaskOption(); + } + + protected String getFileName() { + return getEntity().getFileName(); + } + + protected AbsNormalEntity getEntity() { + return (AbsNormalEntity) getTaskWrapper().getEntity(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ChunkedInputStream.java b/HttpComponent/src/main/java/com/arialyy/aria/http/ChunkedInputStream.java similarity index 98% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/ChunkedInputStream.java rename to HttpComponent/src/main/java/com/arialyy/aria/http/ChunkedInputStream.java index 04ce9390..360cf06f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ChunkedInputStream.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/ChunkedInputStream.java @@ -16,7 +16,7 @@ * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; +package com.arialyy.aria.http; import com.arialyy.aria.util.ALog; import java.io.DataInputStream; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java b/HttpComponent/src/main/java/com/arialyy/aria/http/ConnectionHelp.java similarity index 90% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java rename to HttpComponent/src/main/java/com/arialyy/aria/http/ConnectionHelp.java index 9124c5a6..f092c915 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/ConnectionHelp.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; +package com.arialyy.aria.http; import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.common.ProtocolType; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.ProtocolType; import com.arialyy.aria.core.common.RequestEnum; -import com.arialyy.aria.core.common.http.HttpTaskConfig; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.SSLContextUtil; @@ -44,15 +43,13 @@ import javax.net.ssl.SSLSocketFactory; /** * Created by lyy on 2017/1/18. 链接帮助类 */ -public class ConnectionHelp { +public final class ConnectionHelp { private static final String TAG = "ConnectionHelp"; /** * 处理url参数 - * - * @throws MalformedURLException */ - public static URL handleUrl(String url, HttpTaskConfig taskDelegate) + public static URL handleUrl(String url, HttpTaskOption taskDelegate) throws MalformedURLException { Map params = taskDelegate.getParams(); if (params != null && taskDelegate.getRequestEnum() == RequestEnum.GET) { @@ -99,7 +96,7 @@ public class ConnectionHelp { * * @throws IOException */ - public static HttpURLConnection handleConnection(URL url, HttpTaskConfig taskDelegate) + public static HttpURLConnection handleConnection(URL url, HttpTaskOption taskDelegate) throws IOException { HttpURLConnection conn; URLConnection urlConn; @@ -109,11 +106,11 @@ public class ConnectionHelp { urlConn = url.openConnection(); } if (urlConn instanceof HttpsURLConnection) { - AriaManager manager = AriaManager.getInstance(); + AriaConfig config = AriaConfig.getInstance(); conn = (HttpsURLConnection) urlConn; SSLContext sslContext = - SSLContextUtil.getSSLContextFromAssets(manager.getDownloadConfig().getCaName(), - manager.getDownloadConfig().getCaPath(), ProtocolType.Default); + SSLContextUtil.getSSLContextFromAssets(config.getDConfig().getCaName(), + config.getDConfig().getCaPath(), ProtocolType.Default); if (sslContext == null) { sslContext = SSLContextUtil.getDefaultSLLContext(ProtocolType.Default); } @@ -128,10 +125,8 @@ public class ConnectionHelp { /** * 设置头部参数 - * - * @throws ProtocolException */ - public static HttpURLConnection setConnectParam(HttpTaskConfig delegate, HttpURLConnection conn) { + public static HttpURLConnection setConnectParam(HttpTaskOption delegate, HttpURLConnection conn) { if (delegate.getRequestEnum() == RequestEnum.POST) { conn.setDoInput(true); conn.setDoOutput(true); diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/HttpRecordHandler.java b/HttpComponent/src/main/java/com/arialyy/aria/http/HttpRecordHandler.java new file mode 100644 index 00000000..777a8bf7 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/HttpRecordHandler.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.common.RecordHelper; +import com.arialyy.aria.core.config.Configuration; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.RecordUtil; +import java.util.ArrayList; + +/** + * @author lyy + * Date: 2019-09-23 + */ +public final class HttpRecordHandler extends RecordHandler { + public HttpRecordHandler(AbsTaskWrapper wrapper) { + super(wrapper); + } + + @Override public void handlerTaskRecord(TaskRecord record) { + RecordHelper helper = new RecordHelper(getWrapper(), record); + if (getWrapper().isSupportBP() && record.threadNum > 1) { + if (record.isBlock) { + helper.handleBlockRecord(); + } else { + helper.handleMultiRecord(); + } + } else if (!getWrapper().isSupportBP()) { + helper.handleNoSupportBPRecord(); + } else { + helper.handleSingleThreadRecord(); + } + } + + @Override + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + ThreadRecord tr; + tr = new ThreadRecord(); + tr.taskKey = record.filePath; + tr.threadId = threadId; + tr.startLocation = startL; + tr.isComplete = false; + + tr.threadType = record.taskType; + //最后一个线程的结束位置即为文件的总长度 + if (threadId == (record.threadNum - 1)) { + endL = getFileSize(); + } + tr.endLocation = endL; + tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + + int requestType = getWrapper().getRequestType(); + if (requestType == ITaskWrapper.D_HTTP || requestType == ITaskWrapper.DG_HTTP) { + record.isBlock = Configuration.getInstance().downloadCfg.isUseBlock(); + } else { + record.isBlock = false; + } + record.taskType = requestType; + record.isGroupRecord = getEntity().isGroupChild(); + if (record.isGroupRecord) { + if (getEntity() instanceof DownloadEntity) { + record.dGroupHash = ((DownloadEntity) getEntity()).getGroupHash(); + } + } + + return record; + } + + @Override public int initTaskThreadNum() { + int requestTpe = getWrapper().getRequestType(); + if (requestTpe == ITaskWrapper.U_HTTP + || (requestTpe == ITaskWrapper.D_HTTP && (!getWrapper().isSupportBP()) + || ((HttpTaskOption) getWrapper().getTaskOption()).isChunked())) { + return 1; + } + int threadNum = Configuration.getInstance().downloadCfg.getThreadNum(); + return getFileSize() <= IRecordHandler.SUB_LEN + || getEntity().isGroupChild() + || threadNum == 1 + ? 1 + : threadNum; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java b/HttpComponent/src/main/java/com/arialyy/aria/http/HttpTaskOption.java similarity index 84% rename from Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java rename to HttpComponent/src/main/java/com/arialyy/aria/http/HttpTaskOption.java index 096b29d0..6da0b544 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/HttpTaskOption.java @@ -14,11 +14,14 @@ * limitations under the License. */ -package com.arialyy.aria.core.common.http; +package com.arialyy.aria.http; +import android.text.TextUtils; import com.arialyy.aria.core.common.RequestEnum; -import com.arialyy.aria.core.inf.IHttpFileLenAdapter; -import com.arialyy.aria.core.inf.ITaskConfig; +import com.arialyy.aria.core.inf.ITaskOption; +import com.arialyy.aria.core.processor.IHttpFileLenAdapter; +import com.arialyy.aria.core.processor.IHttpFileNameAdapter; + import java.lang.ref.SoftReference; import java.net.CookieManager; import java.net.Proxy; @@ -28,7 +31,7 @@ import java.util.Map; /** * Http任务设置的信息,如:cookie、请求参数 */ -public class HttpTaskConfig implements ITaskConfig { +public final class HttpTaskOption implements ITaskOption { private CookieManager cookieManager; @@ -80,17 +83,23 @@ public class HttpTaskConfig implements ITaskConfig { private SoftReference fileLenAdapter; + private SoftReference fileNameAdapter; + public IHttpFileLenAdapter getFileLenAdapter() { return fileLenAdapter == null ? null : fileLenAdapter.get(); } - + public IHttpFileNameAdapter getFileNameAdapter() { + return fileNameAdapter == null ? null : fileNameAdapter.get(); + } /** * 如果是匿名内部类,完成后需要将adapter设置为空,否则会出现内存泄漏 */ public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) { this.fileLenAdapter = new SoftReference<>(fileLenAdapter); } - + public void setFileNameAdapter(IHttpFileNameAdapter fileNameAdapter) { + this.fileNameAdapter = new SoftReference<>(fileNameAdapter); + } public Map getFormFields() { return formFields; } @@ -100,7 +109,7 @@ public class HttpTaskConfig implements ITaskConfig { } public String getAttachment() { - return attachment; + return TextUtils.isEmpty(attachment) ? "file" : attachment; } public void setAttachment(String attachment) { @@ -140,7 +149,7 @@ public class HttpTaskConfig implements ITaskConfig { } public String getCharSet() { - return charSet; + return TextUtils.isEmpty(charSet) ? "utf-8" : charSet; } public void setCharSet(String charSet) { @@ -178,4 +187,5 @@ public class HttpTaskConfig implements ITaskConfig { public void setParams(Map params) { this.params = params; } + } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDFileInfoTask.java similarity index 68% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java rename to HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDFileInfoTask.java index 857a7268..91423587 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDFileInfoTask.java @@ -13,23 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; +package com.arialyy.aria.http.download; import android.net.TrafficStats; import android.net.Uri; import android.os.Process; import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; +import android.util.Log; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.common.CompleteInfo; -import com.arialyy.aria.core.common.OnFileInfoCallback; import com.arialyy.aria.core.common.RequestEnum; -import com.arialyy.aria.core.common.http.HttpTaskConfig; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.inf.IHttpFileLenAdapter; -import com.arialyy.aria.exception.AriaIOException; -import com.arialyy.aria.exception.BaseException; -import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.core.processor.IHttpFileLenAdapter; +import com.arialyy.aria.exception.AriaHTTPException; +import com.arialyy.aria.http.ConnectionHelp; +import com.arialyy.aria.http.HttpTaskOption; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; @@ -38,6 +39,7 @@ import com.arialyy.aria.util.RecordUtil; import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; @@ -55,21 +57,20 @@ import java.util.UUID; /** * 下载文件信息获取 */ -public class HttpFileInfoThread implements Runnable { - private static final String TAG = "HttpFileInfoThread"; +final class HttpDFileInfoTask implements IInfoTask, Runnable { + private static final String TAG = "HttpDFileInfoTask"; private DownloadEntity mEntity; private DTaskWrapper mTaskWrapper; private int mConnectTimeOut; - private OnFileInfoCallback onFileInfoCallback; - private HttpTaskConfig mTaskDelegate; + private Callback callback; + private HttpTaskOption taskOption; + private boolean isStop = false, isCancel = false; - public HttpFileInfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) { + HttpDFileInfoTask(DTaskWrapper taskWrapper) { this.mTaskWrapper = taskWrapper; mEntity = taskWrapper.getEntity(); - mConnectTimeOut = - AriaManager.getInstance().getDownloadConfig().getConnectTimeOut(); - onFileInfoCallback = callback; - mTaskDelegate = taskWrapper.asHttp(); + mConnectTimeOut = AriaConfig.getInstance().getDConfig().getConnectTimeOut(); + taskOption = (HttpTaskOption) taskWrapper.getTaskOption(); } @Override public void run() { @@ -77,28 +78,51 @@ public class HttpFileInfoThread implements Runnable { TrafficStats.setThreadStatsTag(UUID.randomUUID().toString().hashCode()); HttpURLConnection conn = null; try { - URL url = ConnectionHelp.handleUrl(mEntity.getUrl(), mTaskDelegate); - conn = ConnectionHelp.handleConnection(url, mTaskDelegate); - ConnectionHelp.setConnectParam(mTaskDelegate, conn); + URL url = ConnectionHelp.handleUrl(mEntity.getUrl(), taskOption); + conn = ConnectionHelp.handleConnection(url, taskOption); + ConnectionHelp.setConnectParam(taskOption, conn); conn.setRequestProperty("Range", "bytes=" + 0 + "-"); + if (AriaConfig.getInstance().getDConfig().isUseHeadRequest()) { + ALog.d(TAG, "head请求"); + conn.setRequestMethod("HEAD"); + } conn.setConnectTimeout(mConnectTimeOut); conn.connect(); handleConnect(conn); } catch (IOException e) { - e.printStackTrace(); - failDownload(new AriaIOException(TAG, - String.format("下载失败,filePath: %s, url: %s", mEntity.getDownloadPath(), mEntity.getUrl())), - true); + failDownload(new AriaHTTPException( + String.format("下载失败,filePath: %s, url: %s", mEntity.getFilePath(), mEntity.getUrl()), + e), true); } finally { if (conn != null) { + try { + InputStream is = conn.getInputStream(); + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } conn.disconnect(); } } } + @Override public void setCallback(Callback callback) { + this.callback = callback; + } + + @Override public void stop() { + isStop = true; + } + + @Override public void cancel() { + isCancel = true; + } + private void handleConnect(HttpURLConnection conn) throws IOException { - if (mTaskDelegate.getRequestEnum() == RequestEnum.POST) { - Map params = mTaskDelegate.getParams(); + if (taskOption.getRequestEnum() == RequestEnum.POST) { + Map params = taskOption.getParams(); if (params != null) { OutputStreamWriter dos = new OutputStreamWriter(conn.getOutputStream()); Set keys = params.keySet(); @@ -114,7 +138,7 @@ public class HttpFileInfoThread implements Runnable { } } - IHttpFileLenAdapter lenAdapter = mTaskWrapper.asHttp().getFileLenAdapter(); + IHttpFileLenAdapter lenAdapter = taskOption.getFileLenAdapter(); if (lenAdapter == null) { lenAdapter = new FileLenAdapter(); } else { @@ -122,9 +146,9 @@ public class HttpFileInfoThread implements Runnable { } long len = lenAdapter.handleFileLen(conn.getHeaderFields()); - if (!FileUtil.checkSDMemorySpace(mEntity.getFilePath(), len)) { - failDownload(new TaskException(TAG, - String.format("下载失败,内存空间不足;filePath: %s, url: %s", mEntity.getDownloadPath(), + if (!FileUtil.checkMemorySpace(mEntity.getFilePath(), len)) { + failDownload(new AriaHTTPException( + String.format("下载失败,内存空间不足;filePath: %s, url: %s", mEntity.getFilePath(), mEntity.getUrl())), false); return; } @@ -144,14 +168,20 @@ public class HttpFileInfoThread implements Runnable { Map> headers = conn.getHeaderFields(); String disposition = conn.getHeaderField("Content-Disposition"); - if (mTaskDelegate.isUseServerFileName()) { + if (taskOption.isUseServerFileName()) { if (!TextUtils.isEmpty(disposition)) { mEntity.setDisposition(CommonUtil.encryptBASE64(disposition)); handleContentDisposition(disposition); } else { - ALog.w(TAG, "Content-Disposition对于端字段为空,使用服务器端文件名失败"); + if (taskOption.getFileNameAdapter() != null) { + String newName = + taskOption.getFileNameAdapter().handleFileName(headers, mEntity.getKey()); + mEntity.setServerFileName(newName); + renameFile(newName); + } } } + CookieManager msCookieManager = new CookieManager(); List cookiesHeader = headers.get("Set-Cookie"); @@ -159,7 +189,7 @@ public class HttpFileInfoThread implements Runnable { for (String cookie : cookiesHeader) { msCookieManager.getCookieStore().add(null, HttpCookie.parse(cookie).get(0)); } - mTaskDelegate.setCookieManager(msCookieManager); + taskOption.setCookieManager(msCookieManager); } mTaskWrapper.setCode(code); @@ -167,7 +197,7 @@ public class HttpFileInfoThread implements Runnable { if (!checkLen(len) && !isChunked) { if (len < 0) { failDownload( - new AriaIOException(TAG, String.format("任务下载失败,文件长度小于0, url: %s", mEntity.getUrl())), + new AriaHTTPException(String.format("任务下载失败,文件长度小于0, url: %s", mEntity.getUrl())), false); } return; @@ -194,7 +224,8 @@ public class HttpFileInfoThread implements Runnable { } else if (!checkLen(len) && !isChunked) { if (len < 0) { failDownload( - new AriaIOException(TAG, String.format("任务下载失败,文件长度小于0, url: %s", mEntity.getUrl())), + new AriaHTTPException( + String.format("任务下载失败,文件长度小于0, url: %s", mEntity.getUrl())), false); } ALog.d(TAG, "len < 0"); @@ -204,26 +235,32 @@ public class HttpFileInfoThread implements Runnable { mTaskWrapper.setNewTask(true); mTaskWrapper.setSupportBP(false); end = true; - } else if (code == HttpURLConnection.HTTP_NOT_FOUND) { - failDownload(new AriaIOException(TAG, - String.format("任务下载失败,errorCode:404, url: %s", mEntity.getUrl())), true); } else if (code == HttpURLConnection.HTTP_MOVED_TEMP || code == HttpURLConnection.HTTP_MOVED_PERM || code == HttpURLConnection.HTTP_SEE_OTHER || code == HttpURLConnection.HTTP_CREATED // 201 跳转 || code == 307) { handleUrlReTurn(conn, conn.getHeaderField("Location")); + } else if (code == 416) { // 处理0k长度的文件的情况 + ALog.w(TAG, "文件长度为0,不支持断点"); + mTaskWrapper.setSupportBP(false); + mTaskWrapper.setNewTask(true); + end = true; + } else if (code >= HttpURLConnection.HTTP_BAD_REQUEST) { + failDownload(new AriaHTTPException( + String.format("任务下载失败,errorCode:%s, url: %s", code, mEntity.getUrl())), false); } else { - failDownload(new AriaIOException(TAG, + failDownload(new AriaHTTPException( String.format("任务下载失败,errorCode:%s, errorMsg: %s, url: %s", code, - conn.getResponseMessage(), mEntity.getUrl())), true); + conn.getResponseMessage(), mEntity.getUrl())), !CheckUtil.httpIsBadRequest(code)); + } + if (isStop || isCancel) { + return; } if (end) { - mTaskDelegate.setChunked(isChunked); - if (onFileInfoCallback != null) { - CompleteInfo info = new CompleteInfo(code, mTaskWrapper); - onFileInfoCallback.onComplete(mEntity.getUrl(), info); - } + taskOption.setChunked(isChunked); + CompleteInfo info = new CompleteInfo(code, mTaskWrapper); + callback.onSucceed(mEntity.getUrl(), info); mEntity.update(); } } @@ -273,13 +310,17 @@ public class HttpFileInfoThread implements Runnable { ALog.d(TAG, String.format("文件重命名为:%s", newName)); File oldFile = new File(mEntity.getFilePath()); String newPath = oldFile.getParent() + "/" + newName; + if (!CheckUtil.checkDPathConflicts(false, newPath, mTaskWrapper.getRequestType())) { + ALog.e(TAG, "文件重命名失败"); + return; + } if (oldFile.exists()) { boolean b = oldFile.renameTo(new File(newPath)); ALog.d(TAG, String.format("文件重命名%s", b ? "成功" : "失败")); } mEntity.setFileName(newName); mEntity.setFilePath(newPath); - RecordUtil.modifyTaskRecord(oldFile.getPath(), newPath); + RecordUtil.modifyTaskRecord(oldFile.getPath(), newPath, mEntity.getTaskType()); } /** @@ -288,8 +329,8 @@ public class HttpFileInfoThread implements Runnable { private void handleUrlReTurn(HttpURLConnection conn, String newUrl) throws IOException { ALog.d(TAG, "30x跳转,新url为【" + newUrl + "】"); if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) { - if (onFileInfoCallback != null) { - onFileInfoCallback.onFail(mEntity, new TaskException(TAG, "获取重定向链接失败"), false); + if (callback != null) { + callback.onFail(mEntity, new AriaHTTPException("获取重定向链接失败"), false); } return; } @@ -298,20 +339,23 @@ public class HttpFileInfoThread implements Runnable { newUrl = uri.getHost() + newUrl; } - if (!CheckUtil.checkUrlNotThrow(newUrl)) { - failDownload(new TaskException(TAG, "下载失败,重定向url错误"), false); + if (!CheckUtil.checkUrl(newUrl)) { + failDownload(new AriaHTTPException("下载失败,重定向url错误"), false); return; } - mTaskDelegate.setRedirectUrl(newUrl); + taskOption.setRedirectUrl(newUrl); mEntity.setRedirect(true); mEntity.setRedirectUrl(newUrl); String cookies = conn.getHeaderField("Set-Cookie"); conn.disconnect(); - URL url = ConnectionHelp.handleUrl(newUrl, mTaskDelegate); - conn = ConnectionHelp.handleConnection(url, mTaskDelegate); - ConnectionHelp.setConnectParam(mTaskDelegate, conn); + URL url = ConnectionHelp.handleUrl(newUrl, taskOption); + conn = ConnectionHelp.handleConnection(url, taskOption); + ConnectionHelp.setConnectParam(taskOption, conn); conn.setRequestProperty("Cookie", cookies); conn.setRequestProperty("Range", "bytes=" + 0 + "-"); + if (AriaConfig.getInstance().getDConfig().isUseHeadRequest()) { + conn.setRequestMethod("HEAD"); + } conn.setConnectTimeout(mConnectTimeOut); conn.connect(); handleConnect(conn); @@ -332,10 +376,17 @@ public class HttpFileInfoThread implements Runnable { return true; } - private void failDownload(BaseException e, boolean needRetry) { - if (onFileInfoCallback != null) { - onFileInfoCallback.onFail(mEntity, e, needRetry); + private void failDownload(AriaHTTPException e, boolean needRetry) { + if (isStop || isCancel) { + return; } + if (callback != null) { + callback.onFail(mEntity, e, needRetry); + } + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); } private static class FileLenAdapter implements IHttpFileLenAdapter { diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGInfoTask.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGInfoTask.java new file mode 100644 index 00000000..9261ad00 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGInfoTask.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http.download; + +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +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.loader.IInfoTask; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.exception.AriaHTTPException; +import com.arialyy.aria.http.HttpTaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 组合任务文件信息,用于获取长度未知时,组合任务的长度 + */ +public final class HttpDGInfoTask implements IInfoTask { + private String TAG = CommonUtil.getClassName(this); + private Callback callback; + private DGTaskWrapper wrapper; + private final Object LOCK = new Object(); + private ExecutorService mPool = null; + private boolean getLenComplete = false; + private AtomicInteger count = new AtomicInteger(); + private AtomicInteger failCount = new AtomicInteger(); + private boolean isStop = false, isCancel = false; + + public interface DGInfoCallback extends Callback { + + /** + * 子任务失败 + */ + void onSubFail(DownloadEntity subEntity, AriaHTTPException e, boolean needRetry); + + /** + * 组合任务停止 + */ + void onStop(long len); + } + + /** + * 子任务回调 + */ + private Callback subCallback = new Callback() { + @Override public void onSucceed(String url, CompleteInfo info) { + count.getAndIncrement(); + checkGetSizeComplete(count.get(), failCount.get()); + ALog.d(TAG, "获取子任务信息完成"); + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + ALog.e(TAG, String.format("获取文件信息失败,url:%s", ((DownloadEntity) entity).getUrl())); + count.getAndIncrement(); + failCount.getAndIncrement(); + ((DGInfoCallback) callback).onSubFail((DownloadEntity) entity, new AriaHTTPException( + String.format("子任务获取文件长度失败,url:%s", ((DownloadEntity) entity).getUrl())), needRetry); + checkGetSizeComplete(count.get(), failCount.get()); + } + }; + + HttpDGInfoTask(DGTaskWrapper wrapper) { + this.wrapper = wrapper; + } + + /** + * 停止 + */ + @Override + public void stop() { + isStop = true; + if (mPool != null) { + mPool.shutdown(); + } + } + + @Override public void cancel() { + isCancel = true; + if (mPool != null) { + mPool.shutdown(); + } + } + + @Override public void run() { + // 如果是isUnknownSize()标志,并且获取大小没有完成,则直接回调onStop + if (mPool != null && !getLenComplete) { + ALog.d(TAG, "获取长度未完成的情况下,停止组合任务"); + mPool.shutdown(); + ((DGInfoCallback)callback).onStop(0); + return; + } + // 处理组合任务大小未知的情况 + if (wrapper.isUnknownSize()) { + mPool = Executors.newCachedThreadPool(); + getGroupSize(); + try { + synchronized (LOCK) { + LOCK.wait(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (!mPool.isShutdown()) { + mPool.shutdown(); + } + } else { + for (DTaskWrapper wrapper : wrapper.getSubTaskWrapper()) { + cloneHeader(wrapper); + } + callback.onSucceed(wrapper.getKey(), new CompleteInfo()); + } + } + + /* + * 获取组合任务大小,使用该方式获取到的组合任务大小,子任务不需要再重新获取文件大小 + */ + private void getGroupSize() { + new Thread(new Runnable() { + @Override public void run() { + for (DTaskWrapper dTaskWrapper : wrapper.getSubTaskWrapper()) { + DownloadEntity subEntity = dTaskWrapper.getEntity(); + if (subEntity.getFileSize() > 0) { + count.getAndIncrement(); + if (subEntity.getCurrentProgress() < subEntity.getFileSize()) { + // 如果没有完成需要拷贝一份数据 + cloneHeader(dTaskWrapper); + } + checkGetSizeComplete(count.get(), failCount.get()); + continue; + } + cloneHeader(dTaskWrapper); + HttpDFileInfoTask infoTask = new HttpDFileInfoTask(dTaskWrapper); + infoTask.setCallback(subCallback); + mPool.execute(infoTask); + } + } + }).start(); + } + + /** + * 检查组合任务大小是否获取完成,获取完成后取消阻塞,并设置组合任务大小 + */ + private void checkGetSizeComplete(int count, int failCount) { + if (isStop || isCancel) { + ALog.w(TAG, "任务已停止或已取消,isStop = " + isStop + ", isCancel = " + isCancel); + notifyLock(); + return; + } + if (failCount == wrapper.getSubTaskWrapper().size()) { + callback.onFail(wrapper.getEntity(), new AriaHTTPException("获取子任务长度失败"), false); + notifyLock(); + return; + } + if (count == wrapper.getSubTaskWrapper().size()) { + long size = 0; + for (DTaskWrapper wrapper : wrapper.getSubTaskWrapper()) { + size += wrapper.getEntity().getFileSize(); + } + wrapper.getEntity().setConvertFileSize(CommonUtil.formatFileSize(size)); + wrapper.getEntity().setFileSize(size); + wrapper.getEntity().update(); + getLenComplete = true; + ALog.d(TAG, String.format("获取组合任务长度完成,组合任务总长度:%s,失败的子任务数:%s", size, failCount)); + callback.onSucceed(wrapper.getKey(), new CompleteInfo()); + notifyLock(); + } + } + + private void notifyLock() { + synchronized (LOCK) { + LOCK.notifyAll(); + } + } + + /** + * 子任务使用父包裹器的属性 + */ + private void cloneHeader(DTaskWrapper taskWrapper) { + HttpTaskOption groupOption = (HttpTaskOption) wrapper.getTaskOption(); + HttpTaskOption subOption = new HttpTaskOption(); + + // 设置属性 + subOption.setFileLenAdapter(groupOption.getFileLenAdapter()); + subOption.setFileNameAdapter(groupOption.getFileNameAdapter()); + subOption.setUseServerFileName(groupOption.isUseServerFileName()); + + subOption.setFileNameAdapter(groupOption.getFileNameAdapter()); + subOption.setRequestEnum(groupOption.getRequestEnum()); + subOption.setHeaders(groupOption.getHeaders()); + subOption.setProxy(groupOption.getProxy()); + subOption.setParams(groupOption.getParams()); + taskWrapper.setTaskOption(subOption); + } + + @Override public void setCallback(Callback callback) { + this.callback = callback; + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGLoader.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGLoader.java new file mode 100644 index 00000000..9f90fcb3 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGLoader.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http.download; + +import android.os.Looper; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.group.AbsGroupLoader; +import com.arialyy.aria.core.group.AbsSubDLoadUtil; +import com.arialyy.aria.core.listener.DownloadGroupListener; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.exception.AriaHTTPException; +import java.io.File; + +/** + * http 组合任务加载器 + */ +final class HttpDGLoader extends AbsGroupLoader { + HttpDGLoader(AbsTaskWrapper groupWrapper, DownloadGroupListener listener) { + super(groupWrapper, listener); + } + + @Override protected void handlerTask(Looper looper) { + if (isBreak()) { + return; + } + mInfoTask.run(); + } + + @Override + protected AbsSubDLoadUtil createSubLoader(DTaskWrapper wrapper, boolean needGetFileInfo) { + HttpSubDLoaderUtil subUtil = new HttpSubDLoaderUtil(getScheduler(), needGetFileInfo, getKey()); + subUtil.setParams(wrapper, null); + return subUtil; + } + + private void startSub() { + if (isBreak()) { + return; + } + onPostStart(); + for (DTaskWrapper wrapper : getWrapper().getSubTaskWrapper()) { + DownloadEntity dEntity = wrapper.getEntity(); + + startSubLoader(createSubLoader(wrapper, dEntity.getFileSize() < 0)); + } + } + + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = infoTask; + mInfoTask.setCallback(new HttpDGInfoTask.DGInfoCallback() { + + @Override + public void onSubFail(DownloadEntity subEntity, AriaHTTPException e, boolean needRetry) { + getState().countFailNum(subEntity.getKey()); + } + + @Override public void onStop(long len) { + getListener().onStop(len); + } + + @Override public void onSucceed(String key, CompleteInfo info) { + startSub(); + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + fail(e, needRetry); + } + }); + } +} diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGLoaderUtil.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGLoaderUtil.java new file mode 100644 index 00000000..e3dff075 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDGLoaderUtil.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http.download; + +import com.arialyy.aria.core.download.DGTaskWrapper; +import com.arialyy.aria.core.group.AbsGroupLoader; +import com.arialyy.aria.core.group.AbsGroupLoaderUtil; +import com.arialyy.aria.core.listener.DownloadGroupListener; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.http.HttpTaskOption; + +/** + * Created by AriaL on 2017/6/30. + * 任务组下载工具 + */ +public final class HttpDGLoaderUtil extends AbsGroupLoaderUtil { + + @Override protected AbsGroupLoader getLoader() { + if (mLoader == null) { + getTaskWrapper().generateTaskOption(HttpTaskOption.class); + mLoader = new HttpDGLoader(getTaskWrapper(), (DownloadGroupListener) getListener()); + } + return mLoader; + } + + @Override protected LoaderStructure buildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new HttpDGInfoTask((DGTaskWrapper) getTaskWrapper())); + structure.accept(getLoader()); + return structure; + } +} \ No newline at end of file diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDLoaderUtil.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDLoaderUtil.java new file mode 100644 index 00000000..d657c464 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDLoaderUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http.download; + +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalLoader; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.NormalThreadStateManager; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.http.HttpRecordHandler; +import com.arialyy.aria.http.HttpTaskOption; + +/** + * @author lyy + * Date: 2019-09-21 + */ +public final class HttpDLoaderUtil extends AbsNormalLoaderUtil { + + @Override public AbsNormalLoader getLoader() { + if (mLoader == null){ + getTaskWrapper().generateTaskOption(HttpTaskOption.class); + mLoader = new NormalLoader(getTaskWrapper(), getListener()); + } + return mLoader; + } + + public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new HttpRecordHandler(getTaskWrapper())) + .addComponent(new NormalThreadStateManager(getListener())) + .addComponent(new HttpDFileInfoTask((DTaskWrapper) getTaskWrapper())) + .addComponent(new NormalTTBuilder(getTaskWrapper(), new HttpDTTBuilderAdapter())); + structure.accept(getLoader()); + return structure; + } +} diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDTTBuilderAdapter.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDTTBuilderAdapter.java new file mode 100644 index 00000000..e4e3186e --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDTTBuilderAdapter.java @@ -0,0 +1,61 @@ +package com.arialyy.aria.http.download; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.task.IThreadTaskAdapter; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.io.IOException; + +final class HttpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter { + + @Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) { + return new HttpDThreadTaskAdapter(config); + } + + @Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) { + if (!record.isBlock) { + if (getTempFile().exists()) { + FileUtil.deleteFile(getTempFile()); + } + } else { + for (int i = 0; i < totalThreadNum; i++) { + File blockFile = + new File(String.format(IRecordHandler.SUB_PATH, getTempFile().getPath(), i)); + if (blockFile.exists()) { + ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i)); + FileUtil.deleteFile(blockFile); + } + } + } + BufferedRandomAccessFile file = null; + try { + if (totalThreadNum > 1 && !record.isBlock) { + file = new BufferedRandomAccessFile(getTempFile().getPath(), "rwd", 8192); + //设置文件长度 + file.setLength(getEntity().getFileSize()); + } + if (getTempFile().exists()) { + FileUtil.deleteFile(getTempFile()); + } + return true; + } catch (IOException e) { + e.printStackTrace(); + ALog.e(TAG, String.format("下载失败,filePath: %s, url: %s", getEntity().getFilePath(), + getEntity().getUrl())); + } finally { + if (file != null) { + try { + file.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return false; + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java similarity index 62% rename from Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java rename to HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java index d396ba46..676c38c3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpDThreadTaskAdapter.java @@ -13,17 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.downloader; +package com.arialyy.aria.http.download; -import com.arialyy.aria.core.common.AbsThreadTask; import com.arialyy.aria.core.common.RequestEnum; import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.config.DownloadConfig; import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.common.http.HttpTaskConfig; -import com.arialyy.aria.exception.AriaIOException; -import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.exception.AriaHTTPException; +import com.arialyy.aria.http.BaseHttpThreadTaskAdapter; +import com.arialyy.aria.http.ConnectionHelp; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.BufferedRandomAccessFile; import java.io.BufferedInputStream; @@ -45,46 +42,48 @@ import java.util.Set; /** * Created by lyy on 2017/1/18. 下载线程 */ -final class HttpThreadTask extends AbsThreadTask { - private final String TAG = "HttpThreadTask"; +final class HttpDThreadTaskAdapter extends BaseHttpThreadTaskAdapter { + private final String TAG = "HttpDThreadTaskAdapter"; + private DTaskWrapper mTaskWrapper; - HttpThreadTask(SubThreadConfig config) { + HttpDThreadTaskAdapter(SubThreadConfig config) { super(config); } - @Override public HttpThreadTask call() throws Exception { - super.call(); - if (mRecord.isComplete) { + @Override protected void handlerThreadTask() { + mTaskWrapper = (DTaskWrapper) getTaskWrapper(); + if (getThreadRecord().isComplete) { handleComplete(); - return this; + return; } HttpURLConnection conn = null; BufferedInputStream is = null; BufferedRandomAccessFile file = null; try { - HttpTaskConfig taskDelegate = getTaskWrapper().asHttp(); - URL url = ConnectionHelp.handleUrl(getConfig().url, taskDelegate); - conn = ConnectionHelp.handleConnection(url, taskDelegate); + URL url = ConnectionHelp.handleUrl(getThreadConfig().url, mTaskOption); + conn = ConnectionHelp.handleConnection(url, mTaskOption); if (mTaskWrapper.isSupportBP()) { ALog.d(TAG, String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(), - mRecord.threadId, mRecord.startLocation, mRecord.endLocation)); - conn.setRequestProperty("Range", String.format("bytes=%s-%s", mRecord.startLocation, - (mRecord.endLocation - 1))); + getThreadRecord().threadId, getThreadRecord().startLocation, + getThreadRecord().endLocation)); + conn.setRequestProperty("Range", + String.format("bytes=%s-%s", getThreadRecord().startLocation, + (getThreadRecord().endLocation - 1))); } else { ALog.w(TAG, "该下载不支持断点"); } - ConnectionHelp.setConnectParam(taskDelegate, conn); + ConnectionHelp.setConnectParam(mTaskOption, conn); conn.setConnectTimeout(getTaskConfig().getConnectTimeOut()); conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数 - if (taskDelegate.isChunked()) { + if (mTaskOption.isChunked()) { conn.setDoInput(true); conn.setChunkedStreamingMode(0); } conn.connect(); // 传递参数 - if (taskDelegate.getRequestEnum() == RequestEnum.POST) { - Map params = taskDelegate.getParams(); + if (mTaskOption.getRequestEnum() == RequestEnum.POST) { + Map params = mTaskOption.getParams(); if (params != null) { OutputStreamWriter dos = new OutputStreamWriter(conn.getOutputStream()); Set keys = params.keySet(); @@ -101,32 +100,34 @@ final class HttpThreadTask extends AbsThreadTask { } is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); - if (taskDelegate.isChunked()) { + if (mTaskOption.isChunked()) { readChunked(is); - } else if (getConfig().isOpenDynamicFile) { + } else if (getThreadConfig().isBlock) { readDynamicFile(is); } else { //创建可设置位置的文件 file = - new BufferedRandomAccessFile(getConfig().tempFile, "rwd", + new BufferedRandomAccessFile(getThreadConfig().tempFile, "rwd", getTaskConfig().getBuffSize()); //设置每条线程写入文件的位置 - file.seek(mRecord.startLocation); + if (getThreadRecord().startLocation > 0) { + file.seek(getThreadRecord().startLocation); + } readNormal(is, file); handleComplete(); } } catch (MalformedURLException e) { - fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), - getEntity().getDownloadPath(), getEntity().getUrl()), e)); + fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), + getEntity().getFilePath(), getEntity().getUrl()), e), false); } catch (IOException e) { - fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), - getEntity().getDownloadPath(), getEntity().getUrl()), e)); + fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), + getEntity().getFilePath(), getEntity().getUrl()), e), true); + } catch (ArrayIndexOutOfBoundsException e) { + fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), + getEntity().getFilePath(), getEntity().getUrl()), e), false); } catch (Exception e) { - fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), - getEntity().getDownloadPath(), getEntity().getUrl()), e)); + fail(new AriaHTTPException(String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), + getEntity().getFilePath(), getEntity().getUrl()), e), false); } finally { try { if (file != null) { @@ -136,15 +137,13 @@ final class HttpThreadTask extends AbsThreadTask { is.close(); } if (conn != null) { + conn.getInputStream().close(); conn.disconnect(); } } catch (IOException e) { e.printStackTrace(); } - - onThreadComplete(); } - return this; } /** @@ -153,11 +152,11 @@ final class HttpThreadTask extends AbsThreadTask { private void readChunked(InputStream is) { FileOutputStream fos = null; try { - fos = new FileOutputStream(getConfig().tempFile, true); + fos = new FileOutputStream(getThreadConfig().tempFile, true); byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; - while (isLive() && (len = is.read(buffer)) != -1) { - if (isBreak()) { + while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) { + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { @@ -168,10 +167,9 @@ final class HttpThreadTask extends AbsThreadTask { } handleComplete(); } catch (IOException e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), - getConfig().url), - e)); + fail(new AriaHTTPException( + String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(), + getThreadConfig().url), e), true); } finally { if (fos != null) { try { @@ -192,21 +190,22 @@ final class HttpThreadTask extends AbsThreadTask { ReadableByteChannel fic = null; try { int len; - fos = new FileOutputStream(getConfig().tempFile, true); + fos = new FileOutputStream(getThreadConfig().tempFile, true); foc = fos.getChannel(); fic = Channels.newChannel(is); + ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); //如果要通过 Future 的 cancel 方法取消正在运行的任务,那么该任务必定是可以 对线程中断做出响应 的任务。 - while (isLive() && (len = fic.read(bf)) != -1) { - if (isBreak()) { + while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) { + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= mRecord.endLocation) { - len = (int) (mRecord.endLocation - mChildCurrentLocation); + if (getRangeProgress() + len >= getThreadRecord().endLocation) { + len = (int) (getThreadRecord().endLocation - getRangeProgress()); bf.flip(); fos.write(bf.array(), 0, len); bf.compact(); @@ -221,10 +220,8 @@ final class HttpThreadTask extends AbsThreadTask { } handleComplete(); } catch (IOException e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), - getConfig().url), - e)); + fail(new AriaHTTPException(String.format("文件下载失败,savePath: %s, url: %s", getEntity().getFilePath(), + getThreadConfig().url), e), true); } finally { try { if (fos != null) { @@ -250,8 +247,8 @@ final class HttpThreadTask extends AbsThreadTask { throws IOException { byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; - while (isLive() && (len = is.read(buffer)) != -1) { - if (isBreak()) { + while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) { + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { @@ -266,31 +263,13 @@ final class HttpThreadTask extends AbsThreadTask { * 处理完成配置文件的更新或事件回调 */ private void handleComplete() { - if (isBreak()) { - return; - } - if (!checkBlock()) { + if (getThreadTask().isBreak()) { return; } - if (getTaskWrapper().asHttp().isChunked()) { - sendCompleteMsg(); + if (!getThreadTask().checkBlock()) { return; } - //支持断点的处理 - if (mTaskWrapper.isSupportBP()) { - writeConfig(true, mRecord.endLocation); - sendCompleteMsg(); - } else { - sendCompleteMsg(); - } - } - - @Override public int getMaxSpeed() { - return mAridManager.getDownloadConfig().getMaxSpeed(); - } - - @Override protected DownloadConfig getTaskConfig() { - return getTaskWrapper().getConfig(); + complete(); } } diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpSubDLoaderUtil.java b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpSubDLoaderUtil.java new file mode 100644 index 00000000..4e9d1c64 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/download/HttpSubDLoaderUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http.download; + +import android.os.Handler; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.group.AbsSubDLoadUtil; +import com.arialyy.aria.core.group.SubRecordHandler; +import com.arialyy.aria.core.loader.GroupSubThreadStateManager; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.SubLoader; + +/** + * @author lyy + * Date: 2019-09-28 + */ +final class HttpSubDLoaderUtil extends AbsSubDLoadUtil { + /** + * @param schedulers 调度器 + * @param needGetInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息 + */ + HttpSubDLoaderUtil( Handler schedulers, boolean needGetInfo, String parentKey) { + super(schedulers, needGetInfo, parentKey); + } + + @Override protected SubLoader getLoader() { + if (mDLoader == null) { + mDLoader = new SubLoader(getWrapper(), getSchedulers()); + mDLoader.setNeedGetInfo(isNeedGetInfo()); + mDLoader.setParentKey(getParentKey()); + } + return mDLoader; + } + + @Override protected LoaderStructure buildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new SubRecordHandler(getWrapper())) + .addComponent(new GroupSubThreadStateManager(getSchedulers(),getKey())) + .addComponent(new NormalTTBuilder(getWrapper(), new HttpDTTBuilderAdapter())) + .addComponent(new HttpDFileInfoTask(getWrapper())); + structure.accept(getLoader()); + return structure; + } +} + diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java new file mode 100644 index 00000000..986a599a --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoader.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http.upload; + +import android.os.Handler; +import android.os.Looper; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.loader.IThreadTaskBuilder; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.exception.AriaHTTPException; +import com.arialyy.aria.util.ALog; +import java.util.List; + +final class HttpULoader extends AbsNormalLoader { + HttpULoader(UTaskWrapper wrapper, IEventListener listener) { + super(wrapper, listener); + } + + @Override public void addComponent(IRecordHandler recordHandler) { + mRecordHandler = recordHandler; + } + + /** + * @deprecated http 上传任务不需要设置这个 + */ + @Deprecated + @Override public void addComponent(IInfoTask infoTask) { + + } + + @Override public void addComponent(IThreadStateManager threadState) { + mStateManager = threadState; + } + + @Override public void addComponent(IThreadTaskBuilder builder) { + mTTBuilder = builder; + } + + @Override protected void handleTask(Looper looper) { + mRecord = mRecordHandler.getRecord(getFileSize()); + mStateManager.setLooper(mRecord, looper); + List tt = mTTBuilder.buildThreadTask(mRecord, + new Handler(looper, mStateManager.getHandlerCallback())); + if (tt == null || tt.isEmpty()) { + ALog.e(TAG, "创建线程任务失败"); + getListener().onFail(false, new AriaHTTPException("创建线程任务失败")); + return; + } + + getListener().onStart(0); + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), tt.get(0)); + + startTimer(); + } + + @Override public long getFileSize() { + return mTaskWrapper.getEntity().getFileSize(); + } + + @Override protected void checkComponent() { + if (mRecordHandler == null) { + throw new NullPointerException("任务记录组件为空"); + } + if (mStateManager == null) { + throw new NullPointerException("任务状态管理组件为空"); + } + if (mTTBuilder == null) { + throw new NullPointerException("线程任务组件为空"); + } + } + + @Override public long getCurrentProgress() { + return mStateManager.getCurrentProgress(); + } +} diff --git a/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoaderUtil.java b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoaderUtil.java new file mode 100644 index 00000000..addeeed4 --- /dev/null +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpULoaderUtil.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.http.upload; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.NormalThreadStateManager; +import com.arialyy.aria.core.loader.UploadThreadStateManager; +import com.arialyy.aria.core.task.IThreadTaskAdapter; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.http.HttpRecordHandler; +import com.arialyy.aria.http.HttpTaskOption; + +/** + * @author lyy + * Date: 2019-09-19 + */ +public final class HttpULoaderUtil extends AbsNormalLoaderUtil { + + @Override public AbsNormalLoader getLoader() { + if (mLoader == null) { + getTaskWrapper().generateTaskOption(HttpTaskOption.class); + mLoader = new HttpULoader((UTaskWrapper) getTaskWrapper(), getListener()); + } + return mLoader; + } + + @Override public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new HttpRecordHandler(getTaskWrapper())) +// .addComponent(new NormalThreadStateManager(getListener())) + .addComponent(new UploadThreadStateManager(getListener())) + .addComponent(new NormalTTBuilder(getTaskWrapper(), new AbsNormalTTBuilderAdapter() { + @Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) { + return new HttpUThreadTaskAdapter(config); + } + + @Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) { + return true; + } + })); + structure.accept(getLoader()); + return structure; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpUThreadTaskAdapter.java similarity index 63% rename from Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java rename to HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpUThreadTaskAdapter.java index 009c3283..9d88d085 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java +++ b/HttpComponent/src/main/java/com/arialyy/aria/http/upload/HttpUThreadTaskAdapter.java @@ -13,22 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.upload.uploader; +package com.arialyy.aria.http.upload; import android.text.TextUtils; -import com.arialyy.aria.core.common.AbsThreadTask; import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.common.http.HttpTaskConfig; -import com.arialyy.aria.core.config.UploadConfig; -import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.exception.BaseException; -import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.exception.AriaHTTPException; +import com.arialyy.aria.http.BaseHttpThreadTaskAdapter; +import com.arialyy.aria.http.ConnectionHelp; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; @@ -44,34 +42,31 @@ import java.util.UUID; /** * Created by Aria.Lao on 2017/7/28. 不支持断点的HTTP上传任务 */ -class HttpThreadTask extends AbsThreadTask { - private final String TAG = "HttpThreadTask"; +final class HttpUThreadTaskAdapter extends BaseHttpThreadTaskAdapter { private final String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成 private final String PREFIX = "--", LINE_END = "\r\n"; private HttpURLConnection mHttpConn; private OutputStream mOutputStream; - HttpThreadTask(SubThreadConfig config) { + HttpUThreadTaskAdapter(SubThreadConfig config) { super(config); } - @Override public HttpThreadTask call() throws Exception { - super.call(); + @Override protected void handlerThreadTask() { File uploadFile = new File(getEntity().getFilePath()); if (!uploadFile.exists()) { - fail(new TaskException(TAG, + fail(new AriaHTTPException( String.format("上传失败,文件不存在;filePath: %s, url: %s", getEntity().getFilePath(), getEntity().getUrl()))); - return this; + return; } URL url; try { - HttpTaskConfig taskDelegate = getTaskWrapper().asHttp(); - url = new URL(CommonUtil.convertUrl(getConfig().url)); + url = new URL(CommonUtil.convertUrl(getThreadConfig().url)); mHttpConn = (HttpURLConnection) url.openConnection(); - mHttpConn.setRequestMethod(taskDelegate.getRequestEnum().name); + mHttpConn.setRequestMethod(mTaskOption.getRequestEnum().name); mHttpConn.setUseCaches(false); mHttpConn.setDoOutput(true); mHttpConn.setDoInput(true); @@ -81,20 +76,19 @@ class HttpThreadTask extends AbsThreadTask { mHttpConn.setRequestProperty("User-Agent", getUserAgent()); mHttpConn.setConnectTimeout(getTaskConfig().getConnectTimeOut()); mHttpConn.setReadTimeout(getTaskConfig().getIOTimeOut()); - //mHttpConn.setRequestProperty("Range", "bytes=" + 0 + "-" + "100"); //内部缓冲区---分段上传防止oom mHttpConn.setChunkedStreamingMode(getTaskConfig().getBuffSize()); //添加Http请求头部 - Set keys = taskDelegate.getHeaders().keySet(); + Set keys = mTaskOption.getHeaders().keySet(); for (String key : keys) { - mHttpConn.setRequestProperty(key, taskDelegate.getHeaders().get(key)); + mHttpConn.setRequestProperty(key, mTaskOption.getHeaders().get(key)); } mOutputStream = mHttpConn.getOutputStream(); PrintWriter writer = - new PrintWriter(new OutputStreamWriter(mOutputStream, taskDelegate.getCharSet()), true); + new PrintWriter(new OutputStreamWriter(mOutputStream, mTaskOption.getCharSet()), true); // 添加参数 - Map params = taskDelegate.getParams(); + Map params = mTaskOption.getParams(); if (params != null && !params.isEmpty()) { for (String key : params.keySet()) { addFormField(writer, key, params.get(key)); @@ -102,28 +96,24 @@ class HttpThreadTask extends AbsThreadTask { } //添加文件上传表单字段 - keys = taskDelegate.getFormFields().keySet(); + keys = mTaskOption.getFormFields().keySet(); for (String key : keys) { - addFormField(writer, key, taskDelegate.getFormFields().get(key)); + addFormField(writer, key, mTaskOption.getFormFields().get(key)); } - uploadFile(writer, taskDelegate.getAttachment(), uploadFile); - getEntity().setResponseStr(finish(writer)); - sendCompleteMsg(); + uploadFile(writer, mTaskOption.getAttachment(), uploadFile); + finish(writer); } catch (Exception e) { e.printStackTrace(); - fail(new TaskException(TAG, + fail(new AriaHTTPException( String.format("上传失败,filePath: %s, url: %s", getEntity().getFilePath(), getEntity().getUrl()), e)); - } finally { - onThreadComplete(); } - return this; } - private void fail(BaseException e1) { + private void fail(AriaHTTPException e1) { try { - sendFailMsg(e1); + fail(e1, false); if (mOutputStream != null) { mOutputStream.close(); } @@ -133,20 +123,17 @@ class HttpThreadTask extends AbsThreadTask { } private String getContentType() { - HttpTaskConfig config = getTaskWrapper().asHttp(); - return - (config.getHeaders() == null || TextUtils.isEmpty(config.getHeaders().get("Content-Type"))) - ? - "multipart/form-data" : config.getHeaders().get("Content-Type"); + //return (mTaskOption.getHeaders() == null || TextUtils.isEmpty( + // mTaskOption.getHeaders().get("Content-Type"))) ? "multipart/form-data" + // : mTaskOption.getHeaders().get("Content-Type"); + return "multipart/form-data"; } private String getUserAgent() { - HttpTaskConfig config = getTaskWrapper().asHttp(); - return - (config.getHeaders() == null || TextUtils.isEmpty(config.getHeaders().get("User-Agent"))) - ? - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)" - : config.getHeaders().get("User-Agent"); + return (mTaskOption.getHeaders() == null || TextUtils.isEmpty( + mTaskOption.getHeaders().get("User-Agent"))) + ? "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)" + : mTaskOption.getHeaders().get("User-Agent"); } /** @@ -159,7 +146,7 @@ class HttpThreadTask extends AbsThreadTask { .append("\"") .append(LINE_END); writer.append("Content-Type: text/plain; charset=") - .append(getTaskWrapper().asHttp().getCharSet()) + .append(mTaskOption.getCharSet()) .append(LINE_END); writer.append(LINE_END); writer.append(value).append(LINE_END); @@ -190,28 +177,23 @@ class HttpThreadTask extends AbsThreadTask { FileInputStream inputStream = new FileInputStream(uploadFile); byte[] buffer = new byte[4096]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - progress(bytesRead); - mOutputStream.write(buffer, 0, bytesRead); - if (isCancel) { + int bytesLen; + while ((bytesLen = inputStream.read(buffer)) != -1) { + mOutputStream.write(buffer, 0, bytesLen); + progress(bytesLen); + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { - mSpeedBandUtil.limitNextBytes(bytesRead); + mSpeedBandUtil.limitNextBytes(bytesLen); } } mOutputStream.flush(); - //outputStream.close(); //不能调用,否则服务器端异常 inputStream.close(); - writer.append(LINE_END); - writer.flush(); - //if (getState().isCancel) { - // getState().isRunning = false; - // return; - //} - //getState().isRunning = false; + writer.append(LINE_END).flush(); + // 保证上传的文件和本地的一致,https://www.cnblogs.com/tugenhua0707/p/8975121.html + writer.append(PREFIX).append(BOUNDARY).append(PREFIX).append(LINE_END).flush(); } /** @@ -221,24 +203,25 @@ class HttpThreadTask extends AbsThreadTask { */ private String finish(PrintWriter writer) throws IOException { StringBuilder response = new StringBuilder(); - - writer.append(LINE_END).flush(); - writer.append(PREFIX).append(BOUNDARY).append(PREFIX).append(LINE_END); - writer.close(); - int status = mHttpConn.getResponseCode(); if (status == HttpURLConnection.HTTP_OK) { - BufferedReader reader = new BufferedReader(new InputStreamReader(mHttpConn.getInputStream())); + BufferedReader reader = + new BufferedReader(new InputStreamReader(ConnectionHelp.convertInputStream(mHttpConn))); String line; - while (isLive() && (line = reader.readLine()) != null) { + while (getThreadTask().isLive() && (line = reader.readLine()) != null) { response.append(line); } reader.close(); mHttpConn.disconnect(); + getEntity().setResponseStr(response.toString()); + complete(); } else { - ALog.e(TAG, "response msg: " + mHttpConn.getResponseMessage() + ",code: " + status); - // fail(); + String msg = "response msg: " + mHttpConn.getResponseMessage() + ",code: " + status; + ALog.e(TAG, msg); + getEntity().setResponseStr(response.toString()); + fail(new AriaHTTPException(msg), false); + response.append(status); } writer.flush(); writer.close(); @@ -246,11 +229,7 @@ class HttpThreadTask extends AbsThreadTask { return response.toString(); } - @Override public int getMaxSpeed() { - return getTaskConfig().getMaxSpeed(); - } - - @Override protected UploadConfig getTaskConfig() { - return getTaskWrapper().getConfig(); + @Override protected UploadEntity getEntity() { + return (UploadEntity) super.getEntity(); } } diff --git a/HttpComponent/src/main/resources/META-INF/MANIFEST.MF b/HttpComponent/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/HttpComponent/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/HttpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil b/HttpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil new file mode 100644 index 00000000..62adaa3f --- /dev/null +++ b/HttpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil @@ -0,0 +1,3 @@ +com.arialyy.aria.http.download.HttpDLoaderUtil +com.arialyy.aria.http.download.HttpDGLoaderUtil +com.arialyy.aria.http.upload.HttpULoaderUtil diff --git a/M3U8Component/.gitignore b/M3U8Component/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/M3U8Component/.gitignore @@ -0,0 +1 @@ +/build diff --git a/M3U8Component/bintray-release.gradle b/M3U8Component/bintray-release.gradle new file mode 100644 index 00000000..1bfb500d --- /dev/null +++ b/M3U8Component/bintray-release.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.novoda.bintray-release' +publish { +// artifactId = 'aria-compiler' +// uploadName = 'AriaCompiler' + artifactId = 'm3u8Component' + uploadName = 'M3U8Component' + userOrg = rootProject.ext.userOrg + groupId = rootProject.ext.groupId + publishVersion = rootProject.ext.publishVersion + desc = rootProject.ext.desc + website = rootProject.ext.website + licences = rootProject.ext.licences +} \ No newline at end of file diff --git a/M3U8Component/build.gradle b/M3U8Component/build.gradle new file mode 100644 index 00000000..834a5421 --- /dev/null +++ b/M3U8Component/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + debug{ + debuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation project(path: ':HttpComponent') + implementation project(path: ':PublicComponent') +} + +//apply from: 'bintray-release.gradle' +ext{ + PUBLISH_ARTIFACT_ID = 'm3u8' +} +apply from: '../gradle/mavenCentral-release.gradle' \ No newline at end of file diff --git a/M3U8Component/consumer-rules.pro b/M3U8Component/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/M3U8Component/proguard-rules.pro b/M3U8Component/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/M3U8Component/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/M3U8Component/src/main/AndroidManifest.xml b/M3U8Component/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d1b50eee --- /dev/null +++ b/M3U8Component/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BandWidthDefConverter.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BandWidthDefConverter.java new file mode 100644 index 00000000..d931a75e --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BandWidthDefConverter.java @@ -0,0 +1,29 @@ +/* + * 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.m3u8; + +import com.arialyy.aria.core.processor.IBandWidthUrlConverter; + +/** + * 点播文件默认的码率转换器 + */ +class BandWidthDefConverter implements IBandWidthUrlConverter { + + @Override public String convert(String m3u8Url, String bandWidthUrl) { + int index = m3u8Url.lastIndexOf("/"); + return m3u8Url.substring(0, index + 1) + bandWidthUrl; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/BaseM3U8Loader.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java similarity index 54% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/BaseM3U8Loader.java rename to M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java index e4312db9..10ee5523 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/BaseM3U8Loader.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/BaseM3U8Loader.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.m3u8; -import com.arialyy.aria.core.common.AbsFileer; +import android.text.TextUtils; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.download.M3U8Entity; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.AbsNormalLoader; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.FileUtil; import java.io.BufferedReader; @@ -30,10 +32,12 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; -public abstract class BaseM3U8Loader extends AbsFileer { +public abstract class BaseM3U8Loader extends AbsNormalLoader { + protected M3U8TaskOption mM3U8Option; - BaseM3U8Loader(IEventListener listener, DTaskWrapper wrapper) { - super(listener, wrapper); + public BaseM3U8Loader(DTaskWrapper wrapper, IEventListener listener) { + super(wrapper, listener); + mM3U8Option = (M3U8TaskOption) wrapper.getM3u8Option(); mTempFile = new File(wrapper.getEntity().getFilePath()); } @@ -51,8 +55,11 @@ public abstract class BaseM3U8Loader extends AbsFileer * 主要就是三部分: 时间戳,进程id,序列号 时间戳41,id10位,序列号12位 * - * @author chiwei - * @param args * @since JDK 1.6 */ - private static volatile IdGenerator INSTANCE = null; private final static long beginTs = 1483200000000L; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8InfoThread.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java similarity index 56% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8InfoThread.java rename to M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java index 8bd337a2..1970009f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8InfoThread.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8InfoTask.java @@ -13,23 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.m3u8; import android.net.TrafficStats; import android.net.Uri; import android.os.Process; import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.core.common.CompleteInfo; -import com.arialyy.aria.core.common.OnFileInfoCallback; -import com.arialyy.aria.core.common.http.HttpTaskConfig; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.downloader.ConnectionHelp; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.ITaskWrapper; -import com.arialyy.aria.exception.M3U8Exception; -import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.core.download.M3U8Entity; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.core.processor.IBandWidthUrlConverter; +import com.arialyy.aria.core.processor.IKeyUrlConverter; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.exception.AriaM3U8Exception; +import com.arialyy.aria.http.ConnectionHelp; +import com.arialyy.aria.http.HttpTaskOption; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; @@ -37,7 +40,6 @@ import com.arialyy.aria.util.FileUtil; import com.arialyy.aria.util.Regular; import java.io.BufferedReader; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -53,38 +55,39 @@ import java.util.regex.Pattern; /** * 解析url中获取到到m3u8文件信息 + * 协议地址:https://tools.ietf.org/html/rfc8216 * https://www.cnblogs.com/renhui/p/10351870.html * https://blog.csdn.net/Guofengpu/article/details/54922865 */ -final class M3U8InfoThread implements Runnable { +final public class M3U8InfoTask implements IInfoTask { public static final String M3U8_INDEX_FORMAT = "%s.index"; private final String TAG = "M3U8InfoThread"; private DownloadEntity mEntity; private DTaskWrapper mTaskWrapper; private int mConnectTimeOut; - private OnFileInfoCallback onFileInfoCallback; private OnGetLivePeerCallback onGetPeerCallback; - private HttpTaskConfig mTaskDelegate; + private HttpTaskOption mHttpOption; + private M3U8TaskOption mM3U8Option; + private Callback mCallback; /** * 是否停止获取切片信息,{@code true}停止获取切片信息 */ private boolean isStop = false; - /** - * m3u8文件信息 - */ - private List mInfos = new ArrayList<>(); - interface OnGetLivePeerCallback { - void onGetPeer(String url); + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } + + public interface OnGetLivePeerCallback { + void onGetPeer(String url, String extInf); } - M3U8InfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) { + public M3U8InfoTask(DTaskWrapper taskWrapper) { this.mTaskWrapper = taskWrapper; mEntity = taskWrapper.getEntity(); - mConnectTimeOut = - AriaManager.getInstance().getDownloadConfig().getConnectTimeOut(); - onFileInfoCallback = callback; - mTaskDelegate = taskWrapper.asHttp(); + mConnectTimeOut = AriaConfig.getInstance().getDConfig().getConnectTimeOut(); + mHttpOption = (HttpTaskOption) taskWrapper.getTaskOption(); + mM3U8Option = (M3U8TaskOption) taskWrapper.getM3u8Option(); mEntity.getM3U8Entity().setLive(mTaskWrapper.getRequestType() == AbsTaskWrapper.M3U8_LIVE); } @@ -93,12 +96,12 @@ final class M3U8InfoThread implements Runnable { TrafficStats.setThreadStatsTag(UUID.randomUUID().toString().hashCode()); HttpURLConnection conn = null; try { - URL url = ConnectionHelp.handleUrl(mEntity.getUrl(), mTaskDelegate); - conn = ConnectionHelp.handleConnection(url, mTaskDelegate); - ConnectionHelp.setConnectParam(mTaskDelegate, conn); + URL url = ConnectionHelp.handleUrl(mEntity.getUrl(), mHttpOption); + conn = ConnectionHelp.handleConnection(url, mHttpOption); + ConnectionHelp.setConnectParam(mHttpOption, conn); conn.setConnectTimeout(mConnectTimeOut); conn.connect(); - handleConnect(conn); + handleConnect(mEntity.getUrl(), conn); } catch (IOException e) { failDownload(e.getMessage(), false); } finally { @@ -108,10 +111,23 @@ final class M3U8InfoThread implements Runnable { } } - private void handleConnect(HttpURLConnection conn) throws IOException { + @Override public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override public void stop() { + this.isStop = true; + } + + @Override public void cancel() { + this.isStop = true; + } + + private void handleConnect(String tsListUrl, HttpURLConnection conn) throws IOException { int code = conn.getResponseCode(); if (code == HttpURLConnection.HTTP_OK) { - BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + BufferedReader reader = + new BufferedReader(new InputStreamReader(ConnectionHelp.convertInputStream(conn))); String line = reader.readLine(); if (TextUtils.isEmpty(line) || !line.equalsIgnoreCase("#EXTM3U")) { failDownload("读取M3U8信息失败,读取不到#EXTM3U标签", false); @@ -119,49 +135,72 @@ final class M3U8InfoThread implements Runnable { } List extInf = new ArrayList<>(); boolean isLive = mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE; - boolean isGenerateIndexFile = mTaskWrapper.getEntity().getM3U8Entity().isGenerateIndexFile(); + boolean isGenerateIndexFile = + ((M3U8TaskOption) mTaskWrapper.getM3u8Option()).isGenerateIndexFile(); + // 写入索引信息的流 + FileOutputStream fos = null; if (isGenerateIndexFile) { - mInfos.add(line); + String indexPath = String.format(M3U8_INDEX_FORMAT, mEntity.getFilePath()); + File indexFile = new File(indexPath); + if (!indexFile.exists()) { + FileUtil.createFile(indexPath); + } else { + //FileUtil.deleteFile(indexPath); + } + fos = new FileOutputStream(indexFile); + ALog.d(TAG, line); + addIndexInfo(true, fos, line); } while ((line = reader.readLine()) != null) { if (isStop) { break; } - if (isGenerateIndexFile) { - mInfos.add(line); - } + ALog.d(TAG, line); if (line.startsWith("#EXT-X-ENDLIST")) { + // 点播文件的下载写入结束标志,直播文件的下载在停止时才写入结束标志 + addIndexInfo(isGenerateIndexFile && !isLive, fos, line); break; } - ALog.d(TAG, line); + if (line.startsWith("#EXTINF")) { - String info = reader.readLine(); - mInfos.add(info); + String url = reader.readLine(); if (isLive) { if (onGetPeerCallback != null) { - onGetPeerCallback.onGetPeer(info); + onGetPeerCallback.onGetPeer(url, line); } } else { - extInf.add(info); + extInf.add(url); } - } else if (line.startsWith("#EXT-X-STREAM-INF")) { - int setBand = mTaskWrapper.asM3U8().getBandWidth(); + ALog.d(TAG, url); + addIndexInfo(isGenerateIndexFile && !isLive, fos, line); + addIndexInfo(isGenerateIndexFile && !isLive, fos, url); + continue; + } + + if (line.startsWith("#EXT-X-STREAM-INF")) { + addIndexInfo(isGenerateIndexFile, fos, line); + int setBand = mM3U8Option.getBandWidth(); int bandWidth = getBandWidth(line); // 多码率的m3u8配置文件,清空信息 - if (isGenerateIndexFile && mInfos != null) { - mInfos.clear(); - } + //if (isGenerateIndexFile && mInfos != null) { + // mInfos.clear(); + //} if (setBand == 0) { handleBandWidth(conn, reader.readLine()); } else if (bandWidth == setBand) { handleBandWidth(conn, reader.readLine()); } else { - failDownload(String.format("【%s】码率不存在", bandWidth), false); + failDownload(String.format("【%s】码率不存在", setBand), false); } return; - } else if (line.startsWith("EXT-X-KEY")) { - getKeyInfo(line); } + + if (line.startsWith("#EXT-X-KEY")) { + addIndexInfo(isGenerateIndexFile, fos, line); + getKeyInfo(tsListUrl, line); + continue; + } + addIndexInfo(isGenerateIndexFile, fos, line); } if (!isLive && extInf.isEmpty()) { @@ -174,56 +213,44 @@ final class M3U8InfoThread implements Runnable { } CompleteInfo info = new CompleteInfo(); info.obj = extInf; - generateIndexFile(); - onFileInfoCallback.onComplete(mEntity.getKey(), info); + onSucceed(info); + if (fos != null) { + fos.close(); + } } else if (code == HttpURLConnection.HTTP_MOVED_TEMP || code == HttpURLConnection.HTTP_MOVED_PERM || code == HttpURLConnection.HTTP_SEE_OTHER || code == HttpURLConnection.HTTP_CREATED // 201 跳转 || code == 307) { handleUrlReTurn(conn, conn.getHeaderField("Location")); - } else if (code == HttpURLConnection.HTTP_NOT_FOUND) { - failDownload("404错误", false); + } else if (code >= HttpURLConnection.HTTP_BAD_REQUEST) { + failDownload("下载失败错误,错误码:" + code, false); } else { failDownload(String.format("不支持的响应,code: %s", code), true); } } + private void onSucceed(CompleteInfo info) { + if (isStop) { + return; + } + mCallback.onSucceed(mEntity.getKey(), info); + } + /** - * 创建索引文件 + * 添加切片信息到索引文件中 + * 直播下载的索引只记录头部信息,不记录EXTINF中的信息,该信息在onGetPeer的方法中添加。 + * 点播下载记录所有信息 + * + * @param write true 将信息写入文件 + * @param info 切片信息 */ - private void generateIndexFile() { - if (mTaskWrapper.getEntity().getM3U8Entity().isGenerateIndexFile()) { - - String indexPath = String.format(M3U8_INDEX_FORMAT, mEntity.getFilePath()); - File indexFile = new File(indexPath); - if (indexFile.exists()) { - FileUtil.deleteFile(indexPath); - } - FileUtil.createFile(indexPath); - - FileOutputStream fos = null; - try { - fos = new FileOutputStream(indexFile); - for (String str : mInfos) { - byte[] by = str.concat("\r\n").getBytes(Charset.forName("UTF-8")); - fos.write(by, 0, by.length); - } - fos.flush(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } + private void addIndexInfo(boolean write, FileOutputStream fos, String info) + throws IOException { + if (!write) { + return; } + fos.write(info.concat("\r\n").getBytes(Charset.forName("UTF-8"))); } /** @@ -243,7 +270,7 @@ final class M3U8InfoThread implements Runnable { /** * 获取加密的密钥信息 */ - private void getKeyInfo(String line) { + private void getKeyInfo(String tsListUrl, String line) { String temp = line.substring(line.indexOf(":") + 1); String[] params = temp.split(","); M3U8Entity m3U8Entity = mEntity.getM3U8Entity(); @@ -252,13 +279,23 @@ final class M3U8InfoThread implements Runnable { m3U8Entity.method = param.split("=")[1]; } else if (param.startsWith("URI")) { m3U8Entity.keyUrl = param.split("=")[1].replaceAll("\"", ""); - m3U8Entity.keyPath = new File(mEntity.getFilePath()).getParent() + "/" + CommonUtil.getStrMd5( - m3U8Entity.keyUrl) + ".key"; + String keyPath; + if (((M3U8TaskOption) mTaskWrapper.getM3u8Option()).getKeyPath() == null) { + keyPath = new File(mEntity.getFilePath()).getParent() + "/" + + CommonUtil.getStrMd5(m3U8Entity.keyUrl) + ".key"; + } else { + keyPath = ((M3U8TaskOption) mTaskWrapper.getM3u8Option()).getKeyPath(); + } + m3U8Entity.keyPath = keyPath; } else if (param.startsWith("IV")) { m3U8Entity.iv = param.split("=")[1]; + } else if (param.startsWith("KEYFORMAT")) { + m3U8Entity.keyFormat = param.split("=")[1]; + } else if (param.startsWith("KEYFORMATVERSIONS")) { + m3U8Entity.keyFormatVersion = param.split("=")[1]; } } - downloadKey(m3U8Entity); + downloadKey(tsListUrl, m3U8Entity); } /** @@ -279,8 +316,8 @@ final class M3U8InfoThread implements Runnable { private void handleUrlReTurn(HttpURLConnection conn, String newUrl) throws IOException { ALog.d(TAG, "30x跳转,新url为【" + newUrl + "】"); if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) { - if (onFileInfoCallback != null) { - onFileInfoCallback.onFail(mEntity, new TaskException(TAG, "获取重定向链接失败"), false); + if (mCallback != null) { + mCallback.onFail(mEntity, new AriaM3U8Exception("获取重定向链接失败"), false); } return; } @@ -290,22 +327,22 @@ final class M3U8InfoThread implements Runnable { newUrl = uri.getHost() + newUrl; } - if (!CheckUtil.checkUrlNotThrow(newUrl)) { + if (!CheckUtil.checkUrl(newUrl)) { failDownload("下载失败,重定向url错误", false); return; } - mTaskDelegate.setRedirectUrl(newUrl); + mHttpOption.setRedirectUrl(newUrl); mEntity.setRedirect(true); mEntity.setRedirectUrl(newUrl); String cookies = conn.getHeaderField("Set-Cookie"); conn.disconnect(); // 关闭上一个连接 - URL url = ConnectionHelp.handleUrl(newUrl, mTaskDelegate); - conn = ConnectionHelp.handleConnection(url, mTaskDelegate); - ConnectionHelp.setConnectParam(mTaskDelegate, conn); + URL url = ConnectionHelp.handleUrl(newUrl, mHttpOption); + conn = ConnectionHelp.handleConnection(url, mHttpOption); + ConnectionHelp.setConnectParam(mHttpOption, conn); conn.setRequestProperty("Cookie", cookies); conn.setConnectTimeout(mConnectTimeOut); conn.connect(); - handleConnect(conn); + handleConnect(newUrl, conn); conn.disconnect(); } @@ -313,9 +350,10 @@ final class M3U8InfoThread implements Runnable { * 处理码率 */ private void handleBandWidth(HttpURLConnection conn, String bandWidthM3u8Url) throws IOException { - IBandWidthUrlConverter converter = mTaskWrapper.asM3U8().getBandWidthUrlConverter(); + IBandWidthUrlConverter converter = mM3U8Option.isUseDefConvert() ? new BandWidthDefConverter() + : mM3U8Option.getBandWidthUrlConverter(); if (converter != null) { - bandWidthM3u8Url = converter.convert(bandWidthM3u8Url); + bandWidthM3u8Url = converter.convert(mEntity.getUrl(), bandWidthM3u8Url); if (!bandWidthM3u8Url.startsWith("http")) { failDownload(String.format("码率转换器转换后的url地址无效,转换后的url:%s", bandWidthM3u8Url), false); return; @@ -323,44 +361,58 @@ final class M3U8InfoThread implements Runnable { } else { ALog.d(TAG, "没有设置码率转换器"); } - mTaskWrapper.asM3U8().setBandWidthUrl(bandWidthM3u8Url); + mM3U8Option.setBandWidthUrl(bandWidthM3u8Url); ALog.d(TAG, String.format("新码率url:%s", bandWidthM3u8Url)); String cookies = conn.getHeaderField("Set-Cookie"); conn.disconnect(); // 关闭上一个连接 - URL url = ConnectionHelp.handleUrl(bandWidthM3u8Url, mTaskDelegate); - conn = ConnectionHelp.handleConnection(url, mTaskDelegate); - ConnectionHelp.setConnectParam(mTaskDelegate, conn); + URL url = ConnectionHelp.handleUrl(bandWidthM3u8Url, mHttpOption); + conn = ConnectionHelp.handleConnection(url, mHttpOption); + ConnectionHelp.setConnectParam(mHttpOption, conn); conn.setRequestProperty("Cookie", cookies); conn.setConnectTimeout(mConnectTimeOut); conn.connect(); - handleConnect(conn); + handleConnect(bandWidthM3u8Url, conn); conn.disconnect(); } private void failDownload(String errorInfo, boolean needRetry) { - onFileInfoCallback.onFail(mEntity, new M3U8Exception(TAG, errorInfo), needRetry); + if (isStop) { + return; + } + mCallback.onFail(mEntity, new AriaM3U8Exception(errorInfo), needRetry); } /** * 密钥不存在,下载密钥 */ - private void downloadKey(M3U8Entity info) { + private void downloadKey(String tsListUr, M3U8Entity info) { HttpURLConnection conn = null; FileOutputStream fos = null; try { File keyF = new File(info.keyPath); if (!keyF.exists()) { ALog.d(TAG, "密钥不存在,下载密钥"); - FileUtil.createFile(keyF.getPath()); + FileUtil.createFile(keyF); } else { return; } - URL url = ConnectionHelp.handleUrl(info.keyUrl, mTaskDelegate); - conn = ConnectionHelp.handleConnection(url, mTaskDelegate); - ConnectionHelp.setConnectParam(mTaskDelegate, conn); + + IKeyUrlConverter keyUrlConverter = mM3U8Option.getKeyUrlConverter(); + String keyUrl = info.keyUrl; + if (keyUrlConverter != null) { + keyUrl = keyUrlConverter.convert(mEntity.getUrl(), tsListUr, keyUrl); + } + if (TextUtils.isEmpty(keyUrl)) { + ALog.e(TAG, "m3u8密钥key url 为空"); + return; + } + + URL url = ConnectionHelp.handleUrl(keyUrl, mHttpOption); + conn = ConnectionHelp.handleConnection(url, mHttpOption); + ConnectionHelp.setConnectParam(mHttpOption, conn); conn.setConnectTimeout(mConnectTimeOut); conn.connect(); - InputStream is = conn.getInputStream(); + InputStream is = ConnectionHelp.convertInputStream(conn); fos = new FileOutputStream(keyF); byte[] buffer = new byte[1024]; int len; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/M3U8Listener.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8Listener.java similarity index 67% rename from Aria/src/main/java/com/arialyy/aria/core/download/M3U8Listener.java rename to M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8Listener.java index 6c46eae1..9f2ce537 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/M3U8Listener.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8Listener.java @@ -13,24 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download; +package com.arialyy.aria.m3u8; import android.os.Bundle; -import android.os.Handler; import android.os.Message; -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.core.inf.TaskSchedulerType; +import com.arialyy.aria.core.listener.BaseListener; +import com.arialyy.aria.core.listener.IDLoadListener; +import com.arialyy.aria.core.listener.ISchedulers; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.DeleteM3u8Record; /** * 下载监听类 */ -public class M3U8Listener extends BaseDListener implements IDownloadListener { - - M3U8Listener(DownloadTask task, Handler outHandler) { - super(task, outHandler); - } +public final class M3U8Listener extends BaseListener implements IDLoadListener { @Override public void onPostPre(long fileSize) { @@ -40,6 +39,12 @@ public class M3U8Listener extends BaseDListener implements IDownloadListener { sendInState2Target(ISchedulers.POST_PRE); } + @Override public void supportBreakpoint(boolean support) { + if (!support) { + sendInState2Target(ISchedulers.NO_SUPPORT_BREAK_POINT); + } + } + /** * 切片开始下载 */ @@ -72,4 +77,15 @@ public class M3U8Listener extends BaseDListener implements IDownloadListener { msg.arg1 = ISchedulers.IS_M3U8_PEER; msg.sendToTarget(); } + + @Override protected void handleCancel() { + int sType = getTask(DownloadTask.class).getSchedulerType(); + if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { + mEntity.setComplete(false); + mEntity.setState(IEntity.STATE_WAIT); + DeleteM3u8Record.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), false); + } else { + DeleteM3u8Record.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), true); + } + } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8TaskOption.java similarity index 64% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java rename to M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8TaskOption.java index c1854005..79be6aae 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8TaskOption.java @@ -13,15 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; - +package com.arialyy.aria.m3u8; + +import com.arialyy.aria.core.inf.ITaskOption; +import com.arialyy.aria.core.processor.IBandWidthUrlConverter; +import com.arialyy.aria.core.processor.IKeyUrlConverter; +import com.arialyy.aria.core.processor.ILiveTsUrlConverter; +import com.arialyy.aria.core.processor.ITsMergeHandler; +import com.arialyy.aria.core.processor.IVodTsUrlConverter; import java.lang.ref.SoftReference; import java.util.List; /** * m3u8任务配信息 */ -public class M3U8TaskConfig { +public final class M3U8TaskOption implements ITaskOption { /** * 所有ts文件的下载地址 @@ -46,7 +52,7 @@ public class M3U8TaskConfig { /** * 合并处理器 */ - private ITsMergeHandler mergeHandler; + private SoftReference mergeHandler; /** * 已完成的ts分片数量 @@ -96,14 +102,63 @@ public class M3U8TaskConfig { /** * 生成索引占位字段 */ - private boolean generateIndexFileTemp; + private boolean generateIndexFile = false; + + /** + * 加密密钥的解密处理器 + */ + private SoftReference keyUrlConverter; + + /** + * 忽略下载失败的ts切片。 + * true:即使有失败的切片,下载完成后也要合并所有切片,并进入complete回调 + */ + private boolean ignoreFailureTs = false; + + /** + * 密钥文件保存路径 + */ + private String keyPath; + + /** + * 是否使用默认的码率转换器和Ts转换器 + */ + private boolean useDefConvert = false; + + public boolean isUseDefConvert() { + return useDefConvert; + } + + public void setUseDefConvert(boolean useDefConvert) { + this.useDefConvert = useDefConvert; + } + + public String getKeyPath() { + return keyPath; + } + + public boolean isIgnoreFailureTs() { + return ignoreFailureTs; + } + + public void setIgnoreFailureTs(boolean ignoreFailureTs) { + this.ignoreFailureTs = ignoreFailureTs; + } + + public IKeyUrlConverter getKeyUrlConverter() { + return keyUrlConverter == null ? null : keyUrlConverter.get(); + } + + public void setKeyUrlConverter(IKeyUrlConverter keyUrlConverter) { + this.keyUrlConverter = new SoftReference<>(keyUrlConverter); + } - public boolean isGenerateIndexFileTemp() { - return generateIndexFileTemp; + public boolean isGenerateIndexFile() { + return generateIndexFile; } - public void setGenerateIndexFileTemp(boolean generateIndexFileTemp) { - this.generateIndexFileTemp = generateIndexFileTemp; + public void setGenerateIndexFile(boolean generateIndexFile) { + this.generateIndexFile = generateIndexFile; } public int getJumpIndex() { @@ -115,7 +170,7 @@ public class M3U8TaskConfig { } public int getMaxTsQueueNum() { - return maxTsQueueNum; + return maxTsQueueNum == 0 ? 4 : maxTsQueueNum; } public void setMaxTsQueueNum(int maxTsQueueNum) { @@ -123,7 +178,7 @@ public class M3U8TaskConfig { } public long getLiveUpdateInterval() { - return liveUpdateInterval; + return liveUpdateInterval == 0 ? 10 * 1000 : liveUpdateInterval; } public void setLiveUpdateInterval(long liveUpdateInterval) { @@ -131,7 +186,7 @@ public class M3U8TaskConfig { } public ILiveTsUrlConverter getLiveTsUrlConverter() { - return liveTsUrlConverter.get(); + return liveTsUrlConverter == null ? null : liveTsUrlConverter.get(); } public void setLiveTsUrlConverter(ILiveTsUrlConverter liveTsUrlConverter) { @@ -187,15 +242,15 @@ public class M3U8TaskConfig { } public ITsMergeHandler getMergeHandler() { - return mergeHandler; + return mergeHandler == null ? null : mergeHandler.get(); } public void setMergeHandler(ITsMergeHandler mergeHandler) { - this.mergeHandler = mergeHandler; + this.mergeHandler = new SoftReference<>(mergeHandler); } public IVodTsUrlConverter getVodUrlConverter() { - return vodUrlConverter.get(); + return vodUrlConverter == null ? null : vodUrlConverter.get(); } public void setVodUrlConverter(IVodTsUrlConverter vodUrlConverter) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java similarity index 51% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java rename to M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java index 6b9365d3..6e30ab2b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/M3U8ThreadTaskAdapter.java @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.m3u8; -import com.arialyy.aria.core.common.AbsThreadTask; +import android.net.Uri; +import android.text.TextUtils; import com.arialyy.aria.core.common.RequestEnum; import com.arialyy.aria.core.common.SubThreadConfig; -import com.arialyy.aria.core.common.http.HttpTaskConfig; -import com.arialyy.aria.core.config.DownloadConfig; -import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.downloader.ConnectionHelp; -import com.arialyy.aria.exception.AriaIOException; -import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.core.task.AbsThreadTaskAdapter; +import com.arialyy.aria.exception.AriaM3U8Exception; +import com.arialyy.aria.http.ConnectionHelp; +import com.arialyy.aria.http.HttpTaskOption; import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CheckUtil; +import com.arialyy.aria.util.CommonUtil; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -45,37 +46,34 @@ import java.util.Set; /** * Created by lyy on 2017/1/18. 下载线程 */ -final class M3U8ThreadTask extends AbsThreadTask { - private final String TAG = "M3U8ThreadTask"; +public final class M3U8ThreadTaskAdapter extends AbsThreadTaskAdapter { + private final String TAG = CommonUtil.getClassName(this); + private HttpTaskOption mHttpTaskOption; + private BufferedInputStream is = null; - M3U8ThreadTask(SubThreadConfig config) { + public M3U8ThreadTaskAdapter(SubThreadConfig config) { super(config); + mHttpTaskOption = (HttpTaskOption) getTaskWrapper().getTaskOption(); } - @Override public M3U8ThreadTask call() throws Exception { - super.call(); - if (mRecord.isComplete) { + @Override protected void handlerThreadTask() { + if (getThreadRecord().isComplete) { handleComplete(); - return this; + return; } HttpURLConnection conn = null; - BufferedInputStream is = null; try { - HttpTaskConfig taskDelegate = getTaskWrapper().asHttp(); - URL url = ConnectionHelp.handleUrl(getConfig().url, taskDelegate); - conn = ConnectionHelp.handleConnection(url, taskDelegate); - ALog.d(TAG, String.format("分片【%s】开始下载", mRecord.threadId)); - ConnectionHelp.setConnectParam(taskDelegate, conn); - conn.setConnectTimeout(getTaskConfig().getConnectTimeOut()); - conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数 - if (taskDelegate.isChunked()) { + URL url = ConnectionHelp.handleUrl(getThreadConfig().url, mHttpTaskOption); + conn = ConnectionHelp.handleConnection(url, mHttpTaskOption); + ALog.d(TAG, String.format("分片【%s】开始下载", getThreadRecord().threadId)); + + if (mHttpTaskOption.isChunked()) { conn.setDoInput(true); conn.setChunkedStreamingMode(0); } - conn.connect(); // 传递参数 - if (taskDelegate.getRequestEnum() == RequestEnum.POST) { - Map params = taskDelegate.getParams(); + if (mHttpTaskOption.getRequestEnum() == RequestEnum.POST) { + Map params = mHttpTaskOption.getParams(); if (params != null) { OutputStreamWriter dos = new OutputStreamWriter(conn.getOutputStream()); Set keys = params.keySet(); @@ -91,24 +89,19 @@ final class M3U8ThreadTask extends AbsThreadTask { } } - is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); - if (taskDelegate.isChunked()) { - readChunked(is); - } else if (getConfig().isOpenDynamicFile) { - readDynamicFile(is); - } + handleConn(conn); } catch (MalformedURLException e) { - fail(mChildCurrentLocation, new TaskException(TAG, - String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId, - getConfig().tempFile.getPath(), getEntity().getUrl()), e)); + fail(new AriaM3U8Exception( + String.format("分片【%s】下载失败,filePath: %s, url: %s", getThreadRecord().threadId, + getThreadConfig().tempFile.getPath(), getEntity().getUrl()), e), false); } catch (IOException e) { - fail(mChildCurrentLocation, new TaskException(TAG, - String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId, - getConfig().tempFile.getPath(), getEntity().getUrl()), e)); + fail(new AriaM3U8Exception( + String.format("分片【%s】下载失败,filePath: %s, url: %s", getThreadRecord().threadId, + getThreadConfig().tempFile.getPath(), getEntity().getUrl()), e), true); } catch (Exception e) { - fail(mChildCurrentLocation, new TaskException(TAG, - String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId, - getConfig().tempFile.getPath(), getEntity().getUrl()), e)); + fail(new AriaM3U8Exception( + String.format("分片【%s】下载失败,filePath: %s, url: %s", getThreadRecord().threadId, + getThreadConfig().tempFile.getPath(), getEntity().getUrl()), e), false); } finally { try { if (is != null) { @@ -120,9 +113,68 @@ final class M3U8ThreadTask extends AbsThreadTask { } catch (IOException e) { e.printStackTrace(); } - onThreadComplete(); } - return this; + } + + private void handleConn(HttpURLConnection conn) throws IOException { + ConnectionHelp.setConnectParam(mHttpTaskOption, conn); + conn.setConnectTimeout(getTaskConfig().getConnectTimeOut()); + conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数 + + conn.connect(); + int code = conn.getResponseCode(); + if (code == HttpURLConnection.HTTP_OK) { + is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); + if (mHttpTaskOption.isChunked()) { + readChunked(is); + } else if (getThreadConfig().isBlock) { + readDynamicFile(is); + } + } else if (code == HttpURLConnection.HTTP_MOVED_TEMP + || code == HttpURLConnection.HTTP_MOVED_PERM + || code == HttpURLConnection.HTTP_SEE_OTHER + || code == HttpURLConnection.HTTP_CREATED // 201 跳转 + || code == 307) { + handleUrlReTurn(conn, conn.getHeaderField("Location")); + } else { + fail(new AriaM3U8Exception( + String.format("连接错误,http错误码:%s,url:%s", code, getThreadConfig().url)), + false); + } + conn.disconnect(); + } + + /** + * 处理30x跳转 + */ + private void handleUrlReTurn(HttpURLConnection conn, String newUrl) throws IOException { + ALog.d(TAG, "30x跳转,新url为【" + newUrl + "】"); + if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null")) { + fail(new AriaM3U8Exception("下载失败,重定向url为空"), false); + return; + } + + if (newUrl.startsWith("/")) { + Uri uri = Uri.parse(getThreadConfig().url); + newUrl = uri.getHost() + newUrl; + } + + if (!CheckUtil.checkUrl(newUrl)) { + fail(new AriaM3U8Exception("下载失败,重定向url错误"), false); + return; + } + String cookies = conn.getHeaderField("Set-Cookie"); + conn.disconnect(); // 关闭上一个连接 + URL url = ConnectionHelp.handleUrl(newUrl, mHttpTaskOption); + conn = ConnectionHelp.handleConnection(url, mHttpTaskOption); + if (!TextUtils.isEmpty(cookies)) { + conn.setRequestProperty("Cookie", cookies); + } + if (mHttpTaskOption.isChunked()) { + conn.setDoInput(true); + conn.setChunkedStreamingMode(0); + } + handleConn(conn); } /** @@ -131,11 +183,11 @@ final class M3U8ThreadTask extends AbsThreadTask { private void readChunked(InputStream is) { FileOutputStream fos = null; try { - fos = new FileOutputStream(getConfig().tempFile, true); + fos = new FileOutputStream(getThreadConfig().tempFile, true); byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; - while (isLive() && (len = is.read(buffer)) != -1) { - if (isBreak()) { + while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) { + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { @@ -146,10 +198,9 @@ final class M3U8ThreadTask extends AbsThreadTask { } handleComplete(); } catch (IOException e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("文件下载失败,savePath: %s, url: %s", getConfig().tempFile.getPath(), - getConfig().url), - e)); + fail(new AriaM3U8Exception( + String.format("文件下载失败,savePath: %s, url: %s", getThreadConfig().tempFile.getPath(), + getThreadConfig().url), e), true); } finally { if (fos != null) { try { @@ -170,14 +221,14 @@ final class M3U8ThreadTask extends AbsThreadTask { ReadableByteChannel fic = null; try { int len; - fos = new FileOutputStream(getConfig().tempFile, true); + fos = new FileOutputStream(getThreadConfig().tempFile, true); foc = fos.getChannel(); fic = Channels.newChannel(is); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); //如果要通过 Future 的 cancel 方法取消正在运行的任务,那么该任务必定是可以 对线程中断做出响应 的任务。 - while (isLive() && (len = fic.read(bf)) != -1) { - if (isBreak()) { + while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) { + if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { @@ -190,10 +241,9 @@ final class M3U8ThreadTask extends AbsThreadTask { } handleComplete(); } catch (IOException e) { - fail(mChildCurrentLocation, new AriaIOException(TAG, - String.format("文件下载失败,savePath: %s, url: %s", getConfig().tempFile.getPath(), - getConfig().url), - e)); + fail(new AriaM3U8Exception( + String.format("文件下载失败,savePath: %s, url: %s", getThreadConfig().tempFile.getPath(), + getThreadConfig().url), e), true); } finally { try { if (fos != null) { @@ -212,32 +262,17 @@ final class M3U8ThreadTask extends AbsThreadTask { } } + private DownloadEntity getEntity() { + return (DownloadEntity) getTaskWrapper().getEntity(); + } + /** * 处理完成配置文件的更新或事件回调 */ private void handleComplete() { - if (isBreak()) { + if (getThreadTask().isBreak()) { return; } - if (getTaskWrapper().asHttp().isChunked()) { - sendCompleteMsg(); - return; - } - - //支持断点的处理 - if (mTaskWrapper.isSupportBP()) { - writeConfig(true, getConfig().tempFile.length()); - sendCompleteMsg(); - } else { - sendCompleteMsg(); - } - } - - @Override public int getMaxSpeed() { - return mAridManager.getDownloadConfig().getMaxSpeed(); - } - - @Override protected DownloadConfig getTaskConfig() { - return getTaskWrapper().getConfig(); + complete(); } } diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java new file mode 100644 index 00000000..f76d1507 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveRecordHandler.java @@ -0,0 +1,101 @@ +/* + * 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.m3u8.live; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.m3u8.M3U8TaskOption; +import com.arialyy.aria.util.DeleteM3u8Record; +import com.arialyy.aria.util.RecordUtil; +import java.util.ArrayList; + +/** + * 直播m3u8文件处理器 + */ +final class LiveRecordHandler extends RecordHandler { + private M3U8TaskOption mOption; + + LiveRecordHandler(AbsTaskWrapper wrapper) { + super(wrapper); + } + + public void setOption(M3U8TaskOption option) { + mOption = option; + } + + @Override public void onPre() { + super.onPre(); + DeleteM3u8Record.getInstance().deleteRecord(getEntity().getFilePath(), true, true); + } + + /** + * @deprecated 直播文件不需要处理任务记录 + */ + @Deprecated + @Override public void handlerTaskRecord(TaskRecord record) { + if (record.threadRecords == null) { + record.threadRecords = new ArrayList<>(); + } + } + + /** + * @deprecated 交由{@link #createThreadRecord(TaskRecord, String, int)} 处理 + */ + @Override + @Deprecated + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + return null; + } + + /** + * 创建线程记录 + * + * @param taskRecord 任务记录 + * @param tsUrl ts下载地址 + * @param threadId 线程id + */ + ThreadRecord createThreadRecord(TaskRecord taskRecord, String tsUrl, int threadId) { + ThreadRecord tr = new ThreadRecord(); + tr.taskKey = taskRecord.filePath; + tr.isComplete = false; + tr.tsUrl = tsUrl; + tr.threadType = taskRecord.taskType; + tr.threadId = threadId; + tr.startLocation = 0; + taskRecord.threadRecords.add(tr); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + record.isBlock = true; + record.taskType = ITaskWrapper.M3U8_LIVE; + record.bandWidth = mOption.getBandWidth(); + return record; + } + + @Override public int initTaskThreadNum() { + return 1; + } +} diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveStateManager.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveStateManager.java new file mode 100644 index 00000000..03f28116 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveStateManager.java @@ -0,0 +1,167 @@ +/* + * 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.m3u8.live; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.listener.ISchedulers; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.m3u8.M3U8Listener; +import com.arialyy.aria.m3u8.M3U8TaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; + +import static com.arialyy.aria.m3u8.M3U8InfoTask.M3U8_INDEX_FORMAT; + +final class LiveStateManager implements IThreadStateManager { + private final String TAG = CommonUtil.getClassName(getClass()); + + private M3U8Listener mListener; + private long mProgress; //当前总进度 + private Looper mLooper; + private DTaskWrapper mTaskWrapper; + private M3U8TaskOption mM3U8Option; + private FileOutputStream mIndexFos; + private M3U8LiveLoader mLoader; + + /** + * @param listener 任务事件 + */ + LiveStateManager(DTaskWrapper wrapper, IEventListener listener) { + mTaskWrapper = wrapper; + mListener = (M3U8Listener) listener; + mM3U8Option = (M3U8TaskOption) mTaskWrapper.getM3u8Option(); + } + + private Handler.Callback mCallback = new Handler.Callback() { + @Override public boolean handleMessage(Message msg) { + int peerIndex = msg.getData().getInt(ISchedulers.DATA_M3U8_PEER_INDEX); + switch (msg.what) { + case STATE_STOP: + if (mLoader.isBreak()) { + ALog.d(TAG, "任务停止"); + quitLooper(); + } + break; + case STATE_CANCEL: + if (mLoader.isBreak()) { + ALog.d(TAG, "任务取消"); + quitLooper(); + } + break; + case STATE_COMPLETE: + mLoader.notifyLock(true, peerIndex); + if (mM3U8Option.isGenerateIndexFile() && !mLoader.isBreak()) { + addExtInf(mLoader.getCurExtInfo().url, mLoader.getCurExtInfo().extInf); + } + mListener.onPeerComplete(mTaskWrapper.getKey(), + msg.getData().getString(ISchedulers.DATA_M3U8_PEER_PATH), peerIndex); + break; + case STATE_RUNNING: + Bundle b = msg.getData(); + if (b != null) { + long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0); + mProgress += len; + } + break; + case STATE_FAIL: + mLoader.notifyLock(false, peerIndex); + mListener.onPeerFail(mTaskWrapper.getKey(), + msg.getData().getString(ISchedulers.DATA_M3U8_PEER_PATH), peerIndex); + break; + } + return true; + } + }; + + void setLoader(M3U8LiveLoader loader) { + mLoader = loader; + } + + /** + * 退出looper循环 + */ + private void quitLooper() { + ALog.d(TAG, "quitLooper"); + mLooper.quit(); + if (mIndexFos != null) { + try { + mIndexFos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 给索引文件添加extInfo信息 + */ + private void addExtInf(String url, String extInf) { + File indexFile = + new File(String.format(M3U8_INDEX_FORMAT, mTaskWrapper.getEntity().getFilePath())); + if (!indexFile.exists()) { + ALog.e(TAG, String.format("索引文件【%s】不存在,添加peer的extInf失败", indexFile.getPath())); + return; + } + try { + if (mIndexFos == null) { + mIndexFos = new FileOutputStream(indexFile, true); + } + mIndexFos.write(extInf.concat("\r\n").getBytes(Charset.forName("UTF-8"))); + mIndexFos.write(url.concat("\r\n").getBytes(Charset.forName("UTF-8"))); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override public boolean isFail() { + return false; + } + + @Override public boolean isComplete() { + return false; + } + + @Override public long getCurrentProgress() { + return mProgress; + } + + @Override public void updateCurrentProgress(long currentProgress) { + mProgress = currentProgress; + } + + @Override public void setLooper(TaskRecord taskRecord, Looper looper) { + mLooper = looper; + } + + @Override public Handler.Callback getHandlerCallback() { + return mCallback; + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveTsDefConverter.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveTsDefConverter.java new file mode 100644 index 00000000..ca74432e --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/LiveTsDefConverter.java @@ -0,0 +1,29 @@ +/* + * 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.m3u8.live; + +import com.arialyy.aria.core.processor.ILiveTsUrlConverter; + +/** + * 默认的m3u8 ts转换器 + */ +class LiveTsDefConverter implements ILiveTsUrlConverter { + @Override public String convert(String m3u8Url, String tsUrl) { + int index = m3u8Url.lastIndexOf("/"); + String parentUrl = m3u8Url.substring(0, index + 1); + return parentUrl + tsUrl; + } +} diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveLoader.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveLoader.java new file mode 100644 index 00000000..71409417 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveLoader.java @@ -0,0 +1,369 @@ +/* + * 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.m3u8.live; + +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.loader.IThreadTaskBuilder; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.processor.ILiveTsUrlConverter; +import com.arialyy.aria.core.processor.ITsMergeHandler; +import com.arialyy.aria.core.task.ThreadTask; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.exception.AriaM3U8Exception; +import com.arialyy.aria.m3u8.BaseM3U8Loader; +import com.arialyy.aria.m3u8.IdGenerator; +import com.arialyy.aria.m3u8.M3U8InfoTask; +import com.arialyy.aria.m3u8.M3U8Listener; +import com.arialyy.aria.m3u8.M3U8TaskOption; +import com.arialyy.aria.m3u8.M3U8ThreadTaskAdapter; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.FileUtil; +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.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * M3U8点播文件下载器 + */ +final class M3U8LiveLoader extends BaseM3U8Loader { + /** + * 最大执行数 + */ + private static int EXEC_MAX_NUM = 4; + private Handler mStateHandler; + private ArrayBlockingQueue mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM); + private ReentrantLock LOCK = new ReentrantLock(); + private Condition mCondition = LOCK.newCondition(); + private LinkedBlockingQueue mPeerQueue = new LinkedBlockingQueue<>(); + private ExtInfo mCurExtInfo; + private M3U8InfoTask mInfoTask; + private ScheduledThreadPoolExecutor mTimer; + private List mPeerUrls = new ArrayList<>(); + + M3U8LiveLoader(DTaskWrapper wrapper, M3U8Listener listener) { + super(wrapper, listener); + if (((M3U8TaskOption) wrapper.getM3u8Option()).isGenerateIndexFile()) { + ALog.i(TAG, "直播文件下载,创建索引文件的操作将导致只能同时下载一个切片"); + EXEC_MAX_NUM = 1; + } + } + + ExtInfo getCurExtInfo() { + return mCurExtInfo; + } + + private void offerPeer(ExtInfo extInfo) { + mPeerQueue.offer(extInfo); + } + + @Override protected void handleTask(Looper looper) { + if (isBreak()) { + return; + } + + // 处理记录 + getRecordHandler().setOption(mM3U8Option); + mRecord = getRecordHandler().getRecord(0); + + // 初始化状态管理器 + getStateManager().setLooper(mRecord, looper); + getStateManager().setLoader(this); + mStateHandler = new Handler(looper, getStateManager().getHandlerCallback()); + + // 循环获取直播文件列表 + startLoaderLiveInfo(); + + // 启动定时器 + startTimer(); + + 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) { + ExtInfo extInfo = mPeerQueue.poll(); + if (extInfo == null) { + break; + } + mCurExtInfo = extInfo; + ThreadTask task = createThreadTask(cacheDir, index, extInfo.url); + getTaskList().add(task); + mFlagQueue.offer(startThreadTask(task, task.getConfig().peerIndex)); + index++; + } + if (mFlagQueue.size() > 0) { + mCondition.await(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + LOCK.unlock(); + } + } + } + }).start(); + } + + @Override protected LiveStateManager getStateManager() { + return (LiveStateManager) super.getStateManager(); + } + + private LiveRecordHandler getRecordHandler() { + return (LiveRecordHandler) mRecordHandler; + } + + @Override public long getFileSize() { + return mTempFile.length(); + } + + void notifyLock(boolean success, int peerId) { + try { + LOCK.lock(); + long id = mFlagQueue.take(); + if (success) { + ALog.d(TAG, String.format("切片【%s】下载成功", peerId)); + } else { + ALog.e(TAG, String.format("切片【%s】下载失败", peerId)); + } + mCondition.signalAll(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + LOCK.unlock(); + } + } + + /** + * 启动线程任务 + * + * @return 线程唯一id标志 + */ + private long startThreadTask(ThreadTask task, int indexId) { + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task); + ((M3U8Listener) getListener()).onPeerStart(mTaskWrapper.getKey(), + task.getConfig().tempFile.getPath(), + indexId); + return IdGenerator.getInstance().nextId(); + } + + /** + * 配置config + */ + private ThreadTask createThreadTask(String cacheDir, int indexId, String tsUrl) { + ThreadRecord tr = getRecordHandler().createThreadRecord(mRecord, tsUrl, indexId); + + SubThreadConfig config = new SubThreadConfig(); + config.url = tsUrl; + config.tempFile = new File(getTsFilePath(cacheDir, indexId)); + config.isBlock = mRecord.isBlock; + config.taskWrapper = mTaskWrapper; + config.record = tr; + config.stateHandler = mStateHandler; + config.peerIndex = indexId; + config.threadType = SubThreadConfig.getThreadType(ITaskWrapper.M3U8_LIVE); + config.updateInterval = SubThreadConfig.getUpdateInterval(ITaskWrapper.M3U8_LIVE); + config.ignoreFailure = mM3U8Option.isIgnoreFailureTs(); + if (!config.tempFile.exists()) { + FileUtil.createFile(config.tempFile); + } + ThreadTask threadTask = new ThreadTask(config); + M3U8ThreadTaskAdapter adapter = new M3U8ThreadTaskAdapter(config); + threadTask.setAdapter(adapter); + return threadTask; + } + + /** + * 合并文件 + * + * @return {@code true} 合并成功,{@code false}合并失败 + */ + private boolean mergeFile() { + ITsMergeHandler mergeHandler = mM3U8Option.getMergeHandler(); + String cacheDir = getCacheDir(); + List 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(getEntity().getFilePath(), partPath); + } + if (isSuccess) { + // 合并成功,删除缓存文件 + for (String pp : partPath) { + FileUtil.deleteFile(pp); + } + File cDir = new File(cacheDir); + FileUtil.deleteDir(cDir); + return true; + } else { + ALog.e(TAG, "合并失败"); + return false; + } + } + + @Override public void addComponent(IRecordHandler recordHandler) { + mRecordHandler = recordHandler; + } + + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = (M3U8InfoTask) infoTask; + mInfoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + ALog.d(TAG, "更新直播的m3u8文件"); + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + + } + }); + + mInfoTask.setOnGetPeerCallback(new M3U8InfoTask.OnGetLivePeerCallback() { + @Override public void onGetPeer(String url, String extInf) { + if (mPeerUrls.contains(url)) { + return; + } + mPeerUrls.add(url); + ILiveTsUrlConverter converter = mM3U8Option.isUseDefConvert() ? + new LiveTsDefConverter() : + mM3U8Option.getLiveTsUrlConverter(); + if (converter != null) { + if (TextUtils.isEmpty(mM3U8Option.getBandWidthUrl())) { + url = converter.convert(getEntity().getUrl(), url); + } else { + url = converter.convert(mM3U8Option.getBandWidthUrl(), url); + } + } + if (TextUtils.isEmpty(url) || !url.startsWith("http")) { + fail(new AriaM3U8Exception(String.format("ts地址错误,url:%s", url)), false); + return; + } + offerPeer(new M3U8LiveLoader.ExtInfo(url, extInf)); + } + }); + } + + private void fail(AriaM3U8Exception e, boolean needRetry) { + getListener().onFail(needRetry, e); + handleComplete(); + } + + private void handleComplete() { + if (mInfoTask != null) { + mInfoTask.setStop(true); + closeInfoTimer(); + if (mM3U8Option.isGenerateIndexFile()) { + if (generateIndexFile(true)) { + getListener().onComplete(); + } else { + getListener().onFail(false, new AriaM3U8Exception("创建索引文件失败")); + } + } else if (mM3U8Option.isMergeFile()) { + if (mergeFile()) { + getListener().onComplete(); + } else { + getListener().onFail(false, new AriaM3U8Exception("合并文件失败")); + } + } else { + getListener().onComplete(); + } + } + } + + /** + * 开始循环加载m3u8信息 + */ + private void startLoaderLiveInfo() { + mTimer = new ScheduledThreadPoolExecutor(1); + mTimer.scheduleWithFixedDelay(new Runnable() { + @Override public void run() { + mInfoTask.run(); + } + }, 0, mM3U8Option.getLiveUpdateInterval(), TimeUnit.MILLISECONDS); + } + + private void closeInfoTimer() { + if (mTimer != null && !mTimer.isShutdown()) { + mTimer.shutdown(); + } + } + + /** + * 需要在{@link #addComponent(IRecordHandler)} 后调用 + */ + @Override public void addComponent(IThreadStateManager threadState) { + mStateManager = threadState; + } + + /** + * @deprecated m3u8 不需要实现这个 + */ + @Deprecated + @Override public void addComponent(IThreadTaskBuilder builder) { + + } + + @Override protected void checkComponent() { + if (mRecordHandler == null) { + throw new NullPointerException("任务记录组件为空"); + } + if (mInfoTask == null) { + throw new NullPointerException(("文件信息组件为空")); + } + if (mStateManager == null) { + throw new NullPointerException("任务状态管理组件为空"); + } + } + + static class ExtInfo { + String url; + String extInf; + + ExtInfo(String url, String extInf) { + this.url = url; + this.extInf = extInf; + } + } +} diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveUtil.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveUtil.java new file mode 100644 index 00000000..f6a65702 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/live/M3U8LiveUtil.java @@ -0,0 +1,61 @@ +/* + * 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.m3u8.live; + +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.http.HttpTaskOption; +import com.arialyy.aria.m3u8.M3U8InfoTask; +import com.arialyy.aria.m3u8.M3U8Listener; +import com.arialyy.aria.m3u8.M3U8TaskOption; + +/** + * M3U8直播文件下载工具,对于直播来说,需要定时更新m3u8文件 + * 工作流程: + * 1、持续获取切片信息,直到调用停止|取消才停止获取切片信息 + * 2、完成所有分片下载后,合并ts文件 + * 3、删除该隐藏文件夹 + * 4、对于直播来说是没有停止的,停止就代表完成 + * 5、不处理直播切片下载失败的状态 + */ +public class M3U8LiveUtil extends AbsNormalLoaderUtil { + + public M3U8LiveUtil() { + } + + @Override public DTaskWrapper getTaskWrapper() { + return (DTaskWrapper) super.getTaskWrapper(); + } + + @Override public M3U8LiveLoader getLoader() { + if (mLoader == null) { + getTaskWrapper().generateM3u8Option(M3U8TaskOption.class); + getTaskWrapper().generateTaskOption(HttpTaskOption.class); + mLoader = new M3U8LiveLoader(getTaskWrapper(), (M3U8Listener) getListener()); + } + return (M3U8LiveLoader) mLoader; + } + + @Override public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new LiveRecordHandler(getTaskWrapper())) + .addComponent(new M3U8InfoTask(getTaskWrapper())) + .addComponent(new LiveStateManager(getTaskWrapper(), getListener())); + structure.accept(getLoader()); + return structure; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodLoader.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodLoader.java similarity index 51% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodLoader.java rename to M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodLoader.java index 0fb68a06..a3d26062 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodLoader.java +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodLoader.java @@ -13,43 +13,53 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.m3u8.vod; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.os.Message; +import android.text.TextUtils; +import android.util.Log; import android.util.SparseArray; -import com.arialyy.aria.core.common.AbsThreadTask; -import com.arialyy.aria.core.common.IThreadState; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; 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.download.M3U8Listener; import com.arialyy.aria.core.event.Event; import com.arialyy.aria.core.event.EventMsgUtil; import com.arialyy.aria.core.event.PeerIndexEvent; -import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.loader.IThreadTaskBuilder; import com.arialyy.aria.core.manager.ThreadTaskManager; -import com.arialyy.aria.core.scheduler.ISchedulers; -import com.arialyy.aria.exception.BaseException; +import com.arialyy.aria.core.processor.IVodTsUrlConverter; +import com.arialyy.aria.core.task.ThreadTask; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.exception.AriaM3U8Exception; +import com.arialyy.aria.m3u8.BaseM3U8Loader; +import com.arialyy.aria.m3u8.M3U8Listener; +import com.arialyy.aria.m3u8.M3U8TaskOption; +import com.arialyy.aria.m3u8.M3U8ThreadTaskAdapter; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.FileUtil; import java.io.File; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * M3U8点播文件下载器 */ -public class M3U8VodLoader extends BaseM3U8Loader { +final class M3U8VodLoader extends BaseM3U8Loader { /** * 最大执行数 */ @@ -65,28 +75,58 @@ public class M3U8VodLoader extends BaseM3U8Loader { private Condition mJumpCondition = JUMP_LOCK.newCondition(); private SparseArray mBeforePeer = new SparseArray<>(); private SparseArray mAfterPeer = new SparseArray<>(); - private VodStateManager mManager; private PeerIndexEvent mCurrentEvent; private String mCacheDir; - private int aIndex = 0, bIndex = 0; - private int mCurrentFlagSize; + private AtomicInteger afterPeerIndex = new AtomicInteger(); + private AtomicInteger beforePeerIndex = new AtomicInteger(); + private AtomicInteger mCompleteNum = new AtomicInteger(); + private AtomicInteger mCurrentFlagSize = new AtomicInteger(); private boolean isJump = false, isDestroy = false; - private int mCompleteNum = 0; private ExecutorService mJumpThreadPool; private Thread jumpThread = null; - - M3U8VodLoader(M3U8Listener listener, DTaskWrapper wrapper) { - super(listener, wrapper); - mFlagQueue = new ArrayBlockingQueue<>(wrapper.asM3U8().getMaxTsQueueNum()); - EXEC_MAX_NUM = wrapper.asM3U8().getMaxTsQueueNum(); + private M3U8TaskOption mM3U8Option; + private Looper mLooper; + + M3U8VodLoader(DTaskWrapper wrapper, M3U8Listener listener) { + super(wrapper, listener); + mM3U8Option = (M3U8TaskOption) wrapper.getM3u8Option(); + mFlagQueue = new ArrayBlockingQueue<>(mM3U8Option.getMaxTsQueueNum()); + EXEC_MAX_NUM = mM3U8Option.getMaxTsQueueNum(); mJumpQueue = new ArrayBlockingQueue<>(10); EventMsgUtil.getDefault().register(this); } - @Override protected IThreadState getStateManager(Looper looper) { - mManager = new VodStateManager(looper, mRecord, mListener); - mStateHandler = new Handler(looper, mManager); - return mManager; + @Override protected M3U8Listener getListener() { + return (M3U8Listener) super.getListener(); + } + + SparseArray getBeforePeer() { + return mBeforePeer; + } + + int getCompleteNum() { + return mCompleteNum.get(); + } + + void setCompleteNum(int completeNum) { + mCompleteNum.set(completeNum); + } + + int getCurrentFlagSize() { + mCurrentFlagSize.set(mFlagQueue.size()); + return mCurrentFlagSize.get(); + } + + void setCurrentFlagSize(int currentFlagSize) { + mCurrentFlagSize.set(currentFlagSize); + } + + boolean isJump() { + return isJump; + } + + File getTempFile() { + return mTempFile; } @Override public void onDestroy() { @@ -98,16 +138,36 @@ public class M3U8VodLoader extends BaseM3U8Loader { } } - @Override protected void onPostPre() { - super.onPostPre(); - initData(); + @Override protected void handleTask(Looper looper) { + if (isBreak()) { + return; + } + mLooper = looper; + mInfoTask.run(); } - @Override public boolean isBreak() { - return super.isBreak() || isDestroy; - } + private void startThreadTask() { + // 处理任务记录 + ((VodRecordHandler) mRecordHandler).setOption(mM3U8Option); + mRecord = mRecordHandler.getRecord(0); + + // 处理任务管理器 + mStateHandler = new Handler(mLooper, getStateManager().getHandlerCallback()); + getStateManager().setVodLoader(this); + getStateManager().setLooper(mRecord, mLooper); + + // 初始化ts数据 + initData(); - @Override protected void handleTask() { + // 启动定时器 + startTimer(); + if (getStateManager().isComplete()){ + Log.d(TAG, "任务已完成"); + getStateManager().handleTaskComplete(); + return; + } + + // 启动线程开始下载ts切片 Thread th = new Thread(new Runnable() { @Override public void run() { while (!isBreak()) { @@ -126,7 +186,7 @@ public class M3U8VodLoader extends BaseM3U8Loader { try { LOCK.lock(); while (mFlagQueue.size() < EXEC_MAX_NUM && !isBreak()) { - if (mCompleteNum == mRecord.threadRecords.size()) { + if (mCompleteNum.get() == mRecord.threadRecords.size()) { break; } @@ -151,6 +211,10 @@ public class M3U8VodLoader extends BaseM3U8Loader { th.start(); } + @Override public long getFileSize() { + return getEntity().getFileSize(); + } + /** * 获取线程记录 */ @@ -158,16 +222,18 @@ public class M3U8VodLoader extends BaseM3U8Loader { ThreadRecord tr = null; try { // 优先下载peer指针之后的数据 - if (bIndex == 0 && aIndex < mAfterPeer.size()) { + if (beforePeerIndex.get() == 0 && afterPeerIndex.get() < mAfterPeer.size()) { //ALog.d(TAG, String.format("afterArray size:%s, index:%s", mAfterPeer.size(), aIndex)); - tr = mAfterPeer.valueAt(aIndex); - aIndex++; + tr = mAfterPeer.valueAt(afterPeerIndex.get()); + afterPeerIndex.getAndIncrement(); } // 如果指针之后的数组没有切片了,则重新初始化指针位置,并获取指针之前的数组获取切片进行下载 - if (mBeforePeer.size() > 0 && (tr == null || bIndex != 0) && bIndex < mBeforePeer.size()) { - tr = mBeforePeer.valueAt(bIndex); - bIndex++; + if (mBeforePeer.size() > 0 + && (tr == null || beforePeerIndex.get() != 0) + && beforePeerIndex.get() < mBeforePeer.size()) { + tr = mBeforePeer.valueAt(beforePeerIndex.get()); + beforePeerIndex.getAndIncrement(); } } catch (Exception e) { e.printStackTrace(); @@ -180,9 +246,9 @@ public class M3U8VodLoader extends BaseM3U8Loader { * 启动线程任务 */ private void addTaskToQueue(ThreadRecord tr) throws InterruptedException { - M3U8ThreadTask task = createThreadTask(mCacheDir, tr, tr.threadId); - getTaskList().put(tr.threadId, task); - mEntity.getM3U8Entity().setPeerIndex(tr.threadId); + ThreadTask task = createThreadTask(mCacheDir, tr, tr.threadId); + getTaskList().add(task); + getEntity().getM3U8Entity().setPeerIndex(tr.threadId); TempFlag flag = startThreadTask(task, tr.threadId); if (flag != null) { mFlagQueue.put(flag); @@ -194,22 +260,27 @@ public class M3U8VodLoader extends BaseM3U8Loader { */ private void initData() { mCacheDir = getCacheDir(); - if (mTaskWrapper.asM3U8().getJumpIndex() != 0) { - mCurrentEvent = - new PeerIndexEvent(mTaskWrapper.getKey(), mTaskWrapper.asM3U8().getJumpIndex()); + if (mM3U8Option.getJumpIndex() != 0) { + mCurrentEvent = new PeerIndexEvent(mTaskWrapper.getKey(), mM3U8Option.getJumpIndex()); resumeTask(); return; } // 设置需要下载的切片 - mCompleteNum = 0; + mCompleteNum.set(0); for (ThreadRecord tr : mRecord.threadRecords) { if (!tr.isComplete) { mAfterPeer.put(tr.threadId, tr); } else { - mCompleteNum++; + mCompleteNum.getAndIncrement(); } } - mManager.updateStateCount(); + getStateManager().updateStateCount(); + if (mCompleteNum.get() <= 0) { + getListener().onStart(0); + } else { + int percent = mCompleteNum.get() * 100 / mRecord.threadRecords.size(); + getListener().onResume(percent); + } } /** @@ -268,7 +339,7 @@ public class M3U8VodLoader extends BaseM3U8Loader { isJump = true; notifyWaitLock(false); - mCurrentFlagSize = mFlagQueue.size(); + mCurrentFlagSize.set(mFlagQueue.size()); // 停止所有正在执行的线程任务 try { TempFlag flag; @@ -325,9 +396,9 @@ public class M3U8VodLoader extends BaseM3U8Loader { } /** - * 重指定位置恢复任务 + * 从指定位置恢复任务 */ - private synchronized void resumeTask() { + synchronized void resumeTask() { if (isBreak()) { ALog.e(TAG, "任务已停止,恢复任务失败"); return; @@ -342,12 +413,12 @@ public class M3U8VodLoader extends BaseM3U8Loader { mBeforePeer.clear(); mAfterPeer.clear(); mFlagQueue.clear(); - aIndex = 0; - bIndex = 0; - mCompleteNum = 0; + afterPeerIndex.set(0); + beforePeerIndex.set(0); + mCompleteNum.set(0); for (ThreadRecord tr : mRecord.threadRecords) { if (tr.isComplete) { - mCompleteNum++; + mCompleteNum.getAndIncrement(); continue; } if (tr.threadId < mCurrentEvent.peerIndex) { @@ -361,7 +432,7 @@ public class M3U8VodLoader extends BaseM3U8Loader { String.format("beforeSize = %s, afterSize = %s, mCompleteNum = %s", mBeforePeer.size(), mAfterPeer.size(), mCompleteNum)); ALog.i(TAG, String.format("完成处理数据的操作,将优先下载【%s】之后的切片", mCurrentEvent.peerIndex)); - mManager.updateStateCount(); + getStateManager().updateStateCount(); try { JUMP_LOCK.lock(); @@ -371,11 +442,7 @@ public class M3U8VodLoader extends BaseM3U8Loader { } } - private M3U8Listener getListener() { - return (M3U8Listener) mListener; - } - - private void notifyWaitLock(boolean isComplete) { + void notifyWaitLock(boolean isComplete) { try { LOCK.lock(); if (isComplete) { @@ -397,7 +464,7 @@ public class M3U8VodLoader extends BaseM3U8Loader { * * @return 线程唯一id标志 */ - private TempFlag startThreadTask(M3U8ThreadTask task, int peerIndex) { + private TempFlag startThreadTask(ThreadTask task, int peerIndex) { if (isBreak()) { ALog.w(TAG, "任务已停止,启动线程任务失败"); return null; @@ -414,246 +481,116 @@ public class M3U8VodLoader extends BaseM3U8Loader { /** * 配置config */ - private M3U8ThreadTask createThreadTask(String cacheDir, ThreadRecord record, int index) { - SubThreadConfig config = new SubThreadConfig<>(); + private ThreadTask createThreadTask(String cacheDir, ThreadRecord record, int index) { + SubThreadConfig config = new SubThreadConfig(); config.url = record.tsUrl; - config.tempFile = new File(getTsFilePath(cacheDir, record.threadId)); + config.tempFile = new File(BaseM3U8Loader.getTsFilePath(cacheDir, record.threadId)); config.isBlock = mRecord.isBlock; - config.isOpenDynamicFile = mRecord.isOpenDynamicFile; config.taskWrapper = mTaskWrapper; config.record = record; config.stateHandler = mStateHandler; config.peerIndex = index; + config.threadType = SubThreadConfig.getThreadType(ITaskWrapper.M3U8_LIVE); + config.updateInterval = SubThreadConfig.getUpdateInterval(ITaskWrapper.M3U8_LIVE); + config.ignoreFailure = mM3U8Option.isIgnoreFailureTs(); if (!config.tempFile.exists()) { - FileUtil.createFile(config.tempFile.getPath()); + FileUtil.createFile(config.tempFile); } - return new M3U8ThreadTask(config); + ThreadTask threadTask = new ThreadTask(config); + M3U8ThreadTaskAdapter adapter = new M3U8ThreadTaskAdapter(config); + threadTask.setAdapter(adapter); + return threadTask; } - /** - * M3U8线程状态管理 - */ - private class VodStateManager implements IThreadState { - private final String TAG = "M3U8ThreadStateManager"; - - /** - * 任务状态回调 - */ - private IEventListener listener; - private int startThreadNum; // 启动的线程总数 - private int cancelNum = 0; // 已经取消的线程的数 - private int stopNum = 0; // 已经停止的线程数 - private int failNum = 0; // 失败的线程数 - private long progress; //当前总进度 - private TaskRecord taskRecord; // 任务记录 - private Looper looper; - - /** - * @param taskRecord 任务记录 - * @param listener 任务事件 - */ - VodStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) { - this.looper = looper; - this.taskRecord = taskRecord; - for (ThreadRecord record : taskRecord.threadRecords) { - if (!record.isComplete) { - startThreadNum++; - } - } - this.listener = listener; - } - - private void updateStateCount() { - cancelNum = 0; - stopNum = 0; - failNum = 0; - } - - /** - * 退出looper循环 - */ - private void quitLooper() { - ALog.d(TAG, "quitLooper"); - looper.quit(); - } - - @Override public boolean handleMessage(Message msg) { - int peerIndex = msg.getData().getInt(ISchedulers.DATA_M3U8_PEER_INDEX); - switch (msg.what) { - case STATE_STOP: - stopNum++; - removeSignThread((AbsThreadTask) msg.obj); - // 处理跳转位置后,恢复任务 - if (isJump && (stopNum == mCurrentFlagSize || mCurrentFlagSize == 0) && !isBreak()) { - resumeTask(); - return true; - } - - if (isBreak()) { - ALog.d(TAG, String.format("vod任务【%s】停止", mTempFile.getName())); - quitLooper(); - } - break; - case STATE_CANCEL: - cancelNum++; - removeSignThread((AbsThreadTask) msg.obj); - - if (isBreak()) { - ALog.d(TAG, String.format("vod任务【%s】取消", mTempFile.getName())); - quitLooper(); - } - break; - case STATE_FAIL: - failNum++; - for (ThreadRecord tr : mRecord.threadRecords) { - if (tr.threadId == peerIndex) { - mBeforePeer.put(peerIndex, tr); - break; - } - } - - getListener().onPeerFail(mTaskWrapper.getKey(), - msg.getData().getString(ISchedulers.DATA_M3U8_PEER_PATH), peerIndex); - if (isFail()) { - ALog.d(TAG, String.format("vod任务【%s】失败", mTempFile.getName())); - Bundle b = msg.getData(); - listener.onFail(b.getBoolean(KEY_RETRY, true), - (BaseException) b.getSerializable(KEY_ERROR_INFO)); - quitLooper(); - } - break; - case STATE_COMPLETE: - if (isBreak()) { - quitLooper(); - } - mCompleteNum++; - // 正在切换位置时,切片完成,队列大小减小 - if (isJump) { - mCurrentFlagSize--; - if (mCurrentFlagSize < 0) { - mCurrentFlagSize = 0; - } - } + @Override public void addComponent(IRecordHandler recordHandler) { + mRecordHandler = recordHandler; + } - removeSignThread((AbsThreadTask) msg.obj); - getListener().onPeerComplete(mTaskWrapper.getKey(), - msg.getData().getString(ISchedulers.DATA_M3U8_PEER_PATH), peerIndex); - handlerPercent(); - if (!isJump) { - notifyWaitLock(true); - } - if (isComplete()) { - ALog.d(TAG, String.format( - "startThreadNum = %s, stopNum = %s, cancelNum = %s, failNum = %s, completeNum = %s, flagQueueSize = %s", - startThreadNum, stopNum, cancelNum, failNum, mCompleteNum, mFlagQueue.size())); - ALog.d(TAG, String.format("vod任务【%s】完成", mTempFile.getName())); - if (mTaskWrapper.asM3U8().isMergeFile()) { - if (mergeFile()) { - listener.onComplete(); - } else { - listener.onFail(false, null); - } - } else { - listener.onComplete(); - } - quitLooper(); + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = infoTask; + final List urls = new ArrayList<>(); + mInfoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + IVodTsUrlConverter converter = mM3U8Option.isUseDefConvert() ? + new VodTsDefConverter() : + mM3U8Option.getVodUrlConverter(); + if (converter != null) { + if (TextUtils.isEmpty(mM3U8Option.getBandWidthUrl())) { + urls.addAll( + converter.convert(getEntity().getUrl(), (List) info.obj)); + } else { + urls.addAll( + converter.convert(mM3U8Option.getBandWidthUrl(), (List) info.obj)); } - break; - case STATE_RUNNING: - progress += (long) msg.obj; - break; + } else { + urls.addAll((Collection) info.obj); + } + if (urls.isEmpty()) { + fail(new AriaM3U8Exception("获取地址失败"), false); + return; + } else if (!urls.get(0).startsWith("http")) { + fail(new AriaM3U8Exception("地址错误,请使用IVodTsUrlConverter处理你的url信息"), false); + return; + } + mM3U8Option.setUrls(urls); + + if (isStop) { + getListener().onStop(getEntity().getCurrentProgress()); + } else if (isCancel) { + getListener().onCancel(); + } else { + startThreadTask(); + } } - return true; - } - private void removeSignThread(AbsThreadTask threadTask) { - int index = getTaskList().indexOfValue(threadTask); - if (index != -1) { - getTaskList().removeAt(index); + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + fail(e, needRetry); } - ThreadTaskManager.getInstance().removeSingleTaskThread(mTaskWrapper.getKey(), threadTask); - } - - /** - * 设置进度 - */ - private void handlerPercent() { - int completeNum = mTaskWrapper.asM3U8().getCompleteNum(); - completeNum++; - mTaskWrapper.asM3U8().setCompleteNum(completeNum); - int percent = completeNum * 100 / taskRecord.threadRecords.size(); - mEntity.setPercent(percent); - mEntity.update(); - } + }); + } - @Override public boolean isFail() { - printInfo("isFail"); - return failNum != 0 && failNum == mFlagQueue.size() && !isJump; + protected void fail(AriaException e, boolean needRetry) { + if (isBreak()) { + return; } + getListener().onFail(needRetry, e); + onDestroy(); + } - @Override public boolean isComplete() { - return mCompleteNum == taskRecord.threadRecords.size() && !isJump; - } + /** + * 需要在 {@link #addComponent(IRecordHandler)}后调用 + */ + @Override public void addComponent(IThreadStateManager threadState) { + mStateManager = threadState; + } - @Override public long getCurrentProgress() { - return progress; - } + /** + * m3u8 不需要实现这个 + */ + @Deprecated + @Override public void addComponent(IThreadTaskBuilder builder) { - private void printInfo(String tag) { - if (false) { - ALog.d(tag, String.format( - "startThreadNum = %s, stopNum = %s, cancelNum = %s, failNum = %s, completeNum = %s, flagQueueSize = %s", - startThreadNum, stopNum, cancelNum, failNum, mCompleteNum, mFlagQueue.size())); - } - } + } - /** - * 合并文件 - * - * @return {@code true} 合并成功,{@code false}合并失败 - */ - private boolean mergeFile() { - if (getEntity().getM3U8Entity().isGenerateIndexFile()) { - return generateIndexFile(); - } - ITsMergeHandler mergeHandler = mTaskWrapper.asM3U8().getMergeHandler(); - String cacheDir = getCacheDir(); - List partPath = new ArrayList<>(); - for (ThreadRecord tr : taskRecord.threadRecords) { - partPath.add(getTsFilePath(cacheDir, tr.threadId)); - } - boolean isSuccess; - if (mergeHandler != null) { - isSuccess = mergeHandler.merge(getEntity().getM3U8Entity(), partPath); + @Override + protected VodStateManager getStateManager() { + return (VodStateManager) mStateManager; + } - if (mergeHandler.getClass().isAnonymousClass()) { - mTaskWrapper.asM3U8().setMergeHandler(null); - } - } else { - isSuccess = FileUtil.mergeFile(taskRecord.filePath, partPath); - } - if (isSuccess) { - // 合并成功,删除缓存文件 - File[] files = new File(cacheDir).listFiles(); - for (File f : files) { - if (f.exists()) { - f.delete(); - } - } - File cDir = new File(cacheDir); - if (cDir.exists()) { - cDir.delete(); - } - return true; - } else { - ALog.e(TAG, "合并失败"); - return false; - } + @Override protected void checkComponent() { + if (mRecordHandler == null) { + throw new NullPointerException("任务记录组件为空"); + } + if (mInfoTask == null) { + throw new NullPointerException(("文件信息组件为空")); + } + if (getStateManager() == null) { + throw new NullPointerException("任务状态管理组件为空"); } } private static class TempFlag { - AbsThreadTask threadTask; + ThreadTask threadTask; int threadId; } } diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodUtil.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodUtil.java new file mode 100644 index 00000000..afa03ad4 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/M3U8VodUtil.java @@ -0,0 +1,61 @@ +/* + * 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.m3u8.vod; + +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.http.HttpTaskOption; +import com.arialyy.aria.m3u8.M3U8InfoTask; +import com.arialyy.aria.m3u8.M3U8Listener; +import com.arialyy.aria.m3u8.M3U8TaskOption; + +/** + * M3U8点播文件下载工具 + * 工作流程: + * 1、创建一个和文件同父路径并且同名隐藏文件夹 + * 2、将所有m3u8的ts文件下载到该文件夹中 + * 3、完成所有分片下载后,合并ts文件 + * 4、删除该隐藏文件夹 + */ +public final class M3U8VodUtil extends AbsNormalLoaderUtil { + + public M3U8VodUtil() { + } + + @Override public DTaskWrapper getTaskWrapper() { + return (DTaskWrapper) super.getTaskWrapper(); + } + + @Override public AbsNormalLoader getLoader() { + if (mLoader == null) { + getTaskWrapper().generateM3u8Option(M3U8TaskOption.class); + getTaskWrapper().generateTaskOption(HttpTaskOption.class); + mLoader = new M3U8VodLoader(getTaskWrapper(), (M3U8Listener) getListener()); + } + return mLoader; + } + + @Override public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new VodRecordHandler(getTaskWrapper())) + .addComponent(new M3U8InfoTask(getTaskWrapper())) + .addComponent(new VodStateManager(getTaskWrapper(), (M3U8Listener) getListener())); + structure.accept(getLoader()); + return structure; + } +} diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java new file mode 100644 index 00000000..cc0d8348 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodRecordHandler.java @@ -0,0 +1,133 @@ +/* + * 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.m3u8.vod; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.M3U8Entity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.m3u8.BaseM3U8Loader; +import com.arialyy.aria.m3u8.M3U8InfoTask; +import com.arialyy.aria.m3u8.M3U8TaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.util.ArrayList; + +/** + * @author lyy + * Date: 2019-09-24 + */ +final class VodRecordHandler extends RecordHandler { + private M3U8TaskOption mOption; + + VodRecordHandler(DTaskWrapper wrapper) { + super(wrapper); + } + + public void setOption(M3U8TaskOption option) { + mOption = option; + } + + /** + * 不处理live的记录 + */ + @Override public void handlerTaskRecord(TaskRecord mTaskRecord) { + String cacheDir = mOption.getCacheDir(); + long currentProgress = 0; + int completeNum = 0; + File targetFile = new File(mTaskRecord.filePath); + if (!targetFile.exists()) { + FileUtil.createFile(targetFile); + } + + M3U8Entity m3U8Entity = ((DownloadEntity) getEntity()).getM3U8Entity(); + // 重新下载所有切片 + boolean reDownload = + (m3U8Entity.getPeerNum() <= 0 || (mOption.isGenerateIndexFile() && !new File( + String.format(M3U8InfoTask.M3U8_INDEX_FORMAT, getEntity().getFilePath())).exists())); + + for (ThreadRecord record : mTaskRecord.threadRecords) { + File temp = new File(BaseM3U8Loader.getTsFilePath(cacheDir, record.threadId)); + if (!record.isComplete || reDownload) { + if (temp.exists()) { + FileUtil.deleteFile(temp); + } + 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(); + } + } + } + mOption.setCompleteNum(completeNum); + getEntity().setCurrentProgress(currentProgress); + mTaskRecord.bandWidth = mOption.getBandWidth(); + } + + /** + * 不处理live的记录 + * + * @param record 任务记录 + * @param threadId 线程id + * @param startL 线程开始位置 + * @param endL 线程结束位置 + */ + @Override + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + ThreadRecord tr; + tr = new ThreadRecord(); + tr.taskKey = record.filePath; + tr.threadId = threadId; + tr.isComplete = false; + tr.startLocation = 0; + tr.threadType = record.taskType; + tr.tsUrl = mOption.getUrls().get(threadId); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + record.isBlock = true; + record.taskType = ITaskWrapper.M3U8_VOD; + record.bandWidth = mOption.getBandWidth(); + return record; + } + + @Override public int initTaskThreadNum() { + if (getWrapper().getRequestType() == ITaskWrapper.M3U8_VOD) { + return + mOption.getUrls() == null || mOption.getUrls().isEmpty() ? 1 : mOption.getUrls().size(); + } + if (getWrapper().getRequestType() == ITaskWrapper.M3U8_LIVE) { + return 1; + } + return 0; + } +} diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodStateManager.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodStateManager.java new file mode 100644 index 00000000..ccdbe186 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodStateManager.java @@ -0,0 +1,317 @@ +/* + * 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.m3u8.vod; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.ISchedulers; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.processor.ITsMergeHandler; +import com.arialyy.aria.core.task.ThreadTask; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.exception.AriaM3U8Exception; +import com.arialyy.aria.m3u8.BaseM3U8Loader; +import com.arialyy.aria.m3u8.M3U8Listener; +import com.arialyy.aria.m3u8.M3U8TaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * m3u8 点播下载状态管理器 + */ +public final class VodStateManager implements IThreadStateManager { + private final String TAG = CommonUtil.getClassName(getClass()); + + private M3U8Listener listener; + private int startThreadNum; // 启动的线程总数 + private AtomicInteger cancelNum = new AtomicInteger(0); // 已经取消的线程的数 + private AtomicInteger stopNum = new AtomicInteger(0); // 已经停止的线程数 + private AtomicInteger failNum = new AtomicInteger(0); // 失败的线程数 + private long progress; + private TaskRecord taskRecord; // 任务记录 + private Looper looper; + private DTaskWrapper wrapper; + private M3U8TaskOption m3U8Option; + private M3U8VodLoader loader; + + /** + * @param listener 任务事件 + */ + VodStateManager(DTaskWrapper wrapper, M3U8Listener listener) { + this.wrapper = wrapper; + this.listener = listener; + m3U8Option = (M3U8TaskOption) wrapper.getM3u8Option(); + progress = wrapper.getEntity().getCurrentProgress(); + } + + private Handler.Callback callback = new Handler.Callback() { + @Override public boolean handleMessage(Message msg) { + int peerIndex = msg.getData().getInt(ISchedulers.DATA_M3U8_PEER_INDEX); + switch (msg.what) { + case STATE_STOP: + stopNum.getAndIncrement(); + removeSignThread((ThreadTask) msg.obj); + // 处理跳转位置后,恢复任务 + if (loader.isJump() + && (stopNum.get() == loader.getCurrentFlagSize() || loader.getCurrentFlagSize() == 0) + && !loader.isBreak()) { + loader.resumeTask(); + return true; + } + + if (loader.isBreak()) { + ALog.d(TAG, String.format("vod任务【%s】停止", loader.getTempFile().getName())); + quitLooper(); + } + break; + case STATE_CANCEL: + cancelNum.getAndIncrement(); + removeSignThread((ThreadTask) msg.obj); + + if (loader.isBreak()) { + ALog.d(TAG, String.format("vod任务【%s】取消", loader.getTempFile().getName())); + quitLooper(); + } + break; + case STATE_FAIL: + failNum.getAndIncrement(); + for (ThreadRecord tr : taskRecord.threadRecords) { + if (tr.threadId == peerIndex) { + loader.getBeforePeer().put(peerIndex, tr); + break; + } + } + + getListener().onPeerFail(wrapper.getKey(), + msg.getData().getString(ISchedulers.DATA_M3U8_PEER_PATH), peerIndex); + if (isFail()) { + ALog.d(TAG, String.format("vod任务【%s】失败", loader.getTempFile().getName())); + Bundle b = msg.getData(); + listener.onFail(b.getBoolean(DATA_RETRY, true), + (AriaException) b.getSerializable(DATA_ERROR_INFO)); + quitLooper(); + } + break; + case STATE_COMPLETE: + if (loader.isBreak()) { + quitLooper(); + } + loader.setCompleteNum(loader.getCompleteNum() + 1); + // 正在切换位置时,切片完成,队列减小 + if (loader.isJump()) { + loader.setCurrentFlagSize(loader.getCurrentFlagSize() - 1); + if (loader.getCurrentFlagSize() < 0) { + loader.setCurrentFlagSize(0); + } + } + + removeSignThread((ThreadTask) msg.obj); + getListener().onPeerComplete(wrapper.getKey(), + msg.getData().getString(ISchedulers.DATA_M3U8_PEER_PATH), peerIndex); + handlerPercent(); + if (!loader.isJump()) { + loader.notifyWaitLock(true); + } + if (isComplete()) { + handleTaskComplete(); + } + break; + case STATE_RUNNING: + Bundle b = msg.getData(); + if (b != null) { + long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0); + progress += len; + } + break; + } + return true; + } + }; + + /** + * 处理m3u8以完成 + */ + void handleTaskComplete() { + ALog.d(TAG, String.format( + "startThreadNum = %s, stopNum = %s, cancelNum = %s, failNum = %s, completeNum = %s, flagQueueSize = %s", + startThreadNum, stopNum, cancelNum, failNum, loader.getCompleteNum(), + loader.getCurrentFlagSize())); + ALog.d(TAG, String.format("vod任务【%s】完成", loader.getTempFile().getName())); + + if (m3U8Option.isGenerateIndexFile()) { + if (loader.generateIndexFile(false)) { + listener.onComplete(); + } else { + listener.onFail(false, new AriaM3U8Exception("创建索引文件失败")); + } + } else if (m3U8Option.isMergeFile()) { + if (mergeFile()) { + listener.onComplete(); + } else { + listener.onFail(false, null); + } + } else { + listener.onComplete(); + } + quitLooper(); + } + + void updateStateCount() { + cancelNum.set(0); + stopNum.set(0); + failNum.set(0); + } + + @Override public void setLooper(TaskRecord taskRecord, Looper looper) { + this.looper = looper; + this.taskRecord = taskRecord; + for (ThreadRecord record : taskRecord.threadRecords) { + if (!record.isComplete) { + startThreadNum++; + } + } + } + + @Override public Handler.Callback getHandlerCallback() { + return callback; + } + + private DownloadEntity getEntity() { + return wrapper.getEntity(); + } + + private M3U8Listener getListener() { + return listener; + } + + void setVodLoader(M3U8VodLoader loader) { + this.loader = loader; + } + + /** + * 退出looper循环 + */ + private void quitLooper() { + ALog.d(TAG, "quitLooper"); + looper.quit(); + } + + private void removeSignThread(ThreadTask threadTask) { + loader.getTaskList().remove(threadTask); + ThreadTaskManager.getInstance().removeSingleTaskThread(wrapper.getKey(), threadTask); + } + + /** + * 设置进度 + */ + private void handlerPercent() { + int completeNum = m3U8Option.getCompleteNum(); + completeNum++; + m3U8Option.setCompleteNum(completeNum); + int percent = completeNum * 100 / taskRecord.threadRecords.size(); + getEntity().setPercent(percent); + getEntity().update(); + } + + @Override public boolean isFail() { + printInfo("isFail"); + return failNum.get() != 0 && failNum.get() == loader.getCurrentFlagSize() && !loader.isJump(); + } + + @Override public boolean isComplete() { + if (m3U8Option.isIgnoreFailureTs()) { + return loader.getCompleteNum() + failNum.get() >= taskRecord.threadRecords.size() + && !loader.isJump(); + } else { + return loader.getCompleteNum() == taskRecord.threadRecords.size() && !loader.isJump(); + } + } + + @Override public long getCurrentProgress() { + return progress; + } + + @Override public void updateCurrentProgress(long currentProgress) { + progress = currentProgress; + } + + private void printInfo(String tag) { + if (false) { + ALog.d(tag, String.format( + "startThreadNum = %s, stopNum = %s, cancelNum = %s, failNum = %s, completeNum = %s, flagQueueSize = %s", + startThreadNum, stopNum, cancelNum, failNum, loader.getCompleteNum(), + loader.getCurrentFlagSize())); + } + } + + /** + * 合并文件 + * + * @return {@code true} 合并成功,{@code false}合并失败 + */ + private boolean mergeFile() { + ITsMergeHandler mergeHandler = m3U8Option.getMergeHandler(); + String cacheDir = loader.getCacheDir(); + List partPath = new ArrayList<>(); + for (ThreadRecord tr : taskRecord.threadRecords) { + partPath.add(BaseM3U8Loader.getTsFilePath(cacheDir, tr.threadId)); + } + boolean isSuccess; + if (mergeHandler != null) { + isSuccess = mergeHandler.merge(getEntity().getM3U8Entity(), partPath); + + if (mergeHandler.getClass().isAnonymousClass()) { + m3U8Option.setMergeHandler(null); + } + } else { + isSuccess = FileUtil.mergeFile(taskRecord.filePath, partPath); + } + if (isSuccess) { + // 合并成功,删除缓存文件 + File[] files = new File(cacheDir).listFiles(); + for (File f : files) { + if (f.exists()) { + f.delete(); + } + } + File cDir = new File(cacheDir); + if (cDir.exists()) { + cDir.delete(); + } + return true; + } else { + ALog.e(TAG, "合并失败"); + return false; + } + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodTsDefConverter.java b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodTsDefConverter.java new file mode 100644 index 00000000..617f78d8 --- /dev/null +++ b/M3U8Component/src/main/java/com/arialyy/aria/m3u8/vod/VodTsDefConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.m3u8.vod; + +import com.arialyy.aria.core.processor.IVodTsUrlConverter; +import java.util.ArrayList; +import java.util.List; + +/** + * 默认的m3u8 ts转换器 + */ +class VodTsDefConverter implements IVodTsUrlConverter { + @Override public List convert(String m3u8Url, List tsUrls) { + int index = m3u8Url.lastIndexOf("/"); + List convertedTsUrl = new ArrayList<>(); + + String parentUrl = m3u8Url.substring(0, index + 1); + for (String temp : tsUrls) { + convertedTsUrl.add(parentUrl + temp); + } + return convertedTsUrl; + } +} diff --git a/M3U8Component/src/main/resources/META-INF/MANIFEST.MF b/M3U8Component/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/M3U8Component/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/M3U8Component/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil b/M3U8Component/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil new file mode 100644 index 00000000..53dd09b6 --- /dev/null +++ b/M3U8Component/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil @@ -0,0 +1,2 @@ +com.arialyy.aria.m3u8.live.M3U8LiveUtil +com.arialyy.aria.m3u8.vod.M3U8VodUtil \ No newline at end of file diff --git a/M3U8Component/src/main/resources/META-INF/services/com.arialyy.aria.core.listener.IEventListener b/M3U8Component/src/main/resources/META-INF/services/com.arialyy.aria.core.listener.IEventListener new file mode 100644 index 00000000..32784f04 --- /dev/null +++ b/M3U8Component/src/main/resources/META-INF/services/com.arialyy.aria.core.listener.IEventListener @@ -0,0 +1 @@ +com.arialyy.aria.m3u8.M3U8Listener \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..3cc74094 --- /dev/null +++ b/NOTICE @@ -0,0 +1,10 @@ +Aria +Copyright 2016 AriaLyy(https://github.com/AriaLyy/Aria) + +This project includes software from the commons-net project (Apache 2.0) +https://github.com/apache/commons-net + + +This project includes software from the jsch project (BSD 2.0) +* Copyright (c) 2002-2015 Atsuhiko Yamanaka, JCraft,Inc. +https://github.com/is/jsch diff --git a/PublicComponent/.gitignore b/PublicComponent/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/PublicComponent/.gitignore @@ -0,0 +1 @@ +/build diff --git a/PublicComponent/bintray-release.gradle b/PublicComponent/bintray-release.gradle new file mode 100644 index 00000000..8d66f20a --- /dev/null +++ b/PublicComponent/bintray-release.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.novoda.bintray-release' +publish { +// artifactId = 'aria-compiler' +// uploadName = 'AriaCompiler' + artifactId = 'publicComponent' + uploadName = 'PublicComponent' + userOrg = rootProject.ext.userOrg + groupId = rootProject.ext.groupId + publishVersion = rootProject.ext.publishVersion + desc = rootProject.ext.desc + website = rootProject.ext.website + licences = rootProject.ext.licences +} \ No newline at end of file diff --git a/PublicComponent/build.gradle b/PublicComponent/build.gradle new file mode 100644 index 00000000..bcf9571f --- /dev/null +++ b/PublicComponent/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + debug{ + debuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + lintOptions { + abortOnError false + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' +} + +//apply from: 'bintray-release.gradle' +ext{ + PUBLISH_ARTIFACT_ID = 'public' +} +apply from: '../gradle/mavenCentral-release.gradle' \ No newline at end of file diff --git a/PublicComponent/consumer-rules.pro b/PublicComponent/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/PublicComponent/proguard-rules.pro b/PublicComponent/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/PublicComponent/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/PublicComponent/src/main/AndroidManifest.xml b/PublicComponent/src/main/AndroidManifest.xml new file mode 100644 index 00000000..14fe4789 --- /dev/null +++ b/PublicComponent/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/AriaConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/AriaConfig.java new file mode 100644 index 00000000..05cede1c --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/AriaConfig.java @@ -0,0 +1,240 @@ +/* + * 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; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkRequest; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +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.DownloadConfig; +import com.arialyy.aria.core.config.UploadConfig; +import com.arialyy.aria.core.config.XMLReader; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.SAXException; + +public class AriaConfig { + private static final String TAG = "AriaConfig"; + + public static final String DOWNLOAD_TEMP_DIR = "/Aria/temp/download/"; + public static final String UPLOAD_TEMP_DIR = "/Aria/temp/upload/"; + public static final String IGNORE_CLASS_KLASS = "shadow$_klass_"; + public static final String IGNORE_CLASS_MONITOR = "shadow$_monitor_"; + + private static volatile AriaConfig Instance; + private static Context APP; + private DownloadConfig mDConfig; + private UploadConfig mUConfig; + private AppConfig mAConfig; + private DGroupConfig mDGConfig; + /** + * 是否已经联网,true 已经联网 + */ + private static boolean isConnectedNet = true; + private Handler mAriaHandler; + + private AriaConfig(Context context) { + APP = context.getApplicationContext(); + } + + public static AriaConfig init(Context context) { + if (Instance == null) { + synchronized (AriaConfig.class) { + if (Instance == null) { + Instance = new AriaConfig(context); + Instance.initData(); + } + } + } + return Instance; + } + + public static AriaConfig getInstance() { + if (Instance == null) { + ALog.e(TAG, "请使用init()初始化"); + } + return Instance; + } + + public Context getAPP() { + return APP; + } + + private void initData() { + initConfig(); + regNetCallBack(APP); + } + + public DownloadConfig getDConfig() { + return mDConfig; + } + + public UploadConfig getUConfig() { + return mUConfig; + } + + public AppConfig getAConfig() { + return mAConfig; + } + + public DGroupConfig getDGConfig() { + return mDGConfig; + } + + public synchronized Handler getAriaHandler() { + if (mAriaHandler == null) { + mAriaHandler = new Handler(Looper.getMainLooper()); + } + return mAriaHandler; + } + + /** + * 注册网络监听,只有配置了检查网络{@link AppConfig#isNetCheck()}才会注册事件 + */ + private void regNetCallBack(Context context) { + isConnectedNet = isNetworkAvailable(); + if (!getAConfig().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(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cm.registerNetworkCallback(request, new ConnectivityManager.NetworkCallback() { + + @Override public void onLost(Network network) { + super.onLost(network); + isConnectedNet = isNetworkAvailable(); + ALog.d(TAG, "onLost, isConnectNet = " + isConnectedNet); + } + + @Override public void onAvailable(Network network) { + super.onAvailable(network); + isConnectedNet = true; + ALog.d(TAG, "onAvailable, isConnectNet = true"); + } + }); + } + } + + public boolean isNetworkAvailable() { + // 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理) + ConnectivityManager connectivityManager = + (ConnectivityManager) getAPP().getSystemService(Context.CONNECTIVITY_SERVICE); + + if (connectivityManager == null) { + + return false; + } else { + // 获取NetworkInfo对象 + NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo(); + + if (networkInfo != null && networkInfo.length > 0) { + for (NetworkInfo info : networkInfo) { + // 判断当前网络状态是否为连接状态 + if (info.getState() == NetworkInfo.State.CONNECTED) { + return true; + } + } + } + } + return false; + } + + + public boolean isConnectedNet() { + return isConnectedNet; + } + + /** + * 初始化配置文件 + */ + 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(); + } + FileUtil.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() + AriaConfig.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); + FileUtil.createFileFormInputStream(APP.getAssets().open("aria_config.xml"), + APP.getFilesDir().getPath() + Configuration.XML_FILE); + } catch (ParserConfigurationException | IOException | SAXException e) { + ALog.e(TAG, e.toString()); + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpUrlEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/FtpUrlEntity.java similarity index 88% rename from Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpUrlEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/FtpUrlEntity.java index ad5ac3ed..428b4c4a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpUrlEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/FtpUrlEntity.java @@ -15,9 +15,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common.ftp; +package com.arialyy.aria.core; -import com.arialyy.aria.core.common.ProtocolType; import java.net.InetAddress; /** @@ -79,21 +78,10 @@ public class FtpUrlEntity implements Cloneable { */ public InetAddress validAddr; - /** - * 证书路径 - */ - public String storePath; - - /** - * 证书密码 - */ - public String storePass; - /** * 连接协议 * {@link ProtocolType} */ - @ProtocolType public String protocol = ProtocolType.Default; /** @@ -101,10 +89,7 @@ public class FtpUrlEntity implements Cloneable { */ public boolean isImplicit = true; - /** - * 私钥别名 - */ - public String keyAlias; + public IdEntity idEntity; @Override public FtpUrlEntity clone() { FtpUrlEntity entity = null; diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/IdEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/IdEntity.java new file mode 100644 index 00000000..9b8a7138 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/IdEntity.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * 证书信息 + */ +public class IdEntity { + + /** + * 私钥证书路径 + */ + public String prvKey; + + /** + * 私钥证书密码 + */ + public String prvPass; + + /** + * 公钥证书路径 + */ + public String pubKey; + + /** + * knowhost文件路径 + */ + public String knowHost; + + /** + * ca 证书密码 + */ + public String storePass; + + /** + * ca证书路径 + */ + public String storePath; + + /** + * ca证书别名 + */ + public String keyAlias; +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ProtocolType.java b/PublicComponent/src/main/java/com/arialyy/aria/core/ProtocolType.java similarity index 66% rename from Aria/src/main/java/com/arialyy/aria/core/common/ProtocolType.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/ProtocolType.java index 829489a9..17ef1d7a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ProtocolType.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/ProtocolType.java @@ -13,22 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core; -import androidx.annotation.StringDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@StringDef({ - ProtocolType.Default, - ProtocolType.SSL, - ProtocolType.SSLv3, - ProtocolType.TLS, - ProtocolType.TLSv1, - ProtocolType.TLSv1_1, - ProtocolType.TLSv1_2 -}) -@Retention(RetentionPolicy.SOURCE) public @interface ProtocolType { +public interface ProtocolType { String Default = "TLS"; String SSL = "SSL"; String SSLv3 = "SSLv3"; diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/TaskOptionParams.java b/PublicComponent/src/main/java/com/arialyy/aria/core/TaskOptionParams.java new file mode 100644 index 00000000..1bac8498 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/TaskOptionParams.java @@ -0,0 +1,132 @@ +/* + * 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; + +import com.arialyy.aria.core.common.BaseOption; +import com.arialyy.aria.core.inf.IEventHandler; +import com.arialyy.aria.core.inf.IOptionConstant; +import com.arialyy.aria.core.processor.FtpInterceptHandler; +import com.arialyy.aria.core.processor.IBandWidthUrlConverter; +import com.arialyy.aria.core.processor.IFtpUploadInterceptor; +import com.arialyy.aria.core.processor.IHttpFileLenAdapter; +import com.arialyy.aria.core.processor.IHttpFileNameAdapter; +import com.arialyy.aria.core.processor.IKeyUrlConverter; +import com.arialyy.aria.core.processor.ILiveTsUrlConverter; +import com.arialyy.aria.core.processor.ITsMergeHandler; +import com.arialyy.aria.core.processor.IVodTsUrlConverter; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 任务配置参数 + * + * @author lyy + * Date: 2019-09-10 + */ +public class TaskOptionParams { + + private static List PROCESSORES = new ArrayList<>(); + + /** + * 普通参数 + */ + private Map params = new HashMap<>(); + + /** + * 事件处理对象 + */ + private Map handler = new HashMap<>(); + + static { + PROCESSORES.add(FtpInterceptHandler.class); + PROCESSORES.add(IBandWidthUrlConverter.class); + PROCESSORES.add(IFtpUploadInterceptor.class); + PROCESSORES.add(IHttpFileLenAdapter.class); + PROCESSORES.add(IHttpFileNameAdapter.class); + PROCESSORES.add(ILiveTsUrlConverter.class); + PROCESSORES.add(ITsMergeHandler.class); + PROCESSORES.add(IVodTsUrlConverter.class); + PROCESSORES.add(IKeyUrlConverter.class); + } + + /** + * 设置任务参数 + * + * @param option 任务配置 + */ + public void setParams(BaseOption option) { + List fields = CommonUtil.getAllFields(option.getClass()); + + for (Field field : fields) { + field.setAccessible(true); + try { + + if (PROCESSORES.contains(field.getType())) { + Object eventHandler = field.get(option); + if (eventHandler != null) { + setObjs(field.getName(), (IEventHandler) eventHandler); + } + } else { + Object params = field.get(option); + if (params != null) { + setParams(field.getName(), params); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * 设置普通参数 + * + * @param key {@link IOptionConstant} + */ + public TaskOptionParams setParams(String key, Object value) { + params.put(key, value); + return this; + } + + /** + * 设置对象参数 + */ + public TaskOptionParams setObjs(String key, IEventHandler handler) { + this.handler.put(key, handler); + return this; + } + + public Map getParams() { + return params; + } + + public Object getParam(String key) { + return params.get(key); + } + + public IEventHandler getHandler(String key) { + return handler.get(key); + } + + public Map getHandler() { + return handler; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java b/PublicComponent/src/main/java/com/arialyy/aria/core/TaskRecord.java similarity index 81% rename from Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/TaskRecord.java index 90a03463..75d3d92e 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/TaskRecord.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.annotation.Ignore; import com.arialyy.aria.orm.annotation.NoNull; @@ -26,9 +27,9 @@ import java.util.List; * 任务上传或下载的任务记录 */ public class TaskRecord extends DbEntity { - public static final int TYPE_HTTP_FTP = 0; - public static final int TYPE_M3U8_VOD = 1; - public static final int TYPE_M3U8_LIVE = 2; + //public static final int TYPE_HTTP_FTP = 0; + //public static final int TYPE_M3U8_VOD = 1; + //public static final int TYPE_M3U8_LIVE = 2; @Ignore public List threadRecords; @@ -41,7 +42,6 @@ public class TaskRecord extends DbEntity { /** * 任务文件路径 */ - @Unique public String filePath; /** @@ -79,14 +79,8 @@ public class TaskRecord extends DbEntity { public boolean isBlock = false; /** - * 是否是使用虚拟文件下载的 - * {@code true}是,{@code false}不是 - */ - public boolean isOpenDynamicFile = false; - - /** - * 线程类型 - * {@link #TYPE_HTTP_FTP}、{@link #TYPE_M3U8_VOD} + * 任务类型 + * {@link ITaskWrapper} */ public int taskType = 0; diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java b/PublicComponent/src/main/java/com/arialyy/aria/core/ThreadRecord.java similarity index 92% rename from Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/ThreadRecord.java index ec29e4fd..546172d7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/ThreadRecord.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.DbEntity; /** @@ -56,7 +57,7 @@ public class ThreadRecord extends DbEntity { /** * 线程类型 - * {@link TaskRecord#TYPE_HTTP_FTP}、{@link TaskRecord#TYPE_M3U8_VOD} + * {@link ITaskWrapper} */ public int threadType = 0; diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsEntity.java similarity index 86% rename from Aria/src/main/java/com/arialyy/aria/core/inf/AbsEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsEntity.java index f7ea95fd..81954d99 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsEntity.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.common; import android.os.Parcel; import android.os.Parcelable; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.annotation.Default; import com.arialyy.aria.orm.annotation.Ignore; @@ -39,6 +41,11 @@ public abstract class AbsEntity extends DbEntity implements IEntity, Parcelable, */ @Ignore private int failNum = 0; + /** + * 剩余时间,单位:s + */ + @Ignore private int timeLeft = Integer.MAX_VALUE; + /** * 扩展字段 */ @@ -79,6 +86,29 @@ public abstract class AbsEntity extends DbEntity implements IEntity, Parcelable, */ private long stopTime = 0; + @Ignore + private int netCode = 200; + + public int getNetCode() { + return netCode; + } + + public void setNetCode(int netCode) { + this.netCode = netCode; + } + + /** + * 获取剩余时间,单位:s + * 如果是m3u8任务,无法获取剩余时间;m2u8任务如果需要获取剩余时间,请设置文件长度{@link #setFileSize(long)} + */ + public int getTimeLeft() { + return timeLeft; + } + + public void setTimeLeft(int timeLeft) { + this.timeLeft = timeLeft; + } + public boolean isComplete() { return isComplete; } @@ -204,6 +234,7 @@ public abstract class AbsEntity extends DbEntity implements IEntity, Parcelable, } @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(this.rowID); dest.writeLong(this.speed); dest.writeString(this.convertSpeed); dest.writeInt(this.failNum); @@ -219,6 +250,7 @@ public abstract class AbsEntity extends DbEntity implements IEntity, Parcelable, } protected AbsEntity(Parcel in) { + this.rowID = in.readLong(); this.speed = in.readLong(); this.convertSpeed = in.readString(); this.failNum = in.readInt(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsGroupEntity.java similarity index 98% rename from Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsGroupEntity.java index c35381d9..4acd8d81 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsGroupEntity.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.common; import android.os.Parcel; import android.os.Parcelable; diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsNormalEntity.java similarity index 89% rename from Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsNormalEntity.java index 52d531db..c4222b2a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsNormalEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/AbsNormalEntity.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.common; import android.os.Parcel; import android.os.Parcelable; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.annotation.Default; /** @@ -44,6 +45,17 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable { private boolean isRedirect = false; //是否重定向 private String redirectUrl; //重定向链接 + /** + * 任务类型 + * {@link ITaskWrapper} + */ + private int taskType; + + + public void setTaskType(int taskType) { + this.taskType = taskType; + } + public String getUrl() { return url; } @@ -84,6 +96,8 @@ public abstract class AbsNormalEntity extends AbsEntity implements Parcelable { this.redirectUrl = redirectUrl; } + public abstract String getFilePath(); + public AbsNormalEntity() { } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/BaseOption.java similarity index 66% rename from Aria/src/main/java/com/arialyy/aria/core/common/AEvent.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/common/BaseOption.java index f5852b92..68955305 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AEvent.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/BaseOption.java @@ -15,23 +15,12 @@ */ package com.arialyy.aria.core.common; -/** - * Aria事件总线 - */ -public class AEvent { - private static final Object LOCK = new Object(); - private static volatile AEvent INSTANCE = null; - - private AEvent() { +import com.arialyy.aria.util.CommonUtil; - } +public abstract class BaseOption { + protected final String TAG; - public static AEvent getInstance() { - if (INSTANCE == null) { - synchronized (LOCK) { - INSTANCE = new AEvent(); - } - } - return INSTANCE; + public BaseOption() { + TAG = CommonUtil.getClassName(getClass()); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java similarity index 87% rename from Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java index 34a04ed3..f6107d28 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java @@ -15,7 +15,7 @@ */ package com.arialyy.aria.core.common; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; /** * Created by AriaL on 2018/3/3. @@ -27,7 +27,7 @@ public class CompleteInfo { */ public int code; - public AbsTaskWrapper wrapper; + public ITaskWrapper wrapper; public Object obj; @@ -35,7 +35,7 @@ public class CompleteInfo { } - public CompleteInfo(int code, AbsTaskWrapper wrapper) { + public CompleteInfo(int code, ITaskWrapper wrapper) { this.code = code; this.wrapper = wrapper; } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/common/ErrorCode.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/ErrorCode.java new file mode 100644 index 00000000..b115b6f0 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/ErrorCode.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.common; + +public enum ErrorCode { + ERROR_CODE_NORMAL(0, "正常"), ERROR_CODE_TASK_ID_NULL(1, "任务id为空的错误码"), + ERROR_CODE_URL_NULL(2, "url 为空"), ERROR_CODE_URL_INVALID(3, "url 无效"), + ERROR_CODE_PAGE_NUM(4, "page和num不能小于1"), ERROR_CODE_GROUP_URL_NULL(5, "组合任务url列表为空"), + ERROR_CODE_UPLOAD_FILE_NULL(7, "上传文件不存在"), + ERROR_CODE_MEMBER_WARNING(8, "为了防止内存泄漏,请使用静态的成员类(public static class xxx)或文件类(A.java)"), + ERROR_CODE_TASK_NOT_EXIST(9, "任务信息不存在"); + + public int code; + public String msg; + + ErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/common/FtpConnectionMode.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/FtpConnectionMode.java new file mode 100644 index 00000000..53fc57f6 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/FtpConnectionMode.java @@ -0,0 +1,30 @@ +/* + * 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 连接模式 + */ +public interface FtpConnectionMode { + /** + * 被动模式 + */ + int DATA_CONNECTION_MODE_PASV = 0; + /** + * 主动模式 + */ + int DATA_CONNECTION_MODE_ACTIVITY = 1; +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHandler.java new file mode 100644 index 00000000..8e5f0c1e --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHandler.java @@ -0,0 +1,225 @@ +/* + * 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.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.core.loader.IRecordHandler; +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.core.wrapper.RecordWrapper; +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 java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +/** + * 处理任务记录,分配线程区间 + */ +public abstract class RecordHandler implements IRecordHandler { + protected final String TAG = CommonUtil.getClassName(this); + + @Deprecated private File mConfigFile; + private TaskRecord mTaskRecord; + private AbsTaskWrapper mTaskWrapper; + private AbsNormalEntity mEntity; + protected String mFilePath; + protected long mFileSize; + + public RecordHandler(AbsTaskWrapper wrapper) { + mTaskWrapper = wrapper; + mEntity = (AbsNormalEntity) mTaskWrapper.getEntity(); + } + + public AbsTaskWrapper getWrapper() { + return mTaskWrapper; + } + + public AbsNormalEntity getEntity() { + return mEntity; + } + + @Override public void onPre() { + + } + + /** + * 获取任务记录,如果任务记录存在,检查任务记录 + * 检查记录 对于分块任务: 子分块不存在或被删除,子线程将重新下载 + * 对于普通任务: 预下载文件不存在,则任务任务呗删除 + * 如果任务记录不存在或线程记录不存在,初始化记录 + * + * @return 任务记录 + */ + @Override + public TaskRecord getRecord(long fileSize) { + mFileSize = fileSize; + mConfigFile = new File(CommonUtil.getFileConfigPath(false, mEntity.getFileName())); + if (mConfigFile.exists()) { + convertDb(); + } else { + onPre(); + mTaskRecord = DbDataHelper.getTaskRecord(getFilePath(), mEntity.getTaskType()); + if (mTaskRecord == null) { + initRecord(true); + }else if (mTaskRecord.threadRecords == null || mTaskRecord.threadRecords.size() == 0){ + if (mTaskRecord.threadRecords == null){ + mTaskRecord.threadRecords = new ArrayList<>(); + } + initRecord(false); + } + handlerTaskRecord(mTaskRecord); + } + saveRecord(); + return mTaskRecord; + } + + /** + * convertDb 是兼容性代码 从3.4.1开始,线程配置信息将存储在数据库中。 将配置文件的内容复制到数据库中,并将配置文件删除 + */ + private void convertDb() { + List 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 keys = pro.keySet(); + // 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录... + // 第一步应该是record 和 state去重取正确的线程数 + Set 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.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); + } + FileUtil.deleteFile(mConfigFile); + } + } + + /** + * 初始化任务记录,分配线程区间,如果任务记录不存在,则创建新的任务记录 + * + * @param newRecord {@code true} 需要创建新{@link TaskRecord} + */ + private void initRecord(boolean newRecord) { + if (newRecord) { + mTaskRecord = createTaskRecord(initTaskThreadNum()); + } + mTaskWrapper.setNewTask(true); + int requestType = mTaskWrapper.getRequestType(); + if (requestType == ITaskWrapper.M3U8_LIVE) { + return; + } + long blockSize = getFileSize() / mTaskRecord.threadNum; + // 处理线程区间记录 + for (int i = 0; i < mTaskRecord.threadNum; i++) { + long startL = i * blockSize, endL = (i + 1) * blockSize; + ThreadRecord tr = createThreadRecord(mTaskRecord, i, startL, endL); + mTaskRecord.threadRecords.add(tr); + } + } + + /** + * 保存任务记录 + */ + 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())); + } + + protected long getFileSize() { + return mFileSize; + } + + /** + * 获取任务路径 + * + * @return 任务文件路径 + */ + private String getFilePath() { + if (mEntity instanceof DownloadEntity) { + return ((DownloadEntity) mTaskWrapper.getEntity()).getFilePath(); + } else { + return ((UploadEntity) mTaskWrapper.getEntity()).getFilePath(); + } + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } + + @Override public boolean checkTaskCompleted() { + if (mTaskRecord == null + || mTaskRecord.threadRecords == null + || mTaskRecord.threadRecords.isEmpty()) { + return false; + } + int completeNum = 0; + for (ThreadRecord tr : mTaskRecord.threadRecords) { + if (tr.isComplete) { + completeNum++; + } + } + return completeNum != 0 && completeNum == mTaskRecord.threadNum; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHelper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHelper.java new file mode 100644 index 00000000..365663a4 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/RecordHelper.java @@ -0,0 +1,207 @@ +/* + * 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.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.io.IOException; + +/** + * 任务记录帮助类,用于处理统一的逻辑 + * + * @author lyy + * Date: 2019-09-19 + */ +public class RecordHelper { + private String TAG = CommonUtil.getClassName(getClass()); + + private AbsTaskWrapper mWrapper; + protected TaskRecord mTaskRecord; + + public RecordHelper(AbsTaskWrapper wrapper, TaskRecord record) { + mWrapper = wrapper; + mTaskRecord = record; + } + + /** + * 处理非分块的,多线程任务 + */ + public void handleMultiRecord() { + // 默认线程分块长度 + long blockSize = mWrapper.getEntity().getFileSize() / mTaskRecord.threadRecords.size(); + File temp = new File(mTaskRecord.filePath); + boolean fileExists = false; + if (!temp.exists()) { + createPlaceHolderFile(temp); + } else { + if (temp.length() != mWrapper.getEntity().getFileSize()) { + FileUtil.deleteFile(temp); + createPlaceHolderFile(temp); + } + fileExists = true; + } + // 处理文件被删除的情况 + if (!fileExists) { + ALog.w(TAG, String.format("文件【%s】被删除,重新分配线程区间", mTaskRecord.filePath)); + for (int i = 0; i < mTaskRecord.threadNum; i++) { + long startL = i * blockSize, endL = (i + 1) * blockSize; + ThreadRecord tr = mTaskRecord.threadRecords.get(i); + tr.startLocation = startL; + tr.isComplete = false; + + //最后一个线程的结束位置即为文件的总长度 + if (tr.threadId == (mTaskRecord.threadNum - 1)) { + endL = mWrapper.getEntity().getFileSize(); + } + tr.endLocation = endL; + } + } + //mWrapper.setNewTask(false); + } + + /** + * 创建非分块的占位文件 + */ + private void createPlaceHolderFile(File temp) { + BufferedRandomAccessFile tempFile; + try { + tempFile = new BufferedRandomAccessFile(temp, "rw"); + tempFile.setLength(mWrapper.getEntity().getFileSize()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 处理分块任务的记录,分块文件(blockFileLen)长度必须需要小于等于线程区间(threadRectLen)的长度 + */ + public void handleBlockRecord() { + // 默认线程分块长度 + long normalRectLen = mWrapper.getEntity().getFileSize() / mTaskRecord.threadRecords.size(); + for (ThreadRecord tr : mTaskRecord.threadRecords) { + long threadRect = tr.blockLen; + + File temp = + new File(String.format(IRecordHandler.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; + } + + //正常情况下,该线程的startLocation的位置 + long realLocation = tr.threadId * normalRectLen + blockFileLen; + /* + * 检查记录文件 + */ + if (blockFileLen == threadRect && blockFileLen != 0) { + 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】的进度记录为:%s", temp.getPath(), realLocation)); + tr.startLocation = realLocation; + tr.isComplete = false; + } + } else { + ALog.i(TAG, String.format("分块【%s】已完成", temp.getPath())); + } + } + } + //mWrapper.setNewTask(false); + } + + /** + * 处理单线程的任务的记录 + */ + public void handleSingleThreadRecord() { + // mTaskRecord.isBlock是为了兼容以前的文件格式 + File file = new File( + mTaskRecord.isBlock ? String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0) + : mTaskRecord.filePath); + ThreadRecord tr = mTaskRecord.threadRecords.get(0); + if (!file.exists()) { + // 目标文件 + File targetFile = new File(mTaskRecord.filePath); + // 处理组合任务其中一个子任务完成的情况 + if (tr.isComplete + && targetFile.exists() + && targetFile.length() != 0 + && targetFile.length() == mWrapper.getEntity().getFileSize()) { + tr.isComplete = true; + } else { + ALog.w(TAG, String.format("文件【%s】不存在,任务将重新开始", file.getPath())); + tr.startLocation = 0; + tr.isComplete = false; + tr.endLocation = mWrapper.getEntity().getFileSize(); + } + } else if (file.length() > mWrapper.getEntity().getFileSize()) { + ALog.i(TAG, String.format("文件【%s】错误,任务重新开始", file.getPath())); + FileUtil.deleteFile(file); + tr.startLocation = 0; + tr.isComplete = false; + tr.endLocation = mWrapper.getEntity().getFileSize(); + } else if (file.length() != 0 && file.length() == mWrapper.getEntity().getFileSize()) { + ALog.d(TAG, "文件长度一致,线程完成"); + 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; + } + } + //mWrapper.setNewTask(false); + } + + /** + * 处理不支持断点的记录 + */ + public void handleNoSupportBPRecord() { + ThreadRecord tr = mTaskRecord.threadRecords.get(0); + tr.startLocation = 0; + tr.endLocation = mWrapper.getEntity().getFileSize(); + tr.taskKey = mTaskRecord.filePath; + tr.blockLen = tr.endLocation; + tr.isComplete = false; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/RequestEnum.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/RequestEnum.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/common/RequestEnum.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/common/RequestEnum.java diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java new file mode 100644 index 00000000..3c49c334 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java @@ -0,0 +1,113 @@ +/* + * 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 com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import java.io.File; + +/** + * 子线程下载信息类 + */ +public class SubThreadConfig { + public static final int TYPE_HTTP = 1; + public static final int TYPE_FTP = 2; + public static final int TYPE_M3U8_PEER = 3; + public static final int TYPE_HTTP_DG_SUB = 4; + public static final int TYPE_FTP_DG_SUB = 5; + + public AbsTaskWrapper taskWrapper; + public boolean isBlock = false; + // 启动的线程 + public int startThreadNum; + // 真正的下载地址,如果是30x,则是30x后的地址 + public String url; + public File tempFile; + // 线程记录 + public ThreadRecord record; + // 状态处理器 + public Handler stateHandler; + // m3u8切片索引 + public int peerIndex; + // 线程任务类型 + public int threadType = TYPE_HTTP; + // 进度更新间隔,单位:毫秒 + public long updateInterval = 1000; + // 扩展数据 + public Object obj; + // 忽略失败 + public boolean ignoreFailure = false; + + /** + * 转换线程任务类型 + * + * @param requestType {@link AbsTaskWrapper#getRequestType()} + * @return {@link #threadType} + */ + public static int getThreadType(int requestType) { + int threadType = SubThreadConfig.TYPE_HTTP; + switch (requestType) { + case ITaskWrapper.D_HTTP: + case ITaskWrapper.U_HTTP: + threadType = SubThreadConfig.TYPE_HTTP; + break; + case ITaskWrapper.D_FTP: + case ITaskWrapper.U_FTP: + threadType = SubThreadConfig.TYPE_FTP; + break; + case ITaskWrapper.D_FTP_DIR: + threadType = SubThreadConfig.TYPE_FTP_DG_SUB; + break; + case ITaskWrapper.DG_HTTP: + threadType = SubThreadConfig.TYPE_HTTP_DG_SUB; + break; + case ITaskWrapper.M3U8_LIVE: + case ITaskWrapper.M3U8_VOD: + threadType = SubThreadConfig.TYPE_M3U8_PEER; + break; + } + return threadType; + } + + /** + * 根据配置肚脐更新间隔 + * + * @param requestType {@link AbsTaskWrapper#getRequestType()} + * @return {@link #updateInterval} + */ + public static long getUpdateInterval(int requestType) { + long updateInterval = 1000; + switch (requestType) { + case ITaskWrapper.D_HTTP: + case ITaskWrapper.D_FTP: + case ITaskWrapper.M3U8_LIVE: + case ITaskWrapper.M3U8_VOD: + updateInterval = AriaConfig.getInstance().getDConfig().getUpdateInterval(); + break; + case ITaskWrapper.D_FTP_DIR: + case ITaskWrapper.DG_HTTP: + updateInterval = AriaConfig.getInstance().getDGConfig().getUpdateInterval(); + break; + case ITaskWrapper.U_HTTP: + case ITaskWrapper.U_FTP: + updateInterval = AriaConfig.getInstance().getUConfig().getUpdateInterval(); + } + return updateInterval; + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/AppConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/AppConfig.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/config/AppConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/AppConfig.java diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/BaseConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/BaseConfig.java similarity index 91% rename from Aria/src/main/java/com/arialyy/aria/core/config/BaseConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/BaseConfig.java index c30791ed..a1f63f8f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/BaseConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/BaseConfig.java @@ -1,7 +1,7 @@ package com.arialyy.aria.core.config; import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.FileUtil; import java.io.Serializable; @@ -24,7 +24,7 @@ abstract class BaseConfig implements Serializable { * 保存配置 */ void save() { - String basePath = AriaManager.getInstance().getAPP().getFilesDir().getPath(); + String basePath = AriaConfig.getInstance().getAPP().getFilesDir().getPath(); String path = null; switch (getType()) { case TYPE_DOWNLOAD: diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/BaseTaskConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/BaseTaskConfig.java similarity index 97% rename from Aria/src/main/java/com/arialyy/aria/core/config/BaseTaskConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/BaseTaskConfig.java index 8d912618..5e33a485 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/BaseTaskConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/BaseTaskConfig.java @@ -16,15 +16,15 @@ package com.arialyy.aria.core.config; -import com.arialyy.aria.core.common.QueueMod; import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; import java.io.Serializable; /** * 通用任务配置 */ public abstract class BaseTaskConfig extends BaseConfig implements Serializable { - + protected String TAG = CommonUtil.getClassName(getClass()); /** * 设置写文件buff大小,该数值大小不能小于2048,数值变小,下载速度会变慢 @@ -65,8 +65,6 @@ public abstract class BaseTaskConfig extends BaseConfig implements Serializable /** * 执行队列类型 - * - * @see QueueMod */ String queueMod = "wait"; diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/ConfigType.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/ConfigType.java similarity index 72% rename from Aria/src/main/java/com/arialyy/aria/core/config/ConfigType.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/ConfigType.java index 357c485d..7a8d8113 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/ConfigType.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/ConfigType.java @@ -15,17 +15,7 @@ */ package com.arialyy.aria.core.config; -import androidx.annotation.IntDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@IntDef({ - ConfigType.DOWNLOAD, - ConfigType.UPLOAD, - ConfigType.APP, - ConfigType.D_GROUP -}) -@Retention(RetentionPolicy.SOURCE) @interface ConfigType { +public interface ConfigType { int DOWNLOAD = 1; int UPLOAD = 2; int APP = 3; diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/Configuration.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/Configuration.java similarity index 95% rename from Aria/src/main/java/com/arialyy/aria/core/config/Configuration.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/Configuration.java index 79c46604..2978e092 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/Configuration.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/Configuration.java @@ -15,7 +15,7 @@ */ package com.arialyy.aria.core.config; -import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.AriaConfig; import com.arialyy.aria.util.FileUtil; import java.io.File; @@ -37,7 +37,7 @@ public final class Configuration { private Configuration() { //删除老版本的配置文件 - String basePath = AriaManager.getInstance().getAPP().getFilesDir().getPath(); + String basePath = AriaConfig.getInstance().getAPP().getFilesDir().getPath(); del351Config(basePath); File newDCfg = new File(String.format("%s%s", basePath, DOWNLOAD_CONFIG_FILE)); File newUCfg = new File(String.format("%s%s", basePath, UPLOAD_CONFIG_FILE)); @@ -89,7 +89,7 @@ public final class Configuration { * @return {@code true}配置存在,{@code false}配置不存在 */ public boolean configExists() { - String basePath = AriaManager.getInstance().getAPP().getFilesDir().getPath(); + String basePath = AriaConfig.getInstance().getAPP().getFilesDir().getPath(); return (new File(String.format("%s%s", basePath, DOWNLOAD_CONFIG_FILE))).exists() && (new File(String.format("%s%s", basePath, UPLOAD_CONFIG_FILE))).exists() && (new File(String.format("%s%s", basePath, APP_CONFIG_FILE))).exists() diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/DGroupConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/DGroupConfig.java similarity index 73% rename from Aria/src/main/java/com/arialyy/aria/core/config/DGroupConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/DGroupConfig.java index 5d63a34b..0dc6a033 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/DGroupConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/DGroupConfig.java @@ -15,9 +15,11 @@ */ package com.arialyy.aria.core.config; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.event.DGMaxNumEvent; +import com.arialyy.aria.core.event.DSpeedEvent; import com.arialyy.aria.core.event.EventMsgUtil; -import com.arialyy.aria.core.event.SpeedEvent; -import com.arialyy.aria.core.queue.DownloadGroupTaskQueue; +import com.arialyy.aria.util.ALog; import java.io.Serializable; /** @@ -30,6 +32,11 @@ public class DGroupConfig extends BaseTaskConfig implements Serializable { */ int subMaxTaskNum = 3; + /** + * 子任务失败时回调stop,默认true + */ + private boolean subFailAsStop = true; + /** * 子任务重试次数,默认为5 */ @@ -52,33 +59,22 @@ public class DGroupConfig extends BaseTaskConfig implements Serializable { @Override public DGroupConfig setMaxSpeed(int maxSpeed) { super.setMaxSpeed(maxSpeed); - EventMsgUtil.getDefault().post(new SpeedEvent(maxSpeed)); + EventMsgUtil.getDefault().post(new DSpeedEvent(maxSpeed)); return this; } public DownloadConfig getSubConfig() { - if (subConfig == null) { - subConfig = new DownloadConfig(); - subConfig.threadNum = 1; - subConfig.useBlock = false; - subConfig.buffSize = buffSize; - subConfig.caName = caName; - subConfig.caPath = caPath; - subConfig.connectTimeOut = connectTimeOut; - subConfig.iOTimeOut = iOTimeOut; - subConfig.isConvertSpeed = isConvertSpeed; - subConfig.maxSpeed = maxSpeed; - subConfig.queueMod = "now"; - subConfig.reTryInterval = subReTryInterval; - subConfig.reTryNum = subReTryNum; - subConfig.updateInterval = updateInterval; - } + subConfig = AriaConfig.getInstance().getDConfig(); return subConfig; } public DGroupConfig setMaxTaskNum(int maxTaskNum) { + if (maxTaskNum <= 0) { + ALog.e(TAG, "组合任务最大任务数不能小于0"); + return this; + } super.setMaxTaskNum(maxTaskNum); - DownloadGroupTaskQueue.getInstance().setMaxTaskNum(maxTaskNum); + EventMsgUtil.getDefault().post(new DGMaxNumEvent(maxTaskNum)); return this; } @@ -113,4 +109,14 @@ public class DGroupConfig extends BaseTaskConfig implements Serializable { save(); return this; } + + public boolean isSubFailAsStop() { + return subFailAsStop; + } + + public DGroupConfig setSubFailAsStop(boolean subFailAsStop) { + this.subFailAsStop = subFailAsStop; + save(); + return this; + } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/DownloadConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/DownloadConfig.java similarity index 76% rename from Aria/src/main/java/com/arialyy/aria/core/config/DownloadConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/DownloadConfig.java index 2a4a300f..accacb5f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/DownloadConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/DownloadConfig.java @@ -15,9 +15,10 @@ */ package com.arialyy.aria.core.config; +import com.arialyy.aria.core.event.DMaxNumEvent; +import com.arialyy.aria.core.event.DSpeedEvent; import com.arialyy.aria.core.event.EventMsgUtil; -import com.arialyy.aria.core.event.SpeedEvent; -import com.arialyy.aria.core.queue.DownloadTaskQueue; +import com.arialyy.aria.util.ALog; import java.io.Serializable; /** @@ -43,13 +44,28 @@ public class DownloadConfig extends BaseTaskConfig implements Serializable { */ boolean useBlock = true; + /** + * 设置http下载获取文件大小是否使用Head请求。true:使用head请求,false:使用默认的get请求 + */ + boolean useHeadRequest = false; + + public boolean isUseHeadRequest() { + return useHeadRequest; + } + + public DownloadConfig setUseHeadRequest(boolean useHeadRequest) { + this.useHeadRequest = useHeadRequest; + save(); + return this; + } + public boolean isUseBlock() { return useBlock; } @Override public DownloadConfig setMaxSpeed(int maxSpeed) { super.setMaxSpeed(maxSpeed); - EventMsgUtil.getDefault().post(new SpeedEvent(maxSpeed)); + EventMsgUtil.getDefault().post(new DSpeedEvent(maxSpeed)); return this; } @@ -60,8 +76,12 @@ public class DownloadConfig extends BaseTaskConfig implements Serializable { } public DownloadConfig setMaxTaskNum(int maxTaskNum) { + if (maxTaskNum <= 0) { + ALog.e(TAG, "下载任务最大任务数不能小于0"); + return this; + } super.setMaxTaskNum(maxTaskNum); - DownloadTaskQueue.getInstance().setMaxTaskNum(maxTaskNum); + EventMsgUtil.getDefault().post(new DMaxNumEvent(maxTaskNum)); return this; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/TTaskConfigAdapeter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/TTaskConfigAdapeter.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/config/TTaskConfigAdapeter.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/TTaskConfigAdapeter.java diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/UploadConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/UploadConfig.java similarity index 75% rename from Aria/src/main/java/com/arialyy/aria/core/config/UploadConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/UploadConfig.java index 3c4996c0..da2108d8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/UploadConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/UploadConfig.java @@ -16,8 +16,9 @@ package com.arialyy.aria.core.config; import com.arialyy.aria.core.event.EventMsgUtil; -import com.arialyy.aria.core.event.SpeedEvent; -import com.arialyy.aria.core.queue.UploadTaskQueue; +import com.arialyy.aria.core.event.UMaxNumEvent; +import com.arialyy.aria.core.event.USpeedEvent; +import com.arialyy.aria.util.ALog; import java.io.Serializable; /** @@ -30,13 +31,17 @@ public class UploadConfig extends BaseTaskConfig implements Serializable { @Override public UploadConfig setMaxSpeed(int maxSpeed) { super.setMaxSpeed(maxSpeed); - EventMsgUtil.getDefault().post(new SpeedEvent(maxSpeed)); + EventMsgUtil.getDefault().post(new USpeedEvent(maxSpeed)); return this; } public UploadConfig setMaxTaskNum(int maxTaskNum) { + if (maxTaskNum <= 0){ + ALog.e(TAG, "上传任务最大任务数不能小于0"); + return this; + } super.setMaxTaskNum(maxTaskNum); - UploadTaskQueue.getInstance().setMaxTaskNum(maxTaskNum); + EventMsgUtil.getDefault().post(new UMaxNumEvent(maxTaskNum)); return this; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/config/XMLReader.java b/PublicComponent/src/main/java/com/arialyy/aria/core/config/XMLReader.java similarity index 94% rename from Aria/src/main/java/com/arialyy/aria/core/config/XMLReader.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/config/XMLReader.java index d3472130..d090d820 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/config/XMLReader.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/config/XMLReader.java @@ -27,13 +27,13 @@ import org.xml.sax.helpers.DefaultHandler; * Created by lyy on 2017/5/22. 读取配置文件 */ public class XMLReader extends DefaultHandler { - private final String TAG = "XMLReader"; + private final String TAG = CommonUtil.getClassName(this); private DownloadConfig mDownloadConfig = Configuration.getInstance().downloadCfg; private UploadConfig mUploadConfig = Configuration.getInstance().uploadCfg; private AppConfig mAppConfig = Configuration.getInstance().appCfg; private DGroupConfig mDGroupConfig = Configuration.getInstance().dGroupCfg; - private @ConfigType int mType; + private int mType; @Override public void startDocument() throws SAXException { super.startDocument(); @@ -144,6 +144,10 @@ public class XMLReader extends DefaultHandler { int subMaxTaskNum = checkInt(value) ? Integer.parseInt(value) : 3; setField("subMaxTaskNum", subMaxTaskNum, ConfigType.D_GROUP); break; + case "subFailAsStop": // 子任务失败时回调stop + setField("subFailAsStop", checkBoolean(value) ? Boolean.valueOf(value) : false, + ConfigType.D_GROUP); + break; case "subReTryNum": // 子任务重试次数 int subReTryNum = checkInt(value) ? Integer.parseInt(value) : 5; setField("subReTryNum", subReTryNum, ConfigType.D_GROUP); @@ -152,6 +156,10 @@ public class XMLReader extends DefaultHandler { int subReTryInterval = checkInt(value) ? Integer.parseInt(value) : 2000; setField("subReTryInterval", subReTryInterval, ConfigType.D_GROUP); break; + case "useHeadRequest": // 是否使用head请求 + boolean useHeadRequest = checkBoolean(value) ? Boolean.valueOf(value) : false; + setField("useHeadRequest", useHeadRequest, ConfigType.DOWNLOAD); + break; } } else if (mType == ConfigType.APP) { String value = attributes.getValue("value"); diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupTaskWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/download/AbsGroupTaskWrapper.java similarity index 72% rename from Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupTaskWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/download/AbsGroupTaskWrapper.java index fe6f199c..f3055b6c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupTaskWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/download/AbsGroupTaskWrapper.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.download; -import com.arialyy.aria.core.download.DownloadGroupEntity; -import com.arialyy.aria.core.event.ErrorEvent; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import java.util.List; /** @@ -25,19 +25,6 @@ import java.util.List; public abstract class AbsGroupTaskWrapper extends AbsTaskWrapper { - /** - * 错误信息 - */ - private ErrorEvent errorEvent; - - public ErrorEvent getErrorEvent() { - return errorEvent; - } - - public void setErrorEvent(ErrorEvent errorEvent) { - this.errorEvent = errorEvent; - } - public AbsGroupTaskWrapper(ENTITY entity) { super(entity); } @@ -45,4 +32,17 @@ public abstract class AbsGroupTaskWrapper getSubTaskWrapper(); public abstract void setSubTaskWrapper(List subTaskWrapper); + + /** + * {@code true} 忽略任务冲突,不考虑组任务hash冲突的情况 + */ + private boolean ignoreTaskOccupy = false; + + public boolean isIgnoreTaskOccupy() { + return ignoreTaskOccupy; + } + + public void setIgnoreTaskOccupy(boolean ignoreTaskOccupy) { + this.ignoreTaskOccupy = ignoreTaskOccupy; + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DGEntityWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DGEntityWrapper.java similarity index 92% rename from Aria/src/main/java/com/arialyy/aria/core/download/DGEntityWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/download/DGEntityWrapper.java index 936b0964..0e63a544 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DGEntityWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DGEntityWrapper.java @@ -15,6 +15,8 @@ */ package com.arialyy.aria.core.download; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.orm.AbsDbWrapper; import com.arialyy.aria.orm.annotation.Many; import com.arialyy.aria.orm.annotation.One; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DGTaskWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DGTaskWrapper.java similarity index 97% rename from Aria/src/main/java/com/arialyy/aria/core/download/DGTaskWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/download/DGTaskWrapper.java index 9b4ca29e..2df6b1fd 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DGTaskWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DGTaskWrapper.java @@ -17,7 +17,6 @@ package com.arialyy.aria.core.download; import com.arialyy.aria.core.config.Configuration; import com.arialyy.aria.core.config.DGroupConfig; -import com.arialyy.aria.core.inf.AbsGroupTaskWrapper; import java.util.ArrayList; import java.util.List; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java similarity index 78% rename from Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java index f86fcb91..041d8f64 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java @@ -15,10 +15,12 @@ */ package com.arialyy.aria.core.download; +import com.arialyy.aria.core.TaskOptionParams; import com.arialyy.aria.core.config.Configuration; import com.arialyy.aria.core.config.DownloadConfig; -import com.arialyy.aria.core.download.m3u8.M3U8TaskConfig; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.inf.ITaskOption; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.util.ComponentUtil; /** * Created by lyy on 2017/1/23. 下载任务实体和下载实体为一对一关系,下载实体删除,任务实体自动删除 @@ -35,31 +37,40 @@ public class DTaskWrapper extends AbsTaskWrapper { */ private boolean isGroupTask = false; - private M3U8TaskConfig m3u8TaskConfig; + /** + * M3u8任务配置信息 + */ + private ITaskOption m3u8Option; + + private TaskOptionParams m3u8Params = new TaskOptionParams(); /** * 文件下载url的临时保存变量 */ private String mTempUrl; + /** * 文件保存路径的临时变量 */ private String mTempFilePath; - /** - * {@code true}强制下载,不考虑文件路径是否被占用 - */ - private boolean forceDownload = false; - public DTaskWrapper(DownloadEntity entity) { super(entity); } - public M3U8TaskConfig asM3U8() { - if (m3u8TaskConfig == null) { - m3u8TaskConfig = new M3U8TaskConfig(); + public ITaskOption getM3u8Option() { + return m3u8Option; + } + + public void generateM3u8Option(Class clazz) { + m3u8Option = ComponentUtil.getInstance().buildTaskOption(clazz, m3u8Params); + } + + public TaskOptionParams getM3U8Params() { + if (m3u8Params == null) { + m3u8Params = new TaskOptionParams(); } - return m3u8TaskConfig; + return m3u8Params; } /** @@ -108,12 +119,4 @@ public class DTaskWrapper extends AbsTaskWrapper { public void setTempFilePath(String mTempFilePath) { this.mTempFilePath = mTempFilePath; } - - public boolean isForceDownload() { - return forceDownload; - } - - public void setForceDownload(boolean forceDownload) { - this.forceDownload = forceDownload; - } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java similarity index 89% rename from Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java index e8ebfeaa..974209f2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DownloadEntity.java @@ -19,9 +19,8 @@ package com.arialyy.aria.core.download; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import com.arialyy.aria.core.download.m3u8.M3U8Entity; -import com.arialyy.aria.core.inf.AbsNormalEntity; -import com.arialyy.aria.core.inf.ITaskWrapper; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.orm.annotation.Ignore; import com.arialyy.aria.orm.annotation.Unique; @@ -32,7 +31,7 @@ import com.arialyy.aria.util.CommonUtil; * Created by lyy on 2015/12/25. * 下载实体 */ -public class DownloadEntity extends AbsNormalEntity implements Parcelable { +public class DownloadEntity extends AbsNormalEntity implements Parcelable, Cloneable { @Unique private String downloadPath; //保存路径 /** @@ -75,21 +74,34 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable { return m3U8Entity; } + /** + * 设置进来的地址,如果需要获取真实的下载地址,请使用{@link #getRealUrl()} + */ @Override public String getKey() { return getUrl(); } + public String getRealUrl(){ + return isRedirect() ? getRedirectUrl() : getUrl(); + } + @Override public int getTaskType() { int type; - if (getUrl().startsWith("ftp")) { - type = ITaskWrapper.D_FTP; - } else { + if (getUrl() == null) { + type = ITaskWrapper.ERROR; + } else if (getUrl().startsWith("http")) { M3U8Entity temp = getM3U8Entity(); if (temp == null) { type = ITaskWrapper.D_HTTP; } else { type = temp.isLive() ? ITaskWrapper.M3U8_LIVE : ITaskWrapper.M3U8_VOD; } + } else if (getUrl().startsWith("ftp")) { + type = ITaskWrapper.D_FTP; + } else if (getUrl().startsWith("sftp")) { + type = ITaskWrapper.D_SFTP; + } else { + type = ITaskWrapper.ERROR; } return type; } @@ -129,26 +141,11 @@ public class DownloadEntity extends AbsNormalEntity implements Parcelable { this.groupHash = groupHash; } - /** - * 后面会删除该方法,请使用{@link #getFilePath()} - */ - @Deprecated - public String getDownloadPath() { - return downloadPath; - } - + @Override public String getFilePath() { return downloadPath; } - /** - * 后面会删除该方法,请使用{@link #setFilePath(String)} - */ - @Deprecated - public DownloadEntity setDownloadPath(String downloadPath) { - return setFilePath(downloadPath); - } - public DownloadEntity setFilePath(String filePath) { this.downloadPath = filePath; return this; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java similarity index 83% rename from Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java index 97662415..9aa6f10e 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/download/DownloadGroupEntity.java @@ -16,8 +16,9 @@ package com.arialyy.aria.core.download; import android.os.Parcel; -import com.arialyy.aria.core.inf.AbsGroupEntity; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import android.text.TextUtils; +import com.arialyy.aria.core.common.AbsGroupEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.annotation.Ignore; import java.util.ArrayList; import java.util.List; @@ -48,7 +49,12 @@ public class DownloadGroupEntity extends AbsGroupEntity { } @Override public int getTaskType() { - return getKey().startsWith("ftp") ? AbsTaskWrapper.D_FTP_DIR : AbsTaskWrapper.DG_HTTP; + if (getSubEntities() == null || getSubEntities().isEmpty() || TextUtils.isEmpty( + getSubEntities().get(0).getUrl())) { + return ITaskWrapper.ERROR; + } + return (groupHash.startsWith("ftp") || groupHash.startsWith("sftp")) ? ITaskWrapper.D_FTP_DIR + : ITaskWrapper.DG_HTTP; } public DownloadGroupEntity() { diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Entity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/download/M3U8Entity.java similarity index 84% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Entity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/download/M3U8Entity.java index 715d62df..b534099b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Entity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/download/M3U8Entity.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.core.download; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import com.arialyy.aria.core.common.TaskRecord; -import com.arialyy.aria.core.common.ThreadRecord; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.orm.annotation.Default; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.DbDataHelper; import java.io.File; @@ -58,11 +60,6 @@ public class M3U8Entity extends DbEntity implements Parcelable { */ private String cacheDir; - /** - * 生成索引 - */ - private boolean generateIndexFile; - /** * 加密key保存地址 */ @@ -83,6 +80,33 @@ public class M3U8Entity extends DbEntity implements Parcelable { */ public String iv; + /** + * key的格式,可能为空 + */ + public String keyFormat; + + /** + * key的格式版本,默认为1,如果是多个版本,使用"/"分隔,如:"1", "1/2", or "1/2/5" + */ + @Default("1") + public String keyFormatVersion = "1"; + + public String getKeyFormat() { + return keyFormat; + } + + public void setKeyFormat(String keyFormat) { + this.keyFormat = keyFormat; + } + + public String getKeyFormatVersion() { + return keyFormatVersion; + } + + public void setKeyFormatVersion(String keyFormatVersion) { + this.keyFormatVersion = keyFormatVersion; + } + public String getKeyPath() { return keyPath; } @@ -115,14 +139,6 @@ public class M3U8Entity extends DbEntity implements Parcelable { this.iv = iv; } - public boolean isGenerateIndexFile() { - return generateIndexFile; - } - - public void setGenerateIndexFile(boolean generateIndexFile) { - this.generateIndexFile = generateIndexFile; - } - public boolean isLive() { return isLive; } @@ -130,7 +146,7 @@ public class M3U8Entity extends DbEntity implements Parcelable { /** * 获取m3u8切片 * 如果任务未完成,则返回所有已下载完成的切片; - * 如果任务已完成,如果你设置{@link M3U8Delegate#merge(boolean)}合并分块的请求,返回null;如果没有设置该请求,则返回所有已下载完成的切片 + * 如果任务已完成,如果你设置了合并分块的请求,返回null;如果没有设置该请求,则返回所有已下载完成的切片 */ public List getCompletedPeer() { if (TextUtils.isEmpty(getCacheDir())) { @@ -138,7 +154,8 @@ public class M3U8Entity extends DbEntity implements Parcelable { return null; } List peers = new ArrayList<>(); - TaskRecord taskRecord = DbDataHelper.getTaskRecord(filePath); + TaskRecord taskRecord = DbDataHelper.getTaskRecord(filePath, + isLive ? ITaskWrapper.M3U8_LIVE : ITaskWrapper.M3U8_VOD); File cacheDir = new File(getCacheDir()); if ((taskRecord == null || taskRecord.threadRecords == null @@ -248,7 +265,6 @@ public class M3U8Entity extends DbEntity implements Parcelable { dest.writeInt(this.peerNum); dest.writeByte(this.isLive ? (byte) 1 : (byte) 0); dest.writeString(this.cacheDir); - dest.writeByte(this.generateIndexFile ? (byte) 1 : (byte) 0); dest.writeString(this.keyPath); dest.writeString(this.keyUrl); dest.writeString(this.method); @@ -261,7 +277,6 @@ public class M3U8Entity extends DbEntity implements Parcelable { this.peerNum = in.readInt(); this.isLive = in.readByte() != 0; this.cacheDir = in.readString(); - this.generateIndexFile = in.readByte() != 0; this.keyPath = in.readString(); this.keyUrl = in.readString(); this.method = in.readString(); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/event/DGMaxNumEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/DGMaxNumEvent.java new file mode 100644 index 00000000..ac5f9286 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/event/DGMaxNumEvent.java @@ -0,0 +1,28 @@ +/* + * 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.event; + +/** + * 组合任务最大下载任务数事件 + */ +public class DGMaxNumEvent { + + public int maxNum; + + public DGMaxNumEvent(int maxNum) { + this.maxNum = maxNum; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/event/DMaxNumEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/DMaxNumEvent.java new file mode 100644 index 00000000..ef1adb02 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/event/DMaxNumEvent.java @@ -0,0 +1,28 @@ +/* + * 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.event; + +/** + * 最大下载任务数事件 + */ +public class DMaxNumEvent { + + public int maxNum; + + public DMaxNumEvent(int maxNum) { + this.maxNum = maxNum; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/event/SpeedEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/DSpeedEvent.java similarity index 92% rename from Aria/src/main/java/com/arialyy/aria/core/event/SpeedEvent.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/event/DSpeedEvent.java index 8feb7f59..4b2e30cb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/event/SpeedEvent.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/event/DSpeedEvent.java @@ -15,11 +15,11 @@ */ package com.arialyy.aria.core.event; -public class SpeedEvent { +public class DSpeedEvent { public int speed; - public SpeedEvent(int speed) { + public DSpeedEvent(int speed) { this.speed = speed; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/event/ErrorEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/ErrorEvent.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/event/ErrorEvent.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/event/ErrorEvent.java diff --git a/Aria/src/main/java/com/arialyy/aria/core/event/Event.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/Event.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/event/Event.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/event/Event.java diff --git a/Aria/src/main/java/com/arialyy/aria/core/event/EventMethodInfo.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/EventMethodInfo.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/event/EventMethodInfo.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/event/EventMethodInfo.java diff --git a/Aria/src/main/java/com/arialyy/aria/core/event/EventMsgUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/EventMsgUtil.java similarity index 98% rename from Aria/src/main/java/com/arialyy/aria/core/event/EventMsgUtil.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/event/EventMsgUtil.java index 7e3cb342..f4a27dd1 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/event/EventMsgUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/event/EventMsgUtil.java @@ -38,7 +38,7 @@ public class EventMsgUtil { private Map> mEventMethods = new ConcurrentHashMap<>(); private ArrayBlockingQueue mEventQueue = new ArrayBlockingQueue<>(10); - private ExecutorService mPool = Executors.newFixedThreadPool(10); + private ExecutorService mPool = Executors.newFixedThreadPool(5); private EventMsgUtil() { ExecutorService pool = Executors.newSingleThreadExecutor(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/event/PeerIndexEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/PeerIndexEvent.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/event/PeerIndexEvent.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/event/PeerIndexEvent.java diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/event/UMaxNumEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/UMaxNumEvent.java new file mode 100644 index 00000000..6c59f59e --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/event/UMaxNumEvent.java @@ -0,0 +1,28 @@ +/* + * 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.event; + +/** + * 最大上传任务数事件 + */ +public class UMaxNumEvent { + + public int maxNum; + + public UMaxNumEvent(int maxNum) { + this.maxNum = maxNum; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/event/USpeedEvent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/event/USpeedEvent.java new file mode 100644 index 00000000..c3693709 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/event/USpeedEvent.java @@ -0,0 +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.event; + +public class USpeedEvent { + + public int speed; + + public USpeedEvent(int speed) { + this.speed = speed; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupLoader.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupLoader.java new file mode 100644 index 00000000..44954e89 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupLoader.java @@ -0,0 +1,375 @@ +/* + * 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.group; + +import android.os.Handler; +import android.os.Looper; +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.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IDGroupListener; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.ILoader; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.loader.IThreadTaskBuilder; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 组合任务加载器 + */ +public abstract class AbsGroupLoader implements ILoaderVisitor, ILoader { + protected final String TAG = CommonUtil.getClassName(getClass()); + + private long mCurrentLocation = 0; + private IDGroupListener mListener; + private ScheduledThreadPoolExecutor mTimer; + private long mUpdateInterval; + private boolean isStop = false, isCancel = false; + private Handler mScheduler; + private SimpleSubQueue mSubQueue = SimpleSubQueue.newInstance(); + private Map mExeLoader = new WeakHashMap<>(); + private Map mCache = new WeakHashMap<>(); + private DGTaskWrapper mGTWrapper; + private GroupRunState mState; + + protected IInfoTask mInfoTask; + + protected AbsGroupLoader(AbsTaskWrapper groupWrapper, IEventListener listener) { + mListener = (IDGroupListener) listener; + mGTWrapper = (DGTaskWrapper) groupWrapper; + mUpdateInterval = Configuration.getInstance().downloadCfg.getUpdateInterval(); + } + + /** + * 处理任务 + */ + protected abstract void handlerTask(Looper looper); + + /** + * 创建子任务加载器工具 + * + * @param needGetFileInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息 + */ + protected abstract AbsSubDLoadUtil createSubLoader(DTaskWrapper wrapper, boolean needGetFileInfo); + + protected IDGroupListener getListener() { + return mListener; + } + + protected DGTaskWrapper getWrapper() { + return mGTWrapper; + } + + protected GroupRunState getState() { + return mState; + } + + public Handler getScheduler() { + return mScheduler; + } + + /** + * 初始化组合任务状态 + */ + private void initState(Looper looper) { + mState = new GroupRunState(getWrapper().getKey(), mListener, mSubQueue); + for (DTaskWrapper wrapper : mGTWrapper.getSubTaskWrapper()) { + long fileLen = checkFileExists(wrapper.getEntity().getFilePath()); + if (wrapper.getEntity().getState() == IEntity.STATE_COMPLETE + && fileLen > 0 + && fileLen == wrapper.getEntity().getFileSize()) { + //mState.updateCompleteNum(); + mCurrentLocation += wrapper.getEntity().getFileSize(); + } else { + if (fileLen <= 0) { + wrapper.getEntity().setCurrentProgress(0); + } + wrapper.getEntity().setState(IEntity.STATE_POST_PRE); + mCache.put(wrapper.getKey(), wrapper); + mCurrentLocation += wrapper.getEntity().getCurrentProgress(); + } + } + if (getWrapper().getSubTaskWrapper().size() != mState.getCompleteNum()) { + getWrapper().setState(IEntity.STATE_POST_PRE); + } + mState.updateProgress(mCurrentLocation); + mScheduler = new Handler(looper, SimpleSchedulers.newInstance(mState, mGTWrapper.getKey())); + } + + /** + * 检查文件是否存在,需要检查普通任务和分块任务的 + * + * @param filePath 文件路径 + * @return 文件存在返回文件长度,不存在返回-1 + */ + private long checkFileExists(String filePath) { + File temp = new File(filePath); + if (temp.exists()) { + return temp.length(); + } + File block = new File(String.format(IRecordHandler.SUB_PATH, filePath, 0)); + if (block.exists()) { + return block.length(); + } else { + return -1; + } + } + + @Override public String getKey() { + return mGTWrapper.getKey(); + } + + /** + * 启动子任务下载 + * + * @param url 子任务下载地址 + */ + void startSubTask(String url) { + if (!checkSubTask(url, "开始")) { + return; + } + if (!mState.isRunning.get()) { + startTimer(); + } + AbsSubDLoadUtil d = getDownloader(url, false); + if (d != null && !d.isRunning()) { + mSubQueue.startTask(d); + } + } + + /** + * 停止子任务下载 + * + * @param url 子任务下载地址 + */ + void stopSubTask(String url) { + if (!checkSubTask(url, "停止")) { + return; + } + AbsSubDLoadUtil d = getDownloader(url, false); + 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 AbsSubDLoadUtil getDownloader(String url, boolean needGetFileInfo) { + AbsSubDLoadUtil d = mExeLoader.get(url); + if (d == null) { + return createSubLoader(mCache.get(url), needGetFileInfo); + } + return d; + } + + @Override public boolean isRunning() { + return mState != null && mState.isRunning.get(); + } + + @Override public void cancel() { + isCancel = true; + if (mInfoTask != null){ + mInfoTask.cancel(); + } + closeTimer(); + mSubQueue.removeAllTask(); + mListener.onCancel(); + } + + @Override public void stop() { + if (mInfoTask != null){ + mInfoTask.stop(); + } + isStop = true; + if (mSubQueue.getExecSize() == 0) { + mListener.onStop(mGTWrapper.getEntity().getCurrentProgress()); + } else { + mSubQueue.stopAllTask(); + } + closeTimer(); + } + + @Override public void run() { + checkComponent(); + if (isStop || isCancel) { + closeTimer(); + return; + } + startRunningFlow(); + } + + /** + * 开始进度流程 + */ + private void startRunningFlow() { + closeTimer(); + Looper.prepare(); + Looper looper = Looper.myLooper(); + if (looper == Looper.getMainLooper()) { + throw new IllegalThreadStateException("不能在主线程程序中调用Loader"); + } + initState(looper); + getState().setSubSize(getWrapper().getSubTaskWrapper().size()); + if (getState().getCompleteNum() != 0 + && getState().getCompleteNum() == getState().getSubSize()) { + mListener.onComplete(); + return; + } + startTimer(); + handlerTask(looper); + Looper.loop(); + } + + /** + * 组合任务获取完成子任务的信息后调用 + */ + protected void onPostStart() { + if (isBreak()) { + return; + } + getListener().onPostPre(getWrapper().getEntity().getFileSize()); + if (getWrapper().getEntity().getFileSize() > 0) { + getListener().onResume(getWrapper().getEntity().getCurrentProgress()); + } else { + getListener().onStart(getWrapper().getEntity().getCurrentProgress()); + } + } + + private synchronized void startTimer() { + mState.isRunning.set(true); + mTimer = new ScheduledThreadPoolExecutor(1); + mTimer.scheduleWithFixedDelay(new Runnable() { + @Override public void run() { + if (!mState.isRunning.get()) { + 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); + } + + /** + * 启动子任务下载器 + */ + protected void startSubLoader(AbsSubDLoadUtil loader) { + mExeLoader.put(loader.getKey(), loader); + mSubQueue.startTask(loader); + } + + @Override public boolean isBreak() { + if (isCancel || isStop) { + //ALog.d(TAG, "isCancel = " + isCancel + ", isStop = " + isStop); + ALog.d(TAG, String.format("任务【%s】已停止或取消了", mGTWrapper.getKey())); + return true; + } + return false; + } + + private synchronized void closeTimer() { + if (mTimer != null && !mTimer.isShutdown()) { + mTimer.shutdown(); + } + } + + protected void fail(AriaException e, boolean needRetry) { + closeTimer(); + getListener().onFail(needRetry, e); + } + + @Override public long getCurrentProgress() { + return mCurrentLocation; + } + + /** + * @deprecated 组合任务不需要实现这个,记录交由其子任务处理 + */ + @Deprecated + @Override public void addComponent(IRecordHandler recordHandler) { + + } + + /** + * @deprecated 组合任务不需要实现这个,线程创建交有子任务处理 + */ + @Deprecated + @Override public void addComponent(IThreadTaskBuilder builder) { + + } + + /** + * @deprecated 组合任务不需要实现这个,其内部是一个子任务调度器,并不是线程状态管理器 + */ + @Deprecated + @Override public void addComponent(IThreadStateManager threadState) { + + } + + /** + * 检查组件: {@link #mInfoTask} + */ + private void checkComponent() { + if (mInfoTask == null) { + throw new NullPointerException(("文件信息组件为空")); + } + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupLoaderUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupLoaderUtil.java new file mode 100644 index 00000000..c333eaee --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsGroupLoaderUtil.java @@ -0,0 +1,108 @@ +/* + * 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.group; + +import com.arialyy.aria.core.inf.IUtil; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.LoaderStructure; +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.CommonUtil; + +/** + * Created by AriaL on 2017/6/30. + * 任务组核心逻辑 + */ +public abstract class AbsGroupLoaderUtil implements IUtil { + + protected String TAG = CommonUtil.getClassName(getClass()); + private IEventListener mListener; + protected AbsGroupLoader mLoader; + private AbsTaskWrapper mTaskWrapper; + private boolean isStop = false, isCancel = false; + + + @Override public IUtil setParams(AbsTaskWrapper taskWrapper, IEventListener listener) { + mTaskWrapper = taskWrapper; + mListener = listener; + mLoader = getLoader(); + return this; + } + + protected abstract AbsGroupLoader getLoader(); + + protected abstract LoaderStructure buildLoaderStructure(); + + public IEventListener getListener() { + return mListener; + } + + public AbsTaskWrapper getTaskWrapper() { + return mTaskWrapper; + } + + @Override public String getKey() { + return mTaskWrapper.getKey(); + } + + @Override public long getFileSize() { + return mTaskWrapper.getEntity().getFileSize(); + } + + @Override public long getCurrentLocation() { + return mLoader.getCurrentProgress(); + } + + @Override public boolean isRunning() { + return mLoader.isRunning(); + } + + public void startSubTask(String url) { + getLoader().startSubTask(url); + } + + public void stopSubTask(String url) { + getLoader().stopSubTask(url); + } + + /** + * 取消下载 + */ + @Override public void cancel() { + isCancel = true; + mLoader.cancel(); + } + + /** + * 停止下载 + */ + @Override public void stop() { + isStop = true; + mLoader.stop(); + } + + @Override public void start() { + if (isStop || isCancel) { + ALog.w(TAG, "启动组合任务失败,任务已停止或已取消"); + return; + } + mListener.onPre(); + + buildLoaderStructure(); + new Thread(mLoader).start(); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsSubDLoadUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsSubDLoadUtil.java new file mode 100644 index 00000000..8a31688c --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/AbsSubDLoadUtil.java @@ -0,0 +1,165 @@ +/* + * 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.group; + +import android.os.Handler; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.inf.IUtil; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.listener.ISchedulers; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.SubLoader; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; + +/** + * 子任务下载器工具,需要在线程池中执行 + */ +public abstract class AbsSubDLoadUtil implements IUtil, Runnable { + protected final String TAG = CommonUtil.getClassName(getClass()); + + protected SubLoader mDLoader; + private DTaskWrapper mWrapper; + private Handler mSchedulers; + private boolean needGetInfo; + private boolean isStop = false, isCancel = false; + private String parentKey; + + /** + * @param schedulers 调度器 + * @param needGetInfo {@code true} 需要获取文件信息。{@code false} 不需要获取文件信息 + */ + protected AbsSubDLoadUtil(Handler schedulers, boolean needGetInfo, String parentKey) { + mSchedulers = schedulers; + this.parentKey = parentKey; + this.needGetInfo = needGetInfo; + } + + @Override public IUtil setParams(AbsTaskWrapper taskWrapper, IEventListener listener) { + mWrapper = (DTaskWrapper) taskWrapper; + mDLoader = getLoader(); + return this; + } + + /** + * 创建加载器 + */ + protected abstract SubLoader getLoader(); + + protected abstract LoaderStructure buildLoaderStructure(); + + public String getParentKey() { + return parentKey; + } + + protected boolean isNeedGetInfo() { + return needGetInfo; + } + + public Handler getSchedulers() { + return mSchedulers; + } + + @Override public String getKey() { + return mDLoader.getKey(); + } + + public DTaskWrapper getWrapper() { + return mWrapper; + } + + public DownloadEntity getEntity() { + return mWrapper.getEntity(); + } + + public TaskRecord getRecord(){ + return getLoader().getRecord(); + } + + @Override public void run() { + if (isStop || isCancel) { + return; + } + buildLoaderStructure(); + new Thread(mDLoader).start(); + } + + /** + * 请在线程池中使用 + */ + @Deprecated + @Override public void start() { + throw new AssertionError("请在线程池中使用"); + } + + /** + * 重新开始任务 + */ + void reStart() { + if (mDLoader != null) { + mDLoader.retryTask(); + } + } + + /** + * @deprecated 子任务不实现这个 + */ + @Deprecated + @Override public long getFileSize() { + return -1; + } + + /** + * 子任务不实现这个 + */ + @Deprecated + @Override public long getCurrentLocation() { + return -1; + } + + @Override public boolean isRunning() { + return mDLoader != null && mDLoader.isRunning(); + } + + @Override public void cancel() { + if (isCancel) { + ALog.w(TAG, "子任务已取消"); + return; + } + isCancel = true; + if (mDLoader != null && isRunning()) { + mDLoader.cancel(); + } else { + mSchedulers.obtainMessage(ISchedulers.CANCEL, this).sendToTarget(); + } + } + + @Override public void stop() { + if (isStop) { + ALog.w(TAG, "任务已停止"); + return; + } + isStop = true; + if (mDLoader != null && isRunning()) { + mDLoader.stop(); + } else { + mSchedulers.obtainMessage(ISchedulers.STOP, this).sendToTarget(); + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupRunState.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/GroupRunState.java similarity index 61% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/GroupRunState.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/group/GroupRunState.java index 75616709..94c487d8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/GroupRunState.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/GroupRunState.java @@ -13,16 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.group; +package com.arialyy.aria.core.group; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.listener.IDGroupListener; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.util.ALog; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; /** * 组合任务执行中的状态信息 */ -class GroupRunState { +public final class GroupRunState { + private String TAG = "GroupRunState"; /** * 子任务数 */ @@ -31,17 +36,17 @@ class GroupRunState { /** * 已经完成的任务数 */ - private int mCompleteNum; + private AtomicInteger mCompleteNum = new AtomicInteger(); /** * 失败的任务数 */ - private int mFailNum; + private AtomicInteger mFailNum = new AtomicInteger(); /** * 停止的任务数 */ - private int mStopNum; + private AtomicInteger mStopNum = new AtomicInteger(); /** * 当前进度 @@ -61,7 +66,7 @@ class GroupRunState { /** * 是否在执行 */ - boolean isRunning = false; + AtomicBoolean isRunning = new AtomicBoolean(false); /** * 子任务失败、停止记录,用于当子任务失败重新被用户点击开始时,更新{@link #mStopNum}或{@link #mFailNum} @@ -71,14 +76,29 @@ class GroupRunState { private String mGroupHash; - GroupRunState(String groupHash, IDGroupListener listener, int subSize, - SimpleSubQueue queue) { + GroupRunState(String groupHash, IDGroupListener listener, SimpleSubQueue queue) { this.listener = listener; this.queue = queue; - mSubSize = subSize; mGroupHash = groupHash; } + public void setSubSize(int subSize) { + mSubSize = subSize; + } + + /** + * 组合任务是否正在自行 + * + * @return {@code true}组合任务正在执行 + */ + public boolean isRunning() { + return isRunning.get(); + } + + public void setRunning(boolean running) { + isRunning.set(running); + } + String getGroupHash() { return mGroupHash; } @@ -86,49 +106,49 @@ class GroupRunState { /** * 获取组合任务子任务数 */ - int getSubSize() { + public int getSubSize() { return mSubSize; } /** * 获取失败的数量 */ - int getFailNum() { - return mFailNum; + public int getFailNum() { + return mFailNum.get(); } /** * 获取停止的数量 */ - int getStopNum() { - return mStopNum; + public int getStopNum() { + return mStopNum.get(); } /** * 获取完成的数量 */ - int getCompleteNum() { - return mCompleteNum; + public int getCompleteNum() { + return mCompleteNum.get(); } /** * 获取当前组合任务总进度 */ - long getProgress() { + public long getProgress() { return mProgress; } /** * 更新完成的数量,mCompleteNum + 1 */ - void updateCompleteNum() { - mCompleteNum++; + public void updateCompleteNum() { + mCompleteNum.getAndIncrement(); } /** * 更新任务进度 */ - void updateProgress(long newProgress) { + public void updateProgress(long newProgress) { this.mProgress = newProgress; } @@ -137,13 +157,13 @@ class GroupRunState { * * @param key {@link AbsTaskWrapper#getKey()} */ - void updateCount(String key) { + public void updateCount(String key) { if (mFailTemp.contains(key)) { mFailTemp.remove(key); - mFailNum--; + mFailNum.getAndDecrement(); } else if (mStopTemp.contains(key)) { mStopTemp.remove(key); - mStopNum--; + mStopNum.getAndDecrement(); } } @@ -152,9 +172,9 @@ class GroupRunState { * * @param key {@link AbsTaskWrapper#getKey()} */ - void countStopNum(String key) { + public void countStopNum(String key) { mStopTemp.add(key); - mStopNum++; + mStopNum.getAndIncrement(); } /** @@ -162,8 +182,8 @@ class GroupRunState { * * @param key {@link AbsTaskWrapper#getKey()} */ - void countFailNum(String key) { + public void countFailNum(String key) { mFailTemp.add(key); - mFailNum++; + mFailNum.getAndIncrement(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/GroupSendParams.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/GroupSendParams.java similarity index 79% rename from Aria/src/main/java/com/arialyy/aria/core/inf/GroupSendParams.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/group/GroupSendParams.java index 2a977b9e..303fff3d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/GroupSendParams.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/GroupSendParams.java @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.group; + +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.task.AbsGroupTask; /** * Created by lyy on 2017/9/8. * 任务组参数传递 */ -public class GroupSendParams { +public final class GroupSendParams { public GROUP_TASK groupTask; public ENTITY entity; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/ISubQueue.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/ISubQueue.java similarity index 91% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/ISubQueue.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/group/ISubQueue.java index fab093dd..70f80442 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/ISubQueue.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/ISubQueue.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.group; +package com.arialyy.aria.core.group; -import com.arialyy.aria.core.common.AbsFileer; -import com.arialyy.aria.core.common.IUtil; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.inf.IUtil; import com.arialyy.aria.core.config.DGroupConfig; /** * 组合任务子任务队列 * - * @param {@link AbsFileer}下载器 + * @param {@link AbsNormalLoader}下载器 */ interface ISubQueue { diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSchedulers.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSchedulers.java new file mode 100644 index 00000000..f593e405 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSchedulers.java @@ -0,0 +1,230 @@ +/* + * 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.group; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.config.Configuration; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.exception.ExceptionFactory; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.NetUtils; +import java.io.File; + +/** + * 组合任务子任务调度器,用于调度任务的开始、停止、失败、完成等情况 + * 该调度器生命周期和{@link AbsGroupLoaderUtil}生命周期一致 + */ +final class SimpleSchedulers implements Handler.Callback { + private final String TAG = CommonUtil.getClassName(this); + private SimpleSubQueue mQueue; + private GroupRunState mGState; + private String mKey; // 组合任务的key + + private SimpleSchedulers(GroupRunState state, String key) { + mQueue = state.queue; + mGState = state; + mKey = key; + } + + static SimpleSchedulers newInstance(GroupRunState state, String key) { + return new SimpleSchedulers(state, key); + } + + @Override public boolean handleMessage(Message msg) { + Bundle b = msg.getData(); + if (b == null) { + ALog.w(TAG, "组合任务子任务调度数据为空"); + return true; + } + String threadName = b.getString(IThreadStateManager.DATA_THREAD_NAME); + AbsSubDLoadUtil loaderUtil = mQueue.getLoaderUtil(threadName); + if (loaderUtil == null) { + ALog.e(TAG, String.format("子任务loader不存在,state:%s,key:%s", msg.what, threadName)); + return true; + } + long curLocation = b.getLong(IThreadStateManager.DATA_THREAD_LOCATION, + loaderUtil.getLoader().getWrapper().getEntity().getCurrentProgress()); + // 处理状态 + switch (msg.what) { + case IThreadStateManager.STATE_RUNNING: + long range = (long) msg.obj; + mGState.listener.onSubRunning(loaderUtil.getEntity(), range); + break; + case IThreadStateManager.STATE_PRE: + mGState.listener.onSubPre(loaderUtil.getEntity()); + mGState.updateCount(loaderUtil.getKey()); + break; + case IThreadStateManager.STATE_START: + mGState.listener.onSubStart(loaderUtil.getEntity()); + break; + case IThreadStateManager.STATE_STOP: + handleStop(loaderUtil, curLocation); + ThreadTaskManager.getInstance().removeSingleTaskThread(mKey, threadName); + break; + case IThreadStateManager.STATE_COMPLETE: + handleComplete(loaderUtil); + ThreadTaskManager.getInstance().removeSingleTaskThread(mKey, threadName); + break; + case IThreadStateManager.STATE_FAIL: + boolean needRetry = b.getBoolean(IThreadStateManager.DATA_RETRY, false); + handleFail(loaderUtil, needRetry); + ThreadTaskManager.getInstance().removeSingleTaskThread(mKey, threadName); + break; + } + return true; + } + + /** + * 处理子任务失败的情况 + * 1、子任务失败次数大于等于配置的重试次数,才能认为子任务停止 + * 2、stopNum + failNum + completeNum + cacheNum == subSize,则认为组合任务停止 + * 3、failNum == subSize,只有全部的子任务都失败了,才能任务组合任务失败 + * + * @param needRetry true 需要重试,false 不需要重试 + */ + private synchronized void handleFail(final AbsSubDLoadUtil loaderUtil, boolean needRetry) { + Log.d(TAG, String.format("handleFail, size = %s, completeNum = %s, failNum = %s, stopNum = %s", + mGState.getSubSize(), mGState.getCompleteNum(), mGState.getFailNum(), + mGState.getSubSize())); + + Configuration config = Configuration.getInstance(); + int num = config.dGroupCfg.getSubReTryNum(); + boolean isNotNetRetry = config.appCfg.isNotNetRetry(); + + if (!needRetry + || (!NetUtils.isConnected(AriaConfig.getInstance().getAPP()) && !isNotNetRetry) + || loaderUtil.getLoader() == null // 如果获取不到文件信息,loader为空 + || loaderUtil.getEntity().getFailNum() > num) { + mQueue.removeTaskFromExecQ(loaderUtil); + mGState.listener.onSubFail(loaderUtil.getEntity(), + ExceptionFactory.getException(ExceptionFactory.TYPE_GROUP, + String.format("任务组子任务【%s】下载失败,下载地址【%s】", loaderUtil.getEntity().getFileName(), + loaderUtil.getEntity().getUrl()), null)); + mGState.countFailNum(loaderUtil.getKey()); + if (mGState.getFailNum() == mGState.getSubSize() + || mGState.getStopNum() + mGState.getFailNum() + mGState.getCompleteNum() + == mGState.getSubSize()) { + mGState.isRunning.set(false); + if (mGState.getCompleteNum() > 0 + && Configuration.getInstance().dGroupCfg.isSubFailAsStop()) { + ALog.e(TAG, String.format("任务组【%s】停止", mGState.getGroupHash())); + mGState.listener.onStop(mGState.getProgress()); + return; + } + mGState.listener.onFail(false, + ExceptionFactory.getException(ExceptionFactory.TYPE_GROUP, + String.format("任务组【%s】下载失败", mGState.getGroupHash()), null)); + return; + } + startNext(); + return; + } + SimpleSubRetryQueue.getInstance().offer(loaderUtil); + } + + /** + * 处理子任务停止的情况 + * 1、所有的子任务已经停止,则认为组合任务停止 + * 2、completeNum + failNum + stopNum = subSize,则认为组合任务停止 + */ + private synchronized void handleStop(AbsSubDLoadUtil loadUtil, long curLocation) { + Log.d(TAG, String.format("handleStop, size = %s, completeNum = %s, failNum = %s, stopNum = %s", + mGState.getSubSize(), mGState.getCompleteNum(), mGState.getFailNum(), + mGState.getSubSize())); + + mGState.listener.onSubStop(loadUtil.getEntity(), curLocation); + mGState.countStopNum(loadUtil.getKey()); + if (mGState.getStopNum() == mGState.getSubSize() + || mGState.getStopNum() + + mGState.getCompleteNum() + + mGState.getFailNum() + + mQueue.getCacheSize() + == mGState.getSubSize()) { + mGState.isRunning.set(false); + mGState.listener.onStop(mGState.getProgress()); + return; + } + startNext(); + } + + /** + * 处理子任务完成的情况,有以下三种情况 + * 1、已经没有缓存的子任务,并且停止的子任务是数{@link GroupRunState#getStopNum()} ()}为0,失败的子任数{@link + * GroupRunState#getFailNum()}为0,则认为组合任务已经完成 + * 2、已经没有缓存的子任务,并且停止的子任务是数{@link GroupRunState#getCompleteNum()}不为0,或者失败的子任数{@link + * GroupRunState#getFailNum()}不为0,则认为组合任务被停止 + * 3、只有有缓存的子任务,则任务组合任务没有完成 + */ + private synchronized void handleComplete(AbsSubDLoadUtil loader) { + ALog.d(TAG, String.format("子任务【%s】完成", loader.getEntity().getFileName())); + Log.d(TAG, + String.format("handleComplete, size = %s, completeNum = %s, failNum = %s, stopNum = %s", + mGState.getSubSize(), mGState.getCompleteNum(), mGState.getFailNum(), + mGState.getStopNum())); + + TaskRecord record = loader.getRecord(); + if (record != null && record.isBlock) { + File partFile = + new File(String.format(IRecordHandler.SUB_PATH, record.filePath, 0)); + partFile.renameTo(new File(record.filePath)); + } + ThreadTaskManager.getInstance().removeTaskThread(loader.getKey()); + mGState.listener.onSubComplete(loader.getEntity()); + mQueue.removeTaskFromExecQ(loader); + mGState.updateCompleteNum(); + if (mGState.getCompleteNum() + mGState.getFailNum() + mGState.getStopNum() + == mGState.getSubSize()) { + if (mGState.getStopNum() == 0 && mGState.getFailNum() == 0) { + mGState.listener.onComplete(); + } else if (mGState.getStopNum() == 0 + && !Configuration.getInstance().dGroupCfg.isSubFailAsStop()) { + mGState.listener.onFail(false, + ExceptionFactory.getException(ExceptionFactory.TYPE_GROUP, + String.format("任务组【%s】下载失败", mGState.getGroupHash()), null)); + } else { + mGState.listener.onStop(mGState.getProgress()); + } + mGState.isRunning.set(false); + return; + } + startNext(); + } + + /** + * 如果有等待中的任务,则启动下一任务 + */ + private void startNext() { + if (mQueue.isStopAll()) { + return; + } + AbsSubDLoadUtil next = mQueue.getNextTask(); + if (next != null) { + ALog.d(TAG, String.format("启动任务:%s", next.getEntity().getFileName())); + mQueue.startTask(next); + return; + } + ALog.i(TAG, "没有下一子任务"); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/SimpleSubQueue.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSubQueue.java similarity index 58% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/SimpleSubQueue.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSubQueue.java index f004345b..811df698 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/SimpleSubQueue.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSubQueue.java @@ -13,36 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.group; +package com.arialyy.aria.core.group; import com.arialyy.aria.core.config.Configuration; import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** - * 组合任务队列,该队列生命周期和{@link AbsGroupUtil}生命周期一致 + * 组合任务队列,该队列生命周期和{@link AbsGroupLoaderUtil}生命周期一致 */ -class SimpleSubQueue implements ISubQueue { - private static final String TAG = "SimpleSubQueue"; +final class SimpleSubQueue implements ISubQueue { + private final String TAG = CommonUtil.getClassName(getClass()); /** * 缓存下载器 */ - private Map mCache = new LinkedHashMap<>(); + private Map mCache = new ConcurrentHashMap<>(); /** * 执行中的下载器 */ - private Map mExec = new LinkedHashMap<>(); + private Map mExec = new ConcurrentHashMap<>(); /** * 最大执行任务数 */ - private int mExecSize; + private int mMaxExecSize; /** * 是否停止任务任务 @@ -50,15 +51,19 @@ class SimpleSubQueue implements ISubQueue { private boolean isStopAll = false; private SimpleSubQueue() { - mExecSize = Configuration.getInstance().dGroupCfg.getSubMaxTaskNum(); + mMaxExecSize = Configuration.getInstance().dGroupCfg.getSubMaxTaskNum(); } static SimpleSubQueue newInstance() { return new SimpleSubQueue(); } - Map getExec() { - return mExec; + synchronized AbsSubDLoadUtil getLoaderUtil(String key) { + AbsSubDLoadUtil sub = mExec.get(key); + if (sub != null) { + return sub; + } + return mCache.get(key); } /** @@ -68,27 +73,32 @@ class SimpleSubQueue implements ISubQueue { return mCache.size(); } + public int getExecSize(){ + return mExec.size(); + } + boolean isStopAll() { return isStopAll; } - @Override public void addTask(SubDLoadUtil fileer) { + @Override public void addTask(AbsSubDLoadUtil fileer) { mCache.put(fileer.getKey(), fileer); } - @Override public void startTask(SubDLoadUtil fileer) { - if (mExec.size() < mExecSize) { + @Override public void startTask(AbsSubDLoadUtil fileer) { + if (mExec.size() < mMaxExecSize) { mCache.remove(fileer.getKey()); mExec.put(fileer.getKey(), fileer); - ALog.d(TAG, String.format("开始执行子任务:%s", fileer.getEntity().getFileName())); - fileer.start(); - } else { - ALog.d(TAG, String.format("执行队列已满,任务进入缓存器中,key: %s", fileer.getKey())); - addTask(fileer); + ALog.d(TAG, + String.format("开始执行子任务:%s,key: %s", fileer.getEntity().getFileName(), fileer.getKey())); + fileer.run(); + return; } + ALog.d(TAG, String.format("执行队列已满,任务进入缓存器中,key: %s", fileer.getKey())); + addTask(fileer); } - @Override public void stopTask(SubDLoadUtil fileer) { + @Override public void stopTask(AbsSubDLoadUtil fileer) { fileer.stop(); mExec.remove(fileer.getKey()); } @@ -96,9 +106,10 @@ class SimpleSubQueue implements ISubQueue { @Override public void stopAllTask() { isStopAll = true; ALog.d(TAG, "停止组合任务"); + mCache.clear(); Set keys = mExec.keySet(); for (String key : keys) { - SubDLoadUtil loader = mExec.get(key); + AbsSubDLoadUtil loader = mExec.get(key); if (loader != null) { ALog.d(TAG, String.format("停止子任务:%s", loader.getEntity().getFileName())); loader.stop(); @@ -111,18 +122,18 @@ class SimpleSubQueue implements ISubQueue { ALog.e(TAG, String.format("修改组合任务子任务队列数失败,num: %s", num)); return; } - if (num == mExecSize) { - ALog.i(TAG, String.format("忽略此次修改,oldSize: %s, num: %s", mExecSize, num)); + if (num == mMaxExecSize) { + ALog.i(TAG, String.format("忽略此次修改,oldSize: %s, num: %s", mMaxExecSize, num)); return; } - int oldSize = mExecSize; - mExecSize = num; + int oldSize = mMaxExecSize; + mMaxExecSize = num; int diff = Math.abs(oldSize - num); if (oldSize < num) { // 处理队列变小的情况,该情况下将停止队尾任务,并将这些任务添加到缓存队列中 if (mExec.size() > num) { Set keys = mExec.keySet(); - List caches = new ArrayList<>(); + List caches = new ArrayList<>(); int i = 0; for (String key : keys) { if (i > num) { @@ -130,39 +141,35 @@ class SimpleSubQueue implements ISubQueue { } i++; } - Collection temp = mCache.values(); + Collection temp = mCache.values(); mCache.clear(); - for (SubDLoadUtil cache : caches) { + for (AbsSubDLoadUtil cache : caches) { addTask(cache); } - for (SubDLoadUtil t : temp) { + for (AbsSubDLoadUtil t : temp) { addTask(t); } } - } else { // 处理队列变大的情况,该情况下将增加任务 - if (mExec.size() < num) { - for (int i = 0; i < diff; i++) { - SubDLoadUtil next = getNextTask(); - if (next != null) { - startTask(next); - } else { - ALog.d(TAG, "子任务中没有缓存任务"); - } + return; + } + // 处理队列变大的情况,该情况下将增加任务 + if (mExec.size() < num) { + for (int i = 0; i < diff; i++) { + AbsSubDLoadUtil next = getNextTask(); + if (next != null) { + startTask(next); + } else { + ALog.d(TAG, "子任务中没有缓存任务"); } } } } - @Override public void removeTaskFromExecQ(SubDLoadUtil fileer) { - if (mExec.containsKey(fileer.getKey())) { - if (fileer.isRunning()) { - fileer.stop(); - } - mExec.remove(fileer.getKey()); - } + @Override public void removeTaskFromExecQ(AbsSubDLoadUtil fileer) { + mExec.remove(fileer.getKey()); } - @Override public void removeTask(SubDLoadUtil fileer) { + @Override public void removeTask(AbsSubDLoadUtil fileer) { removeTaskFromExecQ(fileer); mCache.remove(fileer.getKey()); } @@ -171,7 +178,7 @@ class SimpleSubQueue implements ISubQueue { ALog.d(TAG, "删除组合任务"); Set keys = mExec.keySet(); for (String key : keys) { - SubDLoadUtil loader = mExec.get(key); + AbsSubDLoadUtil loader = mExec.get(key); if (loader != null) { ALog.d(TAG, String.format("停止子任务:%s", loader.getEntity().getFileName())); loader.cancel(); @@ -179,7 +186,7 @@ class SimpleSubQueue implements ISubQueue { } } - @Override public SubDLoadUtil getNextTask() { + @Override public AbsSubDLoadUtil getNextTask() { Iterator keys = mCache.keySet().iterator(); if (keys.hasNext()) { return mCache.get(keys.next()); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSubRetryQueue.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSubRetryQueue.java new file mode 100644 index 00000000..0d95cd05 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SimpleSubRetryQueue.java @@ -0,0 +1,49 @@ +/* + * 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.group; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 子任务重试队列 + */ +final class SimpleSubRetryQueue { + private volatile static SimpleSubRetryQueue INSTANCE = null; + private ExecutorService pool = new ThreadPoolExecutor(5, 100, + 60L, TimeUnit.SECONDS, + new SynchronousQueue()); + + public synchronized static SimpleSubRetryQueue getInstance() { + if (INSTANCE == null) { + synchronized (SimpleSubRetryQueue.class) { + INSTANCE = new SimpleSubRetryQueue(); + } + } + + return INSTANCE; + } + + private SimpleSubRetryQueue() { + + } + + void offer(AbsSubDLoadUtil subDLoadUtil) { + pool.submit(subDLoadUtil.getLoader()); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/group/SubRecordHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SubRecordHandler.java new file mode 100644 index 00000000..5d4021d0 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/group/SubRecordHandler.java @@ -0,0 +1,108 @@ +/* + * 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.group; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.common.RecordHelper; +import com.arialyy.aria.core.config.Configuration; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.RecordUtil; +import java.util.ArrayList; + +/** + * 子任务记录处理 + */ +public class SubRecordHandler extends RecordHandler { + public SubRecordHandler(AbsTaskWrapper wrapper) { + super(wrapper); + } + + @Override public void handlerTaskRecord(TaskRecord record) { + RecordHelper helper = new RecordHelper(getWrapper(), record); + if (getWrapper().isSupportBP() && record.threadNum > 1) { + if (record.isBlock) { + helper.handleBlockRecord(); + } else { + helper.handleMultiRecord(); + } + } else if (!getWrapper().isSupportBP()) { + helper.handleNoSupportBPRecord(); + } else { + helper.handleSingleThreadRecord(); + } + } + + @Override + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + ThreadRecord tr; + tr = new ThreadRecord(); + tr.taskKey = record.filePath; + tr.threadId = threadId; + tr.startLocation = startL; + tr.isComplete = false; + + tr.threadType = record.taskType; + //最后一个线程的结束位置即为文件的总长度 + if (threadId == (record.threadNum - 1)) { + endL = getFileSize(); + } + tr.endLocation = endL; + tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + + int requestType = getWrapper().getRequestType(); + if (requestType == ITaskWrapper.D_HTTP || requestType == ITaskWrapper.DG_HTTP) { + record.isBlock = Configuration.getInstance().downloadCfg.isUseBlock(); + } else { + record.isBlock = false; + } + record.taskType = requestType; + record.isGroupRecord = getEntity().isGroupChild(); + if (record.isGroupRecord) { + if (getEntity() instanceof DownloadEntity) { + record.dGroupHash = ((DownloadEntity) getEntity()).getGroupHash(); + } + } + + return record; + } + + @Override public int initTaskThreadNum() { + int requestTpe = getWrapper().getRequestType(); + if (requestTpe == ITaskWrapper.U_HTTP + || (requestTpe == ITaskWrapper.D_HTTP && (!getWrapper().isSupportBP()) + )) { + return 1; + } + int threadNum = Configuration.getInstance().downloadCfg.getThreadNum(); + return getFileSize() <= IRecordHandler.SUB_LEN + ? 1 + : threadNum; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/inf/EventFlag.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/EventFlag.java new file mode 100644 index 00000000..689cb365 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/EventFlag.java @@ -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.inf; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface EventFlag { +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IEntity.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/core/inf/IEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/inf/IEntity.java diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IEventHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IEventHandler.java new file mode 100644 index 00000000..f2d1628a --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IEventHandler.java @@ -0,0 +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.inf; + +/** + * 事件处理对象 + * + * @author lyy + * Date: 2019-09-10 + */ +public interface IEventHandler { +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IOptionConstant.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IOptionConstant.java new file mode 100644 index 00000000..57321057 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IOptionConstant.java @@ -0,0 +1,52 @@ +/* + * 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; + +/** + * @author lyy + * Date: 2019-09-10 + */ +public interface IOptionConstant { + // ftp 任务设置常量 + String ftpUrlEntity = "urlEntity"; + String charSet = "charSet"; + String clientConfig = "clientConfig"; + String uploadInterceptor = "uploadInterceptor"; + + // http + String useServerFileName = "useServerFileName"; + String requestEnum = "requestEnum"; + String fileLenAdapter = "fileLenAdapter"; + String params = "params"; + String formFields = "formFields"; + String headers = "headers"; + String proxy = "proxy"; + + // m3u8 vod + String bandWidth = "bandWidth"; + String cacheDir = "cacheDir"; + String generateIndexFileTemp = "generateIndexFileTemp"; + String bandWidthUrlConverter = "bandWidthUrlConverter"; + String mergeFile = "mergeFile"; + String mergeHandler = "mergeHandler"; + String vodUrlConverter = "vodUrlConverter"; + String maxTsQueueNum = "maxTsQueueNum"; + String jumpIndex = "jumpIndex"; + + // m3u8 live + String liveTsUrlConverter = "liveTsUrlConverter"; + String liveUpdateInterval = "liveUpdateInterval"; +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/ITaskConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/ITaskOption.java similarity index 95% rename from Aria/src/main/java/com/arialyy/aria/core/inf/ITaskConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/inf/ITaskOption.java index 72ab22c4..5929eb8a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/ITaskConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/ITaskOption.java @@ -19,6 +19,6 @@ package com.arialyy.aria.core.inf; * Created by AriaL on 2017/6/29. * 任务信息设置接口 */ -public interface ITaskConfig { +public interface ITaskOption { } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IThreadStateManager.java similarity index 58% rename from Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/inf/IThreadStateManager.java index 996e6108..c4eaf027 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IThreadStateManager.java @@ -13,22 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.inf; import android.os.Handler; +import android.os.Looper; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.loader.ILoaderComponent; /** * 线程任务状态 */ -public interface IThreadState extends Handler.Callback { +public interface IThreadStateManager extends ILoaderComponent { int STATE_STOP = 0x01; int STATE_FAIL = 0x02; int STATE_CANCEL = 0x03; int STATE_COMPLETE = 0x04; int STATE_RUNNING = 0x05; int STATE_UPDATE_PROGRESS = 0x06; - String KEY_RETRY = "KEY_RETRY"; - String KEY_ERROR_INFO = "KEY_ERROR_INFO"; + int STATE_PRE = 0x07; + int STATE_START = 0x08; + String DATA_RETRY = "DATA_RETRY"; + String DATA_ERROR_INFO = "DATA_ERROR_INFO"; + String DATA_THREAD_NAME = "DATA_THREAD_NAME"; + String DATA_THREAD_LOCATION = "DATA_THREAD_LOCATION"; + String DATA_ADD_LEN = "DATA_ADD_LEN"; // 增加的长度 /** * 任务是否已经失败 @@ -50,4 +58,21 @@ public interface IThreadState extends Handler.Callback { * @return 任务当前进度 */ long getCurrentProgress(); + + /** + * 更新当前进度 + * + * @param currentProgress 当前进度 + */ + void updateCurrentProgress(long currentProgress); + + /** + * 设置消息循环体 + */ + void setLooper(TaskRecord taskRecord, Looper looper); + + /** + * 创建handler 回调 + */ + Handler.Callback getHandlerCallback(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IUtil.java similarity index 86% rename from Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/inf/IUtil.java index 23c6d007..dd23b61b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/IUtil.java @@ -14,11 +14,13 @@ * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.inf; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.listener.IEventListener; import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; /** * Created by lyy on 2016/10/31. @@ -26,6 +28,8 @@ import com.arialyy.aria.core.upload.UploadEntity; */ public interface IUtil { + IUtil setParams(AbsTaskWrapper taskWrapper, IEventListener listener); + /** * 获取任务标志 * diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/Suggest.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/Suggest.java similarity index 95% rename from Aria/src/main/java/com/arialyy/aria/core/common/Suggest.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/inf/Suggest.java index b9c56fb6..86cf1466 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/Suggest.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/Suggest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.inf; public interface Suggest { diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/TaskSchedulerType.java b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/TaskSchedulerType.java similarity index 50% rename from Aria/src/main/java/com/arialyy/aria/core/inf/TaskSchedulerType.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/inf/TaskSchedulerType.java index 77e1dfbf..a521cbc2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/TaskSchedulerType.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/inf/TaskSchedulerType.java @@ -1,17 +1,6 @@ package com.arialyy.aria.core.inf; -import androidx.annotation.IntDef; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@IntDef({ - TaskSchedulerType.TYPE_DEFAULT, - TaskSchedulerType.TYPE_STOP_NOT_NEXT, - TaskSchedulerType.TYPE_STOP_AND_WAIT, - TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY, - TaskSchedulerType.TYPE_START_AND_RESET_STATE -}) -@Retention(RetentionPolicy.SOURCE) public @interface TaskSchedulerType { +public interface TaskSchedulerType { int TYPE_DEFAULT = 1; /** * 停止当前任务并且不自动启动下一任务 diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseDListener.java similarity index 60% rename from Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseDListener.java index fa1ee9a8..862247b3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/BaseDListener.java @@ -13,30 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download; +package com.arialyy.aria.core.listener; -import android.os.Handler; - -import com.arialyy.aria.core.common.BaseListener; -import com.arialyy.aria.core.common.RecordHandler; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.TaskSchedulerType; -import com.arialyy.aria.core.scheduler.ISchedulers; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.CommonUtil; -import com.arialyy.aria.util.RecordUtil; -import java.io.File; +import com.arialyy.aria.util.DeleteDRecord; /** * 下载监听类 */ -public class BaseDListener extends BaseListener - implements IDownloadListener { - - BaseDListener(DownloadTask task, Handler outHandler) { - super(task, outHandler); - } +public class BaseDListener extends BaseListener implements IDLoadListener { @Override public void onPostPre(long fileSize) { @@ -54,15 +42,19 @@ public class BaseDListener extends BaseListener, - TASK extends AbsTask> - implements IEventListener { - protected static String TAG; - protected static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度 +public abstract class BaseListener implements IEventListener { + protected String TAG = getClass().getSimpleName(); + static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度 protected SoftReference outHandler; private long mLastLen; //上一次发送长度 private boolean isFirst = true; - private TASK mTask; - protected long mLastSaveTime; - protected ENTITY mEntity; - protected TASK_WRAPPER mTaskWrapper; + private AbsTask mTask; + long mLastSaveTime; + protected AbsEntity mEntity; + protected AbsTaskWrapper mTaskWrapper; private boolean isConvertSpeed; private long mUpdateInterval; - protected BaseListener(TASK task, Handler outHandler) { + @Override public IEventListener setParams(AbsTask task, Handler outHandler) { this.outHandler = new SoftReference<>(outHandler); mTask = new WeakReference<>(task).get(); mEntity = mTask.getTaskWrapper().getEntity(); @@ -56,10 +52,11 @@ public abstract class BaseListener TASK getTask(Class clazz) { + return (TASK) mTask; } @Override public void onPre() { @@ -116,7 +113,7 @@ public abstract class BaseListener - implements IUploadListener { - - BaseUListener(UploadTask task, Handler outHandler) { - super(task, outHandler); - } +public class BaseUListener extends BaseListener implements IUploadListener { @Override protected void handleCancel() { - int sType = getTask().getSchedulerType(); + int sType = getTask(UploadTask.class).getSchedulerType(); if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { mEntity.setComplete(false); mEntity.setState(IEntity.STATE_WAIT); - RecordUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD, - mTaskWrapper.isRemoveFile(), false); + DeleteURecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), false); } else { - RecordUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD, - mTaskWrapper.isRemoveFile(), true); + DeleteURecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), true); } } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/DownloadGroupListener.java similarity index 61% rename from Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/listener/DownloadGroupListener.java index a5afbe57..12beb071 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/DownloadGroupListener.java @@ -13,38 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download; +package com.arialyy.aria.core.listener; import android.os.Handler; -import com.arialyy.aria.core.common.BaseListener; -import com.arialyy.aria.core.common.RecordHandler; -import com.arialyy.aria.core.download.group.IDGroupListener; -import com.arialyy.aria.core.inf.GroupSendParams; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.group.GroupSendParams; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.TaskSchedulerType; -import com.arialyy.aria.core.scheduler.ISchedulers; -import com.arialyy.aria.exception.BaseException; +import com.arialyy.aria.core.task.AbsTask; +import com.arialyy.aria.core.task.DownloadGroupTask; +import com.arialyy.aria.exception.AriaException; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.DeleteDGRecord; import com.arialyy.aria.util.ErrorHelp; -import com.arialyy.aria.util.RecordUtil; + +import static com.arialyy.aria.core.task.AbsTask.ERROR_INFO_KEY; /** * Created by Aria.Lao on 2017/7/20. 任务组下载事件 */ -class DownloadGroupListener - extends BaseListener - implements IDGroupListener { +public class DownloadGroupListener extends BaseListener implements IDGroupListener { private GroupSendParams mSeedEntity; - DownloadGroupListener(DownloadGroupTask task, Handler outHandler) { - super(task, outHandler); + @Override public IEventListener setParams(AbsTask task, Handler outHandler) { + IEventListener listener = super.setParams(task, outHandler); mSeedEntity = new GroupSendParams<>(); - mSeedEntity.groupTask = task; + mSeedEntity.groupTask = (DownloadGroupTask) task; + return listener; } @Override public void onSubPre(DownloadEntity subEntity) { + handleSubSpeed(subEntity, 0); saveSubState(IEntity.STATE_PRE, subEntity); sendInState2Target(ISchedulers.SUB_PRE, subEntity); } @@ -56,12 +58,15 @@ class DownloadGroupListener @Override public void onSubStart(DownloadEntity subEntity) { + handleSubSpeed(subEntity, 0); saveSubState(IEntity.STATE_RUNNING, subEntity); sendInState2Target(ISchedulers.SUB_START, subEntity); } @Override - public void onSubStop(DownloadEntity subEntity) { + public void onSubStop(DownloadEntity subEntity, long stopLocation) { + subEntity.setCurrentProgress(stopLocation); + handleSubSpeed(subEntity, 0); saveSubState(IEntity.STATE_STOP, subEntity); saveCurrentLocation(); sendInState2Target(ISchedulers.SUB_STOP, subEntity); @@ -69,31 +74,37 @@ class DownloadGroupListener @Override public void onSubComplete(DownloadEntity subEntity) { + handleSubSpeed(subEntity, 0); saveSubState(IEntity.STATE_COMPLETE, subEntity); saveCurrentLocation(); sendInState2Target(ISchedulers.SUB_COMPLETE, subEntity); } @Override - public void onSubFail(DownloadEntity subEntity, BaseException e) { + public void onSubFail(DownloadEntity subEntity, AriaException e) { + handleSubSpeed(subEntity, 0); saveSubState(IEntity.STATE_FAIL, subEntity); saveCurrentLocation(); + mSeedEntity.groupTask.putExpand(ERROR_INFO_KEY, e); sendInState2Target(ISchedulers.SUB_FAIL, subEntity); if (e != null) { e.printStackTrace(); - ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e)); + ErrorHelp.saveError("", ALog.getExceptionString(e)); } } @Override public void onSubCancel(DownloadEntity subEntity) { + handleSubSpeed(subEntity, 0); saveSubState(IEntity.STATE_CANCEL, subEntity); saveCurrentLocation(); sendInState2Target(ISchedulers.SUB_CANCEL, subEntity); } @Override - public void onSubRunning(DownloadEntity subEntity) { + public void onSubRunning(DownloadEntity subEntity, long currentProgress) { + + handleSubSpeed(subEntity, currentProgress); if (System.currentTimeMillis() - mLastSaveTime >= RUN_SAVE_INTERVAL) { saveSubState(IEntity.STATE_RUNNING, subEntity); mLastSaveTime = System.currentTimeMillis(); @@ -101,6 +112,28 @@ class DownloadGroupListener sendInState2Target(ISchedulers.SUB_RUNNING, subEntity); } + private void handleSubSpeed(DownloadEntity subEntity, long currentProgress) { + if (currentProgress == 0) { + subEntity.setSpeed(0); + subEntity.setConvertSpeed("0kb/s"); + return; + } + long speed = currentProgress - subEntity.getCurrentProgress(); + 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())); + subEntity.setCurrentProgress(currentProgress); + + if (speed == 0) { + subEntity.setTimeLeft(Integer.MAX_VALUE); + } else { + subEntity.setTimeLeft( + (int) ((subEntity.getFileSize() - subEntity.getCurrentProgress()) / speed)); + } + } + /** * 将任务状态发送给下载器 * @@ -124,19 +157,18 @@ class DownloadGroupListener subEntity.setPercent(100); subEntity.setConvertSpeed("0kb/s"); subEntity.setSpeed(0); - ALog.i(TAG, String.format("任务【%s】完成,将删除线程任务记录", mEntity.getKey())); - RecordUtil.delTaskRecord(subEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD, false, false); } subEntity.update(); } private void saveCurrentLocation() { - if (mEntity.getSubEntities() == null || mEntity.getSubEntities().isEmpty()) { + DownloadGroupEntity dgEntity = (DownloadGroupEntity) mEntity; + if (dgEntity.getSubEntities() == null || dgEntity.getSubEntities().isEmpty()) { ALog.w(TAG, "保存进度失败,子任务为null"); return; } long location = 0; - for (DownloadEntity e : mEntity.getSubEntities()) { + for (DownloadEntity e : dgEntity.getSubEntities()) { location += e.getCurrentProgress(); } if (location > mEntity.getFileSize()) { @@ -160,13 +192,13 @@ class DownloadGroupListener } @Override protected void handleCancel() { - int sType = getTask().getSchedulerType(); + int sType = getTask(DownloadGroupTask.class).getSchedulerType(); if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { mEntity.setComplete(false); mEntity.setState(IEntity.STATE_WAIT); - RecordUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), false); + DeleteDGRecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), false); } else { - RecordUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), true); + DeleteDGRecord.getInstance().deleteRecord(mEntity, mTaskWrapper.isRemoveFile(), true); } } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/IDGroupListener.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IDGroupListener.java similarity index 78% rename from Aria/src/main/java/com/arialyy/aria/core/download/group/IDGroupListener.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/listener/IDGroupListener.java index 918c45f6..0d371d89 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/IDGroupListener.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IDGroupListener.java @@ -13,17 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.group; +package com.arialyy.aria.core.listener; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.inf.IDownloadListener; -import com.arialyy.aria.exception.BaseException; +import com.arialyy.aria.exception.AriaException; /** * Created by Aria.Lao on 2017/7/20. * 下载任务组事件 */ -public interface IDGroupListener extends IDownloadListener { +public interface IDGroupListener extends IDLoadListener { /** * 子任务预处理 @@ -45,7 +44,7 @@ public interface IDGroupListener extends IDownloadListener { /** * 子任务停止下载 */ - void onSubStop(DownloadEntity subEntity); + void onSubStop(DownloadEntity subEntity, long stopLocation); /** * 子任务下载完成 @@ -55,7 +54,7 @@ public interface IDGroupListener extends IDownloadListener { /** * 子任务下载失败 */ - void onSubFail(DownloadEntity subEntity, BaseException e); + void onSubFail(DownloadEntity subEntity, AriaException e); /** * 子任务取消下载 @@ -64,6 +63,8 @@ public interface IDGroupListener extends IDownloadListener { /** * 子任务执行中 + * + * @param currentProgress 当前进度 */ - void onSubRunning(DownloadEntity subEntity); + void onSubRunning(DownloadEntity subEntity, long currentProgress); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IDownloadListener.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IDLoadListener.java similarity index 90% rename from Aria/src/main/java/com/arialyy/aria/core/inf/IDownloadListener.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/listener/IDLoadListener.java index d5cec1df..fc93f9e4 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IDownloadListener.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IDLoadListener.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.listener; /** * 下载监听 */ -public interface IDownloadListener extends IEventListener { +public interface IDLoadListener extends IEventListener { /** * 预处理完成,准备下载---开始下载之间 diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IEventListener.java similarity index 83% rename from Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/listener/IEventListener.java index 2691797b..cc4156fe 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IEventListener.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IEventListener.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.listener; -import com.arialyy.aria.exception.BaseException; +import android.os.Handler; +import com.arialyy.aria.core.task.AbsTask; +import com.arialyy.aria.exception.AriaException; /** * Created by Aria.Lao on 2017/7/18. @@ -23,6 +25,8 @@ import com.arialyy.aria.exception.BaseException; */ public interface IEventListener { + IEventListener setParams(AbsTask task, Handler outHandler); + /** * 预处理,有时有些地址链接比较慢,这时可以先在这个地方出来一些界面上的UI,如按钮的状态 */ @@ -64,5 +68,5 @@ public interface IEventListener { * @param needRetry 是否需要重试{@code true} 需要 * @param e 失败信息 */ - void onFail(boolean needRetry, BaseException e); + void onFail(boolean needRetry, AriaException e); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulers.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/ISchedulers.java similarity index 97% rename from Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulers.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/listener/ISchedulers.java index 987e5125..75755d7d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/scheduler/ISchedulers.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/ISchedulers.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.arialyy.aria.core.scheduler; +package com.arialyy.aria.core.listener; import android.os.Handler; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadGroupEntity; -import com.arialyy.aria.core.inf.ITask; +import com.arialyy.aria.core.task.ITask; import com.arialyy.aria.core.upload.UploadEntity; /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IUploadListener.java b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IUploadListener.java similarity index 94% rename from Aria/src/main/java/com/arialyy/aria/core/inf/IUploadListener.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/listener/IUploadListener.java index e6196375..b571f37c 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IUploadListener.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/listener/IUploadListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.listener; /** * Created by lyy on 2017/2/9. diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java similarity index 50% rename from Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java index c6988ceb..94f8d3d5 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/AbsNormalLoader.java @@ -13,33 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.loader; import android.os.Looper; -import android.util.SparseArray; -import com.arialyy.aria.core.inf.AbsNormalEntity; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.IEventListener; +import android.util.Log; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IEventListener; import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Created by AriaL on 2017/7/1. - * 控制线程任务状态,如:开始,停止,取消,重试 + * 任务执行器,用于处理任务的开始,停止 + * 流程: + * 1、获取任务记录 + * 2、创建任务状态管理器,用于管理任务的状态 + * 3、创建文件信息获取器,获取文件信息,根据文件信息执行任务 + * 4、创建线程任务执行下载、上传操作 */ -public abstract class AbsFileer> - implements Runnable { - protected final String TAG; - protected IEventListener mListener; - protected TASK_WRAPPER mTaskWrapper; - protected ENTITY mEntity; +public abstract class AbsNormalLoader implements ILoaderVisitor, ILoader { + protected final String TAG = CommonUtil.getClassName(getClass()); + private IEventListener mListener; + protected T mTaskWrapper; protected File mTempFile; - private SparseArray mTask = new SparseArray<>(); + private List mTask = new ArrayList<>(); private ScheduledThreadPoolExecutor mTimer; /** @@ -47,33 +54,42 @@ public abstract class AbsFileer getTaskList() { + public List getTaskList() { return mTask; } @@ -84,12 +100,21 @@ public abstract class AbsFileer partPath = new ArrayList<>(); + for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { + partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + } + FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength); + for (String pp : partPath) { + FileUtil.deleteFile(pp); + } + return true; + } + + /** + * 合并文件 + * + * @return {@code true} 合并成功,{@code false}合并失败 + */ + private boolean mergeFile() { + if (mTaskRecord.threadNum == 1) { + File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0)); + return partFile.renameTo(new File(mTaskRecord.filePath)); + } + + List partPath = new ArrayList<>(); + for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { + partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + } + boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath); + if (isSuccess) { + for (String pp : partPath) { + FileUtil.deleteFile(pp); + } + 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; + } + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IInfoTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IInfoTask.java new file mode 100644 index 00000000..70a532e4 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IInfoTask.java @@ -0,0 +1,62 @@ +/* + * 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.loader; + +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.exception.AriaException; + +/** + * 任务信息采集 + */ +public interface IInfoTask extends ILoaderComponent { + + /** + * 执行任务 + */ + void run(); + + /** + * 设置回调 + */ + void setCallback(Callback callback); + + /** + * 任务停止 + */ + void stop(); + + /** + * 任务取消 + */ + void cancel(); + + interface Callback { + /** + * 处理完成 + * + * @param info 一些回调的信息 + */ + void onSucceed(String key, CompleteInfo info); + + /** + * 请求失败 + * + * @param e 错误信息 + */ + void onFail(AbsEntity entity, AriaException e, boolean needRetry); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoader.java similarity index 62% rename from Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoader.java index 88fa77a9..42895716 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoader.java @@ -13,23 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.loader; -import com.arialyy.aria.core.inf.AbsEntity; -import com.arialyy.aria.exception.BaseException; +public interface ILoader extends Runnable { + + //void start(); -public interface OnFileInfoCallback { /** - * 处理完成 + * 任务是否在执行 * - * @param info 一些回调的信息 + * @return true 任务执行中 */ - void onComplete(String key, CompleteInfo info); + boolean isRunning(); + + void cancel(); + + void stop(); /** - * 请求失败 + * 任务是否被中断(停止,取消) * - * @param e 错误信息 + * @return true 任务中断,false 任务没有中断 */ - void onFail(AbsEntity entity, BaseException e, boolean needRetry); -} \ No newline at end of file + boolean isBreak(); + + String getKey(); + + long getCurrentProgress(); +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderAdapter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderAdapter.java new file mode 100644 index 00000000..004d592e --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderAdapter.java @@ -0,0 +1,47 @@ +/* + * 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.loader; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.common.SubThreadConfig; + +/** + * @author lyy + * Date: 2019-09-18 + */ +public interface ILoaderAdapter { + + /** + * 处理新任务 + * + * @param record 任务记录 + * @param totalThreadNum 任务的线程总数 + * @return {@code true}创建新任务成功 + */ + boolean handleNewTask(TaskRecord record, int totalThreadNum); + + /** + * 创建线程任务 + */ + IThreadTask createThreadTask(SubThreadConfig config); + + /** + * 处理任务记录 + */ + IRecordHandler recordHandler(AbsTaskWrapper wrapper); +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderComponent.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderComponent.java new file mode 100644 index 00000000..465d4dab --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderComponent.java @@ -0,0 +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.loader; + +/** + * 加载器部件 + */ +public interface ILoaderComponent { + + void accept(ILoaderVisitor visitor); + +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderVisitor.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderVisitor.java new file mode 100644 index 00000000..0d20b8ed --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/ILoaderVisitor.java @@ -0,0 +1,44 @@ +/* + * 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.loader; + +import com.arialyy.aria.core.inf.IThreadStateManager; + +/** + * 加载器访问者 + */ +public interface ILoaderVisitor { + + /** + * 处理任务记录 + */ + void addComponent(IRecordHandler recordHandler); + + /** + * 处理任务的文件信息 + */ + void addComponent(IInfoTask infoTask); + + /** + * 线程状态 + */ + void addComponent(IThreadStateManager threadState); + + /** + * 构造线程任务 + */ + void addComponent(IThreadTaskBuilder builder); +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IRecordHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IRecordHandler.java new file mode 100644 index 00000000..c7c77329 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IRecordHandler.java @@ -0,0 +1,87 @@ +/* + * 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.loader; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; + +/** + * @author lyy + * Date: 2019-09-18 + */ +public interface IRecordHandler extends ILoaderComponent { + + int TYPE_DOWNLOAD = 1; + int TYPE_UPLOAD = 2; + int TYPE_M3U8_VOD = 3; + int TYPE_M3U8_LIVE = 4; + + String STATE = "_state_"; + String RECORD = "_record_"; + /** + * 小于1m的文件不启用多线程 + */ + long SUB_LEN = 1024 * 1024; + + /** + * 分块文件路径,文件路径.线程id.part + */ + String SUB_PATH = "%s.%s.part"; + + /** + * 获取任务记录 + */ + TaskRecord getRecord(long fileSize); + + /** + * 记录处理前的操作,可用来删除任务记录 + */ + void onPre(); + + /** + * 处理任务记录 + */ + void handlerTaskRecord(TaskRecord record); + + /** + * 处理线程任务 + * + * @param record 任务记录 + * @param threadId 线程id + * @param startL 线程开始位置 + * @param endL 线程结束位置 + */ + ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL); + + /** + * 新任务创建任务记录 + */ + TaskRecord createTaskRecord(int threadNum); + + /** + * 配置新任务的线程数 + * + * @return 新任务的线程数 + */ + int initTaskThreadNum(); + + /** + * 检查任务是否已完成 + * + * @return true 任务已完成 + */ + boolean checkTaskCompleted(); +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IThreadTaskBuilder.java similarity index 53% rename from Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/loader/IThreadTaskBuilder.java index 11cf9e52..2d3b9dd0 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/IThreadTaskBuilder.java @@ -13,29 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.loader; import android.os.Handler; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import java.io.File; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.task.IThreadTask; +import java.util.List; /** - * 子线程下载信息类 + * 线程任务构造器 */ -public class SubThreadConfig { +public interface IThreadTaskBuilder extends ILoaderComponent { - public TASK_WRAPPER taskWrapper; - public boolean isBlock = false; - // 启动的线程 - public int startThreadNum; - public String url; - public File tempFile; - // 线程记录 - public ThreadRecord record; - // 状态处理器 - public Handler stateHandler; - // 动态文件 - public boolean isOpenDynamicFile; - // m3u8切片索引 - public int peerIndex; -} \ No newline at end of file + /** + * 构造线程任务 + */ + List buildThreadTask(TaskRecord record, Handler stateHandler); + + /** + * 获取创建的线程任务数,需要先调用{@link #buildThreadTask(TaskRecord, Handler)}方法才能获取创建的线程任务数 + */ + int getCreatedThreadNum(); +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/LoaderStructure.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/LoaderStructure.java new file mode 100644 index 00000000..6dbe330a --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/LoaderStructure.java @@ -0,0 +1,45 @@ +/* + * 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.loader; + +import com.arialyy.aria.core.inf.IThreadStateManager; +import java.util.ArrayList; +import java.util.List; + +public class LoaderStructure { + private List parts = new ArrayList<>(); + + public void accept(ILoaderVisitor visitor) { + + for (ILoaderComponent part : parts) { + part.accept(visitor); + } + } + + /** + * 将组件加入到集合,必须添加以下集合: + * 1 {@link IRecordHandler} + * 2 {@link IInfoTask} + * 3 {@link IThreadStateManager} + * 4 {@link IThreadTaskBuilder} + * + * @param component 待添加的组件 + */ + public LoaderStructure addComponent(ILoaderComponent component) { + parts.add(component); + return this; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java new file mode 100644 index 00000000..a3d4db2e --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalLoader.java @@ -0,0 +1,172 @@ +/* + * 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.loader; + +import android.os.Handler; +import android.os.Looper; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.event.EventMsgUtil; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IDLoadListener; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.task.AbsTask; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.util.List; + +/** + * 单文件 + */ +public class NormalLoader extends AbsNormalLoader { + private int startThreadNum; //启动的线程数 + protected boolean isComplete = false; + private Looper looper; + + public NormalLoader(T wrapper, IEventListener listener) { + super(wrapper, listener); + mTempFile = new File(getEntity().getFilePath()); + EventMsgUtil.getDefault().register(this); + setUpdateInterval(wrapper.getConfig().getUpdateInterval()); + } + + public AbsNormalEntity getEntity() { + return (AbsNormalEntity) mTaskWrapper.getEntity(); + } + + @Override public long getFileSize() { + return getEntity().getFileSize(); + } + + /** + * 设置最大下载/上传速度AbsFtpInfoThread + * + * @param maxSpeed 单位为:kb + */ + protected void setMaxSpeed(int maxSpeed) { + for (IThreadTask threadTask : getTaskList()) { + if (threadTask != null && startThreadNum > 0) { + threadTask.setMaxSpeed(maxSpeed / startThreadNum); + } + } + } + + @Override public void onDestroy() { + super.onDestroy(); + EventMsgUtil.getDefault().unRegister(this); + } + + ///** + // * 如果使用"Content-Disposition"中的文件名,需要更新{@link #mTempFile}的路径 + // */ + //public void updateTempFile() { + // if (!mTempFile.getPath().equals(getEntity().getFilePath())) { + // boolean b = mTempFile.renameTo(new File(getEntity().getFilePath())); + // ALog.d(TAG, String.format("更新tempFile文件名%s", b ? "成功" : "失败")); + // } + //} + + protected Looper getLooper() { + return looper; + } + + /** + * 启动单线程任务 + */ + @Override + public void handleTask(Looper looper) { + if (isBreak() || isComplete) { + return; + } + this.looper = looper; + mInfoTask.run(); + } + + protected void startThreadTask() { + if (isBreak()){ + return; + } + + if (getListener() instanceof IDLoadListener) { + ((IDLoadListener) getListener()).onPostPre(getEntity().getFileSize()); + } + File file = new File(getEntity().getFilePath()); + if (file.getParentFile() != null && !file.getParentFile().exists()) { + FileUtil.createDir(file.getPath()); + } + // 处理记录、初始化状态管理器 + mRecord = mRecordHandler.getRecord(getFileSize()); + mStateManager.setLooper(mRecord, looper); + getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord, + new Handler(looper, mStateManager.getHandlerCallback()))); + startThreadNum = mTTBuilder.getCreatedThreadNum(); + + mStateManager.updateCurrentProgress(getEntity().getCurrentProgress()); + if (mStateManager.getCurrentProgress() > 0) { + getListener().onResume(mStateManager.getCurrentProgress()); + } else { + getListener().onStart(mStateManager.getCurrentProgress()); + } + + // 启动线程任务 + for (IThreadTask threadTask : getTaskList()) { + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask); + } + + // 启动定时器 + startTimer(); + } + + @Override public long getCurrentProgress() { + return isRunning() ? mStateManager.getCurrentProgress() : getEntity().getCurrentProgress(); + } + + @Override public void addComponent(IRecordHandler recordHandler) { + mRecordHandler = recordHandler; + if (recordHandler.checkTaskCompleted()) { + mRecord.deleteData(); + isComplete = true; + getListener().onComplete(); + } + } + + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = infoTask; + infoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + startThreadTask(); + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + getListener().onFail(needRetry, e); + } + }); + } + + @Override public void addComponent(IThreadStateManager threadState) { + mStateManager = threadState; + } + + @Override public void addComponent(IThreadTaskBuilder builder) { + mTTBuilder = builder; + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalTTBuilder.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalTTBuilder.java new file mode 100644 index 00000000..23af6113 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalTTBuilder.java @@ -0,0 +1,166 @@ +package com.arialyy.aria.core.loader; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.download.DGTaskWrapper; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.task.ThreadTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.util.ArrayList; +import java.util.List; + +public final class NormalTTBuilder implements IThreadTaskBuilder { + protected String TAG = CommonUtil.getClassName(this); + + private Handler mStateHandler; + private AbsTaskWrapper mWrapper; + private TaskRecord mRecord; + private int mTotalThreadNum; + private int mStartThreadNum; + private AbsNormalTTBuilderAdapter mAdapter; + + public NormalTTBuilder(AbsTaskWrapper wrapper, AbsNormalTTBuilderAdapter adapter) { + if (wrapper instanceof DGTaskWrapper) { + throw new AssertionError("NormalTTBuilder 不适用于组合任务"); + } + mWrapper = wrapper; + mAdapter = adapter; + mAdapter.setWrapper(wrapper); + } + + protected AbsNormalEntity getEntity() { + return (AbsNormalEntity) mWrapper.getEntity(); + } + + public AbsNormalTTBuilderAdapter getAdapter() { + return mAdapter; + } + + /** + * 创建线程任务 + */ + private IThreadTask createThreadTask(SubThreadConfig config) { + ThreadTask task = new ThreadTask(config); + task.setAdapter(mAdapter.getAdapter(config)); + return task; + } + + /** + * 启动断点任务时,创建单线程任务 + * + * @param record 线程记录 + * @param startNum 启动的线程数 + */ + private IThreadTask createSingThreadTask(ThreadRecord record, int startNum) { + return createThreadTask( + mAdapter.getSubThreadConfig(mStateHandler, record, mRecord.isBlock, startNum)); + } + + /** + * 处理不支持断点的任务 + */ + private List handleNoSupportBP() { + List list = new ArrayList<>(); + mStartThreadNum = 1; + mRecord.isBlock = false; + mRecord.update(); + IThreadTask task = createSingThreadTask(mRecord.threadRecords.get(0), 1); + list.add(task); + return list; + } + + /** + * 处理支持断点的任务 + */ + private List handleBreakpoint() { + long fileLength = getEntity().getFileSize(); + long blockSize = fileLength / mTotalThreadNum; + long currentProgress = 0; + List threadTasks = new ArrayList<>(mTotalThreadNum); + + mRecord.fileLength = fileLength; + if (mWrapper.isNewTask() && !mAdapter.handleNewTask(mRecord, mTotalThreadNum)) { + ALog.e(TAG, "初始化线程任务失败"); + return null; + } + + 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__已完成", mWrapper.getKey(), i)); + Message msg = mStateHandler.obtainMessage(); + msg.what = IThreadStateManager.STATE_COMPLETE; + Bundle b = msg.getData(); + if (b == null) { + b = new Bundle(); + } + b.putString(IThreadStateManager.DATA_THREAD_NAME, + CommonUtil.getThreadName(getEntity().getKey(), tr.threadId)); + msg.setData(b); + msg.sendToTarget(); + continue; + } + + //如果有记录,则恢复任务 + long r = tr.startLocation; + //记录的位置需要在线程区间中 + if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) { + currentProgress += r - startL; + } + ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", getEntity().getFileName(), i)); + + IThreadTask task = createSingThreadTask(tr, mStartThreadNum); + if (task == null) { + ALog.e(TAG, "创建线程任务失败"); + return null; + } + threadTasks.add(task); + } + if (currentProgress != getEntity().getCurrentProgress()) { + ALog.d(TAG, String.format("进度修正,当前进度:%s", currentProgress)); + getEntity().setCurrentProgress(currentProgress); + } + //mStateManager.updateProgress(currentProgress); + return threadTasks; + } + + private List handleTask() { + if (mWrapper.isSupportBP()) { + return handleBreakpoint(); + } else { + return handleNoSupportBP(); + } + } + + @Override public List buildThreadTask(TaskRecord record, Handler stateHandler) { + mRecord = record; + mStateHandler = stateHandler; + mTotalThreadNum = mRecord.threadNum; + return handleTask(); + } + + @Override public int getCreatedThreadNum() { + return mStartThreadNum; + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java new file mode 100644 index 00000000..4a8a6bbd --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/NormalThreadStateManager.java @@ -0,0 +1,291 @@ +/* + * 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.loader; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 线程任务管理器,用于处理多线程下载时任务的状态回调 + */ +public class NormalThreadStateManager implements IThreadStateManager { + private final String TAG = CommonUtil.getClassName(this); + + /** + * 任务状态回调 + */ + private IEventListener mListener; + private int mThreadNum; // 启动的线程总数 + private AtomicInteger mCancelNum = new AtomicInteger(0); // 已经取消的线程的数 + private AtomicInteger mStopNum = new AtomicInteger(0); // 已经停止的线程数 + private AtomicInteger mFailNum = new AtomicInteger(0); // 失败的线程数 + private AtomicInteger mCompleteNum = new AtomicInteger(0); // 完成的线程数 + private long mProgress; //当前总进度 + private TaskRecord mTaskRecord; // 任务记录 + private Looper mLooper; + + /** + * @param listener 任务事件 + */ + public NormalThreadStateManager(IEventListener listener) { + mListener = listener; + } + + @Override public void setLooper(TaskRecord taskRecord, Looper looper) { + mTaskRecord = taskRecord; + mThreadNum = mTaskRecord.threadNum; + mLooper = looper; + } + + private void checkLooper() { + if (mTaskRecord == null) { + throw new NullPointerException("任务记录为空"); + } + if (mLooper == null) { + throw new NullPointerException("Looper为空"); + } + } + + private Handler.Callback callback = new Handler.Callback() { + @Override public boolean handleMessage(Message msg) { + checkLooper(); + switch (msg.what) { + case STATE_STOP: + mStopNum.getAndIncrement(); + if (isStop()) { + quitLooper(); + } + break; + case STATE_CANCEL: + mCancelNum.getAndIncrement(); + if (isCancel()) { + quitLooper(); + } + break; + case STATE_FAIL: + mFailNum.getAndIncrement(); + if (isFail()) { + Bundle b = msg.getData(); + mListener.onFail(b.getBoolean(DATA_RETRY, false), + (AriaException) b.getSerializable(DATA_ERROR_INFO)); + quitLooper(); + } + break; + case STATE_COMPLETE: + mCompleteNum.getAndIncrement(); + if (isComplete()) { + ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum); + //if (mTaskRecord.taskType == ITaskWrapper.D_SFTP) { + // mergerSFtp(); + // mListener.onComplete(); + //} else + if (mTaskRecord.isBlock || mTaskRecord.threadNum == 1) { + if (mergeFile()) { + mListener.onComplete(); + } else { + mListener.onFail(false, null); + } + quitLooper(); + break; + } + mListener.onComplete(); + quitLooper(); + } + break; + case STATE_RUNNING: + Bundle b = msg.getData(); + if (b != null) { + long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0); + mProgress += len; + } + + break; + case STATE_UPDATE_PROGRESS: + if (msg.obj == null) { + mProgress = updateBlockProgress(); + } else if (msg.obj instanceof Long) { + mProgress = (long) msg.obj; + } + break; + } + return false; + } + }; + + @Override public void updateCurrentProgress(long currentProgress) { + mProgress = currentProgress; + } + + /** + * 退出looper循环 + */ + private void quitLooper() { + mLooper.quit(); + } + + /** + * 获取当前任务下载进度 + * + * @return 当前任务下载进度 + */ + @Override + public long getCurrentProgress() { + return mProgress; + } + + @Override public Handler.Callback getHandlerCallback() { + return callback; + } + + /** + * 所有子线程是否都已经停止 + */ + public boolean isStop() { + //ALog.d(TAG, + // String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mStopNum.get() == mThreadNum || mStopNum.get() + mCompleteNum.get() == 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.get() != mThreadNum + && (mFailNum.get() == mThreadNum || mFailNum.get() + mCompleteNum.get() == 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.get() == mThreadNum; + } + + /** + * 所有子线程是否都已经取消 + */ + public boolean isCancel() { + //ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", + // mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mCancelNum.get() == 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(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + if (temp.exists()) { + size += temp.length(); + } + } + return size; + } + + /** + * 合并sftp的分块 + */ + private boolean mergerSFtp() { + if (mTaskRecord.threadNum == 1) { + File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0)); + return partFile.renameTo(new File(mTaskRecord.filePath)); + } + + List partPath = new ArrayList<>(); + for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { + partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + } + FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength); + for (String pp : partPath) { + FileUtil.deleteFile(pp); + } + return true; + } + + /** + * 合并文件 + * + * @return {@code true} 合并成功,{@code false}合并失败 + */ + private boolean mergeFile() { + if (mTaskRecord.threadNum == 1) { + File targetFile = new File(mTaskRecord.filePath); + if (targetFile.exists()){ + //没有获得文件长度:不支持断点续传 + if (mTaskRecord.fileLength == 0 && targetFile.length() != 0) { + return true; + } + if (targetFile.length() != 0 && targetFile.length() == mTaskRecord.fileLength) { + return true; + } + } + FileUtil.deleteFile(targetFile); + File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0)); + return partFile.renameTo(targetFile); + } + + List partPath = new ArrayList<>(); + for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { + partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + } + boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath); + if (isSuccess) { + for (String pp : partPath) { + FileUtil.deleteFile(pp); + } + 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; + } + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/SubLoader.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/SubLoader.java new file mode 100644 index 00000000..f46d57d5 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/SubLoader.java @@ -0,0 +1,291 @@ +/* + * 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.loader; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.task.ThreadTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * 子任务加载器 + */ +public final class SubLoader implements ILoader, ILoaderVisitor { + private String TAG = CommonUtil.getClassName(this); + // 是否需要获取信息 + private boolean needGetInfo = true; + private Handler schedulers; + private boolean isCancel = false, isStop = false; + private AbsTaskWrapper wrapper; + private IInfoTask infoTask; + private IThreadTaskBuilder ttBuild; + private IRecordHandler recordHandler; + private List mTask = new ArrayList<>(); + private String parentKey; + private TaskRecord record; + protected IThreadStateManager mStateManager; + + public SubLoader(AbsTaskWrapper wrapper, Handler schedulers) { + this.wrapper = wrapper; + this.schedulers = schedulers; + } + + public AbsTaskWrapper getWrapper() { + return wrapper; + } + + /** + * 发送状态到调度器 + * + * @param state {@link IThreadStateManager} + */ + private void sendNormalState(int state) { + Message msg = schedulers.obtainMessage(); + Bundle b = msg.getData(); + if (b == null) { + b = new Bundle(); + } + b.putString(IThreadStateManager.DATA_THREAD_NAME, getKey()); + msg.what = state; + msg.setData(b); + msg.sendToTarget(); + } + + /** + * 发送失败的状态 + */ + private void sendFailState(boolean needRetry) { + Message msg = schedulers.obtainMessage(); + Bundle b = msg.getData(); + if (b == null) { + b = new Bundle(); + } + b.putString(IThreadStateManager.DATA_THREAD_NAME, getKey()); + b.putBoolean(IThreadStateManager.DATA_RETRY, needRetry); + msg.what = IThreadStateManager.STATE_FAIL; + msg.setData(b); + msg.sendToTarget(); + } + + private void handlerTask() { + if (isBreak()) { + return; + } + Looper looper = Looper.myLooper(); + if (looper == null) { + Looper.prepare(); + looper = Looper.myLooper(); + } + + record = recordHandler.getRecord(wrapper.getEntity().getFileSize()); + if (record == null) { + ALog.d(TAG, "子任务记录为空"); + sendFailState(false); + return; + } + if (record.threadRecords != null + && !TextUtils.isEmpty(record.filePath) + && new File(record.filePath).exists() + && !record.threadRecords.isEmpty() + && record.threadRecords.get(0).isComplete) { + ALog.d(TAG, "子任务已完成,key:" + wrapper.getKey()); + sendNormalState(IThreadStateManager.STATE_COMPLETE); + return; + } + List task = + ttBuild.buildThreadTask(record, new Handler(looper, mStateManager.getHandlerCallback())); + mStateManager.setLooper(record, looper); + if (task == null || task.isEmpty()) { + ALog.e(TAG, "创建子任务的线程任务失败,key:" + wrapper.getKey()); + sendFailState(false); + return; + } + if (TextUtils.isEmpty(parentKey)) { + ALog.e(TAG, "parentKey为空"); + sendFailState(false); + return; + } + + sendNormalState(IThreadStateManager.STATE_PRE); + mTask.addAll(task); + try { + for (IThreadTask iThreadTask : mTask) { + ThreadTaskManager.getInstance().startThread(parentKey, iThreadTask); + } + + sendNormalState(IThreadStateManager.STATE_START); + + mStateManager.updateCurrentProgress(getWrapper().getEntity().getCurrentProgress()); + } catch (Exception e) { + e.printStackTrace(); + } + Looper.loop(); + } + + public TaskRecord getRecord() { + return record; + } + + public void setParentKey(String parentKey) { + this.parentKey = parentKey; + } + + public void setNeedGetInfo(boolean needGetInfo) { + this.needGetInfo = needGetInfo; + } + + public void retryTask() { + try { + if (!mTask.isEmpty()) { + for (IThreadTask iThreadTask : mTask) { + iThreadTask.call(); + } + return; + } + ALog.e(TAG, "子任务的线程任务为空"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override public void stop() { + if (isStop) { + ALog.w(TAG, "子任务已停止"); + return; + } + isStop = true; + if (infoTask != null){ + infoTask.stop(); + } + for (IThreadTask iThreadTask : mTask) { + iThreadTask.stop(); + } + } + + @Override public boolean isRunning() { + if (mTask.isEmpty()) { + return false; + } + for (IThreadTask iThreadTask : mTask) { + if (!iThreadTask.isBreak()) return true; + } + return false; + } + + @Override public void cancel() { + if (isCancel) { + ALog.w(TAG, "子任务已取消"); + return; + } + isCancel = true; + if (infoTask != null){ + infoTask.cancel(); + } + for (IThreadTask iThreadTask : mTask) { + iThreadTask.cancel(); + } + } + + @Override public boolean isBreak() { + if (isCancel || isStop) { + ALog.d(TAG, "isCancel = " + isCancel + ", isStop = " + isStop); + ALog.d(TAG, String.format("任务【%s】已停止或取消了", wrapper.getKey())); + return true; + } + return false; + } + + /** + * 线程名,一个子任务的loader只有一个线程,使用线程名标示key + * + * @return {@link ThreadTask#getThreadName()} + */ + @Override public String getKey() { + return CommonUtil.getThreadName(wrapper.getKey(), 0); + } + + @Override public long getCurrentProgress() { + return isRunning() ? mStateManager.getCurrentProgress() + : getWrapper().getEntity().getCurrentProgress(); + } + + @Override public void addComponent(IRecordHandler recordHandler) { + this.recordHandler = recordHandler; + } + + @Override public void addComponent(IInfoTask infoTask) { + this.infoTask = infoTask; + infoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + handlerTask(); + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + sendFailState(needRetry); + } + }); + } + + @Override public void addComponent(IThreadStateManager threadState) { + mStateManager = threadState; + } + + @Override public void addComponent(IThreadTaskBuilder builder) { + ttBuild = builder; + } + + @Override public void run() { + checkComponent(); + if (isBreak()) { + return; + } + if (needGetInfo) { + infoTask.run(); + return; + } + handlerTask(); + } + + /** + * 检查组件: {@link #recordHandler}、{@link #infoTask}、{@link #ttBuild} + */ + private void checkComponent() { + if (recordHandler == null) { + throw new NullPointerException("任务记录组件为空"); + } + if (infoTask == null) { + throw new NullPointerException(("文件信息组件为空")); + } + if (ttBuild == null) { + throw new NullPointerException("线程任务组件为空"); + } + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/SubTTBuilder.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/SubTTBuilder.java new file mode 100644 index 00000000..8ec3fe15 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/SubTTBuilder.java @@ -0,0 +1,38 @@ +/* + * 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.loader; + +import android.os.Handler; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.task.IThreadTask; +import java.util.List; + +public class SubTTBuilder implements IThreadTaskBuilder{ + + + + @Override public List buildThreadTask(TaskRecord record, Handler stateHandler) { + return null; + } + + @Override public int getCreatedThreadNum() { + return 1; + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/loader/UploadThreadStateManager.java b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/UploadThreadStateManager.java new file mode 100644 index 00000000..00afc259 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/loader/UploadThreadStateManager.java @@ -0,0 +1,275 @@ +/* + * 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.loader; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 线程任务管理器,用于处理多线程下载时任务的状态回调 + */ +public class UploadThreadStateManager implements IThreadStateManager { + private final String TAG = CommonUtil.getClassName(this); + + /** + * 任务状态回调 + */ + private IEventListener mListener; + private int mThreadNum; // 启动的线程总数 + private AtomicInteger mCancelNum = new AtomicInteger(0); // 已经取消的线程的数 + private AtomicInteger mStopNum = new AtomicInteger(0); // 已经停止的线程数 + private AtomicInteger mFailNum = new AtomicInteger(0); // 失败的线程数 + private AtomicInteger mCompleteNum = new AtomicInteger(0); // 完成的线程数 + private long mProgress; //当前总进度 + private TaskRecord mTaskRecord; // 任务记录 + private Looper mLooper; + + /** + * @param listener 任务事件 + */ + public UploadThreadStateManager(IEventListener listener) { + mListener = listener; + } + + @Override public void setLooper(TaskRecord taskRecord, Looper looper) { + mTaskRecord = taskRecord; + mThreadNum = mTaskRecord.threadNum; + mLooper = looper; + } + + private void checkLooper() { + if (mTaskRecord == null) { + throw new NullPointerException("任务记录为空"); + } + if (mLooper == null) { + throw new NullPointerException("Looper为空"); + } + } + + private Handler.Callback callback = new Handler.Callback() { + @Override public boolean handleMessage(Message msg) { + checkLooper(); + switch (msg.what) { + case STATE_STOP: + mStopNum.getAndIncrement(); + if (isStop()) { + quitLooper(); + } + break; + case STATE_CANCEL: + mCancelNum.getAndIncrement(); + if (isCancel()) { + quitLooper(); + } + break; + case STATE_FAIL: + mFailNum.getAndIncrement(); + if (isFail()) { + Bundle b = msg.getData(); + mListener.onFail(b.getBoolean(DATA_RETRY, false), + (AriaException) b.getSerializable(DATA_ERROR_INFO)); + quitLooper(); + } + break; + case STATE_COMPLETE: + mCompleteNum.getAndIncrement(); + if (isComplete()) { + ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum); + //上传文件不需要合并文件 + mListener.onComplete(); + quitLooper(); + } + break; + case STATE_RUNNING: + Bundle b = msg.getData(); + if (b != null) { + long len = b.getLong(IThreadStateManager.DATA_ADD_LEN, 0); + mProgress += len; + } + + break; + case STATE_UPDATE_PROGRESS: + if (msg.obj == null) { + mProgress = updateBlockProgress(); + } else if (msg.obj instanceof Long) { + mProgress = (long) msg.obj; + } + break; + } + return false; + } + }; + + @Override public void updateCurrentProgress(long currentProgress) { + mProgress = currentProgress; + } + + /** + * 退出looper循环 + */ + private void quitLooper() { + mLooper.quit(); + } + + /** + * 获取当前任务下载进度 + * + * @return 当前任务下载进度 + */ + @Override + public long getCurrentProgress() { + return mProgress; + } + + @Override public Handler.Callback getHandlerCallback() { + return callback; + } + + /** + * 所有子线程是否都已经停止 + */ + public boolean isStop() { + //ALog.d(TAG, + // String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mStopNum.get() == mThreadNum || mStopNum.get() + mCompleteNum.get() == 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.get() != mThreadNum + && (mFailNum.get() == mThreadNum || mFailNum.get() + mCompleteNum.get() == 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.get() == mThreadNum; + } + + /** + * 所有子线程是否都已经取消 + */ + public boolean isCancel() { + //ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", + // mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mCancelNum.get() == 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(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + if (temp.exists()) { + size += temp.length(); + } + } + return size; + } + + /** + * 合并sftp的分块 + */ + private boolean mergerSFtp() { + if (mTaskRecord.threadNum == 1) { + File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0)); + return partFile.renameTo(new File(mTaskRecord.filePath)); + } + + List partPath = new ArrayList<>(); + for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { + partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + } + FileUtil.mergeSFtpFile(mTaskRecord.filePath, partPath, mTaskRecord.fileLength); + for (String pp : partPath) { + FileUtil.deleteFile(pp); + } + return true; + } + + /** + * 合并文件 + * + * @return {@code true} 合并成功,{@code false}合并失败 + */ + private boolean mergeFile() { + if (mTaskRecord.threadNum == 1) { + File targetFile = new File(mTaskRecord.filePath); + if (targetFile.exists() && targetFile.length() == mTaskRecord.fileLength){ + return true; + } + FileUtil.deleteFile(targetFile); + File partFile = new File(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, 0)); + return partFile.renameTo(targetFile); + } + + List partPath = new ArrayList<>(); + for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { + partPath.add(String.format(IRecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + } + boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath); + if (isSuccess) { + for (String pp : partPath) { + FileUtil.deleteFile(pp); + } + 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; + } + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java b/PublicComponent/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java similarity index 72% rename from Aria/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java index 7d4ad2d0..1c2bc973 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/manager/ThreadTaskManager.java @@ -16,18 +16,18 @@ package com.arialyy.aria.core.manager; -import com.arialyy.aria.core.common.AbsThreadTask; -import com.arialyy.aria.core.inf.AbsTask; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import android.text.TextUtils; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @@ -35,11 +35,12 @@ import java.util.concurrent.locks.ReentrantLock; * 线程任务管理器 */ public class ThreadTaskManager { + private final String TAG = CommonUtil.getClassName(this); private static volatile ThreadTaskManager INSTANCE = null; - private final String TAG = "ThreadTaskManager"; - private ExecutorService mExePool; - private Map> mThreadTasks = new ConcurrentHashMap<>(); + private static final int CORE_POOL_NUM = 20; private static final ReentrantLock LOCK = new ReentrantLock(); + private ThreadPoolExecutor mExePool; + private Map> mThreadTasks = new ConcurrentHashMap<>(); public static synchronized ThreadTaskManager getInstance() { if (INSTANCE == null) { @@ -49,7 +50,10 @@ public class ThreadTaskManager { } private ThreadTaskManager() { - mExePool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + mExePool = new ThreadPoolExecutor(CORE_POOL_NUM, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + new SynchronousQueue()); + mExePool.allowsCoreThreadTimeOut(); } /** @@ -82,9 +86,9 @@ public class ThreadTaskManager { * 启动线程任务 * * @param key 任务对应的key{@link AbsTaskWrapper#getKey()} - * @param threadTask 线程任务{@link AbsThreadTask} + * @param threadTask 线程任务{@link IThreadTask} */ - public void startThread(String key, AbsThreadTask threadTask) { + public void startThread(String key, IThreadTask threadTask) { try { LOCK.tryLock(2, TimeUnit.SECONDS); if (mExePool.isShutdown()) { @@ -111,7 +115,7 @@ public class ThreadTaskManager { /** * 任务是否在执行 * - * @param key 任务的key{@link AbsTask#getKey()} + * @param key 任务的key * @return {@code true} 任务正在运行 */ public boolean taskIsRunning(String key) { @@ -149,13 +153,56 @@ public class ThreadTaskManager { } } + /** + * 根据线程名删除任务的中的线程 + * + * @param key 任务的key,如果是组合任务,则为组合任务的key + * @param threadName 线程名 + * @return true 删除线程成功;false 删除线程失败 + */ + public boolean removeSingleTaskThread(String key, String threadName) { + try { + LOCK.tryLock(2, TimeUnit.SECONDS); + if (mExePool.isShutdown()) { + ALog.e(TAG, "线程池已经关闭"); + return false; + } + if (TextUtils.isEmpty(threadName)) { + ALog.e(TAG, "线程名为空"); + return false; + } + + key = getKey(key); + Set temp = mThreadTasks.get(key); + if (temp != null && temp.size() > 0) { + FutureContainer tempC = null; + for (FutureContainer container : temp) { + if (container.threadTask.getThreadName().equals(threadName)) { + tempC = container; + break; + } + } + if (tempC != null) { + tempC.threadTask.destroy(); + temp.remove(tempC); + return true; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + LOCK.unlock(); + } + return false; + } + /** * 删除单个线程任务 * * @param key 任务的key * @param task 线程任务 */ - public boolean removeSingleTaskThread(String key, AbsThreadTask task) { + public boolean removeSingleTaskThread(String key, IThreadTask task) { try { LOCK.tryLock(2, TimeUnit.SECONDS); if (mExePool.isShutdown()) { @@ -195,7 +242,7 @@ public class ThreadTaskManager { * * @param task 线程任务 */ - public void retryThread(AbsThreadTask task) { + public void retryThread(IThreadTask task) { try { LOCK.tryLock(2, TimeUnit.SECONDS); if (mExePool.isShutdown()) { @@ -208,7 +255,7 @@ public class ThreadTaskManager { return; } } catch (Exception e) { - ALog.e(TAG, e); + ALog.e(TAG, "", e); return; } mExePool.submit(task); @@ -231,6 +278,6 @@ public class ThreadTaskManager { private class FutureContainer { Future future; - AbsThreadTask threadTask; + IThreadTask threadTask; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpInterceptHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/FtpInterceptHandler.java similarity index 98% rename from Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpInterceptHandler.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/FtpInterceptHandler.java index 53114368..bcf5ef17 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpInterceptHandler.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/FtpInterceptHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common.ftp; +package com.arialyy.aria.core.processor; import android.text.TextUtils; diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IBandWidthUrlConverter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IBandWidthUrlConverter.java similarity index 83% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IBandWidthUrlConverter.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/IBandWidthUrlConverter.java index ffbde948..fee4df66 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IBandWidthUrlConverter.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IBandWidthUrlConverter.java @@ -13,20 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.core.processor; + +import com.arialyy.aria.core.inf.IEventHandler; /** * M3U8 bandWidth 码率url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的, * 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 */ -public interface IBandWidthUrlConverter { +public interface IBandWidthUrlConverter extends IEventHandler { /** * 转换码率地址为可用的http地址,对于某些服务器,返回的切片信息有可能是相对地址,也可能是处理过的, * 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 * + * @param m3u8Url m3u8url地址 * @param bandWidthUrl 原始码率地址 * @return 可正常访问的http地址 */ - String convert(String bandWidthUrl); + String convert(String m3u8Url, String bandWidthUrl); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/IFtpUploadInterceptor.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IFtpUploadInterceptor.java similarity index 60% rename from Aria/src/main/java/com/arialyy/aria/core/common/ftp/IFtpUploadInterceptor.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/IFtpUploadInterceptor.java index 1feddc7d..de480d57 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/IFtpUploadInterceptor.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IFtpUploadInterceptor.java @@ -13,35 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common.ftp; +package com.arialyy.aria.core.processor; +import com.arialyy.aria.core.inf.IEventHandler; import com.arialyy.aria.core.upload.UploadEntity; import java.util.List; /** * FTP文件上传拦截器,如果远端已有同名文件,可使用该拦截器控制覆盖文件或修改该文件上传到服务器端端文件名 - *
    - *   
    - *     Aria.upload(this)
    - *         .loadFtp(FILE_PATH)
    - *         .setUploadUrl(URL)
    - *         .setUploadInterceptor(
    - *             new IFtpUploadInterceptor() {
    - *
    - *               @Override
    - *               public FtpInterceptHandler onIntercept(UploadEntity entity, List fileList) {
    - *                 FtpInterceptHandler.Builder builder = new FtpInterceptHandler.Builder();
    - *                 builder.coverServerFile();
    - *                 //builder.resetFileName("test");
    - *                 return builder.build();
    - *               }
    - *          })
    - *         .create();
    - *   
    - *
    - * 
    */ -public interface IFtpUploadInterceptor { +public interface IFtpUploadInterceptor extends IEventHandler { /** * 处理拦截事件 diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpFileLenAdapter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IHttpFileLenAdapter.java similarity index 86% rename from Aria/src/main/java/com/arialyy/aria/core/inf/IHttpFileLenAdapter.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/IHttpFileLenAdapter.java index 55b8c507..f7090a86 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IHttpFileLenAdapter.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IHttpFileLenAdapter.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.processor; -import java.io.Serializable; +import com.arialyy.aria.core.inf.IEventHandler; import java.net.URLConnection; import java.util.List; import java.util.Map; @@ -23,8 +23,7 @@ import java.util.Map; /** * Http文件长度适配器 */ -public interface IHttpFileLenAdapter extends Serializable { - long serialVersionUID = 1L; +public interface IHttpFileLenAdapter extends IEventHandler { /** * 同伙header中的数据获取文件长度 diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IHttpFileNameAdapter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IHttpFileNameAdapter.java new file mode 100644 index 00000000..f6b40239 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IHttpFileNameAdapter.java @@ -0,0 +1,37 @@ +/* + * 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.processor; + +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.inf.IEventHandler; + +import java.net.URLConnection; +import java.util.List; +import java.util.Map; + +/** + * Http文件名适配器 + */ +public interface IHttpFileNameAdapter extends IEventHandler { + + /** + * 根据header中的数据获取文件名字 + * @param headers header参数{@link URLConnection#getHeaderFields()} + * @param key 这里如果传entity可能会导致entity属性变更而其他地方没有改变,所以就传了个key 减少可操作范围 + * @return 文件长度 + */ + String handleFileName(Map> headers, String key); +} diff --git a/Aria/src/main/java/com/arialyy/aria/exception/FileNotFoundException.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java similarity index 53% rename from Aria/src/main/java/com/arialyy/aria/exception/FileNotFoundException.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java index 12378266..7e453dff 100644 --- a/Aria/src/main/java/com/arialyy/aria/exception/FileNotFoundException.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IKeyUrlConverter.java @@ -13,20 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.exception; +package com.arialyy.aria.core.processor; + +import com.arialyy.aria.core.inf.IEventHandler; /** - * Created by lyy on 2017/1/18. - * Aria 文件异常 + * M3U8 密钥下载地址处理器,可用于解密被加密的密钥的下载地址 */ -public class FileNotFoundException extends BaseException { - private static final String ARIA_FILE_EXCEPTION = "Aria File not found Exception:"; - - public FileNotFoundException(String tag, String detailMessage) { - super(tag, String.format("%s%s", ARIA_FILE_EXCEPTION, detailMessage)); - } +public interface IKeyUrlConverter extends IEventHandler { - public FileNotFoundException(String tag, String message, Exception e) { - super(tag, message, e); - } + /** + * 将被加密的密钥下载地址转换为可使用的http下载地址 + * + * @param m3u8Url 主m3u8的url地址 + * @param tsListUrl m3u8切片列表url地址 + * @param keyUrl 加密的url地址 + * @return 可正常访问的http地址 + */ + String convert(String m3u8Url, String tsListUrl, String keyUrl); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ILiveTsUrlConverter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/ILiveTsUrlConverter.java similarity index 85% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ILiveTsUrlConverter.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/ILiveTsUrlConverter.java index 09a6abb8..473e5208 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ILiveTsUrlConverter.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/ILiveTsUrlConverter.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.core.processor; + +import com.arialyy.aria.core.inf.IEventHandler; /** * M3U8 直播下载,ts url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的 * 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 */ -public interface ILiveTsUrlConverter { +public interface ILiveTsUrlConverter extends IEventHandler { /** * 处理#EXTINF信息,对于某些服务器,返回的切片信息有可能是相对地址,因此,你需要自行转换为可下载http连接 @@ -28,5 +30,5 @@ public interface ILiveTsUrlConverter { * @param tsUrl ts文件下载地址 * @return 转换后的http地址 */ - public String convert(String m3u8Url, String tsUrl); + String convert(String m3u8Url, String tsUrl); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ITsMergeHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/ITsMergeHandler.java similarity index 80% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ITsMergeHandler.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/ITsMergeHandler.java index 35e923d3..7810aa41 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ITsMergeHandler.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/ITsMergeHandler.java @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.core.processor; -import androidx.annotation.Nullable; +import com.arialyy.aria.core.download.M3U8Entity; +import com.arialyy.aria.core.inf.IEventHandler; import java.util.List; /** * Ts文件合并处理,如果你希望使用自行处理ts文件的合并,你可以实现该接口 */ -public interface ITsMergeHandler { +public interface ITsMergeHandler extends IEventHandler { /** * 合并ts文件 @@ -31,5 +32,5 @@ public interface ITsMergeHandler { * @param tsPath ts文件列表 * @return {@code true} 合并成功 */ - boolean merge(@Nullable M3U8Entity m3U8Entity, List tsPath); + boolean merge(M3U8Entity m3U8Entity, List tsPath); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IVodTsUrlConverter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IVodTsUrlConverter.java similarity index 90% rename from Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IVodTsUrlConverter.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/processor/IVodTsUrlConverter.java index 2ccfc7a6..ff7a7b9b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IVodTsUrlConverter.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/IVodTsUrlConverter.java @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download.m3u8; +package com.arialyy.aria.core.processor; +import com.arialyy.aria.core.inf.IEventHandler; import java.util.List; /** * M3U8 点播文件下载,ts url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的 * 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址 */ -public interface IVodTsUrlConverter { +public interface IVodTsUrlConverter extends IEventHandler { /** * 处理#EXTINF信息,对于某些服务器,返回的切片信息有可能是相对地址,因此,你需要自行转换为可下载http连接 diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/processor/ProxyHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/ProxyHandler.java new file mode 100644 index 00000000..a3a8ea3c --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/processor/ProxyHandler.java @@ -0,0 +1,50 @@ +/* + * 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.processor; + +import com.arialyy.aria.core.inf.IEventHandler; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * @author lyy + * Date: 2019-10-7 + * 处理器的动态代理 + */ +public class ProxyHandler implements InvocationHandler { + + private Object mTarget; + + /** + * 绑定代理对象并返回代理类 + */ + public Object bind(Class clazz) { + //绑定该类实现的所有接口,取得代理类 + try { + mTarget = clazz.newInstance(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } + return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); + } + + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return method.invoke(mTarget, args); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsGroupTask.java similarity index 82% rename from Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupTask.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsGroupTask.java index 421fe86d..215412a7 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsGroupTask.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsGroupTask.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.task; -import com.arialyy.aria.core.download.group.AbsGroupUtil; +import com.arialyy.aria.core.download.AbsGroupTaskWrapper; +import com.arialyy.aria.core.group.AbsGroupLoaderUtil; /** * Created by AriaL on 2017/6/29. @@ -35,7 +36,7 @@ public abstract class AbsGroupTask */ public void startSubTask(String url) { if (getUtil() != null) { - ((AbsGroupUtil) getUtil()).startSubTask(url); + ((AbsGroupLoaderUtil) getUtil()).startSubTask(url); } } @@ -46,7 +47,7 @@ public abstract class AbsGroupTask */ public void stopSubTask(String url) { if (getUtil() != null) { - ((AbsGroupUtil) getUtil()).stopSubTask(url); + ((AbsGroupLoaderUtil) getUtil()).stopSubTask(url); } } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTask.java similarity index 72% rename from Aria/src/main/java/com/arialyy/aria/core/inf/AbsTask.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTask.java index fc206bd2..0f6a4872 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTask.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsTask.java @@ -13,14 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.task; import android.content.Context; import android.os.Handler; import android.text.TextUtils; -import com.arialyy.aria.core.common.IUtil; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.inf.IUtil; +import com.arialyy.aria.core.inf.TaskSchedulerType; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.ComponentUtil; import java.util.HashMap; import java.util.Map; @@ -30,7 +36,7 @@ import java.util.Map; public abstract class AbsTask implements ITask { public static final String ERROR_INFO_KEY = "ERROR_INFO_KEY"; - + protected String TAG = CommonUtil.getClassName(getClass()); /** * 是否需要重试,默认为false */ @@ -38,7 +44,7 @@ public abstract class AbsTask protected TASK_WRAPPER mTaskWrapper; protected Handler mOutHandler; protected Context mContext; - boolean isHeighestTask = false; + protected boolean isHeighestTask = false; private boolean isCancel = false, isStop = false; private IUtil mUtil; /** @@ -48,28 +54,42 @@ public abstract class AbsTask /** * 该任务的调度类型 */ - @TaskSchedulerType private int mSchedulerType = TaskSchedulerType.TYPE_DEFAULT; protected IEventListener mListener; - protected String TAG; protected AbsTask() { - TAG = CommonUtil.getClassName(this); } public Handler getOutHandler() { return mOutHandler; } - protected abstract IUtil createUtil(); - - protected synchronized IUtil getUtil() { + synchronized IUtil getUtil() { if (mUtil == null) { - mUtil = createUtil(); + mUtil = ComponentUtil.getInstance().buildUtil(mTaskWrapper, mListener); } return mUtil; } + /** + * 获取剩余时间,单位:s + * 如果是m3u8任务,无法获取剩余时间;m2u8任务如果需要获取剩余时间,请设置文件长度{@link AbsEntity#setFileSize(long)} + */ + public int getTimeLeft() { + return getTaskWrapper().getEntity().getTimeLeft(); + } + + /** + * 转换时间 + * 时间<1 小时,显示分秒,显示样式 00:20 + * 时间 ≥1 小时,显示时分秒,显示样式 01:11:12 + * 时间 ≥1 天,显示天时分,显示样式 1d 01:11 + * 时间 ≥7 天,显示样式 ∞ + */ + public String getConvertTimeLeft() { + return CommonUtil.formatTime(getTaskWrapper().getEntity().getTimeLeft()); + } + /** * 添加扩展数据 读取扩展数据{@link #getExpand(String)} */ @@ -78,7 +98,7 @@ public abstract class AbsTask ALog.e(TAG, "key 为空"); return; } else if (obj == null) { - ALog.w(TAG, "扩展数据为空"); + ALog.i(TAG, "扩展数据为空"); return; } mExpand.put(key, obj); @@ -92,6 +112,19 @@ public abstract class AbsTask this.needRetry = needRetry; } + /** + * 最高优先级命令,最高优先级命令有以下属性 + * 1、在下载队列中,有且只有一个最高优先级任务 + * 2、最高优先级任务会一直存在,直到用户手动暂停或任务完成 + * 3、任务调度器不会暂停最高优先级任务 + * 4、用户手动暂停或任务完成后,第二次重新执行该任务,该命令将失效 + * 5、如果下载队列中已经满了,则会停止队尾的任务 + * 6、把任务设置为最高优先级任务后,将自动执行任务,不需要重新调用start()启动任务 + */ + public void setHighestPriority(boolean isHighestPriority) { + isHeighestTask = isHighestPriority; + } + /** * 读取扩展数据 */ @@ -178,14 +211,18 @@ public abstract class AbsTask start(TaskSchedulerType.TYPE_DEFAULT); } - @Override public void start(@TaskSchedulerType int type) { + @Override public void start(int type) { mSchedulerType = type; + mUtil = getUtil(); + if (mUtil == null) { + ALog.e(TAG, "任务工具创建失败"); + return; + } if (type == TaskSchedulerType.TYPE_START_AND_RESET_STATE) { if (getUtil().isRunning()) { ALog.e(TAG, String.format("任务【%s】重启失败", getTaskName())); return; } - mUtil = createUtil(); mUtil.start(); ALog.d(TAG, String.format("任务【%s】重启成功", getTaskName())); return; @@ -201,7 +238,12 @@ public abstract class AbsTask stop(TaskSchedulerType.TYPE_DEFAULT); } - @Override public void stop(@TaskSchedulerType int type) { + @Override public void stop(int type) { + mUtil = getUtil(); + if (mUtil == null) { + ALog.e(TAG, "任务工具创建失败"); + return; + } isStop = true; mSchedulerType = type; getUtil().stop(); @@ -211,7 +253,12 @@ public abstract class AbsTask cancel(TaskSchedulerType.TYPE_DEFAULT); } - @Override public void cancel(@TaskSchedulerType int type) { + @Override public void cancel(int type) { + mUtil = getUtil(); + if (mUtil == null) { + ALog.e(TAG, "任务工具创建失败"); + return; + } isCancel = true; mSchedulerType = type; getUtil().cancel(); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsThreadTaskAdapter.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsThreadTaskAdapter.java new file mode 100644 index 00000000..a58f4c85 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/AbsThreadTaskAdapter.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.task; + +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.config.BaseTaskConfig; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.BandwidthLimiter; +import com.arialyy.aria.util.CommonUtil; + +/** + * @author lyy + * Date: 2019-09-18 + */ +public abstract class AbsThreadTaskAdapter implements IThreadTaskAdapter { + + protected String TAG = CommonUtil.getClassName(getClass()); + /** + * 速度限制工具 + */ + protected BandwidthLimiter mSpeedBandUtil; + private ThreadRecord mThreadRecord; + private IThreadTaskObserver mObserver; + private AbsTaskWrapper mWrapper; + private SubThreadConfig mThreadConfig; + private IThreadTask mThreadTask; + + protected AbsThreadTaskAdapter(SubThreadConfig config) { + mThreadRecord = config.record; + mWrapper = config.taskWrapper; + mThreadConfig = config; + if (getTaskConfig().getMaxSpeed() > 0) { + mSpeedBandUtil = new BandwidthLimiter(getTaskConfig().getMaxSpeed(), config.startThreadNum); + } + } + + @Override public void call(IThreadTask threadTask) throws Exception { + mThreadTask = threadTask; + handlerThreadTask(); + } + + /** + * 开始处理线程任务 + */ + protected abstract void handlerThreadTask(); + + /** + * 当前线程的下去区间的进度 + */ + protected long getRangeProgress() { + return mObserver.getThreadProgress(); + } + + protected ThreadRecord getThreadRecord() { + return mThreadRecord; + } + + protected AbsTaskWrapper getTaskWrapper() { + return mWrapper; + } + + /** + * 获取任务配置信息 + */ + protected BaseTaskConfig getTaskConfig() { + return getTaskWrapper().getConfig(); + } + + protected IThreadTask getThreadTask() { + return mThreadTask; + } + + /** + * 获取线程配置信息 + */ + protected SubThreadConfig getThreadConfig() { + return mThreadConfig; + } + + @Override public void attach(IThreadTaskObserver observer) { + mObserver = observer; + } + + @Override public void setMaxSpeed(int speed) { + if (mSpeedBandUtil == null) { + mSpeedBandUtil = + new BandwidthLimiter(getTaskConfig().getMaxSpeed(), getThreadConfig().startThreadNum); + } + mSpeedBandUtil.setMaxRate(speed); + } + + protected void complete() { + if (mObserver != null) { + mObserver.updateCompleteState(); + } + } + + protected void fail(AriaException ex, boolean needRetry) { + if (mObserver != null) { + mObserver.updateFailState(ex, needRetry); + } + } + + protected void progress(long len) { + if (mObserver != null) { + mObserver.updateProgress(len); + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadGroupTask.java similarity index 63% rename from Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadGroupTask.java index 7ad5568c..ac5820d3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupTask.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadGroupTask.java @@ -13,20 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.download; +package com.arialyy.aria.core.task; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.common.IUtil; -import com.arialyy.aria.core.download.group.DGroupUtil; -import com.arialyy.aria.core.download.group.FtpDirDownloadUtil; -import com.arialyy.aria.core.download.group.IDGroupListener; -import com.arialyy.aria.core.inf.AbsGroupTask; -import com.arialyy.aria.core.inf.ITaskWrapper; -import com.arialyy.aria.core.scheduler.ISchedulers; -import com.arialyy.aria.util.CheckUtil; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.download.DGTaskWrapper; +import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.listener.ISchedulers; +import com.arialyy.aria.util.ComponentUtil; /** * Created by AriaL on 2017/6/27. @@ -37,8 +33,9 @@ public class DownloadGroupTask extends AbsGroupTask { private DownloadGroupTask(DGTaskWrapper taskWrapper, Handler outHandler) { mTaskWrapper = taskWrapper; mOutHandler = outHandler; - mContext = AriaManager.getInstance().getAPP(); - mListener = new DownloadGroupListener(this, mOutHandler); + mContext = AriaConfig.getInstance().getAPP(); + mListener = + ComponentUtil.getInstance().buildListener(taskWrapper.getRequestType(), this, mOutHandler); } public DownloadGroupEntity getEntity() { @@ -54,23 +51,11 @@ public class DownloadGroupTask extends AbsGroupTask { return DOWNLOAD_GROUP; } - @Override protected synchronized IUtil createUtil() { - int taskType = mTaskWrapper.getRequestType(); - if (taskType == ITaskWrapper.DG_HTTP) { - return new DGroupUtil((IDGroupListener) mListener, mTaskWrapper); - } else if (taskType == ITaskWrapper.D_FTP_DIR) { - return new FtpDirDownloadUtil((IDGroupListener) mListener, mTaskWrapper); - } else { - throw new IllegalArgumentException(String.format("不识别的任务, 任务类型:%s", taskType)); - } - } - public static class Builder { DGTaskWrapper taskEntity; Handler outHandler; public Builder(DGTaskWrapper taskEntity) { - CheckUtil.checkTaskEntity(taskEntity); this.taskEntity = taskEntity; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadTask.java similarity index 52% rename from Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadTask.java index 685682c6..234d7b2e 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/DownloadTask.java @@ -14,48 +14,28 @@ * limitations under the License. */ -package com.arialyy.aria.core.download; +package com.arialyy.aria.core.task; import android.os.Handler; import android.os.Looper; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.common.IUtil; -import com.arialyy.aria.core.download.downloader.SimpleDownloadUtil; -import com.arialyy.aria.core.download.m3u8.M3U8LiveUtil; -import com.arialyy.aria.core.download.m3u8.M3U8VodUtil; -import com.arialyy.aria.core.inf.AbsNormalTask; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.IDownloadListener; -import com.arialyy.aria.core.inf.ITaskWrapper; -import com.arialyy.aria.core.scheduler.ISchedulers; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.listener.ISchedulers; +import com.arialyy.aria.util.ComponentUtil; /** * Created by lyy on 2016/8/11. * 下载任务类 */ -public class DownloadTask extends AbsNormalTask { - public static final String TAG = "DownloadTask"; +public class DownloadTask extends AbsTask { private DownloadTask(DTaskWrapper taskWrapper, Handler outHandler) { mTaskWrapper = taskWrapper; mOutHandler = outHandler; - mContext = AriaManager.getInstance().getAPP(); - if (taskWrapper.getRequestType() == AbsTaskWrapper.M3U8_VOD - || taskWrapper.getRequestType() == AbsTaskWrapper.M3U8_LIVE) { - mListener = new M3U8Listener(this, mOutHandler); - } else { - mListener = new BaseDListener(this, mOutHandler); - } - } - - /** - * 获取文件保存路径 - * - * @deprecated 后续版本将删除该方法,请使用{@link #getFilePath()} - */ - @Deprecated - public String getDownloadPath() { - return getFilePath(); + mContext = AriaConfig.getInstance().getAPP(); + mListener = + ComponentUtil.getInstance().buildListener(taskWrapper.getRequestType(), this, mOutHandler); } /** @@ -79,11 +59,11 @@ public class DownloadTask extends AbsNormalTask { } @Override public int getTaskType() { - return DOWNLOAD; + return ITask.DOWNLOAD; } @Override public String getKey() { - return mTaskWrapper.getEntity().getUrl(); + return mTaskWrapper.getEntity().getKey(); } public DownloadEntity getDownloadEntity() { @@ -94,19 +74,6 @@ public class DownloadTask extends AbsNormalTask { return mTaskWrapper.getEntity().getFileName(); } - @Override protected synchronized IUtil createUtil() { - int taskType = mTaskWrapper.getRequestType(); - if (taskType == ITaskWrapper.M3U8_VOD) { - return new M3U8VodUtil(mTaskWrapper, (M3U8Listener) mListener); - } else if (taskType == ITaskWrapper.M3U8_LIVE) { - return new M3U8LiveUtil(mTaskWrapper, (M3U8Listener) mListener); - } else if (taskType == ITaskWrapper.D_HTTP || taskType == ITaskWrapper.D_FTP) { - return new SimpleDownloadUtil(mTaskWrapper, (IDownloadListener) mListener); - } else { - throw new IllegalArgumentException(String.format("不识别的任务, 任务类型:%s", taskType)); - } - } - public static class Builder { DTaskWrapper taskEntity; Handler outHandler; diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/ITask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/ITask.java similarity index 86% rename from Aria/src/main/java/com/arialyy/aria/core/inf/ITask.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/task/ITask.java index 2cdc9d61..bb92458a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/ITask.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/ITask.java @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.task; -import com.arialyy.aria.core.download.DownloadGroupTask; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.upload.UploadTask; +import com.arialyy.aria.core.inf.TaskSchedulerType; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; /** * Created by lyy on 2017/2/13. - * 任务接口{@link DownloadTask}、{@link UploadTask}、{@link DownloadGroupTask} */ public interface ITask { @@ -93,7 +91,7 @@ public interface ITask { * * @param type {@link TaskSchedulerType} */ - void start(@TaskSchedulerType int type); + void start(int type); /** * 停止任务 @@ -105,7 +103,7 @@ public interface ITask { * * @param type {@link TaskSchedulerType} */ - void stop(@TaskSchedulerType int type); + void stop(int type); /** * 删除任务 @@ -117,7 +115,7 @@ public interface ITask { * * @param type {@link TaskSchedulerType} */ - void cancel(@TaskSchedulerType int type); + void cancel(int type); /** * 读取扩展数据 diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTask.java new file mode 100644 index 00000000..368db5f3 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTask.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.task; + +import java.util.concurrent.Callable; + +/** + * @author lyy + * Date: 2019-09-18 + */ +public interface IThreadTask extends Callable { + + /** + * 销毁任务 + */ + void destroy(); + + /** + * 判断线程任务是否销毁 + * + * @return true 已经销毁 + */ + boolean isDestroy(); + + /** + * 中断任务 + */ + void breakTask(); + + /** + * 当前线程是否完成,对于不支持断点的任务,一律未完成 + * + * @return {@code true} 完成;{@code false} 未完成 + */ + boolean isThreadComplete(); + + /** + * 取消任务 + */ + void cancel(); + + /** + * 停止任务 + */ + void stop(); + + /** + * 设置当前线程最大下载速度 + * + * @param speed 单位为:kb + */ + void setMaxSpeed(int speed); + + /** + * 线程是否存活 + * + * @return {@code true}存活 + */ + boolean isLive(); + + /** + * 任务是否中断,中断条件: + * 1、任务取消 + * 2、任务停止 + * 3、手动中断 + * + * @return {@code true} 中断,{@code false} 不是中断 + */ + boolean isBreak(); + + /** + * 检查下载完成的分块大小,如果下载完成的分块大小大于或小于分配的大小,则需要重新下载该分块 如果是非分块任务,直接返回{@code true} + * + * @return {@code true} 分块分大小正常,{@code false} 分块大小错误 + */ + boolean checkBlock(); + + /** + * 获取线程id + */ + int getThreadId(); + + /** + * 线程名字,命名规则:md5(任务地址 + 线程id) + */ + String getThreadName(); +} diff --git a/Aria/src/main/java/com/arialyy/aria/exception/TaskException.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTaskAdapter.java similarity index 60% rename from Aria/src/main/java/com/arialyy/aria/exception/TaskException.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTaskAdapter.java index e8f7b765..d325f22e 100644 --- a/Aria/src/main/java/com/arialyy/aria/exception/TaskException.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTaskAdapter.java @@ -13,19 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.exception; +package com.arialyy.aria.core.task; /** - * 任务异常 + * 线程适配器 + * + * @author lyy + * Date: 2019-09-18 */ -public class TaskException extends BaseException { - private static final String ARIA_TASK_EXCEPTION = "Aria Task Exception:"; +public interface IThreadTaskAdapter { + + /** + * 执行任务 + */ + void call(IThreadTask threadTask) throws Exception; - public TaskException(String tag, String detailMessage) { - super(tag, String.format("%s, %s", ARIA_TASK_EXCEPTION, detailMessage)); - } + /** + * 设置当前线程最大下载速度 + * + * @param speed 单位为:kb + */ + void setMaxSpeed(int speed); - public TaskException(String tag, String message, Exception e){ - super(tag, message, e); - } + /** + * 注册观察者 + */ + void attach(IThreadTaskObserver observer); } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTaskObserver.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTaskObserver.java new file mode 100644 index 00000000..ee092e8d --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/IThreadTaskObserver.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.task; + +import android.os.Bundle; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.exception.AriaException; + +/** + * 线程任务观察者 + * + * @author lyy + * Date: 2019-09-18 + */ +public interface IThreadTaskObserver { + + /** + * 更新所有状态 + * + * @param state state {@link IThreadStateManager#STATE_STOP}.. + */ + void updateState(int state, Bundle bundle); + + /** + * 更新完成的状态 + */ + void updateCompleteState(); + + /** + * 更新失败的状态 + * + * @param needRetry 是否需要重试,一般是网络错误才需要重试 + */ + void updateFailState(AriaException e, boolean needRetry); + + /** + * 更新进度 + * + * @param len 新增的长度 + */ + void updateProgress(long len); + + /** + * 获取线程当前进度 + */ + long getThreadProgress(); +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java b/PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java similarity index 55% rename from Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java index f061e8b7..8a67263b 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/task/ThreadTask.java @@ -13,75 +13,72 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.task; import android.net.TrafficStats; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Process; -import androidx.annotation.Nullable; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.config.BaseTaskConfig; -import com.arialyy.aria.core.config.DGroupConfig; -import com.arialyy.aria.core.config.DownloadConfig; -import com.arialyy.aria.core.config.UploadConfig; -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.AriaConfig; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.ISchedulers; import com.arialyy.aria.core.manager.ThreadTaskManager; -import com.arialyy.aria.core.scheduler.ISchedulers; -import com.arialyy.aria.exception.BaseException; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.exception.AriaException; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.BufferedRandomAccessFile; +import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.ErrorHelp; import com.arialyy.aria.util.FileUtil; import com.arialyy.aria.util.NetUtils; import java.io.File; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by lyy on 2017/1/18. 任务线程 */ -public abstract class AbsThreadTask> - implements Callable { +public class ThreadTask implements IThreadTask, IThreadTaskObserver { /** * 线程重试次数 */ private final int RETRY_NUM = 2; - - private final String TAG = "AbsThreadTask"; - /** - * 当前子线程相对于总长度的位置 - */ - protected long mChildCurrentLocation = 0; - private ENTITY mEntity; - protected TASK_WRAPPER mTaskWrapper; + private final String TAG = CommonUtil.getClassName(getClass()); + private AbsEntity mEntity; + protected AbsTaskWrapper mTaskWrapper; private int mFailTimes = 0; - private long mLastSaveTime; - private ExecutorService mConfigThreadPool; + private long mLastSaveTime, mLastSendProgressTime; private boolean isNotNetRetry; //断网情况是否重试 private boolean taskBreak = false; //任务跳出 - protected BandwidthLimiter mSpeedBandUtil; //速度限制工具 - protected AriaManager mAridManager; private boolean isDestroy = false; protected boolean isCancel = false, isStop = false; - protected ThreadRecord mRecord; + private ExecutorService mConfigThreadPool; private Handler mStateHandler; - private SubThreadConfig mConfig; + private SubThreadConfig mConfig; + /** + * 当前线程的下去区间的进度 + */ + private long mRangeProgress, mLastRangeProgress; + private IThreadTaskAdapter mAdapter; + private ThreadRecord mRecord; + private String mThreadName; + private long updateInterval; // 更新间隔 private Thread mConfigThread = new Thread(new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - final long currentTemp = mChildCurrentLocation; + final long currentTemp = mRangeProgress; writeConfig(false, currentTemp); } }); - protected AbsThreadTask(SubThreadConfig config) { + public ThreadTask(SubThreadConfig config) { mConfig = config; mTaskWrapper = config.taskWrapper; mRecord = config.record; @@ -89,12 +86,25 @@ public abstract class AbsThreadTask 0) { - mSpeedBandUtil = new BandwidthLimiter(getMaxSpeed(), config.startThreadNum); + isNotNetRetry = AriaConfig.getInstance().getAConfig().isNotNetRetry(); + mRangeProgress = mRecord.startLocation; + mLastRangeProgress = mRangeProgress; + updateInterval = config.updateInterval; + checkFileExist(); + } + + private void checkFileExist() { + if (!getConfig().tempFile.exists()) { + FileUtil.createFile(getConfig().tempFile); } - isNotNetRetry = mAridManager.getAppConfig().isNotNetRetry(); - mChildCurrentLocation = mRecord.startLocation; + } + + /** + * 设置线程任务适配器 + */ + public void setAdapter(IThreadTaskAdapter adapter) { + mAdapter = adapter; + mAdapter.attach(this); } /** @@ -104,89 +114,71 @@ public abstract class AbsThreadTask getConfig() { + public SubThreadConfig getConfig() { return mConfig; } /** * 设置线程是否中断 */ + @Override public void destroy() { this.isDestroy = true; } - /** - * 线程执行完成 - */ - protected void onThreadComplete() { - - } - /** * 线程是否存活 * * @return {@code true}存活 */ - protected boolean isLive() { + @Override + public boolean isLive() { return !Thread.currentThread().isInterrupted() && !isDestroy; } /** * 当前线程是否完成,对于不支持断点的任务,一律未完成 {@code true} 完成;{@code false} 未完成 */ - boolean isThreadComplete() { + @Override + public boolean isThreadComplete() { return mRecord.isComplete; } /** * 获取实体 */ - protected ENTITY getEntity() { + protected AbsEntity getEntity() { return mEntity; } /** * 获取任务驱动对象 */ - protected TASK_WRAPPER getTaskWrapper() { + protected ITaskWrapper getTaskWrapper() { return mTaskWrapper; } - /** - * 获取配置的最大上传/下载速度 - * - * @return 单位为:kb - */ - public abstract int getMaxSpeed(); - - /** - * 读取任务配置 - * - * @return {@link DownloadConfig}、{@link UploadConfig}、{@link DGroupConfig} - */ - protected abstract BaseTaskConfig getTaskConfig(); - /** * 设置当前线程最大下载速度 * * @param speed 单位为:kb */ - + @Override public void setMaxSpeed(int speed) { - if (mSpeedBandUtil == null) { - mSpeedBandUtil = new BandwidthLimiter(getMaxSpeed(), getConfig().startThreadNum); + if (mAdapter != null) { + mAdapter.setMaxSpeed(speed); } - mSpeedBandUtil.setMaxRate(speed); } /** * 中断任务 */ - void breakTask() { + @Override + public void breakTask() { taskBreak = true; if (mTaskWrapper.isSupportBP()) { - final long currentTemp = mChildCurrentLocation; - sendState(IThreadState.STATE_STOP, null); + final long currentTemp = mRangeProgress; + updateState(IThreadStateManager.STATE_STOP, null); ALog.d(TAG, String.format("任务【%s】thread__%s__中断【停止位置:%s】", getFileName(), mRecord.threadId, currentTemp)); writeConfig(false, currentTemp); @@ -195,39 +187,7 @@ public abstract class AbsThreadTask getEntity().getFileSize() && !getTaskWrapper().asHttp() - // .isChunked()) { - // String errorMsg = - // String.format("下载失败,下载长度超出文件真实长度;currentLocation=%s, fileSize=%s", - // getState().CURRENT_LOCATION, - // getEntity().getFileSize()); - // taskBreak = true; - // fail(mChildCurrentLocation, new FileException(TAG, errorMsg), false); - // return; - //} - mChildCurrentLocation += len; + @Override + public synchronized void updateProgress(long len) { + mRangeProgress += len; Thread loopThread = mStateHandler.getLooper().getThread(); if (!loopThread.isAlive() || loopThread.isInterrupted()) { return; } - mStateHandler.obtainMessage(IThreadState.STATE_RUNNING, len).sendToTarget(); + // 不需要太频繁发送,以减少消息队列的压力 + if (System.currentTimeMillis() - mLastSendProgressTime > updateInterval) { + sendRunningState(); + mLastRangeProgress = mRangeProgress; + mLastSendProgressTime = System.currentTimeMillis(); + } + if (System.currentTimeMillis() - mLastSaveTime > 5000 - && mChildCurrentLocation < mRecord.endLocation) { + && mRangeProgress < mRecord.endLocation) { mLastSaveTime = System.currentTimeMillis(); if (!mConfigThreadPool.isShutdown()) { mConfigThreadPool.execute(mConfigThread); @@ -327,71 +353,97 @@ public abstract class AbsThreadTask threadRect) { ALog.i(TAG, String.format("分块【%s】错误,将重新下载该分块", temp.getName())); - temp.delete(); + FileUtil.deleteFile(temp); mRecord.startLocation = mRecord.endLocation - mRecord.blockLen; mRecord.isComplete = false; } else if (blockFileLen < mRecord.blockLen) { mRecord.startLocation = mRecord.endLocation - mRecord.blockLen + blockFileLen; mRecord.isComplete = false; - sendState(IThreadState.STATE_UPDATE_PROGRESS, null); + updateState(IThreadStateManager.STATE_UPDATE_PROGRESS, null); ALog.i(TAG, String.format("修正分块【%s】记录,开始位置:%s,结束位置:%s", temp.getName(), mRecord.startLocation, mRecord.endLocation)); @@ -458,39 +514,28 @@ public abstract class AbsThreadTask { +public class UploadTask extends AbsTask { private UploadTask(UTaskWrapper taskWrapper, Handler outHandler) { mTaskWrapper = taskWrapper; mOutHandler = outHandler; - mListener = new BaseUListener(this, mOutHandler); + mListener = + ComponentUtil.getInstance().buildListener(taskWrapper.getRequestType(), this, mOutHandler); } @Override public int getTaskType() { @@ -39,7 +39,7 @@ public class UploadTask extends AbsNormalTask { } @Override public String getKey() { - return mTaskWrapper.getEntity().getFilePath(); + return mTaskWrapper.getEntity().getKey(); } public UploadEntity getEntity() { @@ -50,10 +50,6 @@ public class UploadTask extends AbsNormalTask { return mTaskWrapper.getEntity().getFileName(); } - @Override protected synchronized IUtil createUtil() { - return new SimpleUploadUtil(mTaskWrapper, (IUploadListener) mListener); - } - public static class Builder { private Handler mOutHandler; private UTaskWrapper mTaskEntity; diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UTaskWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/upload/UTaskWrapper.java similarity index 96% rename from Aria/src/main/java/com/arialyy/aria/core/upload/UTaskWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/upload/UTaskWrapper.java index 29bdb811..42e5d4c8 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UTaskWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/upload/UTaskWrapper.java @@ -17,7 +17,7 @@ package com.arialyy.aria.core.upload; import com.arialyy.aria.core.config.Configuration; import com.arialyy.aria.core.config.UploadConfig; -import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; /** * Created by lyy on 2017/2/9. 上传任务实体 diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java similarity index 87% rename from Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java index 9cff621f..34a6320f 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/upload/UploadEntity.java @@ -17,8 +17,9 @@ package com.arialyy.aria.core.upload; import android.os.Parcel; import android.os.Parcelable; -import com.arialyy.aria.core.inf.AbsNormalEntity; -import com.arialyy.aria.core.inf.ITaskWrapper; +import android.text.TextUtils; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.orm.annotation.Primary; /** @@ -44,6 +45,7 @@ public class UploadEntity extends AbsNormalEntity implements Parcelable { this.responseStr = responseStr; } + @Override public String getFilePath() { return filePath; } @@ -57,7 +59,10 @@ public class UploadEntity extends AbsNormalEntity implements Parcelable { } @Override public int getTaskType() { - return getUrl().startsWith("ftp") ? ITaskWrapper.D_FTP : ITaskWrapper.D_HTTP; + if (TextUtils.isEmpty(getUrl())) { + return ITaskWrapper.ERROR; + } + return getUrl().startsWith("ftp") ? ITaskWrapper.U_FTP : ITaskWrapper.U_HTTP; } public UploadEntity() { diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTaskWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/AbsTaskWrapper.java similarity index 72% rename from Aria/src/main/java/com/arialyy/aria/core/inf/AbsTaskWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/AbsTaskWrapper.java index 5d41213e..f59e4fbb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/AbsTaskWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/AbsTaskWrapper.java @@ -13,27 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.wrapper; -import com.arialyy.aria.core.common.ftp.FtpTaskConfig; -import com.arialyy.aria.core.common.http.HttpTaskConfig; +import com.arialyy.aria.core.TaskOptionParams; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.config.BaseTaskConfig; -import com.arialyy.aria.core.config.DGroupConfig; -import com.arialyy.aria.core.config.DownloadConfig; -import com.arialyy.aria.core.config.UploadConfig; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadGroupEntity; import com.arialyy.aria.core.event.ErrorEvent; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.inf.ITaskOption; import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.util.ComponentUtil; /** * Created by lyy on 2017/2/23. 所有任务实体的父类 */ -public abstract class AbsTaskWrapper - implements ITaskWrapper { - - private HttpTaskConfig httpTaskConfig; - private FtpTaskConfig ftpTaskConfig; +public abstract class AbsTaskWrapper implements ITaskWrapper { /** * 刷新信息 {@code true} 重新刷新下载信息 @@ -47,7 +43,7 @@ public abstract class AbsTaskWrapper /** * 请求类型 {@link ITaskWrapper#D_HTTP}、{@link ITaskWrapper#D_FTP}、{@link - * ITaskWrapper#D_FTP_DIR}。。。 + * ITaskWrapper#D_FTP_DIR}.. */ private int requestType = D_HTTP; @@ -77,6 +73,48 @@ public abstract class AbsTaskWrapper */ private ErrorEvent errorEvent; + /** + * 任务配置 + */ + private ITaskOption taskOption; + + /** + * 任务配置信息 + */ + private TaskOptionParams optionParams = new TaskOptionParams(); + + /** + * {@code true}强制下载\上传,不考虑文件路径是否被占用 + */ + private boolean ignoreFilePathOccupy = false; + + public boolean isIgnoreFilePathOccupy() { + return ignoreFilePathOccupy; + } + + public void setIgnoreFilePathOccupy(boolean ignoreFilePathOccupy) { + this.ignoreFilePathOccupy = ignoreFilePathOccupy; + } + + public void setTaskOption(ITaskOption option) { + this.taskOption = option; + } + + public ITaskOption getTaskOption() { + return taskOption; + } + + public void generateTaskOption(Class clazz) { + taskOption = ComponentUtil.getInstance().buildTaskOption(clazz, optionParams); + } + + public TaskOptionParams getOptionParams() { + if (optionParams == null) { + optionParams = new TaskOptionParams(); + } + return optionParams; + } + public AbsTaskWrapper(ENTITY entity) { this.entity = entity; } @@ -89,42 +127,10 @@ public abstract class AbsTaskWrapper this.errorEvent = errorEvent; } - /** - * 任务识别标志 {@link AbsEntity#getKey()} - */ - public abstract String getKey(); - - /** - * 任务配置 - * - * @return {@link DownloadConfig}、{@link UploadConfig}、{@link DGroupConfig} - */ - public abstract BaseTaskConfig getConfig(); - @Override public ENTITY getEntity() { return entity; } - /** - * 设置或获取HTTP任务相关参数 - */ - public HttpTaskConfig asHttp() { - if (httpTaskConfig == null) { - httpTaskConfig = new HttpTaskConfig(); - } - return httpTaskConfig; - } - - /** - * 设置或获取FTP任务相关参数 - */ - public FtpTaskConfig asFtp() { - if (ftpTaskConfig == null) { - ftpTaskConfig = new FtpTaskConfig(); - } - return ftpTaskConfig; - } - /** * 获取任务下载状态 * @@ -134,6 +140,8 @@ public abstract class AbsTaskWrapper return getEntity().getState(); } + public abstract BaseTaskConfig getConfig(); + public boolean isRefreshInfo() { return refreshInfo; } @@ -154,6 +162,7 @@ public abstract class AbsTaskWrapper entity.setState(state); } + @Override public int getRequestType() { return requestType; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/ITaskWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java similarity index 60% rename from Aria/src/main/java/com/arialyy/aria/core/inf/ITaskWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java index 701247fa..b5442513 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/ITaskWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/ITaskWrapper.java @@ -13,21 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.inf; +package com.arialyy.aria.core.wrapper; -import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.inf.IEntity; /** - * 组合任务实体包裹器,用于加载和任务相关的参数,如:组合任务实体{@link DownloadGroupEntity}、header头部 + * 组合任务实体包裹器,用于加载和任务相关的参数 */ -public interface ITaskWrapper { +public interface ITaskWrapper { - int DEFAULT = 0; + int ERROR = 0; /** * HTTP单任务载 */ int D_HTTP = 1; + /** * HTTP任务组下载 */ @@ -37,6 +38,7 @@ public interface ITaskWrapper { * FTP单文件下载 */ int D_FTP = 3; + /** * FTP文件夹下载,为避免登录过多,子任务由单线程进行处理 */ @@ -46,6 +48,7 @@ public interface ITaskWrapper { * HTTP单文件上传 */ int U_HTTP = 5; + /** * FTP单文件上传 */ @@ -61,5 +64,48 @@ public interface ITaskWrapper { */ int M3U8_LIVE = 8; - ENTITY getEntity(); + /** + * TCP 文件下载 + */ + int D_TCP = 9; + + /** + * TCP 文件上传 + */ + int U_TCP = 10; + + /** + * TCP 分片上传 + */ + int U_TCP_PEER = 11; + + /** + * SFTP 下载 + */ + int D_SFTP = 12; + + /** + * SFTP 上传 + */ + int U_SFTP = 13; + + /** + * 获取任务类型 + * + * @return {@link ITaskWrapper} + */ + int getRequestType(); + + /** + * 获取任务实体 + */ + IEntity getEntity(); + + /** + * 获取任务唯一标志: + * 下载任务,对应文件的下载地址 + * 上传任务,对应需要上传的文件路径 + * 组合任务,对应组合任务的groupHash + */ + String getKey(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/RecordWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/RecordWrapper.java similarity index 91% rename from Aria/src/main/java/com/arialyy/aria/core/common/RecordWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/RecordWrapper.java index 05e77cf8..bbd9c4c1 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/RecordWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/core/wrapper/RecordWrapper.java @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.core.wrapper; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; import com.arialyy.aria.orm.AbsDbWrapper; import com.arialyy.aria.orm.annotation.Many; import com.arialyy.aria.orm.annotation.One; diff --git a/Aria/src/main/java/com/arialyy/aria/exception/ParamException.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaComponentException.java similarity index 73% rename from Aria/src/main/java/com/arialyy/aria/exception/ParamException.java rename to PublicComponent/src/main/java/com/arialyy/aria/exception/AriaComponentException.java index b2299c2f..4b38b9ef 100644 --- a/Aria/src/main/java/com/arialyy/aria/exception/ParamException.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaComponentException.java @@ -15,10 +15,12 @@ */ package com.arialyy.aria.exception; -public class ParamException extends RuntimeException { - private static final String ARIA_NET_EXCEPTION = "Aria Params Exception:"; +public class AriaComponentException extends RuntimeException { + public AriaComponentException(String message, Exception e) { + super(message, e); + } - public ParamException(String message) { - super(String.format("%s%s", ARIA_NET_EXCEPTION, message)); + public AriaComponentException(String s) { + super(s); } } diff --git a/Aria/src/main/java/com/arialyy/aria/exception/BaseException.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaException.java similarity index 70% rename from Aria/src/main/java/com/arialyy/aria/exception/BaseException.java rename to PublicComponent/src/main/java/com/arialyy/aria/exception/AriaException.java index 98073c31..33c01eed 100644 --- a/Aria/src/main/java/com/arialyy/aria/exception/BaseException.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaException.java @@ -15,21 +15,14 @@ */ package com.arialyy.aria.exception; -public abstract class BaseException extends Exception { +public class AriaException extends Exception { - private String tag; - - public BaseException(String tag, String message) { + public AriaException(String message) { super(message); - this.tag = tag; - } - - public BaseException(String tag, String message, Exception e) { - super(String.format("%s\n%s", message == null ? "" : message, e == null ? "" : e.getMessage())); - this.tag = tag; } - public String getTag() { - return tag; + public AriaException(String message, Exception e) { + super(String.format("%s\n%s", message == null ? "" : message, + e == null ? "" : e.getMessage())); } } diff --git a/Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaFTPException.java similarity index 67% rename from Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java rename to PublicComponent/src/main/java/com/arialyy/aria/exception/AriaFTPException.java index 132841e5..6715d038 100644 --- a/Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaFTPException.java @@ -15,14 +15,14 @@ */ package com.arialyy.aria.exception; -public class M3U8Exception extends BaseException{ - private static final String M3U8_EXCEPTION = "M3U8 Exception:"; +public class AriaFTPException extends AriaException { + private static final String FTP_EXCEPTION = "Aria FTP Exception:"; - public M3U8Exception(String tag, String message) { - super(tag, String.format("%s%s", M3U8_EXCEPTION, message)); + public AriaFTPException(String message) { + super(String.format("%s\n%s", FTP_EXCEPTION, message)); } - public M3U8Exception(String tag, String message, Exception e){ - super(tag, message, e); + public AriaFTPException(String message, Exception e) { + super(message, e); } } diff --git a/Aria/src/main/java/com/arialyy/aria/exception/AriaIOException.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaGroupException.java similarity index 66% rename from Aria/src/main/java/com/arialyy/aria/exception/AriaIOException.java rename to PublicComponent/src/main/java/com/arialyy/aria/exception/AriaGroupException.java index db0ffc7a..be5416e1 100644 --- a/Aria/src/main/java/com/arialyy/aria/exception/AriaIOException.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaGroupException.java @@ -15,14 +15,14 @@ */ package com.arialyy.aria.exception; -public class AriaIOException extends BaseException{ - private static final String ARIA_NET_EXCEPTION = "Aria Net Exception:"; +public class AriaGroupException extends AriaException { + private static final String FTP_EXCEPTION = "Aria Group Exception:"; - public AriaIOException(String tag, String message) { - super(tag, String.format("%s%s", ARIA_NET_EXCEPTION, message)); + public AriaGroupException(String message) { + super(String.format("%s\n%s", FTP_EXCEPTION, message)); } - public AriaIOException(String tag, String message, Exception e){ - super(tag, message, e); + public AriaGroupException(String message, Exception e) { + super(message, e); } } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaHTTPException.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaHTTPException.java new file mode 100644 index 00000000..df69fc1c --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaHTTPException.java @@ -0,0 +1,28 @@ +/* + * 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.exception; + +public class AriaHTTPException extends AriaException { + private static final String HTTP_EXCEPTION = "Aria HTTP Exception:"; + + public AriaHTTPException( String message) { + super(String.format("%s\n%s", HTTP_EXCEPTION, message)); + } + + public AriaHTTPException(String message, Exception e) { + super(message, e); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaM3U8Exception.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaM3U8Exception.java new file mode 100644 index 00000000..8186619f --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaM3U8Exception.java @@ -0,0 +1,28 @@ +/* + * 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.exception; + +public class AriaM3U8Exception extends AriaException { + private static final String M3U8_EXCEPTION = "Aria M3U8 Exception:"; + + public AriaM3U8Exception(String message) { + super(String.format("%s\n%s", M3U8_EXCEPTION, message)); + } + + public AriaM3U8Exception(String message, Exception e) { + super(message, e); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaSFTPException.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaSFTPException.java new file mode 100644 index 00000000..998dad5e --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/AriaSFTPException.java @@ -0,0 +1,28 @@ +/* + * 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.exception; + +public class AriaSFTPException extends AriaException { + private static final String SFTP_EXCEPTION = "Aria SFTP Exception:"; + + public AriaSFTPException(String message) { + super(String.format("%s\n%s", SFTP_EXCEPTION, message)); + } + + public AriaSFTPException(String message, Exception e) { + super(message, e); + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/exception/ExceptionFactory.java b/PublicComponent/src/main/java/com/arialyy/aria/exception/ExceptionFactory.java new file mode 100644 index 00000000..121a3c74 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/exception/ExceptionFactory.java @@ -0,0 +1,45 @@ +/* + * 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.exception; + +public class ExceptionFactory { + public static final int TYPE_FTP = 1; + public static final int TYPE_HTTP = 2; + public static final int TYPE_M3U8 = 3; + public static final int TYPE_SFTP = 4; + public static final int TYPE_GROUP = 5; + + public static AriaException getException(int type, String msg, Exception e) { + switch (type) { + case TYPE_FTP: { + return e == null ? new AriaFTPException(msg) : new AriaFTPException(msg, e); + } + case TYPE_HTTP: { + return e == null ? new AriaHTTPException(msg) : new AriaHTTPException(msg, e); + } + case TYPE_M3U8: { + return e == null ? new AriaM3U8Exception(msg) : new AriaM3U8Exception(msg, e); + } + case TYPE_SFTP: { + return e == null ? new AriaSFTPException(msg) : new AriaSFTPException(msg, e); + } + case TYPE_GROUP: { + return e == null ? new AriaGroupException(msg) : new AriaGroupException(msg, e); + } + } + return e == null ? new AriaException(msg) : new AriaException(msg, e); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/AbsDbWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/AbsDbWrapper.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/AbsDbWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/AbsDbWrapper.java diff --git a/aria/src/main/java/com/arialyy/aria/exception/FileException.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/AbsDelegate.java similarity index 57% rename from aria/src/main/java/com/arialyy/aria/exception/FileException.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/AbsDelegate.java index 0c4ffed5..499e81bb 100644 --- a/aria/src/main/java/com/arialyy/aria/exception/FileException.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/AbsDelegate.java @@ -13,20 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.exception; +package com.arialyy.aria.orm; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; /** - * Created by lyy on 2017/1/18. - * Aria 文件异常 + * Created by laoyuyu on 2018/3/22. */ -public class FileException extends BaseException { - private static final String ARIA_FILE_EXCEPTION = "Aria File Exception:"; +abstract class AbsDelegate { + static final String TAG = "AbsDelegate"; - public FileException(String tag, String detailMessage) { - super(tag, String.format("%s%s", ARIA_FILE_EXCEPTION, detailMessage)); + void closeCursor(Cursor cursor) { + SqlUtil.closeCursor(cursor); } - public FileException(String tag, String message, Exception e){ - super(tag, message, e); + /** + * 检查数据库是否关闭,已经关闭的话,打开数据库 + * + * @return 返回数据库 + */ + SQLiteDatabase checkDb(SQLiteDatabase db) { + return SqlUtil.checkDb(db); } } diff --git a/Aria/src/main/java/com/arialyy/aria/orm/ActionPolicy.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/ActionPolicy.java similarity index 98% rename from Aria/src/main/java/com/arialyy/aria/orm/ActionPolicy.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/ActionPolicy.java index 614a35dc..be362bec 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/ActionPolicy.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/ActionPolicy.java @@ -46,7 +46,7 @@ public enum ActionPolicy { /** * 父表有变更时,子表将外键列设置成一个默认的值,default配置的值 */ - SET_DEFAULT("SET DEFAULT"), + SET_DEFAULT("SET ERROR"), /** * 在父表上update/delete记录时,同步update/delete掉子表的匹配记录 diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/DBConfig.java similarity index 80% rename from Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/DBConfig.java index fae9bb31..e7e3ea28 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/DBConfig.java @@ -16,16 +16,12 @@ package com.arialyy.aria.orm; import android.text.TextUtils; -import com.arialyy.aria.core.common.TaskRecord; -import com.arialyy.aria.core.common.ThreadRecord; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadGroupEntity; -import com.arialyy.aria.core.download.DGTaskWrapper; -import com.arialyy.aria.core.download.DTaskWrapper; -import com.arialyy.aria.core.download.m3u8.M3U8Entity; +import com.arialyy.aria.core.download.M3U8Entity; import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.core.upload.UTaskWrapper; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -36,9 +32,9 @@ import java.util.Map; class DBConfig { /*adb pull /mnt/sdcard/Android/data/com.arialyy.simple/files/DB/AriaLyyDb d:/db*/ static boolean DEBUG = false; - static Map mapping = new LinkedHashMap<>(); + static Map> mapping = new LinkedHashMap<>(); static String DB_NAME; - static int VERSION = 55; + static int VERSION = 58; /** * 是否将数据库保存在Sd卡,{@code true} 是 @@ -55,8 +51,8 @@ class DBConfig { } static { - mapping.put("DownloadGroupEntity", DownloadGroupEntity.class); mapping.put("DownloadEntity", DownloadEntity.class); + mapping.put("DownloadGroupEntity", DownloadGroupEntity.class); mapping.put("UploadEntity", UploadEntity.class); mapping.put("ThreadRecord", ThreadRecord.class); mapping.put("TaskRecord", TaskRecord.class); diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DatabaseContext.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/DatabaseContext.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/DatabaseContext.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/DatabaseContext.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DbEntity.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/DbEntity.java similarity index 99% rename from Aria/src/main/java/com/arialyy/aria/orm/DbEntity.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/DbEntity.java index 8651102a..c7515a27 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DbEntity.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/DbEntity.java @@ -24,7 +24,7 @@ import java.util.List; */ public abstract class DbEntity { private static final Object LOCK = new Object(); - protected long rowID = -1; + public long rowID = -1; protected DbEntity() { diff --git a/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateFind.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateFind.java new file mode 100644 index 00000000..7d8de8de --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateFind.java @@ -0,0 +1,641 @@ +/* + * 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.orm; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; +import android.util.SparseArray; + +import com.arialyy.aria.orm.annotation.Many; +import com.arialyy.aria.orm.annotation.One; +import com.arialyy.aria.orm.annotation.Wrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; + +import java.lang.reflect.Field; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Created by laoyuyu on 2018/3/22. + * 查询数据 + */ +class DelegateFind extends AbsDelegate { + private final String PARENT_COLUMN_ALIAS = "p"; + private final String CHILD_COLUMN_ALIAS = "c"; + + private DelegateFind() { + } + + /** + * 获取{@link One}和{@link Many}注解的字段 + * + * @return 返回[OneField, ManyField] ,如果注解依赖错误返回null + */ + private Field[] getOneAndManyField(Class clazz) { + Field[] om = new Field[2]; + Field[] fields = clazz.getDeclaredFields(); + Field one = null, many = null; + boolean hasOne = false, hasMany = false; + for (Field field : fields) { + if (SqlUtil.isOne(field)) { + if (hasOne) { + ALog.w(TAG, "查询数据失败,实体中有多个@One 注解"); + return null; + } + hasOne = true; + one = field; + } + if (SqlUtil.isMany(field)) { + if (hasMany) { + ALog.w(TAG, "查询数据失败,实体中有多个@Many 注解"); + return null; + } + if (!field.getType().isAssignableFrom(List.class)) { + ALog.w(TAG, "查询数据失败,@Many 注解的类型不是List"); + return null; + } + hasMany = true; + many = field; + } + } + + if (one == null || many == null) { + ALog.w(TAG, "查询数据失败,实体中没有@One或@Many注解"); + return null; + } + + if (many.getType() != List.class) { + ALog.w(TAG, "查询数据失败,@Many注解的字段必须是List"); + return null; + } + om[0] = one; + om[1] = many; + return om; + } + + /** + * 查找一对多的关联数据 + * 如果查找不到数据或实体没有被{@link Wrapper}注解,将返回null + * 如果实体中没有{@link One}或{@link Many}注解,将返回null + * 如果实体中有多个{@link One}或{@link Many}注解,将返回nul + * {@link One} 的注解对象必须是{@link DbEntity},{@link Many}的注解对象必须是List,并且List中的类型必须是{@link DbEntity} + */ + List findRelationData(SQLiteDatabase db, Class clazz, + String... expression) { + return exeRelationSql(db, clazz, 1, Integer.MAX_VALUE, expression); + } + + /** + * 查找一对多的关联数据 + * 如果查找不到数据或实体没有被{@link Wrapper}注解,将返回null + * 如果实体中没有{@link One}或{@link Many}注解,将返回null + * 如果实体中有多个{@link One}或{@link Many}注解,将返回nul + * {@link One} 的注解对象必须是{@link DbEntity},{@link Many}的注解对象必须是List,并且List中的类型必须是{@link DbEntity} + */ + List findRelationData(SQLiteDatabase db, Class clazz, + int page, int num, String... expression) { + if (page < 1 || num < 1) { + ALog.w(TAG, "page,num 小于1"); + return null; + } + return exeRelationSql(db, clazz, page, num, expression); + } + + /** + * 执行关联查询,如果不需要分页,page和num传-1 + * + * @param page 当前页 + * @param num 一页的数量 + */ + private List exeRelationSql(SQLiteDatabase db, Class wrapperClazz, + int page, int num, String... expression) { + db = checkDb(db); + if (SqlUtil.isWrapper(wrapperClazz)) { + Field[] om = getOneAndManyField(wrapperClazz); + if (om == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + Field one = om[0], many = om[1]; + try { + Many m = many.getAnnotation(Many.class); + Class parentClazz = Class.forName(one.getType().getName()); + Class childClazz = Class.forName(CommonUtil.getListParamType(many).getName()); + // 检查表 + SqlUtil.checkOrCreateTable(db, parentClazz); + SqlUtil.checkOrCreateTable(db, childClazz); + final String pTableName = parentClazz.getSimpleName(); + final String cTableName = childClazz.getSimpleName(); + List pColumn = SqlUtil.getAllNotIgnoreField(parentClazz); + List cColumn = SqlUtil.getAllNotIgnoreField(childClazz); + StringBuilder pSb = new StringBuilder(); + StringBuilder cSb = new StringBuilder(); + + if (pColumn != null) { + pSb.append(pTableName.concat(".rowid AS ").concat(PARENT_COLUMN_ALIAS).concat("rowid,")); + for (Field f : pColumn) { + String temp = PARENT_COLUMN_ALIAS.concat(f.getName()); + pSb.append(pTableName.concat(".").concat(f.getName())) + .append(" AS ") + .append(temp) + .append(","); + } + } + + if (cColumn != null) { + pSb.append(cTableName.concat(".rowid AS ").concat(CHILD_COLUMN_ALIAS).concat("rowid,")); + for (Field f : cColumn) { + String temp = CHILD_COLUMN_ALIAS.concat(f.getName()); + cSb.append(cTableName.concat(".").concat(f.getName())) + .append(" AS ") + .append(temp) + .append(","); + } + } + + String pColumnAlia = pSb.toString(); + String cColumnAlia = cSb.toString(); + if (!TextUtils.isEmpty(pColumnAlia)) { + pColumnAlia = pColumnAlia.substring(0, pColumnAlia.length() - 1); + } + + if (!TextUtils.isEmpty(cColumnAlia)) { + cColumnAlia = cColumnAlia.substring(0, cColumnAlia.length() - 1); + } + + sb.append("SELECT "); + + if (!TextUtils.isEmpty(pColumnAlia)) { + sb.append(pColumnAlia).append(","); + } + if (!TextUtils.isEmpty(cColumnAlia)) { + sb.append(cColumnAlia); + } + if (TextUtils.isEmpty(pColumnAlia) && TextUtils.isEmpty(cColumnAlia)) { + sb.append(" * "); + } + + sb.append(" FROM ") + .append(pTableName) + .append(" INNER JOIN ") + .append(cTableName) + .append(" ON ") + .append(pTableName.concat(".").concat(m.parentColumn())) + .append(" = ") + .append(cTableName.concat(".").concat(m.entityColumn())); + String sql; + if (expression != null && expression.length > 0) { + if (!CommonUtil.checkSqlExpression(expression)) { + return null; + } + sb.append(" WHERE ").append(expression[0]).append(" "); + sql = sb.toString(); + sql = sql.replace("?", "%s"); + Object[] params = new String[expression.length - 1]; + for (int i = 0, len = params.length; i < len; i++) { + params[i] = String.format("'%s'", SqlUtil.encodeStr(expression[i + 1])); + } + sql = String.format(sql, params); + } else { + sql = sb.toString(); + } + boolean paged = false; + if (page != -1 && num != -1) { + paged = true; + sql = sql.concat(String.format(" Group by %s LIMIT %s,%s", + pTableName.concat(".").concat(m.parentColumn()), (page - 1) * num, num)); + } + Cursor cursor = db.rawQuery(sql, null); + List data = + newInstanceEntity(wrapperClazz, parentClazz, childClazz, cursor, pColumn, cColumn, + paged, db, m.entityColumn(), m.parentColumn()); + + closeCursor(cursor); + return data; + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } else { + ALog.e(TAG, "查询数据失败,实体类没有使用@Wrapper 注解"); + return null; + } + return null; + } + + /** + * 创建关联查询的数据 + * + * @param pColumn 父表的所有字段 + * @param cColumn 字表的所有字段 + */ + private synchronized List newInstanceEntity( + Class wrapperClazz, Class

    parentClazz, + Class childClazz, + Cursor cursor, + List pColumn, List cColumn, boolean paged, SQLiteDatabase db, + String entityColumn, String parentColumn) { + List wrappers = new ArrayList<>(); + SparseArray> childs = new SparseArray<>(); // 所有子表数据 + SparseArray parents = new SparseArray<>(); // 所有父表数据 + + try { + while (cursor.moveToNext()) { + int pRowId = cursor.getInt(cursor.getColumnIndex(PARENT_COLUMN_ALIAS.concat("rowid"))); + if (childs.get(pRowId) == null) { + childs.put(pRowId, new ArrayList()); + parents.put(pRowId, createParent(pRowId, parentClazz, pColumn, cursor)); + } + if (paged) { + List list = createChildren(db, childClazz, pColumn, entityColumn, parentColumn, + parents.get(pRowId)); + if (list != null) { + childs.get(pRowId).addAll(list); + } + } else { + childs.get(pRowId).add(createChild(childClazz, cColumn, cursor)); + } + } + + List wFields = SqlUtil.getAllNotIgnoreField(wrapperClazz); + if (wFields == null || wFields.isEmpty()) { + return null; + } + for (int i = 0; i < parents.size(); i++) { + int pRowId = parents.keyAt(i); + T wrapper = wrapperClazz.newInstance(); + boolean isPSet = false, isCSet = false; // 保证One 或 Many 只设置一次 + for (Field f : wFields) { + if (!isPSet && f.getAnnotation(One.class) != null) { + f.set(wrapper, parents.get(pRowId)); + isPSet = true; + } + if (!isCSet && f.getAnnotation(Many.class) != null) { + f.set(wrapper, childs.get(pRowId)); + isCSet = true; + } + } + wrapper.handleConvert(); //处理下转换 + wrappers.add(wrapper); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return wrappers; + } + + /** + * 创建子对象集合 + */ + private List createChildren(SQLiteDatabase db, Class childClazz, + List pColumn, + String entityColumn, String parentColumn, DbEntity parents) + throws IllegalAccessException { + + for (Field field : pColumn) { + field.setAccessible(true); + if (field.getName().equals(parentColumn)) { + Object o = field.get(parents); + if (o instanceof String) { + o = URLEncoder.encode((String) o); + } + return findData(db, childClazz, entityColumn + "='" + o + "'"); + } + } + return new ArrayList(); + } + + /** + * 创建子对象 + */ + private T createChild(Class childClazz, List cColumn, + Cursor cursor) + throws InstantiationException, IllegalAccessException { + T child = childClazz.newInstance(); + child.rowID = cursor.getInt(cursor.getColumnIndex(CHILD_COLUMN_ALIAS.concat("rowid"))); + for (Field field : cColumn) { + field.setAccessible(true); + int columnIndex = cursor.getColumnIndex(CHILD_COLUMN_ALIAS.concat(field.getName())); + setFieldValue(field.getType(), field, columnIndex, cursor, child); + } + return child; + } + + /** + * 创建父对象 + */ + private T createParent(int rowId, Class parentClazz, List pColumn, + Cursor cursor) + throws InstantiationException, IllegalAccessException { + T parent = parentClazz.newInstance(); + parent.rowID = rowId; + for (Field field : pColumn) { + field.setAccessible(true); + int columnIndex = cursor.getColumnIndex(PARENT_COLUMN_ALIAS.concat(field.getName())); + setFieldValue(field.getType(), field, columnIndex, cursor, parent); + } + return parent; + } + + /** + * 条件查寻数据 + */ + List findData(SQLiteDatabase db, Class clazz, String... expression) { + db = checkDb(db); + if (!CommonUtil.checkSqlExpression(expression)) { + return null; + } + String sql = String.format("SELECT rowid, * FROM %s WHERE %s", CommonUtil.getClassName(clazz), + expression[0]); + String[] params = new String[expression.length - 1]; + try { + // 处理系统出现的问题:https://github.com/AriaLyy/Aria/issues/450 + System.arraycopy(expression, 1, params, 0, params.length); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + return exeNormalDataSql(db, clazz, sql, params); + } + + /** + * 获取分页数据 + */ + List findData(SQLiteDatabase db, Class clazz, int page, int num, + String... expression) { + if (page < 1 || num < 1) { + ALog.w(TAG, "page, bum 小于1"); + return null; + } + db = checkDb(db); + if (!CommonUtil.checkSqlExpression(expression)) { + return null; + } + String sql = String.format("SELECT rowid, * FROM %s WHERE %s LIMIT %s,%s", + CommonUtil.getClassName(clazz), + expression[0], (page - 1) * num, num); + + String[] params = new String[expression.length - 1]; + try { + // 处理系统出现的问题:https://github.com/AriaLyy/Aria/issues/450 + System.arraycopy(expression, 1, params, 0, params.length); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + return exeNormalDataSql(db, clazz, sql, params); + } + + /** + * 模糊查寻数据 + */ + List findDataByFuzzy(SQLiteDatabase db, Class clazz, + String conditions) { + db = checkDb(db); + if (TextUtils.isEmpty(conditions)) { + throw new IllegalArgumentException("sql语句表达式不能为null或\"\""); + } + if (!conditions.toUpperCase().contains("LIKE")) { + throw new IllegalArgumentException("sql语句表达式未包含LIEK"); + } + String sql = String.format("SELECT rowid, * FROM %s, WHERE %s", CommonUtil.getClassName(clazz), + conditions); + return exeNormalDataSql(db, clazz, sql, null); + } + + /** + * 分页、模糊搜索数据 + */ + List findDataByFuzzy(SQLiteDatabase db, Class clazz, + int page, int num, String conditions) { + if (page < 1 || num < 1) { + ALog.w(TAG, "page, bum 小于1"); + return null; + } + db = checkDb(db); + if (TextUtils.isEmpty(conditions)) { + throw new IllegalArgumentException("sql语句表达式不能为null或\"\""); + } + if (!conditions.toUpperCase().contains("LIKE")) { + throw new IllegalArgumentException("sql语句表达式未包含LIEK"); + } + String sql = String.format("SELECT rowid, * FROM %s WHERE %s LIMIT %s,%s", + CommonUtil.getClassName(clazz), conditions, (page - 1) * num, num); + return exeNormalDataSql(db, clazz, sql, null); + } + + /** + * 查找表的所有数据 + */ + List findAllData(SQLiteDatabase db, Class clazz) { + db = checkDb(db); + String sql = String.format("SELECT rowid, * FROM %s", CommonUtil.getClassName(clazz)); + return exeNormalDataSql(db, clazz, sql, null); + } + + /** + * 执行查询普通数据的sql语句,并创建对象 + * + * @param sql sql 查询语句 + * @param selectionArgs 查询参数,如何sql语句中查询条件含有'?'则该参数不能为空 + */ + private List exeNormalDataSql(SQLiteDatabase db, Class clazz, + String sql, String[] selectionArgs) { + SqlUtil.checkOrCreateTable(db, clazz); + Cursor cursor; + try { + if (selectionArgs != null) { + String[] temp = new String[selectionArgs.length]; + int i = 0; + for (String arg : selectionArgs) { + temp[i] = SqlUtil.encodeStr(arg); + i++; + } + //sql执行失败 android.database.sqlite.SQLiteException: no such column: filePath 异常 + cursor = db.rawQuery(sql, temp); + } else { + cursor = db.rawQuery(sql, null); + } + List data = cursor.getCount() > 0 ? newInstanceEntity(clazz, cursor) : null; + closeCursor(cursor); + return data; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 根据数据游标创建一个具体的对象 + */ + private synchronized List newInstanceEntity(Class clazz, + Cursor cursor) { + List fields = CommonUtil.getAllFields(clazz); + List entitys = new ArrayList<>(); + if (fields != null && fields.size() > 0) { + try { + while (cursor.moveToNext()) { + T entity = clazz.newInstance(); + String primaryName = ""; + for (Field field : fields) { + field.setAccessible(true); + if (SqlUtil.isIgnore(field)) { + continue; + } + + Class type = field.getType(); + if (SqlUtil.isPrimary(field) && (type == int.class || type == Integer.class)) { + primaryName = field.getName(); + } + + int column = cursor.getColumnIndex(field.getName()); + if (column == -1) continue; + setFieldValue(type, field, column, cursor, entity); + } + //当设置了主键,而且主键的类型为integer时,查询RowID等于主键 + entity.rowID = cursor.getInt( + cursor.getColumnIndex(TextUtils.isEmpty(primaryName) ? "rowid" : primaryName)); + //mDataCache.put(getCacheKey(entity), entity); + entitys.add(entity); + } + closeCursor(cursor); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + return entitys; + } + + /** + * 设置字段的值 + * + * @throws IllegalAccessException + */ + private void setFieldValue(Class type, Field field, int columnIndex, Cursor cursor, + DbEntity entity) + throws IllegalAccessException { + if (cursor == null || cursor.isClosed()) { + ALog.e(TAG, "cursor没有初始化"); + return; + } + if (type == String.class) { + String temp = cursor.getString(columnIndex); + if (!TextUtils.isEmpty(temp)) { + field.set(entity, URLDecoder.decode(temp)); + } + } else if (type == int.class || type == Integer.class) { + field.setInt(entity, cursor.getInt(columnIndex)); + } else if (type == float.class || type == Float.class) { + field.setFloat(entity, cursor.getFloat(columnIndex)); + } else if (type == double.class || type == Double.class) { + field.setDouble(entity, cursor.getDouble(columnIndex)); + } else if (type == long.class || type == Long.class) { + field.setLong(entity, cursor.getLong(columnIndex)); + } else if (type == boolean.class || type == Boolean.class) { + String temp = cursor.getString(columnIndex); + if (TextUtils.isEmpty(temp)) { + field.setBoolean(entity, false); + } else { + field.setBoolean(entity, !temp.equalsIgnoreCase("false")); + } + } else if (type == java.util.Date.class || type == java.sql.Date.class) { + field.set(entity, new Date(URLDecoder.decode(cursor.getString(columnIndex)))); + } else if (type == byte[].class) { + field.set(entity, cursor.getBlob(columnIndex)); + } else if (type == Map.class) { + String temp = cursor.getString(columnIndex); + if (!TextUtils.isEmpty(temp)) { + field.set(entity, SqlUtil.str2Map(URLDecoder.decode(temp))); + } + } else if (type == List.class) { + String value = cursor.getString(columnIndex); + if (!TextUtils.isEmpty(value)) { + field.set(entity, SqlUtil.str2List(URLDecoder.decode(value), field)); + } + } + } + + /** + * 获取所在行Id + */ + int[] getRowId(SQLiteDatabase db, Class clazz) { + db = checkDb(db); + Cursor cursor = db.rawQuery("SELECT rowid, * FROM " + CommonUtil.getClassName(clazz), null); + int[] ids = new int[cursor.getCount()]; + int i = 0; + while (cursor.moveToNext()) { + ids[i] = cursor.getInt(cursor.getColumnIndex("rowid")); + i++; + } + cursor.close(); + return ids; + } + + /** + * 获取行Id + */ + int getRowId(SQLiteDatabase db, Class clazz, Object[] wheres, Object[] values) { + db = checkDb(db); + if (wheres.length <= 0 || values.length <= 0) { + ALog.e(TAG, "请输入删除条件"); + return -1; + } else if (wheres.length != values.length) { + ALog.e(TAG, "groupHash 和 vaule 长度不相等"); + return -1; + } + StringBuilder sb = new StringBuilder(); + sb.append("SELECT rowid FROM ").append(CommonUtil.getClassName(clazz)).append(" WHERE "); + int i = 0; + for (Object where : wheres) { + sb.append(where).append("=").append("'").append(values[i]).append("'"); + sb.append(i >= wheres.length - 1 ? "" : ","); + i++; + } + Cursor c = db.rawQuery(sb.toString(), null); + int id = c.getColumnIndex("rowid"); + c.close(); + return id; + } + + /** + * 通过rowId判断数据是否存在 + */ + boolean itemExist(SQLiteDatabase db, Class clazz, long rowId) { + return itemExist(db, CommonUtil.getClassName(clazz), rowId); + } + + /** + * 通过rowId判断数据是否存在 + */ + boolean itemExist(SQLiteDatabase db, String tableName, long rowId) { + db = checkDb(db); + String sql = "SELECT rowid FROM " + tableName + " WHERE rowid=" + rowId; + Cursor cursor = db.rawQuery(sql, null); + boolean isExist = cursor.getCount() > 0; + cursor.close(); + return isExist; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DelegateManager.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateManager.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/DelegateManager.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateManager.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DelegateUpdate.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateUpdate.java similarity index 91% rename from Aria/src/main/java/com/arialyy/aria/orm/DelegateUpdate.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateUpdate.java index 669e0280..ce41af35 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DelegateUpdate.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateUpdate.java @@ -20,7 +20,6 @@ import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import com.arialyy.aria.orm.annotation.Primary; import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CheckUtil; import com.arialyy.aria.util.CommonUtil; import java.lang.reflect.Field; import java.lang.reflect.Type; @@ -39,8 +38,9 @@ class DelegateUpdate extends AbsDelegate { */ synchronized void delData(SQLiteDatabase db, Class clazz, String... expression) { + SqlUtil.checkOrCreateTable(db, clazz); db = checkDb(db); - if (!CheckUtil.checkSqlExpression(expression)) { + if (!CommonUtil.checkSqlExpression(expression)) { return; } @@ -48,7 +48,7 @@ class DelegateUpdate extends AbsDelegate { sql = sql.replace("?", "%s"); Object[] params = new String[expression.length - 1]; for (int i = 0, len = params.length; i < len; i++) { - params[i] = String.format("'%s'", encodeStr(expression[i + 1])); + params[i] = String.format("'%s'", SqlUtil.encodeStr(expression[i + 1])); } sql = String.format(sql, params); db.execSQL(sql); @@ -58,6 +58,7 @@ class DelegateUpdate extends AbsDelegate { * 修改某行数据 */ synchronized void updateData(SQLiteDatabase db, DbEntity dbEntity) { + SqlUtil.checkOrCreateTable(db, dbEntity.getClass()); db = checkDb(db); ContentValues values = createValues(dbEntity); if (values != null) { @@ -110,6 +111,7 @@ class DelegateUpdate extends AbsDelegate { if (oldClazz == null || oldClazz != entity.getClass() || table == null) { oldClazz = entity.getClass(); table = CommonUtil.getClassName(oldClazz); + SqlUtil.checkOrCreateTable(db, oldClazz); } ContentValues value = createValues(entity); @@ -131,6 +133,7 @@ class DelegateUpdate extends AbsDelegate { * 插入数据 */ synchronized void insertData(SQLiteDatabase db, DbEntity dbEntity) { + SqlUtil.checkOrCreateTable(db, dbEntity.getClass()); db = checkDb(db); ContentValues values = createValues(dbEntity); if (values != null) { @@ -147,7 +150,7 @@ class DelegateUpdate extends AbsDelegate { */ private ContentValues createValues(DbEntity dbEntity) { List fields = CommonUtil.getAllFields(dbEntity.getClass()); - if (fields != null && fields.size() > 0) { + if (fields.size() > 0) { ContentValues values = new ContentValues(); try { for (Field field : fields) { @@ -157,9 +160,9 @@ class DelegateUpdate extends AbsDelegate { } String value = null; Type type = field.getType(); - if (type == Map.class && checkMap(field)) { + if (type == Map.class && SqlUtil.checkMap(field)) { value = SqlUtil.map2Str((Map) field.get(dbEntity)); - } else if (type == List.class && checkList(field)) { + } else if (type == List.class && SqlUtil.checkList(field)) { value = SqlUtil.list2Str(dbEntity, field); } else { Object obj = field.get(dbEntity); @@ -167,7 +170,7 @@ class DelegateUpdate extends AbsDelegate { value = field.get(dbEntity).toString(); } } - values.put(field.getName(), encodeStr(value)); + values.put(field.getName(), SqlUtil.encodeStr(value)); } return values; } catch (IllegalAccessException e) { diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DelegateWrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateWrapper.java similarity index 95% rename from Aria/src/main/java/com/arialyy/aria/orm/DelegateWrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateWrapper.java index 53ab959d..15ae2eed 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DelegateWrapper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/DelegateWrapper.java @@ -85,15 +85,14 @@ public class DelegateWrapper { * @return {@code true}该字段的对应的value已存在 */ boolean checkDataExist(Class clazz, String... expression) { - return mDManager.getDelegate(DelegateCommon.class) - .checkDataExist(mDb, clazz, expression); + return SqlUtil.checkDataExist(mDb, clazz, expression); } /** * 清空表数据 */ void clean(Class clazz) { - mDManager.getDelegate(DelegateCommon.class).clean(mDb, clazz); + SqlUtil.clean(mDb, clazz); } /** @@ -193,7 +192,7 @@ public class DelegateWrapper { * 查找某张表是否存在 */ public boolean tableExists(Class clazz) { - return mDManager.getDelegate(DelegateCommon.class).tableExists(mDb, clazz); + return SqlUtil.tableExists(mDb, clazz); } /** diff --git a/Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/SqlHelper.java similarity index 53% rename from Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/SqlHelper.java index 4b2683db..7690d152 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/SqlHelper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/SqlHelper.java @@ -21,10 +21,18 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Build; +import android.text.TextUtils; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.M3U8Entity; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; import com.arialyy.aria.util.ALog; import java.io.File; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -36,15 +44,13 @@ import java.util.Set; final class SqlHelper extends SQLiteOpenHelper { private static final String TAG = "SqlHelper"; private static volatile SqlHelper INSTANCE = null; + private static boolean mainTmpDirSet = false; private Context mContext; - private DelegateCommon mDelegate; - synchronized static SqlHelper init(Context context) { if (INSTANCE == null) { synchronized (SqlHelper.class) { - DelegateCommon delegate = DelegateManager.getInstance().getDelegate(DelegateCommon.class); - INSTANCE = new SqlHelper(context.getApplicationContext(), delegate); + INSTANCE = new SqlHelper(context.getApplicationContext()); } } return INSTANCE; @@ -54,11 +60,10 @@ final class SqlHelper extends SQLiteOpenHelper { return INSTANCE; } - private SqlHelper(Context context, DelegateCommon delegate) { + private SqlHelper(Context context) { super(DBConfig.SAVE_IN_SDCARD ? new DatabaseContext(context) : context, DBConfig.DB_NAME, null, DBConfig.VERSION); mContext = context; - mDelegate = delegate; } @Override public void onOpen(SQLiteDatabase db) { @@ -76,18 +81,14 @@ final class SqlHelper extends SQLiteOpenHelper { // 需要使用如下语句: db.execSQL("PRAGMA foreign_keys=ON;"); } - if (DBConfig.DEBUG) { - db.enableWriteAheadLogging(); - } } @Override public void onCreate(SQLiteDatabase db) { - DelegateCommon delegate = DelegateManager.getInstance().getDelegate(DelegateCommon.class); Set tables = DBConfig.mapping.keySet(); for (String tableName : tables) { Class clazz = DBConfig.mapping.get(tableName); - if (!delegate.tableExists(db, clazz)) { - delegate.createTable(db, clazz); + if (!SqlUtil.tableExists(db, clazz)) { + SqlUtil.createTable(db, clazz); } } } @@ -103,14 +104,18 @@ final class SqlHelper extends SQLiteOpenHelper { } else if (oldVersion < 53) { handle366Update(db); } else { - handleDbUpdate(db, null, null); + handleDbUpdate(db, null); + } + // 处理380版本TaskRecord 增加的记录类型判断 + if (newVersion == 57) { + addTaskRecordType(db); } } } @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion > newVersion) { - handleDbUpdate(db, null, null); + handleDbUpdate(db, null); } } @@ -139,18 +144,50 @@ final class SqlHelper extends SQLiteOpenHelper { SQLiteDatabase.CREATE_IF_NECESSARY); } } + db.enableWriteAheadLogging(); return db; } + @Override public SQLiteDatabase getWritableDatabase() { + if (!mainTmpDirSet) { + createDbCacheDir(); + return super.getWritableDatabase(); + } + return super.getWritableDatabase(); + } + + /** + * 用于修复 Too many open files 的问题 + * https://github.com/AriaLyy/Aria/issues/664 + */ + @Override + public SQLiteDatabase getReadableDatabase() { + if (!mainTmpDirSet) { + createDbCacheDir(); + return super.getReadableDatabase(); + } + return super.getReadableDatabase(); + } + + private void createDbCacheDir() { + String cacheDir = mContext.getCacheDir().getPath() + "/AriaDbCacheDir"; + File cacheFile = new File(cacheDir); + if (!cacheFile.exists()){ + boolean rs = cacheFile.mkdirs(); + ALog.d(TAG, rs + ""); + } + super.getReadableDatabase() + .execSQL("PRAGMA temp_store_directory = '" + cacheDir + "'"); + mainTmpDirSet = true; + } + /** * 处理数据库升级 * * @param modifyColumns 需要修改的表字段的映射,key为表名, * value{@code Map}中的Map的key为老字段名称,value为该老字段对应的新字段名称 - * @param delColumns 需要删除的表字段,key为表名,value{@code List}为需要删除的字段列表 */ - private void handleDbUpdate(SQLiteDatabase db, Map> modifyColumns, - Map> delColumns) { + private void handleDbUpdate(SQLiteDatabase db, Map> modifyColumns) { if (db == null) { ALog.e("SqlHelper", "db 为 null"); return; @@ -163,14 +200,57 @@ final class SqlHelper extends SQLiteOpenHelper { db.beginTransaction(); Set tables = DBConfig.mapping.keySet(); for (String tableName : tables) { - Class clazz = DBConfig.mapping.get(tableName); - if (mDelegate.tableExists(db, clazz)) { - //修改表名为中介表名 + Class clazz = DBConfig.mapping.get(tableName); + if (SqlUtil.tableExists(db, clazz)) { + // ----------- 1、获取旧表字段、新表字段 + Cursor columnC = + db.rawQuery(String.format("PRAGMA table_info(%s)", tableName), null); + + // 获取新表的所有字段名称 + List newTabColumns = SqlUtil.getColumns(clazz); + // 获取旧表的所有字段名称 + List oldTabColumns = new ArrayList<>(); + + while (columnC.moveToNext()) { + String columnName = columnC.getString(columnC.getColumnIndex("name")); + oldTabColumns.add(columnName); + } + columnC.close(); + + // ----------- 2、为防止字段增加失败的情况,先给旧表增加字段 + List newAddColum = getNewColumn(newTabColumns, oldTabColumns); + // 删除重命名的字段 + Map modifyMap = null; + if (modifyColumns != null) { + modifyMap = modifyColumns.get(tableName); + if (modifyMap != null) { + Iterator it = newAddColum.iterator(); + while (it.hasNext()) { + String s = it.next(); + if (modifyMap.get(s) != null) { + it.remove(); + } + } + } + } + + // 给旧表增加字段,防止新增字段失败 + if (newAddColum.size() > 0) { + String sql = "ALTER TABLE %s ADD COLUMN %s %s"; + for (String nc : newAddColum) { + String temp = + String.format(sql, tableName, nc, SqlUtil.getColumnTypeByFieldName(clazz, nc)); + ALog.d(TAG, "添加表字段的sql:" + temp); + db.execSQL(temp); + } + } + + // ----------- 3、将旧表备份下,并创建新表 String alertSql = String.format("ALTER TABLE %s RENAME TO %s_temp", tableName, tableName); db.execSQL(alertSql); //创建新表 - mDelegate.createTable(db, clazz); + SqlUtil.createTable(db, clazz); String sql = String.format("SELECT COUNT(*) FROM %s_temp", tableName); Cursor cursor = db.rawQuery(sql, null); @@ -178,54 +258,53 @@ final class SqlHelper extends SQLiteOpenHelper { long count = cursor.getLong(0); cursor.close(); - // 复制数据 + // ----------- 4、将旧表数据复制到新表 if (count > 0) { - // 获取旧表的所有字段名称 - Cursor columnC = - db.rawQuery(String.format("PRAGMA table_info(%s_temp)", tableName), null); + + // 旧表需要删除的字段,删除旧表有而新表没的字段 + List diffTab = getDiffColumn(newTabColumns, oldTabColumns); StringBuilder params = new StringBuilder(); - while (columnC.moveToNext()) { - String columnName = columnC.getString(columnC.getColumnIndex("name")); - if (delColumns != null && delColumns.get(tableName) != null) { - List delColumn = delColumns.get(tableName); - if (delColumn != null && !delColumn.isEmpty()) { - if (delColumn.contains(columnName)) { - continue; - } - } - } + // 需要修改的列名映射表 + //Map modifyMap = null; + if (modifyColumns != null) { + modifyMap = modifyColumns.get(tableName); + } - params.append(columnName).append(","); + for (String column : oldTabColumns) { + if (!diffTab.isEmpty() && diffTab.contains(column) + // 如果旧表字段有修改,忽略这个删除 + && !(modifyMap != null && modifyMap.containsKey(column))) { + continue; + } + params.append(column).append(","); } - columnC.close(); String oldParamStr = params.toString(); oldParamStr = oldParamStr.substring(0, oldParamStr.length() - 1); String newParamStr = oldParamStr; // 处理字段名称改变 - if (modifyColumns != null) { - Map columnMap = modifyColumns.get(tableName); - if (columnMap != null && !columnMap.isEmpty()) { - Set keys = columnMap.keySet(); - for (String key : keys) { - if (newParamStr.contains(key)) { - newParamStr = newParamStr.replace(key, columnMap.get(key)); - } + if (modifyMap != null && !modifyMap.isEmpty()) { + Set keys = modifyMap.keySet(); + for (String key : keys) { + if (newParamStr.contains(key)) { + newParamStr = newParamStr.replace(key, modifyMap.get(key)); } } } + //恢复数据 String insertSql = String.format("INSERT INTO %s (%s) SELECT %s FROM %s_temp", tableName, newParamStr, oldParamStr, tableName); - ALog.d(TAG, "insertSql = " + insertSql); + ALog.d(TAG, "恢复数据的sql:" + insertSql); + db.execSQL(insertSql); } - //删除中介表 - mDelegate.dropTable(db, tableName + "_temp"); + // ----------- 5、删除备份的表 + SqlUtil.dropTable(db, tableName + "_temp"); } else { - mDelegate.createTable(db, clazz); + SqlUtil.createTable(db, clazz); } } db.setTransactionSuccessful(); @@ -236,10 +315,115 @@ final class SqlHelper extends SQLiteOpenHelper { } } + /** + * 取新表差值(需要删除的字段):旧表有而新表没的字段 + * + * @param newTab 新表字段 + * @param oldTab 旧表字段 + * @return 旧表有而新表没的字段 + */ + private List getDiffColumn(List newTab, List oldTab) { + List temp = new ArrayList<>(oldTab); // 拷贝旧表字段 + temp.removeAll(newTab); + return temp; + } + + /** + * 获取新增字段 + * + * @param newTab 新表字段 + * @param oldTab 就表字段 + * @return 新表有而旧表没的字段 + */ + private List getNewColumn(List newTab, List oldTab) { + List temp = new ArrayList<>(newTab); + temp.removeAll(oldTab); + return temp; + } + + /** + * 给TaskRecord 增加任务类型 + */ + private void addTaskRecordType(SQLiteDatabase db) { + try { + SqlUtil.checkOrCreateTable(db, ThreadRecord.class); + SqlUtil.checkOrCreateTable(db, TaskRecord.class); + SqlUtil.checkOrCreateTable(db, UploadEntity.class); + SqlUtil.checkOrCreateTable(db, DownloadEntity.class); + + db.beginTransaction(); + /* + * 增加下载实体的类型 + */ + String dSql = "SELECT downloadPath, url FROM DownloadEntity"; + Cursor c = db.rawQuery(dSql, null); + while (c.moveToNext()) { + int type; + String filePath = c.getString(0); + String url = c.getString(1); + if (url.startsWith("ftp") || url.startsWith("sftp")) { + type = ITaskWrapper.D_FTP; + } else { + if (SqlUtil.tableExists(db, M3U8Entity.class)) { + Cursor m3u8c = db.rawQuery("SELECT isLive FROM M3U8Entity WHERE filePath=\"" + + SqlUtil.encodeStr(filePath) + + "\"", null); + if (m3u8c.moveToNext()) { + String temp = m3u8c.getString(0); + type = + (TextUtils.isEmpty(temp) ? false : Boolean.valueOf(temp)) ? ITaskWrapper.M3U8_LIVE + : ITaskWrapper.M3U8_VOD; + } else { + type = ITaskWrapper.D_HTTP; + } + m3u8c.close(); + } else { + type = ITaskWrapper.D_HTTP; + } + } + db.execSQL("UPDATE DownloadEntity SET taskType=? WHERE downloadPath=?", + new Object[] { type, filePath }); + db.execSQL("UPDATE TaskRecord SET taskType=? WHERE filePath=?", + new Object[] { type, filePath }); + db.execSQL("UPDATE ThreadRecord SET threadType=? WHERE taskKey=?", + new Object[] { type, filePath }); + } + c.close(); + + /* + * 增加上传实体的类型 + */ + String uSql = "SELECT filePath, url FROM UploadEntity"; + c = db.rawQuery(uSql, null); + while (c.moveToNext()) { + int type; + String filePath = c.getString(c.getColumnIndex("filePath")); + String url = c.getString(c.getColumnIndex("url")); + if (url.startsWith("ftp") || url.startsWith("sftp")) { + type = ITaskWrapper.D_FTP; + } else { + type = ITaskWrapper.D_HTTP; + } + db.execSQL("UPDATE UploadEntity SET taskType=? WHERE filePath=?", + new Object[] { type, filePath }); + db.execSQL("UPDATE TaskRecord SET taskType=? WHERE filePath=?", + new Object[] { type, filePath }); + db.execSQL("UPDATE ThreadRecord SET threadType=? WHERE taskKey=?", + new Object[] { type, filePath }); + } + c.close(); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + /** * 删除重复的repeat数据 */ private void delRepeatThreadRecord(SQLiteDatabase db) { + SqlUtil.checkOrCreateTable(db, ThreadRecord.class); String repeatSql = "DELETE FROM ThreadRecord WHERE (rowid) " + "IN (SELECT rowid FROM ThreadRecord GROUP BY taskKey, threadId, endLocation HAVING COUNT(*) > 1) " + "AND rowid NOT IN (SELECT MIN(rowid) FROM ThreadRecord GROUP BY taskKey, threadId, endLocation HAVING COUNT(*)> 1)"; @@ -258,7 +442,7 @@ final class SqlHelper extends SQLiteOpenHelper { modifyMap.put("ThreadRecord", threadRecordModify); // 执行升级操作 - handleDbUpdate(db, modifyMap, null); + handleDbUpdate(db, modifyMap); delRepeatThreadRecord(db); } @@ -266,6 +450,7 @@ final class SqlHelper extends SQLiteOpenHelper { * 处理365版本以下的升级 */ private void handle365Update(SQLiteDatabase db) { + SqlUtil.checkOrCreateTable(db, ThreadRecord.class); db.execSQL("UPDATE ThreadRecord SET threadId=0 WHERE threadId=-1"); Map> modifyMap = new HashMap<>(); @@ -275,7 +460,7 @@ final class SqlHelper extends SQLiteOpenHelper { modifyMap.put("ThreadRecord", threadRecordModify); // 执行升级操作 - handleDbUpdate(db, modifyMap, null); + handleDbUpdate(db, modifyMap); delRepeatThreadRecord(db); } @@ -286,8 +471,8 @@ final class SqlHelper extends SQLiteOpenHelper { String[] taskTables = new String[] { "UploadTaskEntity", "DownloadTaskEntity", "DownloadGroupTaskEntity" }; for (String taskTable : taskTables) { - if (mDelegate.tableExists(db, taskTable)) { - mDelegate.dropTable(db, taskTable); + if (SqlUtil.tableExists(db, taskTable)) { + SqlUtil.dropTable(db, taskTable); } } @@ -309,7 +494,7 @@ final class SqlHelper extends SQLiteOpenHelper { modifyMap.put("ThreadRecord", threadRecordModify); // 执行升级操作 - handleDbUpdate(db, modifyMap, null); + handleDbUpdate(db, modifyMap); delRepeatThreadRecord(db); } @@ -320,8 +505,8 @@ final class SqlHelper extends SQLiteOpenHelper { String[] taskTables = new String[] { "UploadTaskEntity", "DownloadTaskEntity", "DownloadGroupTaskEntity" }; for (String taskTable : taskTables) { - if (mDelegate.tableExists(db, taskTable)) { - mDelegate.dropTable(db, taskTable); + if (SqlUtil.tableExists(db, taskTable)) { + SqlUtil.dropTable(db, taskTable); } } @@ -330,7 +515,7 @@ final class SqlHelper extends SQLiteOpenHelper { String[] keys = new String[] { "downloadPath", "groupName" }; int i = 0; for (String tableName : tables) { - if (!mDelegate.tableExists(db, tableName)) { + if (!SqlUtil.tableExists(db, tableName)) { continue; } String pColumn = keys[i]; @@ -362,15 +547,6 @@ final class SqlHelper extends SQLiteOpenHelper { dGEntityModifyMap.put("groupName", "groupHash"); modifyMap.put("DownloadGroupEntity", dGEntityModifyMap); - // 删除字段 - Map> delColumnMap = new HashMap<>(); - List dEntityDel = new ArrayList<>(); - dEntityDel.add("taskKey"); - delColumnMap.put("DownloadEntity", dEntityDel); - List dgEntityDel = new ArrayList<>(); - dgEntityDel.add("subtask"); - delColumnMap.put("DownloadGroupEntity", dgEntityDel); - - handleDbUpdate(db, modifyMap, delColumnMap); + handleDbUpdate(db, modifyMap); } } \ No newline at end of file diff --git a/PublicComponent/src/main/java/com/arialyy/aria/orm/SqlUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/SqlUtil.java new file mode 100644 index 00000000..f975e34f --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/orm/SqlUtil.java @@ -0,0 +1,590 @@ +/* + * 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.orm; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.orm.annotation.Default; +import com.arialyy.aria.orm.annotation.Foreign; +import com.arialyy.aria.orm.annotation.Ignore; +import com.arialyy.aria.orm.annotation.Many; +import com.arialyy.aria.orm.annotation.NoNull; +import com.arialyy.aria.orm.annotation.One; +import com.arialyy.aria.orm.annotation.Primary; +import com.arialyy.aria.orm.annotation.Unique; +import com.arialyy.aria.orm.annotation.Wrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by lyy on 2017/7/24. + * sql工具 + */ +final class SqlUtil { + private static final String TAG = CommonUtil.getClassName("SqlUtil"); + + /** + * 检查表是否存在,不存在则创建表 + */ + static void checkOrCreateTable(SQLiteDatabase db, Class clazz) { + if (!tableExists(db, clazz)) { + createTable(db, clazz); + } + } + + static void closeCursor(Cursor cursor) { + synchronized (AbsDelegate.class) { + if (cursor != null && !cursor.isClosed()) { + try { + cursor.close(); + } catch (android.database.SQLException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 查找表是否存在 + * + * @param clazz 数据库实体 + * @return true,该数据库实体对应的表存在;false,不存在 + */ + static boolean tableExists(SQLiteDatabase db, Class clazz) { + return tableExists(db, CommonUtil.getClassName(clazz)); + } + + /** + * 查找表是否存在 + * + * @param tableName 表名 + * @return true,该数据库实体对应的表存在;false,不存在 + */ + static boolean tableExists(SQLiteDatabase db, String tableName) { + db = checkDb(db); + Cursor cursor = null; + try { + String sql = + String.format("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='%s'", + tableName); + cursor = db.rawQuery(sql, null); + if (cursor != null && cursor.moveToNext()) { + int count = cursor.getInt(0); + if (count > 0) { + return true; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + closeCursor(cursor); + } + return false; + } + + /** + * 检查list参数是否合法,list只能是{@code List} + * + * @return {@code true} 合法 + */ + static boolean checkList(Field list) { + Class t = CommonUtil.getListParamType(list); + if (t == String.class) { + return true; + } else { + ALog.d(TAG, "map参数错误,支持List的参数字段"); + return false; + } + } + + /** + * 检查map参数是否合法,map只能是{@code Map} + * + * @return {@code true} 合法 + */ + static boolean checkMap(Field map) { + Class[] ts = CommonUtil.getMapParamType(map); + if (ts != null + && ts[0] != null + && ts[1] != null + && ts[0] == String.class + && ts[1] == String.class) { + return true; + } else { + ALog.d(TAG, "map参数错误,支持Map的参数字段"); + return false; + } + } + + /** + * 删除指定的表 + */ + static void dropTable(SQLiteDatabase db, String tableName) { + db = checkDb(db); + String deleteSQL = String.format("DROP TABLE IF EXISTS %s", tableName); + //db.beginTransaction(); + db.execSQL(deleteSQL); + //db.setTransactionSuccessful(); + //db.endTransaction(); + } + + /** + * 清空表数据 + */ + static void clean(SQLiteDatabase db, Class clazz) { + db = checkDb(db); + String tableName = CommonUtil.getClassName(clazz); + if (tableExists(db, clazz)) { + String sql = "DELETE FROM " + tableName; + db.execSQL(sql); + } + } + + /** + * 检查某个字段的值是否存在 + * + * @param expression 字段和值"url=xxx" + * @return {@code true}该字段的对应的value已存在 + */ + static boolean checkDataExist(SQLiteDatabase db, Class clazz, + String... expression) { + db = checkDb(db); + if (!CommonUtil.checkSqlExpression(expression)) { + return false; + } + String sql = String.format("SELECT rowid, * FROM %s WHERE %s ", CommonUtil.getClassName(clazz), + expression[0]); + sql = sql.replace("?", "%s"); + Object[] params = new String[expression.length - 1]; + for (int i = 0, len = params.length; i < len; i++) { + params[i] = String.format("'%s'", SqlUtil.encodeStr(expression[i + 1])); + } + sql = String.format(sql, params); + Cursor cursor = db.rawQuery(sql, null); + final boolean isExist = cursor.getCount() > 0; + closeCursor(cursor); + return isExist; + } + + /** + * 通过class 获取该class的表字段 + * + * @return 表字段列表 + */ + static List getColumns(Class clazz) { + List columns = new ArrayList<>(); + List fields = CommonUtil.getAllFields(clazz); + for (Field field : fields) { + field.setAccessible(true); + if (SqlUtil.isIgnore(field)) { + continue; + } + columns.add(field.getName()); + } + return columns; + } + + /** + * 检查数据库是否关闭,已经关闭的话,打开数据库 + * + * @return 返回数据库 + */ + static SQLiteDatabase checkDb(SQLiteDatabase db) { + if (db == null || !db.isOpen()) { + db = SqlHelper.getInstance().getDb(); + } + return db; + } + + /** + * 创建表 + * + * @param clazz 数据库实体 + */ + static void createTable(SQLiteDatabase db, Class clazz) { + db = checkDb(db); + List fields = CommonUtil.getAllFields(clazz); + if (fields != null && fields.size() > 0) { + //外键Map,在Sqlite3中foreign修饰的字段必须放在最后 + final List foreignArray = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + sb.append("CREATE TABLE IF NOT EXISTS ") + .append(CommonUtil.getClassName(clazz)) + .append(" ("); + for (Field field : fields) { + field.setAccessible(true); + if (SqlUtil.isIgnore(field)) { + continue; + } + Class type = field.getType(); + String columnType = getColumnType(type); + if (columnType == null) { + continue; + } + sb.append(field.getName()); + sb.append(" ").append(columnType); + + if (SqlUtil.isPrimary(field)) { + Primary pk = field.getAnnotation(Primary.class); + sb.append(" PRIMARY KEY"); + if (pk.autoincrement() && (type == int.class || type == Integer.class)) { + sb.append(" AUTOINCREMENT"); + } + } + + if (SqlUtil.isForeign(field)) { + foreignArray.add(field); + } + + if (SqlUtil.isNoNull(field)) { + sb.append(" NOT NULL"); + } + + if (SqlUtil.isDefault(field)) { + Default d = field.getAnnotation(Default.class); + if (!TextUtils.isEmpty(d.value())) { + sb.append(" ERROR ").append("'").append(d.value()).append("'"); + } + } + + if (SqlUtil.isUnique(field)) { + sb.append(" UNIQUE"); + } + + sb.append(","); + } + + for (Field field : foreignArray) { + Foreign foreign = field.getAnnotation(Foreign.class); + sb.append("FOREIGN KEY (") + .append(field.getName()) + .append(") REFERENCES ") + .append(CommonUtil.getClassName(foreign.parent())) + .append("(") + .append(foreign.column()) + .append(")"); + ActionPolicy update = foreign.onUpdate(); + ActionPolicy delete = foreign.onDelete(); + if (update != ActionPolicy.NO_ACTION) { + sb.append(" ON UPDATE ").append(update.function); + } + + if (delete != ActionPolicy.NO_ACTION) { + sb.append(" ON DELETE ").append(update.function); + } + sb.append(","); + } + + String str = sb.toString(); + str = str.substring(0, str.length() - 1) + ");"; + ALog.d(TAG, "创建表的sql:" + str); + db.execSQL(str); + } + } + + /** + * 根据字段名获取字段类型 + */ + static String getColumnTypeByFieldName(Class tabClass, String fieldName) { + List fields = CommonUtil.getAllFields(tabClass); + for (Field field : fields) { + if (field.getName().equals(fieldName)) { + return getColumnType(field.getType()); + } + } + return null; + } + + /** + * 获取字段类型 + */ + static String getColumnType(Class fieldtype) { + if (fieldtype == String.class || fieldtype.isEnum()) { + return "VARCHAR"; + } else if (fieldtype == int.class || fieldtype == Integer.class) { + return "INTEGER"; + } else if (fieldtype == float.class || fieldtype == Float.class) { + return "FLOAT"; + } else if (fieldtype == double.class || fieldtype == Double.class) { + return "DOUBLE"; + } else if (fieldtype == long.class || fieldtype == Long.class) { + return "BIGINT"; + } else if (fieldtype == boolean.class || fieldtype == Boolean.class) { + return "BOOLEAN"; + } else if (fieldtype == java.util.Date.class || fieldtype == java.sql.Date.class) { + return "DATA"; + } else if (fieldtype == byte.class || fieldtype == Byte.class) { + return "BLOB"; + } else if (fieldtype == Map.class || fieldtype == List.class) { + return "TEXT"; + } else { + return null; + } + } + + /** + * URL编码字符串 + * + * @param str 原始字符串 + * @return 编码后的字符串 + */ + static String encodeStr(String str) { + str = str.replaceAll("\\+", "%2B"); + return URLEncoder.encode(str); + } + + /** + * 获取主键字段名 + */ + static String getPrimaryName(Class clazz) { + List fields = CommonUtil.getAllFields(clazz); + String column; + if (fields != null && !fields.isEmpty()) { + + for (Field field : fields) { + field.setAccessible(true); + if (isPrimary(field)) { + column = field.getName(); + return column; + } + } + } + return null; + } + + /** + * 获取类中所有不被忽略的字段 + */ + static List getAllNotIgnoreField(Class clazz) { + List fields = CommonUtil.getAllFields(clazz); + List temp = new ArrayList<>(); + if (fields != null && fields.size() > 0) { + for (Field f : fields) { + f.setAccessible(true); + if (!isIgnore(f)) { + temp.add(f); + } + } + return temp; + } else { + return null; + } + } + + /** + * 列表数据转字符串 + * + * @param field list反射字段 + */ + static String list2Str(DbEntity dbEntity, Field field) throws IllegalAccessException { + List list = (List) field.get(dbEntity); + if (list == null || list.isEmpty()) return ""; + StringBuilder sb = new StringBuilder(); + for (Object aList : list) { + sb.append(aList).append("$$"); + } + return sb.toString(); + } + + /** + * 字符串转列表 + * + * @param str 数据库中的字段 + * @return 如果str为null,则返回null + */ + static List str2List(String str, Field field) { + if (TextUtils.isEmpty(str)) return null; + String[] datas = str.split("\\$\\$"); + List list = new ArrayList(); + Class clazz = CommonUtil.getListParamType(field); + if (clazz != null) { + String type = clazz.getName(); + for (String data : datas) { + list.add(checkData(type, data)); + } + } + + return list; + } + + /** + * 字符串转Map,只支持 + *

    +   *   {@code Map}
    +   * 
    + */ + static Map str2Map(String str) { + Map map = new HashMap<>(); + if (TextUtils.isEmpty(str)) { + return map; + } + boolean isDecode = false; + if (str.endsWith("_&_decode_&_")) { + isDecode = true; + str = str.substring(0, str.length() - 12); + } + String[] element = str.split(","); + for (String data : element) { + String[] s = data.split("\\$"); + if (isDecode) { + map.put(CommonUtil.decryptBASE64(s[0]), CommonUtil.decryptBASE64(s[1])); + } else { + map.put(s[0], s[1]); + } + } + return map; + } + + /** + * Map转字符串,只支持 + *
    +   *   {@code Map}
    +   * 
    + */ + static String map2Str(Map map) { + StringBuilder sb = new StringBuilder(); + Set keys = map.keySet(); + for (String key : keys) { + sb.append(CommonUtil.encryptBASE64(key)) + .append("$") + .append(CommonUtil.encryptBASE64(map.get(key))) + .append(","); + } + String str = sb.toString(); + str = TextUtils.isEmpty(str) ? str : str.substring(0, str.length() - 1); + //3.3.10版本之前没有decode,需要加标志 + if (map.size() != 0) { + str += "_&_decode_&_"; + } + return str; + } + + /** + * shadow$_klass_、shadow$_monitor_、{@link Ignore}、rowID、{@link Field#isSynthetic()}、{@link + * Modifier#isFinal(int)}、{@link Modifier#isStatic(int)}将被忽略 + * + * @return true 忽略该字段 + */ + static boolean isIgnore(Field field) { + // field.isSynthetic(), 使用as热启动App时,AS会自动给你的class添加change字段 + Ignore ignore = field.getAnnotation(Ignore.class); + int modifiers = field.getModifiers(); + String fieldName = field.getName(); + return (ignore != null && ignore.value()) || fieldName.equals("rowID") || fieldName.equals( + AriaConfig.IGNORE_CLASS_KLASS) || fieldName.equals(AriaConfig.IGNORE_CLASS_MONITOR) + || field.isSynthetic() || Modifier + .isStatic(modifiers) || Modifier.isFinal(modifiers); + } + + /** + * 判断是否是Wrapper注解 + * + * @return {@code true} 是 + */ + static boolean isWrapper(Class clazz) { + Wrapper w = clazz.getAnnotation(Wrapper.class); + return w != null; + } + + /** + * 判断是否一对多注解 + */ + static boolean isMany(Field field) { + Many oneToMany = field.getAnnotation(Many.class); + return oneToMany != null; + } + + /** + * 判断是否是一对一注解 + */ + static boolean isOne(Field field) { + One oneToOne = field.getAnnotation(One.class); + return oneToOne != null; + } + + /** + * 判断是否是主键约束 + * + * @return {@code true}主键约束 + */ + static boolean isPrimary(Field field) { + Primary pk = field.getAnnotation(Primary.class); + return pk != null; + } + + /** + * 判断是否是外键约束 + * + * @return {@code true}外键约束 + */ + static boolean isForeign(Field field) { + Foreign fk = field.getAnnotation(Foreign.class); + return fk != null; + } + + /** + * 判断是否是非空约束 + * + * @return {@code true}为非空约束 + */ + static boolean isNoNull(Field field) { + NoNull nn = field.getAnnotation(NoNull.class); + return nn != null; + } + + /** + * 判断是否是default + * + * @return {@code true}为default + */ + static boolean isDefault(Field field) { + Default nn = field.getAnnotation(Default.class); + return nn != null; + } + + /** + * 判断是否是Unique + * + * @return {@code true}为Unique + */ + static boolean isUnique(Field field) { + Unique nn = field.getAnnotation(Unique.class); + return nn != null; + } + + private static Object checkData(String type, String data) { + if (type.equalsIgnoreCase("java.lang.String")) { + return data; + } else if (type.equalsIgnoreCase("int") || type.equals("java.lang.Integer")) { + return Integer.parseInt(data); + } else if (type.equalsIgnoreCase("double") || type.equals("java.lang.Double")) { + return Double.parseDouble(data); + } else if (type.equalsIgnoreCase("float") || type.equals("java.lang.Float")) { + return Float.parseFloat(data); + } + return null; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Default.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Default.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Default.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Default.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Foreign.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Foreign.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Foreign.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Foreign.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Ignore.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Ignore.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Ignore.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Ignore.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Many.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Many.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Many.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Many.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/NoNull.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/NoNull.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/NoNull.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/NoNull.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/One.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/One.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/One.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/One.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Primary.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Primary.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Primary.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Primary.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Table.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Table.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Table.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Table.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Unique.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Unique.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Unique.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Unique.java diff --git a/Aria/src/main/java/com/arialyy/aria/orm/annotation/Wrapper.java b/PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Wrapper.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/orm/annotation/Wrapper.java rename to PublicComponent/src/main/java/com/arialyy/aria/orm/annotation/Wrapper.java diff --git a/Aria/src/main/java/com/arialyy/aria/util/ALog.java b/PublicComponent/src/main/java/com/arialyy/aria/util/ALog.java similarity index 96% rename from Aria/src/main/java/com/arialyy/aria/util/ALog.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/ALog.java index 5e5d4b7e..84a9a819 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/ALog.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/ALog.java @@ -63,10 +63,8 @@ public class ALog { return println(Log.ERROR, tag, msg); } - public static int e(String tag, Throwable e) { - String msg = getExceptionString(e); - ErrorHelp.saveError(tag, "", msg); - return println(Log.ERROR, tag, msg); + public static void e(String tag, String msg, Throwable e) { + Log.e(tag, msg, e); } /** diff --git a/Aria/src/main/java/com/arialyy/aria/util/AriaCrashHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/util/AriaCrashHandler.java similarity index 96% rename from Aria/src/main/java/com/arialyy/aria/util/AriaCrashHandler.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/AriaCrashHandler.java index f6b30f82..d529b252 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/AriaCrashHandler.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/AriaCrashHandler.java @@ -59,7 +59,7 @@ public class AriaCrashHandler implements Thread.UncaughtExceptionHandler { mExecutorService.execute(new Runnable() { @Override public void run() { - ErrorHelp.saveError(name, "", ALog.getExceptionString(ex)); + ErrorHelp.saveError("", ALog.getExceptionString(ex)); } }); } diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/AriaServiceLoader.java b/PublicComponent/src/main/java/com/arialyy/aria/util/AriaServiceLoader.java new file mode 100644 index 00000000..a7e486a1 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/AriaServiceLoader.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +/** + * aria 服务加载器 + * 参考{@link ServiceLoader} + */ +public class AriaServiceLoader { + private static final String TAG = "AriaServiceLoader"; + private static final String PREFIX = "META-INF/services/"; + + // The class or interface representing the service being loaded + private final Class service; + + // The class loader used to locate, load, and instantiate providers + private final ClassLoader loader; + + // The access control context taken when the ServiceLoader is created + // Android-changed: do not use legacy security code. + // private final AccessControlContext acc; + + private LazyLoader lazyLoader; + + public void reload() { + lazyLoader = new LazyLoader(service, loader); + } + + private AriaServiceLoader(Class svc, ClassLoader cl) { + service = svc; + loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; + // Android-changed: Do not use legacy security code. + // On Android, System.getSecurityManager() is always null. + // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; + reload(); + } + + public static AriaServiceLoader load(Class service, ClassLoader loader) { + return new AriaServiceLoader<>(service, loader); + } + + public static AriaServiceLoader load(Class service) { + Thread.currentThread().setContextClassLoader(AriaServiceLoader.class.getClassLoader()); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return AriaServiceLoader.load(service, cl); + } + + public S getService(String serviceName) { + return lazyLoader.loadService(serviceName); + } + + private class LazyLoader { + private Class service; + private ClassLoader loader; + private Enumeration configs = null; + private List pending = null; + + private LazyLoader(Class service, ClassLoader loader) { + this.service = service; + this.loader = loader; + parseConfig(); + } + + // Parse the content of the given URL as a provider-configuration file. + // + // @param service + // The service type for which providers are being sought; + // used to construct error detail strings + // + // @param u + // The URL naming the configuration file to be parsed + // + // @return A (possibly empty) iterator that will yield the provider-class + // names in the given configuration file that are not yet members + // of the returned set + // + // @throws ServiceConfigurationError + // If an I/O error occurs while reading from the given URL, or + // if a configuration-file format error is detected + // + private List parse(Class service, URL u) throws ServiceConfigurationError { + InputStream in = null; + BufferedReader r = null; + ArrayList names = new ArrayList<>(); + try { + in = u.openStream(); + r = new BufferedReader(new InputStreamReader(in, "utf-8")); + int lc = 1; + while ((lc = parseLine(service, u, r, lc, names)) >= 0) ; + } catch (IOException x) { + fail(service, "Error reading configuration file", x); + } finally { + try { + if (r != null) r.close(); + if (in != null) in.close(); + } catch (IOException y) { + fail(service, "Error closing configuration file", y); + } + } + return names; + } + + private void fail(Class service, String msg, Throwable cause) + throws ServiceConfigurationError { + throw new ServiceConfigurationError(service.getName() + ": " + msg, + cause); + } + + private void fail(Class service, String msg) + throws ServiceConfigurationError { + throw new ServiceConfigurationError(service.getName() + ": " + msg); + } + + private void fail(Class service, URL u, int line, String msg) + throws ServiceConfigurationError { + fail(service, u + ":" + line + ": " + msg); + } + + // Parse a single line from the given configuration file, adding the name + // on the line to the names list. + // + private int parseLine(Class service, URL u, BufferedReader r, int lc, + List names) + throws IOException, ServiceConfigurationError { + String ln = r.readLine(); + if (ln == null) { + return -1; + } + int ci = ln.indexOf('#'); + if (ci >= 0) ln = ln.substring(0, ci); + ln = ln.trim(); + int n = ln.length(); + if (n != 0) { + if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) { + fail(service, u, lc, "Illegal configuration-file syntax"); + } + int cp = ln.codePointAt(0); + if (!Character.isJavaIdentifierStart(cp)) { + fail(service, u, lc, "Illegal provider-class name: " + ln); + } + for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { + cp = ln.codePointAt(i); + if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { + fail(service, u, lc, "Illegal provider-class name: " + ln); + } + } + if (!names.contains(ln)) { + names.add(ln); + } + } + return lc + 1; + } + + private S loadService(String serviceName) { + if (pending == null){ + ALog.e(TAG, "pending为空"); + return null; + } + for (String s : pending) { + if (s.equals(serviceName)) { + Class c = null; + try { + c = Class.forName(serviceName, false, loader); + } catch (ClassNotFoundException x) { + fail(service, + // Android-changed: Let the ServiceConfigurationError have a cause. + "Provider " + serviceName + " not found", x); + // "Provider " + cn + " not found"); + } + if (!service.isAssignableFrom(c)) { + // Android-changed: Let the ServiceConfigurationError have a cause. + ClassCastException cce = new ClassCastException( + service.getCanonicalName() + " is not assignable from " + c.getCanonicalName()); + fail(service, + "Provider " + serviceName + " not a subtype", cce); + // fail(service, + // "Provider " + cn + " not a subtype"); + } + try { + return service.cast(c.newInstance()); + } catch (Throwable x) { + fail(service, "Provider " + serviceName + " could not be instantiated", x); + } + } + } + + throw new Error(); // This cannot happen + } + + /** + * 解析配置文件 + */ + private void parseConfig() { + if (configs == null) { + try { + String fullName = PREFIX + service.getName(); + if (loader == null) { + configs = ClassLoader.getSystemResources(fullName); + } else { + configs = loader.getResources(fullName); + } + } catch (IOException x) { + fail(service, "Error locating configuration files", x); + } + } + while ((pending == null) || pending.isEmpty()) { + if (!configs.hasMoreElements()) { + return; + } + pending = parse(service, configs.nextElement()); + } + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/BandwidthLimiter.java b/PublicComponent/src/main/java/com/arialyy/aria/util/BandwidthLimiter.java similarity index 98% rename from Aria/src/main/java/com/arialyy/aria/core/common/BandwidthLimiter.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/BandwidthLimiter.java index b42a58ff..fff1e553 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/BandwidthLimiter.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/BandwidthLimiter.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.arialyy.aria.core.common; +package com.arialyy.aria.util; /** * 速度限制 diff --git a/Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java b/PublicComponent/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/BufferedRandomAccessFile.java diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/CheckUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/CheckUtil.java new file mode 100644 index 00000000..8e92c028 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/CheckUtil.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.aria.util; + +import android.text.TextUtils; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.orm.DbEntity; +import java.lang.reflect.Modifier; +import java.net.HttpURLConnection; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by Lyy on 2016/9/23. + * 检查帮助类 + */ +public class CheckUtil { + private static final String TAG = "CheckUtil"; + + /** + * 检查ip是否正确 + * + * @param ip ipv4 地址 + * @return {@code true} ip 正确,{@code false} ip 错误 + */ + public static boolean checkIp(String ip) { + if (TextUtils.isEmpty(ip) || ip.length() < 7 || ip.length() > 15) { + return false; + } + Pattern p = Pattern.compile(Regular.REG_IP_V4); + Matcher m = p.matcher(ip); + return m.find() && m.groupCount() > 0; + } + + /** + * 判断http请求是否有效 + * {@link HttpURLConnection#HTTP_BAD_GATEWAY} or {@link HttpURLConnection#HTTP_BAD_METHOD} + * or {@link HttpURLConnection#HTTP_BAD_REQUEST} 无法重试 + * + * @param errorCode http 返回码 + * @return {@code true} 无效请求;{@code false} 有效请求 + */ + public static boolean httpIsBadRequest(int errorCode) { + return errorCode == HttpURLConnection.HTTP_BAD_GATEWAY + || errorCode == HttpURLConnection.HTTP_BAD_METHOD + || errorCode == HttpURLConnection.HTTP_BAD_REQUEST; + } + + /** + * 判断ftp请求是否有效 + * + * @return {@code true} 无效请求;{@code false} 有效请求 + */ + public static boolean ftpIsBadRequest(int errorCode) { + return errorCode >= 400 && errorCode < 600; + } + + /** + * 检查和处理下载任务的路径冲突 + * + * @param isForceDownload true,如果路径冲突,将删除其它任务的记录的 + * @param filePath 文件保存路径 + * @param type {@link AbsTaskWrapper#getRequestType()} + * @return false 任务不再执行,true 任务继续执行 + */ + public static boolean checkDPathConflicts(boolean isForceDownload, String filePath, int type) { + if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", filePath)) { + if (!isForceDownload) { + ALog.e(TAG, String.format("下载失败,保存路径【%s】已经被其它任务占用,请设置其它保存路径", filePath)); + return false; + } else { + ALog.w(TAG, String.format("保存路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath)); + RecordUtil.delTaskRecord(filePath, type, false, true); + return true; + } + } + return true; + } + + /** + * 检查和处理上传任务的路径冲突 + * + * @param isForceUpload true,如果路径冲突,将删除其它任务的记录的 + * @param filePath 文件保存路径 + * @param type {@link AbsTaskWrapper#getRequestType()} + * @return false 任务不再执行,true 任务继续执行 + */ + public static boolean checkUPathConflicts(boolean isForceUpload, String filePath, int type) { + if (DbEntity.checkDataExist(UploadEntity.class, "filePath=?", filePath)) { + if (!isForceUpload) { + ALog.e(TAG, String.format("上传失败,文件路径【%s】已经被其它任务占用,请设置其它文件路径", filePath)); + return false; + } else { + ALog.w(TAG, String.format("文件路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath)); + RecordUtil.delTaskRecord(filePath, type, false, true); + return true; + } + } + return true; + } + + /** + * 检查和处理组合任务的路径冲突 + * + * @param isForceDownload true,如果路径冲突,将删除其它任务的记录的 + * @param dirPath 文件保存路径 + * @return false 任务不再执行,true 任务继续执行 + */ + public static boolean checkDGPathConflicts(boolean isForceDownload, String dirPath) { + if (DbEntity.checkDataExist(DownloadGroupEntity.class, "dirPath=?", dirPath)) { + if (!isForceDownload) { + ALog.e(TAG, String.format("下载失败,文件夹路径【%s】已经被其它任务占用,请设置其它文件路径", dirPath)); + return false; + } else { + ALog.w(TAG, String.format("文件夹路径【%s】已经被其它任务占用,当前任务将覆盖该路径", dirPath)); + DeleteDGRecord.getInstance().deleteRecord(dirPath, false, true); + return true; + } + } + return true; + } + + /** + * 检查成员类是否是静态和public + */ + public static void checkMemberClass(Class clazz) { + int modifiers = clazz.getModifiers(); + //ALog.d(TAG, "isMemberClass = " + // + clazz.isMemberClass() + // + "; isStatic = " + // + Modifier.isStatic(modifiers) + // + "; isPrivate = " + // + Modifier.isPrivate(modifiers)); + if (!clazz.isMemberClass() || !Modifier.isStatic(modifiers)) { + ALog.e(TAG, String.format("为了防止内存泄漏,请使用静态的成员类(public static class %s)或文件类(%s.java)", + clazz.getSimpleName(), clazz.getSimpleName())); + } + } + + /** + * 检查分页数据,需要查询的页数,从1开始,如果page小于1 或 num 小于1,则抛出{@link NullPointerException} + * + * @param page 从1 开始 + * @param num 每页数量 + */ + public static void checkPageParams(int page, int num) { + if (page < 1 || num < 1) throw new NullPointerException("page和num不能小于1"); + } + + /** + * 检测url是否合法 + * + * @return {@code true} 合法,{@code false} 非法 + */ + public static boolean checkUrl(String url) { + if (TextUtils.isEmpty(url)) { + ALog.e(TAG, "url不能为null"); + return false; + } else if (!url.startsWith("http") && !url.startsWith("ftp") && !url.startsWith("sftp")) { + ALog.e(TAG, "url【" + url + "】错误"); + return false; + } + int index = url.indexOf("://"); + if (index == -1) { + ALog.e(TAG, "url【" + url + "】不合法"); + } + return true; + } + + /** + * 检测下载链接组是否为null + * + * @return true 组合任务url为空 + */ + public static boolean checkDownloadUrlsIsEmpty(List urls) { + if (urls == null || urls.isEmpty()) { + ALog.e(TAG, "链接组不能为null"); + return true; + } + return false; + } + + /** + * 检测上传地址是否为null + */ + public static void checkUploadPathIsEmpty(String uploadPath) { + if (TextUtils.isEmpty(uploadPath)) { + throw new IllegalArgumentException("上传地址不能为null"); + } + } +} \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java similarity index 73% rename from Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java index 2030159f..067cef7b 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/CommonUtil.java @@ -16,6 +16,7 @@ package com.arialyy.aria.util; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -23,25 +24,20 @@ import android.net.Uri; import android.os.Environment; import android.text.TextUtils; import android.util.Base64; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.common.ftp.FtpUrlEntity; -import com.arialyy.aria.core.command.AbsGroupCmd; -import com.arialyy.aria.core.command.AbsNormalCmd; -import com.arialyy.aria.core.command.GroupCmdFactory; -import com.arialyy.aria.core.command.NormalCmdFactory; -import com.arialyy.aria.core.inf.AbsGroupTaskWrapper; -import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.ITask; +import android.util.Log; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.FtpUrlEntity; import dalvik.system.DexFile; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -50,10 +46,12 @@ import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; @@ -66,6 +64,147 @@ public class CommonUtil { private static final String TAG = "CommonUtil"; public static final String SERVER_CHARSET = "ISO-8859-1"; private static long lastClickTime; + /** + * android、androidx、support的fragment、dialogFragment类名 + */ + private static List mFragmentClassName = new ArrayList<>(); + private static List mDialogFragmentClassName = new ArrayList<>(); + + static { + mFragmentClassName.add("androidx.fragment.app.Fragment"); + mFragmentClassName.add("androidx.fragment.app.DialogFragment"); + mFragmentClassName.add("android.app.Fragment"); + mFragmentClassName.add("android.app.DialogFragment"); + mFragmentClassName.add("android.support.v4.app.Fragment"); + mFragmentClassName.add("android.support.v4.app.DialogFragment"); + + mDialogFragmentClassName.add("androidx.fragment.app.DialogFragment"); + mDialogFragmentClassName.add("android.app.DialogFragment"); + mDialogFragmentClassName.add("android.support.v4.app.DialogFragment"); + } + + /** + * 获取fragment的activityz + * + * @return 获取失败,返回null + */ + public static Activity getFragmentActivity(Object obj) { + try { + Method method = obj.getClass().getMethod("getActivity"); + return (Activity) method.invoke(obj); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 判断注解对象是否是fragment + * + * @return true 对象是fragment + */ + public static boolean isFragment(Class subClazz) { + Class parentClass = subClazz.getSuperclass(); + if (parentClass == null) { + return false; + } else { + String parentName = parentClass.getName(); + if (mFragmentClassName.contains(parentName)) { + return true; + } else { + return isFragment(parentClass); + } + } + } + + /** + * 判断对象是否是DialogFragment + * + * @return true 对象是DialogFragment + */ + public static boolean isDialogFragment(Class subClazz) { + Class parentClass = subClazz.getSuperclass(); + if (parentClass == null) { + return false; + } else { + String parentName = parentClass.getName(); + if (mDialogFragmentClassName.contains(parentName)) { + return true; + } else { + return isDialogFragment(parentClass); + } + } + } + + public static boolean isLocalOrAnonymousClass(Class clazz) { + // JVM Spec 4.8.6: A class must have an EnclosingMethod + // attribute if and only if it is a local class or an + // anonymous class. + return clazz.isLocalClass() || clazz.isAnonymousClass(); + } + + public static String getTargetName(Object obj) { + String targetName; + if (isLocalOrAnonymousClass(obj.getClass())) { + Log.w(TAG, String.format("%s 是匿名内部类或局部类,将使用其主类的对象", obj.getClass().getName())); + String clsName = obj.getClass().getName(); + int $Index = clsName.lastIndexOf("$"); + targetName = clsName.substring(0, $Index); + } else { + targetName = obj.getClass().getName(); + } + return targetName; + } + + /** + * 获取线程名称,命名规则:md5(任务地址 + 线程id) + * + * @param url 任务地址 + * @param threadId 线程id + */ + public static String getThreadName(String url, int threadId) { + return getStrMd5(url.concat(String.valueOf(threadId))); + } + + /** + * 检查sql的expression是否合法 + * + * @return false 不合法 + */ + public static boolean checkSqlExpression(String... expression) { + if (expression.length == 0) { + ALog.e(TAG, "sql语句表达式不能为null"); + return false; + } + //if (expression.length == 1) { + // ALog.e(TAG, String.format("表达式需要写入参数,参数信息:%s", Arrays.toString(expression))); + // return false; + //} + String where = expression[0]; + //if (!where.contains("?")) { + // ALog.e(TAG, String.format("请在where语句的'='后编写?,参数信息:%s", Arrays.toString(expression))); + // return false; + //} + Pattern pattern = Pattern.compile("\\?"); + Matcher matcher = pattern.matcher(where); + int count = 0; + while (matcher.find()) { + count++; + } + if (count < expression.length - 1) { + ALog.e(TAG, String.format("条件语句的?个数不能小于参数个数,参数信息:%s", Arrays.toString(expression))); + return false; + } + if (count > expression.length - 1) { + ALog.e(TAG, String.format("条件语句的?个数不能大于参数个数, 参数信息:%s", Arrays.toString(expression))); + return false; + } + return true; + } /** * 是否是快速点击,500ms内快速点击无效 @@ -95,6 +234,18 @@ public class CommonUtil { return new String(str.getBytes(charSet), SERVER_CHARSET); } + /** + * 将字符串转换为Ftp服务器默认的ISO-8859-1编码 + * + * @param charSet 字符串编码 + * @param str 需要转换的字符串 + * @return 转换后的字符串 + */ + public static String convertSFtpChar(String charSet, String str) + throws UnsupportedEncodingException { + return new String(str.getBytes(), charSet); + } + /** * 获取某包下所有类 * @@ -406,7 +557,7 @@ public class CommonUtil { md.update(str.getBytes()); return new BigInteger(1, md.digest()).toString(16); } catch (NoSuchAlgorithmException e) { - ALog.e(TAG, e.getMessage()); + e.printStackTrace(); } return ""; } @@ -440,25 +591,6 @@ public class CommonUtil { } } - /** - * 通过流创建文件 - */ - public static void createFileFormInputStream(InputStream is, String path) { - try { - FileOutputStream fos = new FileOutputStream(path); - byte[] buf = new byte[1024]; - int len; - while ((len = is.read(buf)) > 0) { - fos.write(buf, 0, len); - } - is.close(); - fos.flush(); - fos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - /** * 校验文件MD5码 */ @@ -501,7 +633,7 @@ public class CommonUtil { try { is = new FileInputStream(updateFile); } catch (FileNotFoundException e) { - ALog.e(TAG, e); + e.printStackTrace(); return null; } @@ -516,7 +648,7 @@ public class CommonUtil { try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { - ALog.e(TAG, e); + e.printStackTrace(); return null; } @@ -538,31 +670,11 @@ public class CommonUtil { try { is.close(); } catch (IOException e) { - ALog.e(TAG, e); + e.printStackTrace(); } } } - /** - * 创建任务命令 - * - * @param taskType {@link ITask#DOWNLOAD}、{@link ITask#DOWNLOAD_GROUP}、{@link ITask#UPLOAD} - */ - public static AbsNormalCmd createNormalCmd(T entity, int cmd, - int taskType) { - return NormalCmdFactory.getInstance().createCmd(entity, cmd, taskType); - } - - /** - * 创建任务组命令 - * - * @param childUrl 子任务url - */ - public static AbsGroupCmd createGroupCmd(String target, T entity, - int cmd, String childUrl) { - return GroupCmdFactory.getInstance().createCmd(target, entity, cmd, childUrl); - } - /** * 创建隐性的Intent */ @@ -616,6 +728,16 @@ public class CommonUtil { Collections.addAll(fields, personClazz.getDeclaredFields()); } Collections.addAll(fields, clazz.getDeclaredFields()); + List ignore = new ArrayList<>(); + for (Field field : fields) { + if (field.getName().equals(AriaConfig.IGNORE_CLASS_KLASS) || field.getName() + .equals(AriaConfig.IGNORE_CLASS_MONITOR)) { + ignore.add(field); + } + } + if (!ignore.isEmpty()) { + fields.removeAll(ignore); + } return fields; } @@ -646,7 +768,7 @@ public class CommonUtil { field = clazz.getField(name); } catch (NoSuchFieldException e1) { if (clazz.getSuperclass() == null) { - return field; + return null; } else { field = getField(clazz.getSuperclass(), name); } @@ -658,6 +780,32 @@ public class CommonUtil { return field; } + /** + * 获取类里面的指定对象,如果该类没有则从父类查询 + */ + public static Method getMethod(Class clazz, String methodName, Class... params){ + Method method = null; + try { + method = clazz.getDeclaredMethod(methodName, params); + } catch (NoSuchMethodException e) { + try { + method = clazz.getMethod(methodName, params); + } catch (NoSuchMethodException ex) { + if (clazz.getSuperclass() == null) { + return null; + } else { + method = getMethod(clazz.getSuperclass(), methodName, params); + } + } + } + + if (method != null){ + method.setAccessible(true); + } + + return method; + } + /** * 字符串转hashcode */ @@ -764,14 +912,47 @@ public class CommonUtil { return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "tb"; } + /** + * 转换时间 + * 时间<1 小时,显示分秒,显示样式 00:20 + * 时间 ≥1 小时,显示时分秒,显示样式 01:11:12 + * 时间 ≥1 天,显示天时分,显示样式 1d 01:11 + * 时间 ≥7 天,显示样式 ∞ + * + * @param seconds 单位为s的时间 + */ + public static String formatTime(int seconds) { + String standardTime; + if (seconds <= 0) { + standardTime = "00:00"; + } else if (seconds < 60) { + standardTime = String.format(Locale.getDefault(), "00:%02d", seconds % 60); + } else if (seconds < 3600) { + standardTime = String.format(Locale.getDefault(), "%02d:%02d", seconds / 60, seconds % 60); + } else if (seconds < 86400) { + standardTime = + String.format(Locale.getDefault(), "%02d:%02d:%02d", seconds / 3600, seconds % 3600 / 60, + seconds % 60); + } else if (seconds < 604800) { + standardTime = + String.format(Locale.getDefault(), "%dd %02d:%02d", seconds / 86400, + seconds % 86400 / 3600, + seconds % 3600); + } else { + standardTime = "∞"; + } + + return standardTime; + } + /** * 通过文件名获取下载配置文件路径 * * @param fileName 文件名 */ public static String getFileConfigPath(boolean isDownload, String fileName) { - return AriaManager.getInstance().getAPP().getFilesDir().getPath() + (isDownload - ? AriaManager.DOWNLOAD_TEMP_DIR - : AriaManager.UPLOAD_TEMP_DIR) + fileName + ".properties"; + return AriaConfig.getInstance().getAPP().getFilesDir().getPath() + (isDownload + ? AriaConfig.DOWNLOAD_TEMP_DIR + : AriaConfig.UPLOAD_TEMP_DIR) + fileName + ".properties"; } } \ No newline at end of file diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java new file mode 100644 index 00000000..88e51222 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/ComponentUtil.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import android.os.Handler; +import com.arialyy.aria.core.TaskOptionParams; +import com.arialyy.aria.core.inf.IEventHandler; +import com.arialyy.aria.core.inf.ITaskOption; +import com.arialyy.aria.core.inf.IUtil; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.task.AbsTask; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import java.lang.ref.SoftReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +/** + * 组件工具,用于跨组件创建对应的工具类 + * + * @author lyy + * Date: 2019-09-23 + */ +public class ComponentUtil { + public static final int COMPONENT_TYPE_HTTP = 1; + public static final int COMPONENT_TYPE_FTP = 2; + public static final int COMPONENT_TYPE_M3U8 = 3; + public static final int COMPONENT_TYPE_SFTP = 4; + + private String TAG = CommonUtil.getClassName(getClass()); + private static volatile ComponentUtil INSTANCE = null; + + private ComponentUtil() { + + } + + public static ComponentUtil getInstance() { + if (INSTANCE == null) { + synchronized (ComponentUtil.class) { + if (INSTANCE == null) { + INSTANCE = new ComponentUtil(); + } + } + } + + return INSTANCE; + } + + /** + * 检查组件是否存在,不存在抛出异常退出 + * + * @param componentType 组件类型{@link #COMPONENT_TYPE_FTP}, {@link #COMPONENT_TYPE_M3U8}, {@link + * #COMPONENT_TYPE_HTTP} + * @return true 组件存在 + */ + public boolean checkComponentExist(int componentType) { + String errorStr = "", className = null; + switch (componentType) { + case COMPONENT_TYPE_M3U8: + className = "com.arialyy.aria.m3u8.M3U8TaskOption"; + errorStr = "m3u8插件不存在,请添加m3u8插件"; + break; + case COMPONENT_TYPE_FTP: + className = "com.arialyy.aria.ftp.FtpTaskOption"; + errorStr = "ftp插件不存在,请添加ftp插件"; + break; + case COMPONENT_TYPE_HTTP: + className = "com.arialyy.aria.http.HttpTaskOption"; + errorStr = "http插件不存在,请添加http插件"; + break; + case COMPONENT_TYPE_SFTP: + className = "com.arialyy.aria.sftp.SFtpTaskOption"; + errorStr = "sftp插件不存在,请添加sftp插件"; + break; + } + + try { + getClass().getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + throw new IllegalArgumentException(errorStr); + } + return true; + } + + /** + * 创建任务工具 + * + * @return 返回任务工具 + */ + public synchronized T buildUtil(AbsTaskWrapper wrapper, + IEventListener listener) { + int requestType = wrapper.getRequestType(); + String className = null; + switch (requestType) { + case ITaskWrapper.M3U8_LIVE: + className = "com.arialyy.aria.m3u8.live.M3U8LiveUtil"; + break; + case ITaskWrapper.M3U8_VOD: + className = "com.arialyy.aria.m3u8.vod.M3U8VodUtil"; + break; + case ITaskWrapper.D_FTP: + className = "com.arialyy.aria.ftp.download.FtpDLoaderUtil"; + break; + case ITaskWrapper.D_HTTP: + className = "com.arialyy.aria.http.download.HttpDLoaderUtil"; + break; + case ITaskWrapper.U_FTP: + className = "com.arialyy.aria.ftp.upload.FtpULoaderUtil"; + break; + case ITaskWrapper.U_HTTP: + className = "com.arialyy.aria.http.upload.HttpULoaderUtil"; + break; + case ITaskWrapper.D_FTP_DIR: + className = "com.arialyy.aria.ftp.download.FtpDGLoaderUtil"; + break; + case ITaskWrapper.DG_HTTP: + className = "com.arialyy.aria.http.download.HttpDGLoaderUtil"; + break; + case ITaskWrapper.D_SFTP: + className = "com.arialyy.aria.sftp.download.SFtpDLoaderUtil"; + break; + case ITaskWrapper.U_SFTP: + className = "com.arialyy.aria.sftp.upload.SFtpULoaderUtil"; + break; + } + if (className == null) { + ALog.e(TAG, "不识别的类名:" + className); + return null; + } + T util = null; + try { + Class clazz = (Class) getClass().getClassLoader().loadClass(className); + Constructor con = clazz.getConstructor(); + util = con.newInstance(); + Method method = + CommonUtil.getMethod(clazz, "setParams", AbsTaskWrapper.class, IEventListener.class); + method.invoke(util, wrapper, listener); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return util; + } + + /** + * 创建任务事件监听 + * + * @param wrapperType 任务类型{@link ITaskWrapper} + * @return 返回事件监听,如果创建失败返回null + */ + public synchronized T buildListener(int wrapperType, AbsTask task, + Handler outHandler) { + String className = null, errorStr = "请添加FTP插件"; + switch (wrapperType) { + case ITaskWrapper.M3U8_LIVE: + case ITaskWrapper.M3U8_VOD: + className = "com.arialyy.aria.m3u8.M3U8Listener"; + errorStr = "请添加m3u8插件"; + break; + case ITaskWrapper.D_FTP: + case ITaskWrapper.D_HTTP: + case ITaskWrapper.D_SFTP: + className = "com.arialyy.aria.core.listener.BaseDListener"; + break; + case ITaskWrapper.U_FTP: + case ITaskWrapper.U_HTTP: + case ITaskWrapper.U_SFTP: + className = "com.arialyy.aria.core.listener.BaseUListener"; + break; + case ITaskWrapper.DG_HTTP: + case ITaskWrapper.D_FTP_DIR: + className = "com.arialyy.aria.core.listener.DownloadGroupListener"; + break; + } + if (className == null) { + return null; + } + T listener = null; + try { + Class clazz = (Class) getClass().getClassLoader().loadClass(className); + Constructor con = clazz.getConstructor(); + listener = con.newInstance(); + Method method = + CommonUtil.getMethod(clazz, "setParams", AbsTask.class, Handler.class); + method.invoke(listener, task, outHandler); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(errorStr); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return listener; + } + + /** + * 构建TaskOption信息 + * + * @param clazz 实现{@link ITaskOption}接口的配置信息类 + * @param params 任务配置信息参数 + * @return 构建失败,返回null + */ + public synchronized T buildTaskOption(Class clazz, + TaskOptionParams params) { + List fields = CommonUtil.getAllFields(clazz); + T taskOption = null; + try { + taskOption = clazz.newInstance(); + for (Field field : fields) { + field.setAccessible(true); + Class type = field.getType(); + String key = field.getName(); + if (type != SoftReference.class) { + Object obj = params.getParams().get(key); + if (obj == null) { + continue; + } + field.set(taskOption, obj); + } else { + IEventHandler handler = params.getHandler().get(key); + if (handler == null) { + continue; + } + field.set(taskOption, new SoftReference<>(handler)); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } + return taskOption; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/util/DbDataHelper.java b/PublicComponent/src/main/java/com/arialyy/aria/util/DbDataHelper.java similarity index 73% rename from Aria/src/main/java/com/arialyy/aria/util/DbDataHelper.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/DbDataHelper.java index 95cb5789..8a2fabf1 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/DbDataHelper.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/DbDataHelper.java @@ -15,12 +15,13 @@ */ package com.arialyy.aria.util; -import com.arialyy.aria.core.common.RecordWrapper; -import com.arialyy.aria.core.common.TaskRecord; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; import com.arialyy.aria.core.download.DGEntityWrapper; 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.wrapper.ITaskWrapper; import com.arialyy.aria.orm.DbEntity; import java.io.File; import java.util.ArrayList; @@ -35,15 +36,20 @@ public class DbDataHelper { * 获取任务记录 * * @param filePath 文件地址 + * @param taskType 任务类型{@link ITaskWrapper} * @return 没有记录返回null,有记录则返回任务记录 */ - public static TaskRecord getTaskRecord(String filePath) { - List record = - DbEntity.findRelationData(RecordWrapper.class, "TaskRecord.filePath=?", filePath); - if (record == null || record.size() == 0) { - return null; + public static TaskRecord getTaskRecord(String filePath, int taskType) { + TaskRecord taskRecord = + DbEntity.findFirst(TaskRecord.class, "filePath=? AND taskType=?", filePath, + String.valueOf(taskType)); + if (taskRecord != null) { + taskRecord.threadRecords = + DbEntity.findDatas(ThreadRecord.class, "taskKey=? AND threadType=?", filePath, + String.valueOf(taskType)); } - return record.get(0).taskRecord; + + return taskRecord; } /** @@ -52,7 +58,7 @@ public class DbDataHelper { * @param groupHash 组合任务Hash * @return 实体不存在,返回null */ - public static DownloadGroupEntity getDGEntity(String groupHash) { + public static DownloadGroupEntity getDGEntityByHash(String groupHash) { List wrapper = DbEntity.findRelationData(DGEntityWrapper.class, "DownloadGroupEntity.groupHash=?", groupHash); @@ -60,6 +66,20 @@ public class DbDataHelper { return wrapper == null || wrapper.size() == 0 ? null : wrapper.get(0).groupEntity; } + /** + * 获取组合任务实体、ftpDir任务实体 + * + * @param dirPath 组合任务Hash + * @return 实体不存在,返回null + */ + public static DownloadGroupEntity getDGEntityByPath(String dirPath) { + List wrapper = + DbEntity.findRelationData(DGEntityWrapper.class, "DownloadGroupEntity.dirPath=?", + dirPath); + + return wrapper == null || wrapper.size() == 0 ? null : wrapper.get(0).groupEntity; + } + /** * 获取组合任务实体、ftpDir任务实体 * @@ -85,7 +105,11 @@ public class DbDataHelper { entity.setUrl(url); entity.setFilePath(groupHash + "_" + i); int lastIndex = url.lastIndexOf(File.separator); - entity.setFileName(url.substring(lastIndex + 1)); + //去除url末尾携带的的参数 + int endIndex = url.lastIndexOf("?"); + + if(endIndex<0||endIndex records = + DbEntity.findRelationData(RecordWrapper.class, "dGroupHash=?", groupEntity.getGroupHash()); + + // 删除子任务记录 + if (records == null || records.isEmpty()) { + ALog.w(TAG, "组任务记录已删除"); + } else { + for (RecordWrapper record : records) { + if (record == null || record.taskRecord == null) { + continue; + } + // 删除分块文件 + if (record.taskRecord.isBlock) { + removeBlockFile(record.taskRecord); + } + DbEntity.deleteData(ThreadRecord.class, "taskKey=?", record.taskRecord.filePath); + record.taskRecord.deleteData(); + } + } + + // 删除组合任务子任务的文件 + List subs = groupEntity.getSubEntities(); + if (subs != null) { + for (DownloadEntity sub : subs) { + if (needRemoveFile || !groupEntity.isComplete()) { + FileUtil.deleteFile(sub.getFilePath()); + } + } + } + + // 删除文件夹 + if (!TextUtils.isEmpty(groupEntity.getDirPath())) { + if (needRemoveFile || !groupEntity.isComplete()) { + FileUtil.deleteFile(groupEntity.getDirPath()); + } + } + + deleteEntity(needRemoveEntity, groupEntity.getGroupHash()); + } + + private void deleteEntity(boolean needRemoveEntity, String groupHash) { + if (needRemoveEntity) { + DbEntity.deleteData(DownloadEntity.class, "groupHash=?", groupHash); + DbEntity.deleteData(DownloadGroupEntity.class, "groupHash=?", groupHash); + } + } + + /** + * 删除多线程分块下载的分块文件 + */ + private void removeBlockFile(TaskRecord record) { + for (int i = 0, len = record.threadNum; i < len; i++) { + FileUtil.deleteFile(String.format(IRecordHandler.SUB_PATH, record.filePath, i)); + } + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteDRecord.java b/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteDRecord.java new file mode 100644 index 00000000..bc228a8c --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteDRecord.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import android.text.TextUtils; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.orm.DbEntity; +import java.io.File; + +/** + * 删除下载记录 + */ +public class DeleteDRecord implements IDeleteRecord { + private String TAG = CommonUtil.getClassName(this); + private static volatile DeleteDRecord INSTANCE = null; + + private DeleteDRecord() { + + } + + public static DeleteDRecord getInstance() { + if (INSTANCE == null) { + synchronized (DeleteDRecord.class) { + if (INSTANCE == null) { + INSTANCE = new DeleteDRecord(); + } + } + } + return INSTANCE; + } + + /** + * @param filePath 文件保存路径 + * @param removeTarget true,无论下载成功以否,都将删除下载下来的文件。false,只会删除下载任务没有完成的文件 + * @param needRemoveEntity 是否需要删除实体 + */ + @Override public void deleteRecord(String filePath, boolean removeTarget, + boolean needRemoveEntity) { + if (TextUtils.isEmpty(filePath)) { + throw new NullPointerException("删除记录失败,文件路径为空"); + } + if (!filePath.startsWith("/")) { + throw new IllegalArgumentException(String.format("文件路径错误,filePath:%s", filePath)); + } + DownloadEntity entity = DbEntity.findFirst(DownloadEntity.class, "downloadPath=?", filePath); + if (entity == null) { + ALog.e(TAG, "删除下载记录失败,没有在数据库中找到对应的实体文件,filePath:" + filePath); + return; + } + deleteRecord(entity, removeTarget, needRemoveEntity); + } + + /** + * @param absEntity 记录关联的实体 + * @param needRemoveFile true,无论下载成功以否,都将删除下载下来的文件。false,只会删除下载任务没有完成的文件 + * @param needRemoveEntity 是否需要删除实体 + */ + @Override + public void deleteRecord(AbsEntity absEntity, boolean needRemoveFile, boolean needRemoveEntity) { + if (absEntity == null) { + ALog.e(TAG, "删除下载记录失败,实体为空"); + return; + } + DownloadEntity entity = (DownloadEntity) absEntity; + final String filePath = entity.getFilePath(); + File targetFile = new File(filePath); + + // 兼容以前版本 + if (entity.getTaskType() == ITaskWrapper.M3U8_VOD + || entity.getTaskType() == ITaskWrapper.M3U8_LIVE) { + DeleteM3u8Record.getInstance().deleteRecord(entity, needRemoveFile, needRemoveEntity); + return; + } + + TaskRecord record = DbDataHelper.getTaskRecord(entity.getFilePath(), entity.getTaskType()); + if (record == null) { + ALog.e(TAG, "删除下载记录失败,记录为空,将删除实体记录,filePath:" + entity.getFilePath()); + FileUtil.deleteFile(targetFile); + deleteEntity(needRemoveEntity, filePath); + return; + } + + // 删除下载的线程记录和任务记录 + DbEntity.deleteData(ThreadRecord.class, "taskKey=? AND threadType=?", filePath, + String.valueOf(entity.getTaskType())); + DbEntity.deleteData(TaskRecord.class, "filePath=? AND taskType=?", filePath, + String.valueOf(entity.getTaskType())); + + if (needRemoveFile || !entity.isComplete()) { + FileUtil.deleteFile(targetFile); + if (record.isBlock) { + removeBlockFile(record); + } + } + + deleteEntity(needRemoveEntity, filePath); + } + + private void deleteEntity(boolean needRemoveEntity, String filePath){ + if (needRemoveEntity) { + DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath); + } + } + + /** + * 删除多线程分块下载的分块文件 + */ + private void removeBlockFile(TaskRecord record) { + for (int i = 0, len = record.threadNum; i < len; i++) { + FileUtil.deleteFile(String.format(IRecordHandler.SUB_PATH, record.filePath, i)); + } + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteM3u8Record.java b/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteM3u8Record.java new file mode 100644 index 00000000..919eeb70 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteM3u8Record.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import android.text.TextUtils; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.M3U8Entity; +import com.arialyy.aria.orm.DbEntity; +import java.io.File; + +/** + * 删除m3u8记录 + */ +public class DeleteM3u8Record implements IDeleteRecord { + private String TAG = CommonUtil.getClassName(this); + + private static volatile DeleteM3u8Record INSTANCE = null; + + private DeleteM3u8Record() { + + } + + public static DeleteM3u8Record getInstance() { + if (INSTANCE == null) { + synchronized (DeleteM3u8Record.class) { + if (INSTANCE == null) { + INSTANCE = new DeleteM3u8Record(); + } + } + } + return INSTANCE; + } + + /** + * @param filePath 文件保存路径 + * @param removeTarget true,无论下载成功以否,都将删除下载下来的文件。false,只会删除下载任务没有完成的文件 + * @param needRemoveEntity 是否需要删除实体 + */ + @Override public void deleteRecord(String filePath, boolean removeTarget, + boolean needRemoveEntity) { + if (TextUtils.isEmpty(filePath)) { + throw new NullPointerException("删除记录失败,文件路径为空"); + } + if (!filePath.startsWith("/")) { + throw new IllegalArgumentException(String.format("文件路径错误,filePath:%s", filePath)); + } + DownloadEntity entity = DbEntity.findFirst(DownloadEntity.class, "downloadPath=?", filePath); + if (entity == null) { + ALog.e(TAG, "删除下载记录失败,没有在数据库中找到对应的实体文件,filePath:" + filePath); + return; + } + deleteRecord(entity, removeTarget, needRemoveEntity); + } + + @Override + public void deleteRecord(AbsEntity absEntity, boolean needRemoveFile, boolean needRemoveEntity) { + if (absEntity == null) { + ALog.e(TAG, "删除下载记录失败,实体为空"); + return; + } + + DownloadEntity entity = (DownloadEntity) absEntity; + final String filePath = entity.getFilePath(); + TaskRecord record = DbDataHelper.getTaskRecord(filePath, entity.getTaskType()); + if (record == null) { + ALog.e(TAG, "删除下载记录失败,记录为空,将删除实体记录,filePath:" + entity.getFilePath()); + deleteEntity(entity.getTaskType(), needRemoveEntity, filePath); + return; + } + + if (needRemoveFile || !entity.isComplete()) { + removeTsCache(new File(filePath), record.bandWidth); + FileUtil.deleteFile(filePath); + } + + deleteEntity(entity.getTaskType(), needRemoveEntity, filePath); + } + + private void deleteEntity(int taskType, boolean needRemoveEntity, String filePath){ + // 删除下载的线程记录和任务记录 + DbEntity.deleteData(ThreadRecord.class, "taskKey=? AND threadType=?", filePath, + String.valueOf(taskType)); + DbEntity.deleteData(TaskRecord.class, "filePath=? AND taskType=?", filePath, + String.valueOf(taskType)); + DbEntity.deleteData(M3U8Entity.class, "filePath=?", filePath); + + if (needRemoveEntity) { + DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath); + } + } + + /** + * 删除ts文件,和索引文件(如果有的话) + */ + private static void removeTsCache(File targetFile, long bandWidth) { + + // 删除key + M3U8Entity entity = DbEntity.findFirst(M3U8Entity.class, "filePath=?", targetFile.getPath()); + if (entity != null && !TextUtils.isEmpty(entity.keyPath)){ + File keyFile = new File(entity.keyPath); + FileUtil.deleteFile(keyFile); + } + + // 删除ts + String cacheDir = null; + if (!targetFile.isDirectory()) { + cacheDir = + String.format("%s/.%s_%s", targetFile.getParent(), targetFile.getName(), bandWidth); + } + + if (!TextUtils.isEmpty(cacheDir)) { + File cacheDirF = new File(cacheDir); + if (!cacheDirF.exists()) { + return; + } + File[] files = cacheDirF.listFiles(); + for (File f : files) { + if (f.exists()) { + f.delete(); + } + } + File cDir = new File(cacheDir); + if (cDir.exists()) { + cDir.delete(); + } + } + + File indexFile = new File(String.format("%s.index", targetFile.getPath())); + + if (indexFile.exists()) { + indexFile.delete(); + } + + } +} diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteURecord.java b/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteURecord.java new file mode 100644 index 00000000..bb70c618 --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/DeleteURecord.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import android.text.TextUtils; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.orm.DbEntity; + +/** + * 删除上传记录 + */ +public class DeleteURecord implements IDeleteRecord { + private String TAG = CommonUtil.getClassName(this); + + private static volatile DeleteURecord INSTANCE = null; + + private DeleteURecord() { + + } + + public static DeleteURecord getInstance() { + if (INSTANCE == null) { + synchronized (DeleteURecord.class) { + if (INSTANCE == null) { + INSTANCE = new DeleteURecord(); + } + } + } + return INSTANCE; + } + + /** + * 删除上传记录 + * + * @param filePath 上传文件的地址 + * @param needRemoveFile 上传完成后是否需要删除本地文件。true,上传完成后删除本地文件 + * @param needRemoveEntity 是否需要删除实体,true 删除实体 + */ + @Override public void deleteRecord(String filePath, boolean needRemoveFile, + boolean needRemoveEntity) { + if (TextUtils.isEmpty(filePath)) { + throw new NullPointerException("删除记录失败,文件路径为空"); + } + if (!filePath.startsWith("/")) { + throw new IllegalArgumentException(String.format("文件路径错误,filePath:%s", filePath)); + } + + UploadEntity entity = DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath); + if (entity == null) { + ALog.e(TAG, "删除上传记录失败,没有在数据库中找到对应的实体文件,filePath:" + filePath); + return; + } + deleteRecord(entity, needRemoveFile, needRemoveEntity); + } + + @Override + public void deleteRecord(AbsEntity absEntity, boolean needRemoveFile, boolean needRemoveEntity) { + if (absEntity == null) { + ALog.e(TAG, "删除上传记录失败,实体为空"); + return; + } + + UploadEntity entity = (UploadEntity) absEntity; + + // 删除下载的线程记录和任务记录 + DbEntity.deleteData(ThreadRecord.class, "taskKey=? AND threadType=?", entity.getFilePath(), + String.valueOf(entity.getTaskType())); + DbEntity.deleteData(TaskRecord.class, "filePath=? AND taskType=?", entity.getFilePath(), + String.valueOf(entity.getTaskType())); + + if (needRemoveFile) { + FileUtil.deleteFile(entity.getFilePath()); + } + + deleteEntity(needRemoveEntity, entity.getFilePath()); + } + + private void deleteEntity(boolean needRemoveEntity, String filePath) { + if (needRemoveEntity) { + DbEntity.deleteData(UploadEntity.class, "filePath=?", filePath); + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/util/ErrorHelp.java b/PublicComponent/src/main/java/com/arialyy/aria/util/ErrorHelp.java similarity index 75% rename from Aria/src/main/java/com/arialyy/aria/util/ErrorHelp.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/ErrorHelp.java index 2fbee6a1..99dadd66 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/ErrorHelp.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/ErrorHelp.java @@ -16,10 +16,9 @@ package com.arialyy.aria.util; import android.annotation.SuppressLint; -import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.AriaConfig; import java.io.File; import java.io.FileWriter; -import java.io.IOException; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Date; @@ -36,8 +35,8 @@ public class ErrorHelp { * @param msg 错误提示 * @param ex 异常 */ - public static void saveError(String tag, String msg, String ex) { - writeLogToFile(tag, String.format("\nmsg【%s】\nException:%s", msg, ex)); + public static void saveError(String msg, String ex) { + writeLogToFile(String.format("\nmsg【%s】\nException:%s", msg, ex)); } /** @@ -47,39 +46,27 @@ public class ErrorHelp { */ private static String getLogPath() { String path = String.format("%slog/AriaCrash_%s.log", - CommonUtil.getAppPath(AriaManager.getInstance().getAPP()), + CommonUtil.getAppPath(AriaConfig.getInstance().getAPP()), getData("yyyy-MM-dd_HH_mm_ss")); - File log = new File(path); - if (!log.getParentFile().exists()) { - log.getParentFile().mkdirs(); - } - if (!log.exists()) { - try { - log.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - } - } + FileUtil.createFile(path); return path; } /** * 把日志记录到文件 */ - private static int writeLogToFile(String tag, String message) { + private static int writeLogToFile(String message) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append(getData("yyyy-MM-dd HH:mm:ss")); stringBuffer.append(" "); - stringBuffer.append(tag); - stringBuffer.append(" "); stringBuffer.append(message); stringBuffer.append("\n\n"); PrintWriter writer = null; try { File file = new File(getLogPath()); if (!file.exists()) { - FileUtil.createFile(file.getPath()); + FileUtil.createFile(file); } writer = new PrintWriter(new FileWriter(file.getPath(), true)); writer.append(stringBuffer); diff --git a/Aria/src/main/java/com/arialyy/aria/util/FileUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java similarity index 75% rename from Aria/src/main/java/com/arialyy/aria/util/FileUtil.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java index 937caba2..a68c5989 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/FileUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/FileUtil.java @@ -25,7 +25,8 @@ import android.os.StatFs; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; +import android.util.Log; +import com.arialyy.aria.core.AriaConfig; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -34,17 +35,16 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.SequenceInputStream; import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.ByteBuffer; -import java.nio.channels.Channels; import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.Collections; -import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; @@ -61,6 +61,54 @@ public class FileUtil { private static final String EXTERNAL_STORAGE_PATH = Environment.getExternalStorageDirectory().getPath(); + /** + * 获取文件后缀名 + * @return 获取不到文件名后缀,返回null + */ + public static String getFileExtensionName(String fileName) { + if (TextUtils.isEmpty(fileName)) { + return null; + } + int endP = fileName.lastIndexOf("."); + return endP > -1 ? fileName.substring(endP + 1) : null; + } + + /** + * 通过流创建文件 + * + * @param dest 输出路径 + */ + public static void createFileFormInputStream(InputStream is, String dest) { + try { + FileOutputStream fos = new FileOutputStream(dest); + byte[] buf = new byte[1024]; + int len; + while ((len = is.read(buf)) > 0) { + fos.write(buf, 0, len); + } + is.close(); + fos.flush(); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 获取m3u8 ts文件的缓存目录。 + * 缓存文件夹格式:父文件夹/.文件名_码率 + * + * @param path m3u8 文件下载路径 + * @return m3u8 ts文件的缓存目录 + */ + public static String getTsCacheDir(String path, int bandWidth) { + if (TextUtils.isEmpty(path)) { + throw new NullPointerException("m3u8文保存路径为空"); + } + File file = new File(path); + return String.format("%s/.%s_%s", file.getParent(), file.getName(), bandWidth); + } + /** * 创建目录 当目录不存在的时候创建文件,否则返回false */ @@ -86,22 +134,23 @@ public class FileUtil { ALog.e(TAG, "文件路径不能为null"); return false; } - File file = new File(path); + return createFile(new File(path)); + } + + /** + * 创建文件 当文件不存在的时候就创建一个文件。 如果文件存在,先删除原文件,然后重新创建一个新文件 + * + * @return {@code true} 创建成功、{@code false} 创建失败 + */ + public static boolean createFile(File file) { if (file.getParentFile() == null || !file.getParentFile().exists()) { ALog.d(TAG, "目标文件所在路径不存在,准备创建……"); if (!createDir(file.getParent())) { - ALog.d(TAG, "创建目录文件所在的目录失败!文件路径【" + path + "】"); - } - } - // 创建目标文件 - if (file.exists()) { - final File to = new File(file.getAbsolutePath() + System.currentTimeMillis()); - if (file.renameTo(to)) { - to.delete(); - } else { - file.delete(); + ALog.d(TAG, "创建目录文件所在的目录失败!文件路径【" + file.getPath() + "】"); } } + // 文件存在,删除文件 + deleteFile(file); try { if (file.createNewFile()) { //ALog.d(TAG, "创建文件成功:" + file.getAbsolutePath()); @@ -172,6 +221,27 @@ public class FileUtil { return false; } + /** + * 删除文件夹 + */ + public static boolean deleteDir(File dirFile) { + // 如果dir对应的文件不存在,则退出 + if (!dirFile.exists()) { + return false; + } + + if (dirFile.isFile()) { + return dirFile.delete(); + } else { + + for (File file : dirFile.listFiles()) { + deleteDir(file); + } + } + + return dirFile.delete(); + } + /** * 将对象写入文件 * @@ -253,16 +323,24 @@ public class FileUtil { * @return {@code true} 合并成功,{@code false}合并失败 */ public static boolean mergeFile(String targetPath, List subPaths) { + Log.d(TAG, "开始合并文件"); File file = new File(targetPath); FileOutputStream fos = null; FileChannel foc = null; + long startTime = System.currentTimeMillis(); try { + if (file.exists() && file.isDirectory()) { + ALog.w(TAG, String.format("路径【%s】是文件夹,将删除该文件夹", targetPath)); + FileUtil.deleteDir(file); + } if (!file.exists()) { - file.createNewFile(); + FileUtil.createFile(file); } + fos = new FileOutputStream(targetPath); foc = fos.getChannel(); List streams = new LinkedList<>(); + long fileLen = 0; for (String subPath : subPaths) { File f = new File(subPath); if (!f.exists()) { @@ -274,19 +352,82 @@ public class FileUtil { return false; } - streams.add(new FileInputStream(subPath)); + FileInputStream fis = new FileInputStream(subPath); + FileChannel fic = fis.getChannel(); + foc.transferFrom(fic, fileLen, f.length()); + fileLen += f.length(); + fis.close(); + } + ALog.d(TAG, String.format("合并文件耗时:%sms", (System.currentTimeMillis() - startTime))); + return true; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (foc != null) { + foc.close(); + } + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(); } - Enumeration en = Collections.enumeration(streams); - SequenceInputStream sis = new SequenceInputStream(en); - ReadableByteChannel fic = Channels.newChannel(sis); - ByteBuffer bf = ByteBuffer.allocate(8196); - while (fic.read(bf) != -1) { - bf.flip(); - foc.write(bf); - bf.compact(); + } + return false; + } + + /** + * 合并sftp的分块文件,sftp的分块可能会超出规定的长度,因此需要使用本方法 + * + * @param targetPath 目标文件 + * @param subPaths 碎片文件路径 + * @param targetFileSize 文件长度 + * @return {@code true} 合并成功,{@code false}合并失败 + */ + public static boolean mergeSFtpFile(String targetPath, List subPaths, + long targetFileSize) { + File file = new File(targetPath); + FileOutputStream fos = null; + FileChannel foc = null; + long startTime = System.currentTimeMillis(); + try { + if (file.exists() && file.isDirectory()) { + ALog.w(TAG, String.format("路径【%s】是文件夹,将删除该文件夹", targetPath)); + FileUtil.deleteDir(file); } - fic.close(); - sis.close(); + if (!file.exists()) { + FileUtil.createFile(file); + } + + fos = new FileOutputStream(targetPath); + foc = fos.getChannel(); + List streams = new LinkedList<>(); + int i = 0; + int threadNum = subPaths.size(); + long tempLen = targetFileSize / threadNum; + for (String subPath : subPaths) { + File f = new File(subPath); + if (!f.exists()) { + ALog.d(TAG, String.format("合并文件失败,文件【%s】不存在", subPath)); + for (FileInputStream fis : streams) { + fis.close(); + } + streams.clear(); + + return false; + } + + long blockLen = i == (threadNum - 1) ? targetFileSize - tempLen * i : tempLen; + FileInputStream fis = new FileInputStream(subPath); + FileChannel fic = fis.getChannel(); + foc.transferFrom(fic, blockLen * i, blockLen); + fis.close(); + i++; + } + + ALog.d(TAG, String.format("合并文件耗时:%sms,合并后的文件长度:%s", (System.currentTimeMillis() - startTime), + file.length())); return true; } catch (IOException e) { e.printStackTrace(); @@ -327,7 +468,7 @@ public class FileUtil { ALog.d(TAG, String.format("block = %s", block)); File subFile = new File(subPath); if (!subFile.exists()) { - subFile.createNewFile(); + createFile(subFile); } FileOutputStream fos = new FileOutputStream(subFile); FileChannel sfoc = fos.getChannel(); @@ -357,66 +498,25 @@ public class FileUtil { } /** - * 检查SD内存空间是否充足 + * 检查内存空间是否充足 * - * @param filePath 文件保存路径 - * @param fileSize 文件大小 - * @return {@code false} 内存空间不足,{@code true}内存空间足够 + * @param path 文件路径 + * @param fileSize 下载的文件的大小 + * @return true 空间足够 */ - public static boolean checkSDMemorySpace(String filePath, long fileSize) { - List dirs = FileUtil.getSDPathList(AriaManager.getInstance().getAPP()); - if (dirs == null || dirs.isEmpty()) { - return true; - } - for (String path : dirs) { - if (filePath.contains(path)) { - if (fileSize > 0 && fileSize > getAvailableExternalMemorySize(path)) { - return false; - } + public static boolean checkMemorySpace(String path, long fileSize) { + File temp = new File(path); + if (!temp.exists()) { + if (!temp.getParentFile().exists()) { + FileUtil.createDir(temp.getParentFile().getPath()); } + path = temp.getParentFile().getPath(); } - return true; - } - /** - * sdcard 可用大小 - * - * @param sdcardPath sdcard 根路径 - * @return 单位为:byte - */ - public static long getAvailableExternalMemorySize(String sdcardPath) { - StatFs stat = new StatFs(sdcardPath); + StatFs stat = new StatFs(path); long blockSize = stat.getBlockSize(); long availableBlocks = stat.getAvailableBlocks(); - return availableBlocks * blockSize; - } - - /** - * sdcard 总大小 - * - * @param sdcardPath sdcard 根路径 - * @return 单位为:byte - */ - public static long getTotalExternalMemorySize(String sdcardPath) { - StatFs stat = new StatFs(sdcardPath); - long blockSize = stat.getBlockSize(); - long totalBlocks = stat.getBlockCount(); - return totalBlocks * blockSize; - } - - /** - * 获取SD卡目录列表 - */ - public static List getSDPathList(Context context) { - List paths; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - paths = getVolumeList(context); - } else { - List mounts = readMountsFile(); - List volds = readVoldFile(); - paths = compareMountsWithVold(mounts, volds); - } - return paths; + return fileSize <= availableBlocks * blockSize; } /** @@ -467,19 +567,101 @@ public class FileUtil { } } + /** + * 检查SD内存空间是否充足 + * + * @param filePath 文件保存路径 + * @param fileSize 文件大小 + * @return {@code false} 内存空间不足,{@code true}内存空间足够 + */ + @Deprecated + private static boolean checkSDMemorySpace(String filePath, long fileSize) { + List dirs = FileUtil.getSDPathList(AriaConfig.getInstance().getAPP()); + if (dirs == null || dirs.isEmpty()) { + return true; + } + for (String path : dirs) { + if (filePath.contains(path)) { + if (fileSize > 0 && fileSize > getAvailableExternalMemorySize(path)) { + return false; + } + } + } + return true; + } + + /** + * sdcard 可用大小 + * + * @param sdcardPath sdcard 根路径 + * @return 单位为:byte + */ + private static long getAvailableExternalMemorySize(String sdcardPath) { + StatFs stat = new StatFs(sdcardPath); + long blockSize = stat.getBlockSize(); + long availableBlocks = stat.getAvailableBlocks(); + return availableBlocks * blockSize; + } + + /** + * sdcard 总大小 + * + * @param sdcardPath sdcard 根路径 + * @return 单位为:byte + */ + private static long getTotalExternalMemorySize(String sdcardPath) { + StatFs stat = new StatFs(sdcardPath); + long blockSize = stat.getBlockSize(); + long totalBlocks = stat.getBlockCount(); + return totalBlocks * blockSize; + } + + /** + * 获取SD卡目录列表 + */ + private static List getSDPathList(Context context) { + List paths = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + try { + paths = getVolumeList(context); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } else { + List mounts = readMountsFile(); + List volds = readVoldFile(); + paths = compareMountsWithVold(mounts, volds); + } + return paths; + } + /** * getSDPathList */ - private static List getVolumeList(final Context context) { + private static List getVolumeList(final Context context) + throws NoSuchMethodException, InvocationTargetException, + IllegalAccessException { List pathList = new ArrayList<>(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { StorageManager manager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); List volumes = manager.getStorageVolumes(); + + Class volumeClass = StorageVolume.class; + Method getPath = volumeClass.getDeclaredMethod("getPath"); + //Method isRemovable = volumeClass.getDeclaredMethod("isRemovable"); + getPath.setAccessible(true); + //isRemovable.setAccessible(true); + for (StorageVolume volume : volumes) { String state = volume.getState(); + if (state.equals(Environment.MEDIA_MOUNTED)) { - pathList.add(volume.toString()); + pathList.add((String) getPath.invoke(volume)); } } } else { @@ -511,7 +693,7 @@ public class FileUtil { return list; } - private static boolean canWrite(String dirPath) { + public static boolean canWrite(String dirPath) { File dir = new File(dirPath); if (dir.canWrite()) { return true; @@ -546,12 +728,12 @@ public class FileUtil { /** * Raturns all available SD-Cards in the system (include emulated) Warning: Hack! Based on Android * source code of version 4.3 - * (API 18) Because there is no standard way to get it. TODO: Test on future Android versions + * (API 18) Because there is no standard way to get it. * 4.2+ * * @return paths to all available SD-Cards in the system (include emulated) */ - public static List getStorageDirectories() { + private static List getStorageDirectories() { // Final set of paths final List rv = new ArrayList<>(); // Primary physical SD-CARD (not emulated) diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/IDeleteRecord.java b/PublicComponent/src/main/java/com/arialyy/aria/util/IDeleteRecord.java new file mode 100644 index 00000000..2479ab3e --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/IDeleteRecord.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import com.arialyy.aria.core.common.AbsEntity; + +/** + * 删除记录 + */ +public interface IDeleteRecord { + + /** + * 删除记录 + * + * @param key 记录关联的key + * @param needRemoveFile 是否需要删除文件 + * @param needRemoveEntity 是否需要删除实体,true 删除实体 + */ + void deleteRecord(String key, boolean needRemoveFile, boolean needRemoveEntity); + + /** + * 删除记录 + * + * @param absEntity 记录关联的实体 + * @param needRemoveFile 是否需要删除文件 + * @param needRemoveEntity 是否需要删除实体,true 删除实体 + */ + void deleteRecord(AbsEntity absEntity, boolean needRemoveFile, boolean needRemoveEntity); +} diff --git a/Aria/src/main/java/com/arialyy/aria/util/NetUtils.java b/PublicComponent/src/main/java/com/arialyy/aria/util/NetUtils.java similarity index 96% rename from Aria/src/main/java/com/arialyy/aria/util/NetUtils.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/NetUtils.java index 2200f03a..b94d2865 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/NetUtils.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/NetUtils.java @@ -23,8 +23,7 @@ import android.net.NetworkInfo; import android.os.Build; import android.telephony.TelephonyManager; import android.text.TextUtils; -import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.AriaConfig; /** * 跟网络相关的工具类 @@ -58,11 +57,11 @@ public class NetUtils { * @return {@code true} 网络已连接、{@code false}网络未连接 */ public static boolean isConnected(Context context) { - if (!Aria.get(context).getAppConfig().isNetCheck()) { + if (!AriaConfig.getInstance().getAConfig().isNetCheck()) { return true; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return AriaManager.getInstance().isConnectedNet(); + return AriaConfig.getInstance().isNetworkAvailable(); } ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/PublicComponent/src/main/java/com/arialyy/aria/util/RecordUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/RecordUtil.java new file mode 100644 index 00000000..1c71703d --- /dev/null +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/RecordUtil.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.util; + +import android.text.TextUtils; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.download.DownloadGroupEntity; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.orm.DbEntity; +import java.io.File; + +/** + * 任务记录处理工具 + */ +public class RecordUtil { + private static final String TAG = "RecordUtil"; + + /** + * 删除任务组记录 + * + * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; + * {@code false} 如果任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 + */ + public static void delGroupTaskRecordByHash(String groupHash, boolean removeFile) { + if (TextUtils.isEmpty(groupHash)) { + ALog.e(TAG, "删除下载任务组记录失败,groupHash为null"); + return; + } + DownloadGroupEntity groupEntity = DbDataHelper.getDGEntityByHash(groupHash); + DeleteDGRecord.getInstance().deleteRecord(groupEntity, removeFile, true); + } + + /** + * 删除任务记录,默认删除文件 + * + * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; + * {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 + * 如果是上传任务,无论任务是否完成,都只删除记录 + */ + public static void delNormalTaskRecord(AbsNormalEntity entity, boolean removeFile) { + switch (entity.getTaskType()) { + case ITaskWrapper.D_FTP: + case ITaskWrapper.D_HTTP: + case ITaskWrapper.D_SFTP: + case ITaskWrapper.D_TCP: + DeleteDRecord.getInstance().deleteRecord(entity, removeFile, true); + break; + case ITaskWrapper.U_FTP: + case ITaskWrapper.U_HTTP: + case ITaskWrapper.U_SFTP: + DeleteURecord.getInstance().deleteRecord(entity, removeFile, true); + break; + case ITaskWrapper.M3U8_LIVE: + case ITaskWrapper.M3U8_VOD: + DeleteM3u8Record.getInstance().deleteRecord(entity, removeFile, true); + break; + } + } + + /** + * 删除任务记录,默认删除文件,删除任务实体 + * + * @param filePath 文件路径 + * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; + * @param type {@link AbsTaskWrapper#getRequestType()} + * {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 + * 如果是上传任务,无论任务是否完成,都只删除记录 + * 上传任务的记录 + */ + public static void delTaskRecord(String filePath, int type, boolean removeFile, + boolean removeEntity) { + switch (type) { + case ITaskWrapper.D_FTP: + case ITaskWrapper.D_HTTP: + case ITaskWrapper.D_SFTP: + case ITaskWrapper.D_TCP: + DeleteDRecord.getInstance().deleteRecord(filePath, removeFile, removeEntity); + break; + case ITaskWrapper.U_FTP: + case ITaskWrapper.U_HTTP: + case ITaskWrapper.U_SFTP: + DeleteURecord.getInstance().deleteRecord(filePath, removeFile, removeEntity); + break; + case ITaskWrapper.M3U8_LIVE: + case ITaskWrapper.M3U8_VOD: + DeleteM3u8Record.getInstance().deleteRecord(filePath, removeFile, removeEntity); + break; + } + } + + /** + * 修改任务路径,修改文件路径和任务记录信息。如果是分块任务,则修改分块文件的路径。 + * + * @param oldPath 旧的文件路径 + * @param newPath 新的文件路径 + * @param taskType 任务类型{@link ITaskWrapper} + */ + public static void modifyTaskRecord(String oldPath, String newPath, int taskType) { + if (oldPath.equals(newPath)) { + ALog.w(TAG, "修改任务记录失败,新文件路径和旧文件路径一致"); + return; + } + TaskRecord record = DbDataHelper.getTaskRecord(oldPath, taskType); + if (record == null) { + if (new File(oldPath).exists()) { + ALog.w(TAG, "修改任务记录失败,文件【" + oldPath + "】对应的任务记录不存在"); + } + return; + } + if (!record.isBlock) { + File oldFile = new File(oldPath); + if (oldFile.exists()) { + oldFile.renameTo(new File(newPath)); + } + } + + record.filePath = newPath; + record.update(); + // 修改线程记录 + if (record.threadRecords != null && !record.threadRecords.isEmpty()) { + for (ThreadRecord tr : record.threadRecords) { + tr.taskKey = newPath; + File blockFile = new File(String.format(IRecordHandler.SUB_PATH, oldPath, tr.threadId)); + if (blockFile.exists()) { + blockFile.renameTo( + new File(String.format(IRecordHandler.SUB_PATH, newPath, tr.threadId))); + } + } + DbEntity.updateManyData(record.threadRecords); + } + } + + /** + * 检查分块任务是否存在 + * + * @param filePath 文件保存路径 + * @return {@code true} 分块文件存在 + */ + public static boolean blockTaskExists(String filePath) { + return new File(String.format(IRecordHandler.SUB_PATH, filePath, 0)).exists(); + } + + /** + * 获取分块文件的快大小 + * + * @param fileLen 文件总长度 + * @param blockId 分块id + * @param blockNum 分块数量 + * @return 分块长度 + */ + public static long getBlockLen(long fileLen, int blockId, int blockNum) { + final long averageLen = fileLen / blockNum; + return blockId == blockNum - 1 ? (fileLen - blockId * averageLen) : averageLen; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/util/Regular.java b/PublicComponent/src/main/java/com/arialyy/aria/util/Regular.java similarity index 100% rename from Aria/src/main/java/com/arialyy/aria/util/Regular.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/Regular.java diff --git a/Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java b/PublicComponent/src/main/java/com/arialyy/aria/util/SSLContextUtil.java similarity index 93% rename from Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/SSLContextUtil.java index a44f36ea..0271104f 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/SSLContextUtil.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/SSLContextUtil.java @@ -16,8 +16,8 @@ package com.arialyy.aria.util; import android.text.TextUtils; -import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.common.ProtocolType; +import com.arialyy.aria.core.AriaConfig; +import com.arialyy.aria.core.ProtocolType; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -56,9 +56,8 @@ public class SSLContextUtil { * @param caPath 保存在assets目录下的CA证书完整路径 * @param protocol 连接协议 */ - public static SSLContext getSSLContextFromAssets(String caAlias, String caPath, - @ProtocolType String protocol) { - if (TextUtils.isEmpty(caAlias) || TextUtils.isEmpty(caPath)){ + public static SSLContext getSSLContextFromAssets(String caAlias, String caPath, String protocol) { + if (TextUtils.isEmpty(caAlias) || TextUtils.isEmpty(caPath)) { return null; } try { @@ -67,7 +66,7 @@ public class SSLContextUtil { if (sslContext != null) { return sslContext; } - InputStream caInput = AriaManager.getInstance().getAPP().getAssets().open(caPath); + InputStream caInput = AriaConfig.getInstance().getAPP().getAssets().open(caPath); Certificate ca = loadCert(caInput); return createContext(caAlias, ca, protocol, cacheKey); } catch (IOException | CertificateException e) { @@ -83,8 +82,7 @@ public class SSLContextUtil { * @param caPath CA证书路径 * @param protocol 连接协议 */ - public static SSLContext getSSLContext(String caAlias, String caPath, - @ProtocolType String protocol) { + public static SSLContext getSSLContext(String caAlias, String caPath, String protocol) { if (TextUtils.isEmpty(caAlias) || TextUtils.isEmpty(caPath)) { return null; } @@ -105,8 +103,8 @@ public class SSLContextUtil { /** * @param cacheKey 别名 + 证书路径,然后取md5 */ - private static SSLContext createContext(String caAlias, Certificate ca, - @ProtocolType String protocol, String cacheKey) { + private static SSLContext createContext(String caAlias, Certificate ca, String protocol, + String cacheKey) { try { String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); @@ -154,7 +152,7 @@ public class SSLContextUtil { /** * 服务器证书不是由 CA 签署的,而是自签署时,获取默认的SSL */ - public static SSLContext getDefaultSLLContext(@ProtocolType String protocol) { + public static SSLContext getDefaultSLLContext(String protocol) { SSLContext sslContext = null; try { sslContext = diff --git a/Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java b/PublicComponent/src/main/java/com/arialyy/aria/util/WeakHandler.java similarity index 94% rename from Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java rename to PublicComponent/src/main/java/com/arialyy/aria/util/WeakHandler.java index 7f39f5d7..cc14d039 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/WeakHandler.java +++ b/PublicComponent/src/main/java/com/arialyy/aria/util/WeakHandler.java @@ -27,9 +27,6 @@ package com.arialyy.aria.util; import android.os.Handler; import android.os.Looper; import android.os.Message; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import java.lang.ref.WeakReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -51,7 +48,7 @@ import java.util.concurrent.locks.ReentrantLock; // hard reference to Callback. We need to keep callback in memory private final ExecHandler mExec; private Lock mLock = new ReentrantLock(); - @SuppressWarnings("ConstantConditions") @VisibleForTesting final ChainedRef mRunnables = + @SuppressWarnings("ConstantConditions") final ChainedRef mRunnables = new ChainedRef(mLock, null); /** @@ -76,7 +73,7 @@ import java.util.concurrent.locks.ReentrantLock; * * @param callback The callback interface in which to handle messages, or null. */ - public WeakHandler(@Nullable Handler.Callback callback) { + public WeakHandler(Handler.Callback callback) { mCallback = callback; // Hard referencing body mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler } @@ -86,7 +83,7 @@ import java.util.concurrent.locks.ReentrantLock; * * @param looper The looper, must not be null. */ - public WeakHandler(@NonNull Looper looper) { + public WeakHandler(Looper looper) { mCallback = null; mExec = new ExecHandler(looper); } @@ -98,7 +95,7 @@ import java.util.concurrent.locks.ReentrantLock; * @param looper The looper, must not be null. * @param callback The callback interface in which to handle messages, or null. */ - public WeakHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) { + public WeakHandler(Looper looper, Handler.Callback callback) { mCallback = callback; mExec = new ExecHandler(looper, new WeakReference<>(callback)); } @@ -113,7 +110,7 @@ import java.util.concurrent.locks.ReentrantLock; * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ - public final boolean post(@NonNull Runnable r) { + public final boolean post(Runnable r) { return mExec.post(wrapRunnable(r)); } @@ -133,7 +130,7 @@ import java.util.concurrent.locks.ReentrantLock; * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ - public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) { + public final boolean postAtTime(Runnable r, long uptimeMillis) { return mExec.postAtTime(wrapRunnable(r), uptimeMillis); } @@ -367,7 +364,7 @@ import java.util.concurrent.locks.ReentrantLock; return mExec.getLooper(); } - private WeakRunnable wrapRunnable(@NonNull Runnable r) { + private WeakRunnable wrapRunnable(Runnable r) { //noinspection ConstantConditions if (r == null) { throw new NullPointerException("Runnable can't be null"); @@ -398,7 +395,7 @@ import java.util.concurrent.locks.ReentrantLock; mCallback = callback; } - @Override public void handleMessage(@NonNull Message msg) { + @Override public void handleMessage(Message msg) { if (mCallback == null) { return; } @@ -432,14 +429,14 @@ import java.util.concurrent.locks.ReentrantLock; } static class ChainedRef { - @Nullable ChainedRef next; - @Nullable ChainedRef prev; - @NonNull final Runnable runnable; - @NonNull final WeakRunnable wrapper; + ChainedRef next; + ChainedRef prev; + final Runnable runnable; + final WeakRunnable wrapper; - @NonNull Lock lock; + Lock lock; - public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) { + public ChainedRef(Lock lock, Runnable r) { this.runnable = r; this.lock = lock; this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this)); @@ -462,7 +459,7 @@ import java.util.concurrent.locks.ReentrantLock; return wrapper; } - public void insertAfter(@NonNull ChainedRef candidate) { + public void insertAfter(ChainedRef candidate) { lock.lock(); try { if (this.next != null) { @@ -477,7 +474,7 @@ import java.util.concurrent.locks.ReentrantLock; } } - @Nullable public WeakRunnable remove(Runnable obj) { + public WeakRunnable remove(Runnable obj) { lock.lock(); try { ChainedRef curr = this.next; // Skipping head diff --git a/PublicComponent/src/main/resources/META-INF/MANIFEST.MF b/PublicComponent/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/PublicComponent/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/PublicComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.listener.IEventListener b/PublicComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.listener.IEventListener new file mode 100644 index 00000000..6120f8d5 --- /dev/null +++ b/PublicComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.listener.IEventListener @@ -0,0 +1,3 @@ +com.arialyy.aria.core.listener.BaseDListener +com.arialyy.aria.core.listener.BaseUListener +com.arialyy.aria.core.listener.DownloadGroupListener \ No newline at end of file diff --git a/README.md b/README.md index 2143013f..7153d79f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Aria -![图标](https://github.com/AriaLyy/DownloadUtil/blob/v_3.0/app/src/main/res/mipmap-hdpi/ic_launcher.png)
    +![图标](https://github.com/AriaLyy/DownloadUtil/blob/master/img/ic_launcher.png)
    ## [ENGLISH DOC](https://github.com/AriaLyy/Aria/blob/master/ENGLISH_README.md)
    ## [中文文档](https://aria.laoyuyu.me/aria_doc) Aria项目源于工作中遇到的一个文件下载管理的需求,当时被下载折磨的痛不欲生,从那时起便萌生了编写一个简单易用,稳当高效的下载框架,aria经历了1.0到3.0的开发,算是越来越接近当初所制定的目标了。 @@ -12,18 +12,19 @@ Aria有以下特点: - 支持下载FTP文件夹 - 支持HTTP表单上传 - 支持文件FTP断点续传上传 - - 支持FTPS断点续传,[see](https://aria.laoyuyu.me/aria_doc/download/ftps.html) + - 支持FTPS断点续传,[see](https://aria.laoyuyu.me/aria_doc/api/ftp_params.html#%E4%BA%8C%E3%80%81ftps) + - 支持SFTP断点续传,[sftp下载](https://aria.laoyuyu.me/aria_doc/download/sftp_normal.html),[sftp上传](https://aria.laoyuyu.me/aria_doc/upload/sftp_normal.html) + 支持https地址下载 - 在配置文件中很容易就可以设置CA证书的信息 + 支持[多线程分块下载](https://aria.laoyuyu.me/aria_doc/start/config.html),能更有效的发挥机器IO性能 + 支持300、301、302重定向下载链接下载 - + 支持m3u8协议的文件下载[m3u8下载](https://aria.laoyuyu.me/aria_doc/download/m3u8.html) + + 支持m3u8、hls协议的文件下载[m3u8下载](https://aria.laoyuyu.me/aria_doc/download/m3u8.html) + 支持m3u8边下边看的下载支持,[点击查看详情](https://aria.laoyuyu.me/aria_doc/download/m3u8_vod.html) + 下载支持文件长度动态增加,文件下载初始化时将不再占用过多的内存空间,见[动态长度配置](https://aria.laoyuyu.me/aria_doc/start/config.html#%E4%B8%8B%E8%BD%BD%E5%8A%A8%E6%80%81%E6%96%87%E4%BB%B6%E8%AF%B4%E6%98%8E) [怎样使用Aria?](#使用) -如果你觉得Aria对你有帮助,您的star和issues将是对我最大支持.`^_^` +如果你觉得Aria对你有帮助,你的star和issues将是对我最大支持,当然,也非常欢迎你能PR,[PR方法](https://www.zhihu.com/question/21682976/answer/79489643)`^_^` ## 示例 * 多任务下载 @@ -43,23 +44,36 @@ Aria有以下特点: ![m3u8点播文件边下边看](https://github.com/AriaLyy/Aria/blob/master/img/m3u8VodDownload.gif) ## 引入库 -[![Core](https://api.bintray.com/packages/arialyy/maven/AriaApi/images/download.svg)](https://bintray.com/arialyy/maven/AriaApi/_latestVersion) -[![Compiler](https://api.bintray.com/packages/arialyy/maven/AriaCompiler/images/download.svg)](https://bintray.com/arialyy/maven/AriaCompiler/_latestVersion) +[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/AriaLyy/Aria/blob/master/LICENSE) +[![Core](https://img.shields.io/badge/Core-3.8.16-blue)](https://github.com/AriaLyy/Aria) +[![Compiler](https://img.shields.io/badge/Compiler-3.8.16-blue)](https://github.com/AriaLyy/Aria) +[![FtpComponent](https://img.shields.io/badge/FtpComponent-3.8.16-orange)](https://github.com/AriaLyy/Aria) +[![FtpComponent](https://img.shields.io/badge/SFtpComponent-3.8.16-orange)](https://github.com/AriaLyy/Aria) +[![M3U8Component](https://img.shields.io/badge/M3U8Component-3.8.16-orange)](https://github.com/AriaLyy/Aria) + ```java -compile 'com.arialyy.aria:aria-core:3.6.6' -annotationProcessor 'com.arialyy.aria:aria-compiler:3.6.6' -``` -如果出现android support依赖错误,请将 `compile 'com.arialyy.aria:aria-core:'`替换为 -``` -api('com.arialyy.aria:aria-core:'){ - exclude group: 'com.android.support' +repositories { + google() + mavenCentral() } + +dependencies { + implementation 'me.laoyuyu.aria:core:3.8.16' + annotationProcessor 'me.laoyuyu.aria:compiler:3.8.16' + implementation 'me.laoyuyu.aria:ftp:3.8.16' # 如果需要使用ftp,请增加该组件 + implementation 'me.laoyuyu.aria:sftp:3.8.16' # 如果需要使用ftp,请增加该组件 + implementation 'me.laoyuyu.aria:m3u8:3.8.16' # 如果需要使用m3u8下载功能,请增加该组件 +} + ``` + 如果你使用的是kotlin,请使用kotlin官方提供的方法配置apt,[kotlin kapt官方配置传送门](https://www.kotlincn.net/docs/reference/kapt.html) __⚠️注意:3.5.4以下版本升级时,需要更新[配置文件](https://aria.laoyuyu.me/aria_doc/start/config.html)!!__ +__⚠️注意:3.8 以上版本已经适配了AndroidX和support库都可以使用 + *** ## 使用 由于Aria涉及到文件和网络的操作,因此需要你在manifest文件中添加以下权限,如果你希望在6.0以上的系统中使用Aria,那么你需要动态向安卓系统申请文件系统读写权限,[如何使用安卓系统权限](https://developer.android.com/training/permissions/index.html?hl=zh-cn) @@ -126,17 +140,19 @@ protected void onCreate(Bundle savedInstanceState) { } ``` +### [文档地址](https://aria.laoyuyu.me/aria_doc/) + +### QQ群:524329160 + ### 版本日志 - + v_3.6.6 - - fix bug https://github.com/AriaLyy/Aria/issues/426 - - fix bug https://github.com/AriaLyy/Aria/issues/429 - - fix bug https://github.com/AriaLyy/Aria/issues/428 - - fix bug https://github.com/AriaLyy/Aria/issues/427 - - fix bug https://github.com/AriaLyy/Aria/issues/431 - - fix bug https://github.com/AriaLyy/Aria/issues/441 - - 修复普通下载任务、组合任务共享执行队列、缓存池的问题 - - 修复组合任务启动失败时,`DownloadGroupEntity`的状态变为执行中的问题 + + v_3.8.16 + - 修复单线程下载时,文件已经完成,重复下载回调下载失败的问题 + - 修复一个重新下载文件时,同名路径文件没有被被删除的问题 + - fix bug https://github.com/AriaLyy/Aria/issues/807 + - fix bug https://github.com/AriaLyy/Aria/issues/811 + - fix bug https://github.com/AriaLyy/Aria/issues/851 + - 修复组合任务在获取子任务信息的过程中,手动停止或删除,没有回调的问题 [更多版本记录](https://github.com/AriaLyy/Aria/blob/master/DEV_LOG.md) @@ -147,6 +163,7 @@ protected void onCreate(Bundle savedInstanceState) { -keep class **$$DownloadListenerProxy{ *; } -keep class **$$UploadListenerProxy{ *; } -keep class **$$DownloadGroupListenerProxy{ *; } +-keep class **$$DGSubListenerProxy{ *; } -keepclasseswithmembernames class * { @Download.* ; @Upload.* ; @@ -155,9 +172,21 @@ protected void onCreate(Bundle savedInstanceState) { ``` +## 感谢 +感谢小伙伴们提供的PR ``^_^` +[DaveBoy](https://github.com/DaveBoy) +[liuxiaowei](https://github.com/liuxiaowei) +[chenfei0928](https://github.com/chenfei0928) + ## 其他 有任何问题,可以在[issues](https://github.com/AriaLyy/Aria/issues)给我留言反馈。
    在提交问题前,希望你已经查看过[wiki](https://aria.laoyuyu.me/aria_doc/)或搜索过[issues](https://github.com/AriaLyy/Aria/issues)。
    + [我的博客](https://www.laoyuyu.me) + +## 捐赠 + Aria是作业利用业余时间开发的一个项目,如果你喜欢我写的软件,可以考虑给我捐赠以支持我的工作
    + + *** @@ -177,13 +206,3 @@ License 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. - - - - - - - - - - diff --git a/SFtpComponent/.gitignore b/SFtpComponent/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/SFtpComponent/.gitignore @@ -0,0 +1 @@ +/build diff --git a/AriaFtpPlug/bintray-release.gradle b/SFtpComponent/bintray-release.gradle similarity index 81% rename from AriaFtpPlug/bintray-release.gradle rename to SFtpComponent/bintray-release.gradle index 43708895..85f62f0f 100644 --- a/AriaFtpPlug/bintray-release.gradle +++ b/SFtpComponent/bintray-release.gradle @@ -1,9 +1,9 @@ apply plugin: 'com.novoda.bintray-release' publish { - artifactId = 'aria-ftp-plug' + artifactId = 'sftpComponent' + uploadName = 'SFTPComponent' userOrg = rootProject.ext.userOrg groupId = rootProject.ext.groupId - uploadName = 'FtpPlug' publishVersion = rootProject.ext.publishVersion desc = rootProject.ext.desc website = rootProject.ext.website diff --git a/SFtpComponent/build.gradle b/SFtpComponent/build.gradle new file mode 100644 index 00000000..66af9f6e --- /dev/null +++ b/SFtpComponent/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + debug{ + debuggable true + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + api "com.jcraft:jsch:0.1.55" + api "com.jcraft:jzlib:1.1.3" + implementation project(path: ':PublicComponent') +} + +//apply from: 'bintray-release.gradle' +ext{ + PUBLISH_ARTIFACT_ID = 'sftp' +} +apply from: '../gradle/mavenCentral-release.gradle' \ No newline at end of file diff --git a/SFtpComponent/consumer-rules.pro b/SFtpComponent/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/SFtpComponent/proguard-rules.pro b/SFtpComponent/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/SFtpComponent/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/SFtpComponent/src/main/AndroidManifest.xml b/SFtpComponent/src/main/AndroidManifest.xml new file mode 100644 index 00000000..02ef15d0 --- /dev/null +++ b/SFtpComponent/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java new file mode 100644 index 00000000..e4f6d1c6 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/AbsSFtpInfoTask.java @@ -0,0 +1,108 @@ +/* + * 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.sftp; + +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.ILoaderVisitor; +import com.arialyy.aria.core.wrapper.AbsTaskWrapper; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.exception.AriaSFTPException; +import com.arialyy.aria.util.CommonUtil; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; +import java.io.UnsupportedEncodingException; + +/** + * 进行登录,获取session,获取文件信息 + */ +public abstract class AbsSFtpInfoTask implements IInfoTask { + protected String TAG = CommonUtil.getClassName(this); + private Callback callback; + private WP wrapper; + private SFtpTaskOption option; + private boolean isStop = false, isCancel = false; + + public AbsSFtpInfoTask(WP wp) { + this.wrapper = wp; + this.option = (SFtpTaskOption) wrapper.getTaskOption(); + } + + protected abstract void getFileInfo(Session session) + throws JSchException, UnsupportedEncodingException, SftpException; + + @Override public void stop() { + isStop = true; + } + + @Override public void cancel() { + isCancel = true; + } + + protected void handleFail(AriaException e, boolean needRetry) { + if (isStop || isCancel){ + return; + } + callback.onFail(getWrapper().getEntity(), e, needRetry); + } + + protected void onSucceed(CompleteInfo info){ + if (isStop || isCancel){ + return; + } + callback.onSucceed(getWrapper().getKey(), info); + } + + @Override public void run() { + try { + FtpUrlEntity entity = option.getUrlEntity(); + String key = CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + 0); + Session session = SFtpSessionManager.getInstance().getSession(key); + if (session == null) { + session = SFtpUtil.getInstance().getSession(entity, 0); + } + getFileInfo(session); + } catch (JSchException e) { + fail(new AriaSFTPException("jsch错误", e), false); + } catch (UnsupportedEncodingException e) { + fail(new AriaSFTPException("字符编码错误", e), false); + } catch (SftpException e) { + fail(new AriaSFTPException("sftp错误,错误类型:" + e.id, e), false); + } + } + + protected SFtpTaskOption getOption() { + return option; + } + + protected WP getWrapper() { + return wrapper; + } + + protected void fail(AriaException e, boolean needRetry) { + callback.onFail(getWrapper().getEntity(), e, needRetry); + } + + @Override public void setCallback(Callback callback) { + this.callback = callback; + } + + @Override public void accept(ILoaderVisitor visitor) { + visitor.addComponent(this); + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java new file mode 100644 index 00000000..9f43e1a8 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpSessionManager.java @@ -0,0 +1,99 @@ +/* + * 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.sftp; + +import android.text.TextUtils; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.jcraft.jsch.Session; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * SFTP session 管理器 + * 1、管理session + * 2、定时清除无效的session + */ +public class SFtpSessionManager { + private String TAG = CommonUtil.getClassName(this); + private static volatile SFtpSessionManager INSTANCE = null; + private Map sessionDeque = new HashMap<>(); + + public synchronized static SFtpSessionManager getInstance() { + if (INSTANCE == null) { + synchronized (SFtpSessionManager.class) { + INSTANCE = new SFtpSessionManager(); + } + } + return INSTANCE; + } + + private SFtpSessionManager() { + + } + + /** + * 获取session,获取完成session后,检查map中的所有session,移除所有失效的session + * + * @param key md5(host + port + userName + threadId) + * @return 如果session不可用,返回null + */ + public Session getSession(String key) { + if (TextUtils.isEmpty(key)) { + ALog.e(TAG, "从缓存获取session失败,key为空"); + return null; + } + Session session = sessionDeque.get(key); + if (session == null) { + ALog.w(TAG, "从缓存获取session失败,key:" + key); + } + cleanIdleSession(); + return session; + } + + /** + * 添加session + */ + public void addSession(Session session, int threadId) { + if (session == null) { + ALog.e(TAG, "添加session到管理器失败,session 为空"); + return; + } + String key = + CommonUtil.getStrMd5( + session.getHost() + session.getPort() + session.getUserName() + threadId); + sessionDeque.put(key, session); + } + + /** + * 移除所有闲置的session,闲置条件: + * 1、session 为空 + * 2、session未连接 + */ + private void cleanIdleSession() { + if (sessionDeque.size() > 0) { + Iterator> i = sessionDeque.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = i.next(); + Session session = entry.getValue(); + if (session == null || !session.isConnected()) { + i.remove(); + } + } + } + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpTaskOption.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpTaskOption.java new file mode 100644 index 00000000..ce6501cc --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpTaskOption.java @@ -0,0 +1,40 @@ +/* + * 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.sftp; + +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.inf.ITaskOption; + +public class SFtpTaskOption implements ITaskOption { + + /** + * 账号和密码 + */ + private FtpUrlEntity urlEntity; + + /** + * 字符编码,默认为"utf-8" + */ + private String charSet = "utf-8"; + + public FtpUrlEntity getUrlEntity() { + return urlEntity; + } + + public String getCharSet() { + return charSet; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java new file mode 100644 index 00000000..17ba6445 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/SFtpUtil.java @@ -0,0 +1,174 @@ +/* + * 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.sftp; + +import android.text.TextUtils; +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.IdEntity; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.UserInfo; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Properties; + +/** + * sftp工具类 + * + * @author lyy + */ +public class SFtpUtil { + private final String TAG = CommonUtil.getClassName(getClass()); + + private static SFtpUtil INSTANCE; + + private SFtpUtil() { + + } + + public synchronized static SFtpUtil getInstance() { + if (INSTANCE == null) { + synchronized (SFtpUtil.class) { + INSTANCE = new SFtpUtil(); + } + } + return INSTANCE; + } + + /** + * 创建jsch 的session + * + * @param threadId 线程id,默认0 + * @throws JSchException + * @throws UnsupportedEncodingException + */ + public Session getSession(FtpUrlEntity entity, int threadId) throws JSchException, + UnsupportedEncodingException { + + JSch jSch = new JSch(); + + IdEntity idEntity = entity.idEntity; + + if (idEntity.prvKey != null) { + if (idEntity.pubKey == null) { + jSch.addIdentity(idEntity.prvKey, + entity.password == null ? null : idEntity.prvPass.getBytes("UTF-8")); + } else { + jSch.addIdentity(idEntity.prvKey, idEntity.pubKey, + entity.password == null ? null : idEntity.prvPass.getBytes("UTF-8")); + } + } + + setKnowHost(jSch, entity); + + Session session; + if (TextUtils.isEmpty(entity.user)) { + session = jSch.getSession(null, entity.hostName, Integer.parseInt(entity.port)); + } else { + session = jSch.getSession(entity.user, entity.hostName, Integer.parseInt(entity.port)); + } + if (!TextUtils.isEmpty(entity.password)) { + session.setPassword(entity.password); + } + Properties config = new Properties(); + + // 不检查公钥,需要在connect之前配置,但是不安全,no 模式会自动将配对信息写入know_host文件 + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config);// 为Session对象设置properties + session.setTimeout(5000);// 设置超时 + session.setIdentityRepository(jSch.getIdentityRepository()); + session.connect(); + SFtpSessionManager.getInstance().addSession(session, threadId); + return session; + } + + private void setKnowHost(JSch jSch, FtpUrlEntity entity) throws JSchException { + IdEntity idEntity = entity.idEntity; + if (idEntity.knowHost != null) { + File knowFile = new File(idEntity.knowHost); + if (!knowFile.exists()) { + FileUtil.createFile(knowFile); + } + jSch.setKnownHosts(idEntity.knowHost); + + //HostKeyRepository hkr = jSch.getHostKeyRepository(); + //hkr.add(new HostKey(entity.hostName, HostKey.SSHRSA, getPubKey(idEntity.pubKey)), new JschUserInfo()); + // + //HostKey[] hks = hkr.getHostKey(); + //if (hks != null) { + // System.out.println("Host keys in " + hkr.getKnownHostsRepositoryID()); + // for (int i = 0; i < hks.length; i++) { + // HostKey hk = hks[i]; + // System.out.println(hk.getHost() + " " + + // hk.getType() + " " + + // hk.getFingerPrint(jSch)); + // } + //} + } + } + + private byte[] getPubKey(String pubKeyPath) { + try { + File f = new File(pubKeyPath); + FileInputStream fis = new FileInputStream(f); + byte[] buf = new byte[(int) f.length()]; + int len = fis.read(buf); + fis.close(); + return buf; + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private static class JschUserInfo implements UserInfo { + + @Override public String getPassphrase() { + return null; + } + + @Override public String getPassword() { + return null; + } + + @Override public boolean promptPassword(String message) { + System.out.println(message); + return true; + } + + @Override public boolean promptPassphrase(String message) { + System.out.println(message); + return false; + } + + @Override public boolean promptYesNo(String message) { + System.out.println(message); + return false; + } + + @Override public void showMessage(String message) { + System.out.println(message); + } + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java new file mode 100644 index 00000000..97429f4f --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDInfoTask.java @@ -0,0 +1,69 @@ +/* + * 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.sftp.download; + +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.exception.AriaSFTPException; +import com.arialyy.aria.sftp.AbsSFtpInfoTask; +import com.arialyy.aria.sftp.SFtpTaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpATTRS; +import com.jcraft.jsch.SftpException; +import java.io.UnsupportedEncodingException; + +/** + * 进行登录,获取session,获取文件信息 + */ +final class SFtpDInfoTask extends AbsSFtpInfoTask { + + SFtpDInfoTask(DTaskWrapper wrapper) { + super(wrapper); + } + + @Override protected void getFileInfo(Session session) throws JSchException, + UnsupportedEncodingException { + SFtpTaskOption option = (SFtpTaskOption) getWrapper().getTaskOption(); + ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); + channel.connect(1000); + + //channel.setFilenameEncoding(option.getCharSet()); + //channel.setFilenameEncoding("gbk"); + + String remotePath = option.getUrlEntity().remotePath; + String temp = CommonUtil.convertSFtpChar(option.getCharSet(), remotePath); + SftpATTRS attr = null; + try { + attr = channel.stat(temp); + } catch (Exception e) { + ALog.e(TAG, String.format("文件不存在,remotePath:%s", remotePath)); + } + + if (attr != null) { + getWrapper().getEntity().setFileSize(attr.getSize()); + CompleteInfo info = new CompleteInfo(); + info.code = 200; + onSucceed(info); + } else { + handleFail(new AriaSFTPException(String.format("文件不存在,remotePath:%s", remotePath)), false); + } + channel.disconnect(); + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java new file mode 100644 index 00000000..3c497591 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoader.java @@ -0,0 +1,159 @@ +/* + * 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.sftp.download; + +import android.os.Handler; +import android.os.Looper; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.event.EventMsgUtil; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IDLoadListener; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.loader.IThreadTaskBuilder; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.FileUtil; +import java.io.File; + +final class SFtpDLoader extends AbsNormalLoader { + + private int startThreadNum; //启动的线程数 + private boolean isComplete = false; + private Looper looper; + + SFtpDLoader(DTaskWrapper wrapper, IEventListener listener) { + super(wrapper, listener); + mTempFile = new File(getEntity().getFilePath()); + EventMsgUtil.getDefault().register(this); + setUpdateInterval(wrapper.getConfig().getUpdateInterval()); + } + + private DownloadEntity getEntity() { + return mTaskWrapper.getEntity(); + } + + @Override public long getFileSize() { + return getEntity().getFileSize(); + } + + /** + * 设置最大下载/上传速度AbsFtpInfoThread + * + * @param maxSpeed 单位为:kb + */ + protected void setMaxSpeed(int maxSpeed) { + for (IThreadTask threadTask : getTaskList()) { + if (threadTask != null && startThreadNum > 0) { + threadTask.setMaxSpeed(maxSpeed / startThreadNum); + } + } + } + + @Override public void onDestroy() { + super.onDestroy(); + EventMsgUtil.getDefault().unRegister(this); + } + + /** + * 启动单线程任务 + */ + @Override + public void handleTask(Looper looper) { + if (isBreak() || isComplete) { + return; + } + this.looper = looper; + mInfoTask.run(); + } + + private void startThreadTask() { + if (isBreak()) { + return; + } + + if (getListener() instanceof IDLoadListener) { + ((IDLoadListener) getListener()).onPostPre(getEntity().getFileSize()); + } + File file = new File(getEntity().getFilePath()); + if (file.getParentFile() != null && !file.getParentFile().exists()) { + FileUtil.createDir(file.getPath()); + } + // 处理记录、初始化状态管理器 + mRecord = mRecordHandler.getRecord(getFileSize()); + mStateManager.setLooper(mRecord, looper); + + // 创建线程任务 + getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord, + new Handler(looper, mStateManager.getHandlerCallback()))); + startThreadNum = mTTBuilder.getCreatedThreadNum(); + + mStateManager.updateCurrentProgress(getEntity().getCurrentProgress()); + if (mStateManager.getCurrentProgress() > 0) { + getListener().onResume(mStateManager.getCurrentProgress()); + } else { + getListener().onStart(mStateManager.getCurrentProgress()); + } + + // 启动线程任务 + for (IThreadTask threadTask : getTaskList()) { + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask); + } + + // 启动定时器 + startTimer(); + } + + @Override public long getCurrentProgress() { + return isRunning() ? mStateManager.getCurrentProgress() : getEntity().getCurrentProgress(); + } + + @Override public void addComponent(IRecordHandler recordHandler) { + mRecordHandler = recordHandler; + if (recordHandler.checkTaskCompleted()) { + mRecord.deleteData(); + isComplete = true; + getListener().onComplete(); + } + } + + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = infoTask; + infoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + startThreadTask(); + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + getListener().onFail(needRetry, e); + } + }); + } + + @Override public void addComponent(IThreadStateManager threadState) { + mStateManager = threadState; + } + + @Override public void addComponent(IThreadTaskBuilder builder) { + mTTBuilder = builder; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java new file mode 100644 index 00000000..c0a32b69 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDLoaderUtil.java @@ -0,0 +1,51 @@ +/* + * 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.sftp.download; + +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.NormalThreadStateManager; +import com.arialyy.aria.sftp.SFtpTaskOption; + +/** + * sftp下载工具 + * + * @author lyy + */ +public class SFtpDLoaderUtil extends AbsNormalLoaderUtil { + + @Override public AbsNormalLoader getLoader() { + if (mLoader == null) { + getTaskWrapper().generateTaskOption(SFtpTaskOption.class); + mLoader = new SFtpDLoader((DTaskWrapper) getTaskWrapper(), getListener()); + } + return mLoader; + } + + @Override public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new SFtpDRecordHandler((DTaskWrapper) getTaskWrapper())) + .addComponent(new NormalThreadStateManager(getListener())) + .addComponent(new SFtpDInfoTask((DTaskWrapper) getTaskWrapper())) + .addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpDTTBuilderAdapter( + (DTaskWrapper) getTaskWrapper()))); + structure.accept(getLoader()); + return structure; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java new file mode 100644 index 00000000..7a923dd3 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDRecordHandler.java @@ -0,0 +1,92 @@ +/* + * 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.sftp.download; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.common.RecordHelper; +import com.arialyy.aria.core.config.Configuration; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.RecordUtil; +import java.util.ArrayList; + +/** + * @author lyy + * Date: 2019-09-19 + */ +final class SFtpDRecordHandler extends RecordHandler { + + SFtpDRecordHandler(DTaskWrapper wrapper) { + super(wrapper); + } + + @Override public void handlerTaskRecord(TaskRecord record) { + RecordHelper helper = new RecordHelper(getWrapper(), record); + if (record.threadNum == 1) { + helper.handleSingleThreadRecord(); + } else { + if (getWrapper().isSupportBP()) { + if (record.isBlock) { + helper.handleBlockRecord(); + } else { + helper.handleMultiRecord(); + } + } + } + } + + @Override + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + ThreadRecord tr; + tr = new ThreadRecord(); + tr.taskKey = record.filePath; + tr.threadId = threadId; + tr.startLocation = startL; + tr.isComplete = false; + tr.threadType = record.taskType; + //最后一个线程的结束位置即为文件的总长度 + if (threadId == (record.threadNum - 1)) { + endL = getFileSize(); + } + tr.endLocation = endL; + tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + record.isBlock = threadNum > 1; + record.taskType = ITaskWrapper.D_SFTP; + record.isGroupRecord = false; + + return record; + } + + @Override public int initTaskThreadNum() { + int threadNum = Configuration.getInstance().downloadCfg.getThreadNum(); + return getFileSize() <= IRecordHandler.SUB_LEN + || threadNum == 1 + ? 1 + : threadNum; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java new file mode 100644 index 00000000..28e2d614 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDTTBuilderAdapter.java @@ -0,0 +1,92 @@ +/* + * 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.sftp.download; + +import android.os.Handler; +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.task.IThreadTaskAdapter; +import com.arialyy.aria.sftp.SFtpSessionManager; +import com.arialyy.aria.sftp.SFtpTaskOption; +import com.arialyy.aria.sftp.SFtpUtil; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import java.io.File; +import java.io.UnsupportedEncodingException; + +final class SFtpDTTBuilderAdapter extends AbsNormalTTBuilderAdapter { + private SFtpTaskOption option; + + SFtpDTTBuilderAdapter(DTaskWrapper wrapper) { + option = (SFtpTaskOption) wrapper.getTaskOption(); + } + + @Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) { + return new SFtpDThreadTaskAdapter(config); + } + + @Override + protected SubThreadConfig getSubThreadConfig(Handler stateHandler, ThreadRecord threadRecord, + boolean isBlock, int startNum) { + SubThreadConfig config = + super.getSubThreadConfig(stateHandler, threadRecord, isBlock, startNum); + + FtpUrlEntity entity = option.getUrlEntity(); + String key = + CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + threadRecord.threadId); + Session session = SFtpSessionManager.getInstance().getSession(key); + if (session == null) { + try { + session = SFtpUtil.getInstance().getSession(entity, threadRecord.threadId); + } catch (JSchException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + config.obj = session; + + return config; + } + + @Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) { + + if (!record.isBlock) { + if (getTempFile().exists()) { + FileUtil.deleteFile(getTempFile()); + } + //CommonUtil.createFile(mTempFile.getPath()); + } else { + for (int i = 0; i < totalThreadNum; i++) { + File blockFile = + new File(String.format(IRecordHandler.SUB_PATH, getTempFile().getPath(), i)); + if (blockFile.exists()) { + ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i)); + FileUtil.deleteFile(blockFile); + } + } + } + return true; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java new file mode 100644 index 00000000..c64c99c4 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/download/SFtpDThreadTaskAdapter.java @@ -0,0 +1,159 @@ +/* + * 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.sftp.download; + +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.task.AbsThreadTaskAdapter; +import com.arialyy.aria.exception.AriaSFTPException; +import com.arialyy.aria.sftp.SFtpTaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; +import com.jcraft.jsch.SftpProgressMonitor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; + +/** + * sftp 线程任务适配器 + * + * @author lyy + */ +final class SFtpDThreadTaskAdapter extends AbsThreadTaskAdapter { + private ChannelSftp channelSftp; + private Session session; + private SFtpTaskOption option; + + SFtpDThreadTaskAdapter(SubThreadConfig config) { + super(config); + session = (Session) config.obj; + option = (SFtpTaskOption) getTaskWrapper().getTaskOption(); + } + + @Override protected void handlerThreadTask() { + if (session == null) { + fail(new AriaSFTPException("session 为空"), false); + return; + } + try { + int timeout = getTaskConfig().getConnectTimeOut(); + if (!session.isConnected()) { + session.connect(timeout); + } + channelSftp = (ChannelSftp) session.openChannel("sftp"); + channelSftp.connect(timeout); + ALog.d(TAG, + String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getTaskWrapper().getKey(), + getThreadRecord().threadId, getThreadRecord().startLocation, + getThreadRecord().endLocation)); + + // 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码 + String charSet = option.getCharSet(); + String remotePath = + CommonUtil.convertSFtpChar(charSet, option.getUrlEntity().remotePath); + download(remotePath); + } catch (SftpException e) { + fail(new AriaSFTPException("sftp错误,错误类型:" + e.id, e), false); + } catch (UnsupportedEncodingException e) { + fail(new AriaSFTPException("字符编码错误", e), false); + } catch (IOException e) { + fail(new AriaSFTPException("", e), true); + } catch (JSchException e) { + fail(new AriaSFTPException("jsch 错误", e), false); + } finally { + channelSftp.disconnect(); + } + } + + /** + * 下载 + */ + private void download(String remotePath) throws SftpException, IOException { + InputStream is = + channelSftp.get(remotePath, new Monitor(), getThreadRecord().startLocation); + FileOutputStream fos = new FileOutputStream(getThreadConfig().tempFile, true); + FileChannel foc = fos.getChannel(); + ReadableByteChannel fic = Channels.newChannel(is); + ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); + int len; + while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) { + if (getThreadTask().isBreak()) { + break; + } + if (mSpeedBandUtil != null) { + mSpeedBandUtil.limitNextBytes(len); + } + if (getRangeProgress() + len >= getThreadRecord().endLocation) { + len = (int) (getThreadRecord().endLocation - getRangeProgress()); + bf.flip(); + fos.write(bf.array(), 0, len); + bf.compact(); + progress(len); + break; + } else { + bf.flip(); + foc.write(bf); + bf.compact(); + progress(len); + } + } + fos.flush(); + fos.close(); + is.close(); + } + + private class Monitor implements SftpProgressMonitor { + + private Monitor() { + } + + @Override public void init(int op, String src, String dest, long max) { + ALog.d(TAG, String.format("op = %s; src = %s; dest = %s; max = %s", op, src, dest, max)); + } + + /** + * @param count 已传输的数据 + * @return false 取消任务 + */ + @Override public boolean count(long count) { + + /* + * jsch 如果是恢复任务,第一次回调count会将已下载的长度返回,后面才是新增的文件长度。 + * 所以恢复任务的话,需要忽略一次回调 + */ + if (getRangeProgress() > getThreadRecord().endLocation) { + return false; + } + return !getThreadTask().isBreak(); + } + + @Override public void end() { + if (getThreadTask().isBreak()) { + return; + } + + complete(); + } + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java new file mode 100644 index 00000000..cdac02d0 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUInfoTask.java @@ -0,0 +1,69 @@ +/* + * 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.sftp.upload; + +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.sftp.AbsSFtpInfoTask; +import com.arialyy.aria.sftp.SFtpTaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpATTRS; +import com.jcraft.jsch.SftpException; +import java.io.UnsupportedEncodingException; + +final class SFtpUInfoTask extends AbsSFtpInfoTask { + static final int ISCOMPLETE = 0xa1; + + SFtpUInfoTask(UTaskWrapper uTaskWrapper) { + super(uTaskWrapper); + } + + @Override protected void getFileInfo(Session session) + throws JSchException, UnsupportedEncodingException, SftpException { + SFtpTaskOption option = (SFtpTaskOption) getWrapper().getTaskOption(); + ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); + channel.connect(1000); + + String remotePath = option.getUrlEntity().remotePath; + String temp = CommonUtil.convertSFtpChar(getOption().getCharSet(), remotePath) + + "/" + + getWrapper().getEntity().getFileName(); + + SftpATTRS attr = null; + try { + attr = channel.stat(temp); + } catch (Exception e) { + ALog.d(TAG, String.format("文件不存在,remotePath:%s", remotePath)); + } + + boolean isComplete = false; + UploadEntity entity = getWrapper().getEntity(); + if (attr != null && attr.getSize() == entity.getFileSize()) { + isComplete = true; + } + + CompleteInfo info = new CompleteInfo(); + info.code = isComplete ? ISCOMPLETE : 200; + info.obj = attr; + channel.disconnect(); + onSucceed(info); + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java new file mode 100644 index 00000000..0448ccb3 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoader.java @@ -0,0 +1,158 @@ +/* + * 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.sftp.upload; + +import android.os.Handler; +import android.os.Looper; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.event.EventMsgUtil; +import com.arialyy.aria.core.inf.IThreadStateManager; +import com.arialyy.aria.core.listener.IDLoadListener; +import com.arialyy.aria.core.listener.IEventListener; +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.IInfoTask; +import com.arialyy.aria.core.loader.IRecordHandler; +import com.arialyy.aria.core.loader.IThreadTaskBuilder; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.core.task.IThreadTask; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.exception.AriaException; +import com.arialyy.aria.util.FileUtil; +import com.jcraft.jsch.SftpATTRS; +import java.io.File; + +final class SFtpULoader extends AbsNormalLoader { + + private Looper looper; + + SFtpULoader(UTaskWrapper wrapper, IEventListener listener) { + super(wrapper, listener); + mTempFile = new File(getEntity().getFilePath()); + EventMsgUtil.getDefault().register(this); + setUpdateInterval(wrapper.getConfig().getUpdateInterval()); + } + + private UploadEntity getEntity() { + return mTaskWrapper.getEntity(); + } + + @Override public long getFileSize() { + return getEntity().getFileSize(); + } + + /** + * 设置最大下载/上传速度AbsFtpInfoThread + * + * @param maxSpeed 单位为:kb + */ + protected void setMaxSpeed(int maxSpeed) { + for (IThreadTask threadTask : getTaskList()) { + if (threadTask != null) { + threadTask.setMaxSpeed(maxSpeed); + } + } + } + + @Override public void onDestroy() { + super.onDestroy(); + EventMsgUtil.getDefault().unRegister(this); + } + + /** + * 启动单线程任务 + */ + @Override + public void handleTask(Looper looper) { + if (isBreak()) { + return; + } + this.looper = looper; + mInfoTask.run(); + } + + private void startThreadTask(SftpATTRS attrs) { + if (isBreak()) { + return; + } + + if (getListener() instanceof IDLoadListener) { + ((IDLoadListener) getListener()).onPostPre(getEntity().getFileSize()); + } + File file = new File(getEntity().getFilePath()); + if (file.getParentFile() != null && !file.getParentFile().exists()) { + FileUtil.createDir(file.getPath()); + } + // 处理记录、初始化状态管理器 + SFtpURecordHandler recordHandler = (SFtpURecordHandler) mRecordHandler; + recordHandler.setFtpAttrs(attrs); + mRecord = recordHandler.getRecord(getFileSize()); + mStateManager.setLooper(mRecord, looper); + + // 创建线程任务 + getTaskList().addAll(mTTBuilder.buildThreadTask(mRecord, + new Handler(looper, mStateManager.getHandlerCallback()))); + + mStateManager.updateCurrentProgress(getEntity().getCurrentProgress()); + if (mStateManager.getCurrentProgress() > 0) { + getListener().onResume(mStateManager.getCurrentProgress()); + } else { + getListener().onStart(mStateManager.getCurrentProgress()); + } + + // 启动线程任务 + for (IThreadTask threadTask : getTaskList()) { + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), threadTask); + } + + // 启动定时器 + startTimer(); + } + + @Override public long getCurrentProgress() { + return isRunning() ? mStateManager.getCurrentProgress() : getEntity().getCurrentProgress(); + } + + @Override public void addComponent(IRecordHandler recordHandler) { + mRecordHandler = recordHandler; + } + + @Override public void addComponent(IInfoTask infoTask) { + mInfoTask = infoTask; + infoTask.setCallback(new IInfoTask.Callback() { + @Override public void onSucceed(String key, CompleteInfo info) { + if (info.code == SFtpUInfoTask.ISCOMPLETE) { + getListener().onComplete(); + } else { + startThreadTask((SftpATTRS) info.obj); + } + } + + @Override public void onFail(AbsEntity entity, AriaException e, boolean needRetry) { + getListener().onFail(needRetry, e); + } + }); + } + + @Override public void addComponent(IThreadStateManager threadState) { + mStateManager = threadState; + } + + @Override public void addComponent(IThreadTaskBuilder builder) { + mTTBuilder = builder; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java new file mode 100644 index 00000000..97f0d1f9 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpULoaderUtil.java @@ -0,0 +1,53 @@ +/* + * 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.sftp.upload; + +import com.arialyy.aria.core.loader.AbsNormalLoader; +import com.arialyy.aria.core.loader.AbsNormalLoaderUtil; +import com.arialyy.aria.core.loader.LoaderStructure; +import com.arialyy.aria.core.loader.NormalTTBuilder; +import com.arialyy.aria.core.loader.NormalThreadStateManager; +import com.arialyy.aria.core.loader.UploadThreadStateManager; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.sftp.SFtpTaskOption; + +/** + * sftp下载工具 + * + * @author lyy + */ +public class SFtpULoaderUtil extends AbsNormalLoaderUtil { + + @Override public AbsNormalLoader getLoader() { + if (mLoader == null) { + getTaskWrapper().generateTaskOption(SFtpTaskOption.class); + mLoader = new SFtpULoader((UTaskWrapper) getTaskWrapper(), getListener()); + } + return mLoader; + } + + @Override public LoaderStructure BuildLoaderStructure() { + LoaderStructure structure = new LoaderStructure(); + structure.addComponent(new SFtpURecordHandler((UTaskWrapper) getTaskWrapper())) +// .addComponent(new NormalThreadStateManager(getListener())) + .addComponent(new UploadThreadStateManager(getListener())) + .addComponent(new SFtpUInfoTask((UTaskWrapper) getTaskWrapper())) + .addComponent(new NormalTTBuilder(getTaskWrapper(), new SFtpUTTBuilderAdapter( + (UTaskWrapper) getTaskWrapper()))); + structure.accept(getLoader()); + return structure; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java new file mode 100644 index 00000000..37ae3877 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpURecordHandler.java @@ -0,0 +1,115 @@ +/* + * 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.sftp.upload; + +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.RecordHandler; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.core.wrapper.ITaskWrapper; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.RecordUtil; +import com.jcraft.jsch.SftpATTRS; +import java.util.ArrayList; + +/** + * 上传任务记录处理器 + */ +final class SFtpURecordHandler extends RecordHandler { + private SftpATTRS ftpAttrs; + + SFtpURecordHandler(UTaskWrapper wrapper) { + super(wrapper); + } + + void setFtpAttrs(SftpATTRS ftpAttrs) { + this.ftpAttrs = ftpAttrs; + } + + @Override public void handlerTaskRecord(TaskRecord record) { + if (record.threadRecords == null || record.threadRecords.isEmpty()) { + record.threadRecords = new ArrayList<>(); + record.threadRecords.add( + createThreadRecord(record, 0, ftpAttrs == null ? 0 : ftpAttrs.getSize(), getFileSize())); + } + + if (ftpAttrs != null) { + UploadEntity entity = (UploadEntity) getWrapper().getEntity(); + //远程文件已完成 + if (ftpAttrs.getSize() == getFileSize()) { + record.threadRecords.get(0).isComplete = true; + ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "】"); + } else if (ftpAttrs.getSize() == 0) { + getWrapper().setNewTask(true); + ALog.d(TAG, "FTP服务器上已存在该文件【" + entity.getFileName() + "】,但文件长度为0,重新上传该文件"); + } else { + ALog.w(TAG, "FTP服务器已存在未完成的文件【" + + entity.getFileName() + + ",size: " + + ftpAttrs.getSize() + + "】" + + "尝试从位置:" + + ftpAttrs.getSize() + + "开始上传"); + getWrapper().setNewTask(false); + + // 修改记录 + ThreadRecord threadRecord = record.threadRecords.get(0); + //修改本地保存的停止地址为服务器上对应文件的大小 + threadRecord.startLocation = ftpAttrs.getSize(); + } + } else { + ALog.d(TAG, "SFTP服务器上不存在该文件"); + getWrapper().setNewTask(true); + ThreadRecord tr = record.threadRecords.get(0); + tr.startLocation = 0; + tr.endLocation = getFileSize(); + tr.isComplete = false; + } + } + + @Override + public ThreadRecord createThreadRecord(TaskRecord record, int threadId, long startL, long endL) { + ThreadRecord tr; + tr = new ThreadRecord(); + tr.taskKey = record.filePath; + tr.threadId = threadId; + tr.startLocation = startL; + tr.isComplete = false; + tr.threadType = record.taskType; + tr.endLocation = getFileSize(); + tr.blockLen = RecordUtil.getBlockLen(getFileSize(), threadId, record.threadNum); + return tr; + } + + @Override public TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = getEntity().getFileName(); + record.filePath = getEntity().getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + record.isBlock = false; + record.taskType = ITaskWrapper.U_SFTP; + record.isGroupRecord = getEntity().isGroupChild(); + + return record; + } + + @Override public int initTaskThreadNum() { + return 1; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java new file mode 100644 index 00000000..5a82b770 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUTTBuilderAdapter.java @@ -0,0 +1,73 @@ +/* + * 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.sftp.upload; + +import android.os.Handler; +import com.arialyy.aria.core.FtpUrlEntity; +import com.arialyy.aria.core.TaskRecord; +import com.arialyy.aria.core.ThreadRecord; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.loader.AbsNormalTTBuilderAdapter; +import com.arialyy.aria.core.task.IThreadTaskAdapter; +import com.arialyy.aria.core.upload.UTaskWrapper; +import com.arialyy.aria.sftp.SFtpSessionManager; +import com.arialyy.aria.sftp.SFtpTaskOption; +import com.arialyy.aria.sftp.SFtpUtil; +import com.arialyy.aria.util.CommonUtil; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import java.io.UnsupportedEncodingException; + +final class SFtpUTTBuilderAdapter extends AbsNormalTTBuilderAdapter { + private SFtpTaskOption option; + + SFtpUTTBuilderAdapter(UTaskWrapper wrapper) { + option = (SFtpTaskOption) wrapper.getTaskOption(); + } + + @Override public IThreadTaskAdapter getAdapter(SubThreadConfig config) { + return new SFtpUThreadTaskAdapter(config); + } + + @Override + protected SubThreadConfig getSubThreadConfig(Handler stateHandler, ThreadRecord threadRecord, + boolean isBlock, int startNum) { + SubThreadConfig config = + super.getSubThreadConfig(stateHandler, threadRecord, isBlock, startNum); + + FtpUrlEntity entity = option.getUrlEntity(); + String key = + CommonUtil.getStrMd5(entity.hostName + entity.port + entity.user + threadRecord.threadId); + Session session = SFtpSessionManager.getInstance().getSession(key); + if (session == null) { + try { + session = SFtpUtil.getInstance().getSession(entity, threadRecord.threadId); + } catch (JSchException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + config.obj = session; + + return config; + } + + @Override public boolean handleNewTask(TaskRecord record, int totalThreadNum) { + + return true; + } +} diff --git a/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java new file mode 100644 index 00000000..9910d0a3 --- /dev/null +++ b/SFtpComponent/src/main/java/com/arialyy/aria/sftp/upload/SFtpUThreadTaskAdapter.java @@ -0,0 +1,196 @@ +/* + * 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.sftp.upload; + +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.task.AbsThreadTaskAdapter; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.exception.AriaSFTPException; +import com.arialyy.aria.sftp.SFtpTaskOption; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import com.arialyy.aria.util.CommonUtil; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; +import com.jcraft.jsch.SftpProgressMonitor; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * sftp 线程任务适配器 + * + * @author lyy + */ +final class SFtpUThreadTaskAdapter extends AbsThreadTaskAdapter { + private ChannelSftp channelSftp; + private Session session; + private SFtpTaskOption option; + + SFtpUThreadTaskAdapter(SubThreadConfig config) { + super(config); + session = (Session) config.obj; + option = (SFtpTaskOption) getTaskWrapper().getTaskOption(); + } + + @Override protected void handlerThreadTask() { + if (session == null) { + fail(new AriaSFTPException("session 为空"), false); + return; + } + try { + ALog.d(TAG, + String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getTaskWrapper().getKey(), + getThreadRecord().threadId, getThreadRecord().startLocation, + getThreadRecord().endLocation)); + int timeout = getTaskConfig().getConnectTimeOut(); + if (!session.isConnected()) { + session.connect(timeout); + } + // 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码 + String charSet = option.getCharSet(); + String remotePath = + CommonUtil.convertSFtpChar(charSet, option.getUrlEntity().remotePath); + channelSftp = (ChannelSftp) session.openChannel("sftp"); + channelSftp.connect(timeout); + + if (!dirIsExist(remotePath)) { + createDir(remotePath); + } + channelSftp.cd(remotePath); + upload(remotePath); + } catch (SftpException e) { + fail(new AriaSFTPException("sftp错误,错误类型:" + e.id, e), false); + } catch (UnsupportedEncodingException e) { + fail(new AriaSFTPException("字符编码错误", e), false); + } catch (IOException e) { + fail(new AriaSFTPException("", e), true); + } catch (JSchException e) { + fail(new AriaSFTPException("jsch 错误", e), false); + } finally { + channelSftp.disconnect(); + } + } + + /** + * 远程文件夹是否存在 + * + * @return true 文件夹存在 + */ + private boolean dirIsExist(String remotePath) { + try { + channelSftp.ls(remotePath); + } catch (SftpException e) { + return false; + } + return true; + } + + /** + * 创建文件夹 + * + * @throws SftpException + */ + private void createDir(String remotePath) throws SftpException { + String[] folders = remotePath.split("/"); + + for (String folder : folders) { + if (folder.length() > 0) { + try { + channelSftp.cd(folder); + } catch (SftpException e) { + channelSftp.mkdir(folder); + channelSftp.cd(folder); + } + } + } + } + + /** + * 恢复上传 + * + * @throws SftpException + * @throws IOException + */ + private void upload(String remotePath) throws SftpException, IOException { + UploadEntity entity = (UploadEntity) getTaskWrapper().getEntity(); + remotePath = remotePath.concat("/").concat(entity.getFileName()); + BufferedRandomAccessFile brf = new BufferedRandomAccessFile(getThreadConfig().tempFile, "r"); + int mode = ChannelSftp.OVERWRITE; + boolean isResume = false; + if (getThreadRecord().startLocation > 0) { + brf.seek(getThreadRecord().startLocation); + mode = ChannelSftp.APPEND; + isResume = true; + } + OutputStream os = channelSftp.put(remotePath, new Monitor(isResume), mode); + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = brf.read(buffer)) != -1) { + if (getThreadTask().isBreak()) { + break; + } + os.write(buffer, 0, bytesRead); + if (mSpeedBandUtil != null) { + mSpeedBandUtil.limitNextBytes(bytesRead); + } + } + os.flush(); + os.close(); + brf.close(); + } + + private class Monitor implements SftpProgressMonitor { + + private boolean isResume; + + private Monitor(boolean isResume) { + this.isResume = isResume; + } + + @Override public void init(int op, String src, String dest, long max) { + ALog.d(TAG, String.format("op = %s; src = %s; dest = %s; max = %s", op, src, dest, max)); + } + + /** + * @param count 已传输的数据 + * @return false 取消任务 + */ + @Override public boolean count(long count) { + + /* + * jsch 如果是恢复任务,第一次回调count会将已下载的长度返回,后面才是新增的文件长度。 + * 所以恢复任务的话,需要忽略一次回调 + */ + if (!isResume) { + progress(count); + } + isResume = false; + + return !getThreadTask().isBreak(); + } + + @Override public void end() { + if (getThreadTask().isBreak()) { + return; + } + + complete(); + } + } +} diff --git a/SFtpComponent/src/main/resources/META-INF/MANIFEST.MF b/SFtpComponent/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/SFtpComponent/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/SFtpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil b/SFtpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil new file mode 100644 index 00000000..16943901 --- /dev/null +++ b/SFtpComponent/src/main/resources/META-INF/services/com.arialyy.aria.core.inf.IUtil @@ -0,0 +1,2 @@ +com.arialyy.aria.sftp.download.SFtpDLoaderUtil +com.arialyy.aria.sftp.upload.SFtpULoaderUtil \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 76cc2bad..a317d495 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,6 +15,8 @@ android { versionName "1.0" multiDexEnabled true vectorDrawables.useSupportLibrary = true + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } signingConfigs { @@ -28,9 +30,14 @@ android { } buildTypes { + debug { + debuggable true + minifyEnabled false //混淆 + } release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + zipAlignEnabled true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } @@ -41,38 +48,48 @@ android { packagingOptions { exclude 'META-INF/services/javax.annotation.processing.Processor' + merge 'META-INF/services/com.arialyy.aria.core.inf.IUtil' } lintOptions { abortOnError false } - - // sourceSets { - // main.kotlin.srcDirs += 'src/main/kotlin' - // main.java.srcDirs += 'src/main/java' - // } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0' + implementation 'androidx.cardview:cardview:1.0.0' implementation 'com.google.android.material:material:1.0.0' - implementation "org.jetbrains.kotlin:kotlin-stdlib:${rootProject.ext.kotlin_version}" - api project(':Aria') - kapt project(':AriaCompiler') - // api 'com.arialyy.aria:aria-core:3.6.4' - // kapt 'com.arialyy.aria:aria-compiler:3.6.4' - - // compile 'com.arialyy.aria:aria-core:3.3.14' - // annotationProcessor 'com.arialyy.aria:aria-compiler:3.3.14' api 'com.github.PhilJay:MPAndroidChart:v3.0.3' - implementation project(':AppFrame') - debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' implementation 'com.github.bumptech.glide:glide:3.7.0' implementation 'com.pddstudio:highlightjs-android:1.5.0' implementation 'org.greenrobot:eventbus:3.1.1' + implementation project(':AppFrame') + + // aria + kapt project(':AriaCompiler') + implementation project(':Aria') + implementation project(':M3U8Component') + implementation project(':FtpComponent') + implementation project(path: ':AriaAnnotations') + implementation project(path: ':SFtpComponent') + + +// implementation 'com.arialyy.aria:core:3.8.12' +// kapt 'com.arialyy.aria:compiler:3.8.12' +// implementation 'com.arialyy.aria:ftpComponent:3.8.12' // 如果需要使用ftp,请增加该组件 +// implementation 'com.arialyy.aria:sftpComponent:3.8.12' // 如果需要使用ftp,请增加该组件 +// implementation 'com.arialyy.aria:m3u8Component:3.8.12' // 如果需要使用m3u8下载功能,请增加该组件 + + debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' } repositories { mavenCentral() diff --git a/app/gradle.properties b/app/gradle.properties index 89e0d99e..70172ced 100644 --- a/app/gradle.properties +++ b/app/gradle.properties @@ -4,7 +4,7 @@ # Gradle settings configured through the IDE *will override* # any settings specified in this file. -# For more details on how to configure your build environment visit +# For more details on how to configure your build environment addComponent # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. @@ -13,6 +13,6 @@ # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit +# This option should only be used with decoupled projects. More details, addComponent # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..142c0793 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,135 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +################################### 混淆配置 start ########################################### +#指定代码的压缩级别 +-optimizationpasses 1 +#包明不混合大小写 +-dontusemixedcaseclassnames +#不去忽略非公共的库类 +-dontskipnonpubliclibraryclasses +#优化 不优化输入的类文件 +-dontoptimize +#预校验 +-dontpreverify +#混淆时是否记录日志 +-verbose +# 混淆时所采用的算法 +#-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* +-optimizations !code/simplification/cast,!field/*,!class/merging/* +#-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* +#忽略警告 +#-ignorewarning +################################### 混淆配置 end ############################################ + + +################## 记录生成的日志数据,gradle build时在本项目根目录输出 ################# +####### 输出文件夹 build/outputs/mapping +#apk 包内所有 class 的内部结构 +-dump build/outputs/mapping/class_files.txt +#未混淆的类和成员 +-printseeds build/outputs/mapping/kpa_seeds.txt +#列出从 apk 中删除的代码 +-printusage build/outputs/mapping/kpa_unused.txt +#混淆前后的映射 +-printmapping build/outputs/mapping/kpa_mapping.txt +################## 记录生成的日志数据,gradle build时 在本项目根目录输出-end ################# + +################## 常用属性配置-start ################## +# 保护注解 +-keepattributes *Annotation* +# 保护support v4 包 +-dontwarn android.support.v4.app.** +-keep class android.support.v4.app.**{ *; } +# 保护andorid x +-keep class com.google.android.material.** {*;} +-keep class androidx.** {*;} +-keep public class * extends androidx.** +-keep interface androidx.** {*;} +-dontwarn com.google.android.material.** +-dontnote com.google.android.material.** +-dontwarn androidx.** +# 保护一些奇葩的问题 +-dontwarn org.xmlpull.v1.XmlPullParser +-dontwarn org.xmlpull.v1.XmlSerializer +-keep class org.xmlpull.v1.* {*;} + +# 保护JS接口 +-keepclassmembers class * { + @android.webkit.JavascriptInterface ; +} +##保持 native 方法不被混淆 +-keepclasseswithmembernames class * { + native ; +} +##保持 Parcelable 不被混淆 +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} +##保持 Serializable 不被混淆 +-keepnames class * implements java.io.Serializable +# +#保持 Serializable 不被混淆并且enum 类也不被混淆 +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} +#避免混淆泛型 如果混淆报错建议关掉 +#–keepattributes Signature +# webview + js +-keepattributes *JavascriptInterface* + +################## 常用属性配置-end ################## + +################## kotlin-start ################## +-keep class kotlin.** { *; } +-keep class kotlin.Metadata { *; } +-dontwarn kotlin.** +-keepclassmembers class **$WhenMappings { + ; +} +-keepclassmembers class kotlin.Metadata { + public ; +} +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + static void checkParameterIsNotNull(java.lang.Object, java.lang.String); +} +################## kotlin-end ################## + +-dontwarn com.arialyy.aria.** +-keep class com.arialyy.aria.**{*;} +-keep class **$$DownloadListenerProxy{ *; } +-keep class **$$UploadListenerProxy{ *; } +-keep class **$$DownloadGroupListenerProxy{ *; } +-keep class **$$DGSubListenerProxy{ *; } +-keepclasseswithmembernames class * { + @Download.* ; + @Upload.* ; + @DownloadGroup.* ; +} +-keep class com.arialyy.aria.ftp.download.FtpDLoaderUtil{*;} +-adaptresourcefilenames **.IUtil +-adaptresourcefilecontents **.IUtil \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/arial/downloaddemo/ApiTest.kt b/app/src/androidTest/java/com/example/arial/downloaddemo/ApiTest.kt new file mode 100644 index 00000000..4c8597a3 --- /dev/null +++ b/app/src/androidTest/java/com/example/arial/downloaddemo/ApiTest.kt @@ -0,0 +1,30 @@ +package com.example.arial.downloaddemo + +import androidx.test.runner.AndroidJUnit4 +import com.arialyy.aria.util.CommonUtil +import org.junit.Test +import org.junit.runner.RunWith +import java.net.URLDecoder +import java.net.URLEncoder +import kotlin.math.pow + +@RunWith(AndroidJUnit4::class) +class ApiTest { + + @Test + fun testAddChar(){ + var str = "\\\\+道+歉+信\u0026感 谢 信" + str = str.replace("\\+".toRegex(), "%2B") + println("========") + println(str) + println(URLEncoder.encode(str)) + println(URLDecoder.decode(str)) + } + + @Test + fun testSpeed() { + val speed = 1024.0.pow(4.0) * 2 + println("speed = ${CommonUtil.formatFileSize(speed)}") + + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/arial/downloaddemo/ApplicationTest.java b/app/src/androidTest/java/com/example/arial/downloaddemo/ApplicationTest.java deleted file mode 100644 index c9a9d76d..00000000 --- a/app/src/androidTest/java/com/example/arial/downloaddemo/ApplicationTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2016 AriaLyy(DownloadUtil) - * - * 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.example.arial.downloaddemo; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b577538a..c7abf97c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,13 +1,9 @@ - - - - - - + - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + android:windowSoftInputMode="stateHidden|adjustResize" /> + + + android:resource="@xml/aria_fileprovider_paths" /> + + + + + + + diff --git a/app/src/main/assets/aria_config.xml b/app/src/main/assets/aria_config.xml index af36efe6..ed5bd5b3 100644 --- a/app/src/main/assets/aria_config.xml +++ b/app/src/main/assets/aria_config.xml @@ -5,21 +5,24 @@ - + - + - + + + + @@ -27,7 +30,7 @@ - + - + @@ -113,10 +116,10 @@ - + - + @@ -126,7 +129,8 @@ - + + @@ -158,4 +162,4 @@ - \ No newline at end of file + diff --git a/app/src/main/assets/help_code/FtpUpload.java b/app/src/main/assets/help_code/FtpUpload.java index 48ad3c99..1a977623 100644 --- a/app/src/main/assets/help_code/FtpUpload.java +++ b/app/src/main/assets/help_code/FtpUpload.java @@ -23,7 +23,7 @@ import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.common.ftp.FtpInterceptHandler; import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor; import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.core.upload.UploadTask; +import com.arialyy.aria.core.task.UploadTask; import com.arialyy.frame.util.FileUtil; import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; diff --git a/app/src/main/assets/help_code/KotlinHttpDownload.kt b/app/src/main/assets/help_code/KotlinHttpDownload.kt index 9fc3bfcf..a1dfe1cb 100644 --- a/app/src/main/assets/help_code/KotlinHttpDownload.kt +++ b/app/src/main/assets/help_code/KotlinHttpDownload.kt @@ -21,9 +21,9 @@ import android.widget.Button import android.widget.Toast import com.arialyy.annotations.Download import com.arialyy.aria.core.Aria -import com.arialyy.aria.core.download.normal.HttpNormalTarget -import com.arialyy.aria.core.download.DownloadTask -import com.arialyy.aria.core.inf.IEntity +import com.arialyy.aria.core.download.target.HttpNormalTarget +import com.arialyy.aria.core.task.DownloadTask +import com.arialyy.aria.inf.IEntity import com.arialyy.simple.R import com.arialyy.simple.base.BaseActivity import com.arialyy.simple.databinding.ActivitySingleBinding @@ -59,7 +59,7 @@ class KotlinDownloadActivity : BaseActivity() { target = Aria.download(this) .load(DOWNLOAD_URL) binding.progress = target.percent - if (target.taskState == IEntity.STATE_STOP) { + if (target.taskState == com.arialyy.aria.inf.IEntity.STATE_STOP) { mStart.text = "恢复" } else if (target.isRunning) { mStart.text = "停止" @@ -71,7 +71,7 @@ class KotlinDownloadActivity : BaseActivity() { * 注解方法不能添加internal修饰符,否则会出现e: [kapt] An exception occurred: java.lang.IllegalArgumentException: peerIndex 1 for '$a' not in range (received 0 arguments)错误 */ @Download.onTaskRunning - fun running(task: DownloadTask) { + fun running(task: com.arialyy.aria.core.task.DownloadTask) { Log.d(TAG, task.percent.toString()) val len = task.fileSize if (len == 0L) { @@ -83,40 +83,40 @@ class KotlinDownloadActivity : BaseActivity() { } @Download.onWait - fun onWait(task: DownloadTask) { + fun onWait(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == DOWNLOAD_URL) { Log.d(TAG, "wait ==> " + task.downloadEntity.fileName) } } @Download.onPre - fun onPre(task: DownloadTask) { + fun onPre(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == DOWNLOAD_URL) { mStart.text = "停止" } } @Download.onTaskStart - fun taskStart(task: DownloadTask) { + fun taskStart(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == DOWNLOAD_URL) { binding.fileSize = task.convertFileSize } } @Download.onTaskComplete - fun complete(task: DownloadTask) { + fun complete(task: com.arialyy.aria.core.task.DownloadTask) { Log.d(TAG, "完成") } @Download.onTaskResume - fun taskResume(task: DownloadTask) { + fun taskResume(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == DOWNLOAD_URL) { mStart.text = "停止" } } @Download.onTaskStop - fun taskStop(task: DownloadTask) { + fun taskStop(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == DOWNLOAD_URL) { mStart.text = "恢复" binding.speed = "" @@ -124,7 +124,7 @@ class KotlinDownloadActivity : BaseActivity() { } @Download.onTaskCancel - fun taskCancel(task: DownloadTask) { + fun taskCancel(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == DOWNLOAD_URL) { binding.progress = 0 Toast.makeText(this@KotlinDownloadActivity, "取消下载", Toast.LENGTH_SHORT) @@ -136,7 +136,7 @@ class KotlinDownloadActivity : BaseActivity() { } @Download.onTaskFail - fun taskFail(task: DownloadTask) { + fun taskFail(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == DOWNLOAD_URL) { Toast.makeText(this@KotlinDownloadActivity, "下载失败", Toast.LENGTH_SHORT) .show() diff --git a/app/src/main/assets/id_rsa b/app/src/main/assets/id_rsa new file mode 100644 index 00000000..c5796584 --- /dev/null +++ b/app/src/main/assets/id_rsa @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,8CCEACDEBFE735A306E5E8FA84E90106 + +s9LGwTfk9PmcqF50jcFNpSB0O93ehxyD9FIahADuPwYnJCEBFQ+Hm06QOagVxB7z +44e23/SCAmo3cWes/IVXUag4+/p8UIcFZc7/CWIBlWaWKuyuP9vD4SD9EiPj6XlC +qaAsYcWxPodLoh4RQP08Chw1TET+XkOQuf1FuOil4eEeJYHBCpILF2I9lv7ujo3m +S3vS/LLpRUISOm7qVskpq0o1EXibdoSRRqvyzl7hzv+4ik7pDBl1Xun+E05So8Bz +aQP6Y8MpuowcFn0Q/OP10vcMm0tT8HwCz2qhfwN+3QQ/MdpePUw9DxCKsFS6cR6u +zILuS4a+/huQ2v2ebgTJK4gH/FYaHDP7jIVLKTB6lu4vxABHOrP19gz/Th7ZA/4i +JW3wR1VmYpSaGYVRnvzQ9sn/sYjntVOcXfOADHxy7VK2k7Ejwot88OJ96kkNCt26 +REcb3wM0AFu1sJSpDKLBqAvZEwOMhrbQW6KIorfKLYtywfEkwVdHa8JQY35Z5RrZ +qV5XvdW4LVyD0W4yjaeaeWVDShts2e7T+M9BV7LqYS+IKLrVWdQbn+7ymhi69UT8 +mLbiuLshyBDmjMqltS0Wa1Ejd3MTI2pmzgO5G8qTDL7oa2OiUO4aNCy9sy87fogH +iuE+tXrxmFeb8kSmFlblyrOi3EyRSkQEcg+JdooWZMTrxaIQUhYhlA+10JU2pc5w +cPg//c1Z2Hlgrx9tlecxDafPgH7AnLuG6J3AgZBnE1511h21iprnEKafMC9h+Lp8 +JmIeFnI3wLgfAz1olKeoRa/aHZGUiiD+GQxI21Q+XEXChAYf9vR7MuKFWqTI8jk7 +OfUMPXnKSvPBgTyNyTR8Kf3YBvm0HoWWSQr0YS7CbxsWNzdwpsnKn/qhwdiCt5kK +T1gS+yKTk6F/Z2+D0yBSaopOTumNYEDmDW3Er+r2fvM+UBTYdvYNCkaUysX1BK94 +4jmpeZZtagfppNlcQePuPv2zgckBjfeAdn4oQQqwyDS10ovWnTVEqn9yO0njZjXy +PhFZTX2R4Cn6UrW3AlLGCCdl3ogeybuoFF4dYMh6TiMgkD45XbsUrNq+iEBHSiFz +YRK4o40lMGvBxSa2bhZHtyoQlUJkptyFriCt9cH4WDbEIafLe/N29AP7ZUOQUOD8 +ZgyyUmER270uBzQig8hRXBJADZXYKqT3hUrosOt64tjw2CNIkMJPz/KVDOM66prb +WiiMV2l+xGnu0tLMCgp7TcCclBEPTlGfpsuP/iFu70j8u4vDjxNwWr3QyuqX0Krs +M6pfLHp4RSfUN9AbrjoSAu4XBVE5d8JNf7YGpx2D+HIV1hAEux9IWj+/lAA9eAEq +DnzI3JeYlWRCCF8Jg2VGt8fwQDsgTtFsHcaCuZqTPXGwsT3EIi/+9wQJshDyP/D4 +OWa8HFvkuluLeEDTW7B2u3+AFS01Gt92/fPcY06j+OJL4atW1c5XCF8inlt8Thop ++a33yUHpN8uGzkmKUz/onLxvMLYcTDDAXS2dtslij0ldSNC484GpWPHmLfW1jQCv +99FqBUbzGMy2RHP1xRHvqK2kV1N72QMde5fDLUP+vygtWFh/Nobi/guPR5OuO60R +-----END RSA PRIVATE KEY----- diff --git a/app/src/main/assets/id_rsa.pub b/app/src/main/assets/id_rsa.pub new file mode 100644 index 00000000..7606849d --- /dev/null +++ b/app/src/main/assets/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBlgTuAhvU/Z0ni2PszmIXkDvANMWNYHfiCfvOxRKoK/jCGmKw/TDF1Z49P5M5CMpKwTaQKDxfolYeCgvH9eBmY1NiE26COWexgoqxrw4pWE9/h2Thx7cVWjGWZZ4iwyYfT19/5SgPNujiW9cWGV9N8VXAKe6Sl7v+6TtjLHNCNEn7B90m6sRIYHXsAQ7W7TBlUD5S6wcAsL6v6RVMVVBoIXykNW+zQK6prAWPCtPWcEw3WwfCIUQfJLS8gMC++Ox/bT9gqR+uZTk8MGxafxa9lrvIvpcZh8uVzaP428E3mlSkJnhTAzk43VoKAKxDA1Z+Qehig7VP9GsSn2HGuYE7 AriaL@DESKTOP-BBML2VA diff --git a/app/src/main/java/com/arialyy/simple/DbTestActivity.java b/app/src/main/java/com/arialyy/simple/DbTestActivity.java index b1dbe3d4..8815192a 100644 --- a/app/src/main/java/com/arialyy/simple/DbTestActivity.java +++ b/app/src/main/java/com/arialyy/simple/DbTestActivity.java @@ -1,12 +1,11 @@ package com.arialyy.simple; import android.os.Bundle; - import android.util.Log; import android.view.View; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.orm.DbEntity; import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.databinding.ActivityDbTestBinding; @@ -38,7 +37,7 @@ public class DbTestActivity extends BaseActivity { } } - private void searchAll(){ + private void searchAll() { long startT = System.currentTimeMillis(); //List data = DownloadEntity.findRelationData(DownloadEntity.class); @@ -56,7 +55,7 @@ public class DbTestActivity extends BaseActivity { DownloadEntity entity = new DownloadEntity(); entity.setUrl(url); entity.setFileName("ssssssssssssssssss"); - entity.setDownloadPath(key); + entity.setFilePath(key); DTaskWrapper dte = new DTaskWrapper(entity); diff --git a/app/src/main/java/com/arialyy/simple/MainActivity.java b/app/src/main/java/com/arialyy/simple/MainActivity.java index 0f354836..3769f716 100644 --- a/app/src/main/java/com/arialyy/simple/MainActivity.java +++ b/app/src/main/java/com/arialyy/simple/MainActivity.java @@ -38,12 +38,14 @@ import com.arialyy.simple.base.adapter.AbsRVAdapter; import com.arialyy.simple.base.adapter.RvItemClickSupport; import com.arialyy.simple.core.download.DownloadActivity; import com.arialyy.simple.core.download.FtpDownloadActivity; +import com.arialyy.simple.core.download.SFtpDownloadActivity; import com.arialyy.simple.core.download.group.DownloadGroupActivity; import com.arialyy.simple.core.download.group.FTPDirDownloadActivity; import com.arialyy.simple.core.download.m3u8.M3U8LiveDLoadActivity; import com.arialyy.simple.core.download.m3u8.M3U8VodDLoadActivity; import com.arialyy.simple.core.upload.FtpUploadActivity; import com.arialyy.simple.core.upload.HttpUploadActivity; +import com.arialyy.simple.core.upload.SFtpUploadActivity; import com.arialyy.simple.databinding.ActivityMainBinding; import com.arialyy.simple.modlue.CommonModule; import com.arialyy.simple.to.NormalTo; @@ -116,6 +118,14 @@ public class MainActivity extends BaseActivity { module.startNextActivity(MainActivity.this, data.get(position), M3U8LiveDLoadActivity.class); break; + case 8: // d_sftp + module.startNextActivity(MainActivity.this, data.get(position), + SFtpDownloadActivity.class); + break; + case 9: // u_sftp + module.startNextActivity(MainActivity.this, data.get(position), + SFtpUploadActivity.class); + break; } } }); @@ -132,7 +142,7 @@ public class MainActivity extends BaseActivity { T.showShort(MainActivity.this, "没有文件读写权限"); finish(); } - }, Manifest.permission.WRITE_EXTERNAL_STORAGE); + }, Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE); } } } diff --git a/app/src/main/java/com/arialyy/simple/base/BaseApplication.java b/app/src/main/java/com/arialyy/simple/base/BaseApplication.java index d082101e..15d6f2fc 100644 --- a/app/src/main/java/com/arialyy/simple/base/BaseApplication.java +++ b/app/src/main/java/com/arialyy/simple/base/BaseApplication.java @@ -21,7 +21,7 @@ import android.os.StrictMode; import com.arialyy.aria.core.Aria; import com.arialyy.frame.core.AbsFrame; import com.arialyy.simple.BuildConfig; -import com.squareup.leakcanary.LeakCanary; +//import com.squareup.leakcanary.LeakCanary; /** * Created by Lyy on 2016/9/27. @@ -42,12 +42,12 @@ public class BaseApplication extends Application { // .build()); //StrictMode.setThreadPolicy( // new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); - if (LeakCanary.isInAnalyzerProcess(this)) {//1 - //This process is dedicated to LeakCanary for heap analysis. - //You should not init your app in this process. - return; - } - LeakCanary.install(this); + //if (LeakCanary.isInAnalyzerProcess(this)) {//1 + // //This process is dedicated to LeakCanary for heap analysis. + // //You should not init your app in this process. + // return; + //} + //LeakCanary.install(this); } //registerReceiver(new ConnectionChangeReceiver(), new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); diff --git a/app/src/main/java/com/arialyy/simple/core/download/ComponentActivity.java b/app/src/main/java/com/arialyy/simple/core/download/ComponentActivity.java index a4f3c792..eb93a5f0 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/ComponentActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/ComponentActivity.java @@ -71,6 +71,10 @@ public class ComponentActivity extends BaseActivity { DownloadDialog dialog = new DownloadDialog(ComponentActivity.this); dialog.show(); break; + case 2: + new DownloadDialogFragment(ComponentActivity.this).show(getSupportFragmentManager(), + "df"); + break; } } }); diff --git a/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java index fa83b425..adf1cae1 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java @@ -28,7 +28,8 @@ import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.base.adapter.RvItemClickSupport; import com.arialyy.simple.common.NormalToAdapter; -import com.arialyy.simple.core.download.mutil.MultiTaskActivity; +import com.arialyy.simple.core.download.fragment.MultiFragmentActivity; +import com.arialyy.simple.core.download.mutil.MultiTaskListActivity; import com.arialyy.simple.databinding.ActivityDownloadMeanBinding; import com.arialyy.simple.modlue.CommonModule; import com.arialyy.simple.to.NormalTo; @@ -76,7 +77,7 @@ public class DownloadActivity extends BaseActivity break; case 1: module.startNextActivity(DownloadActivity.this, data.get(position), - MultiTaskActivity.class); + MultiTaskListActivity.class); break; case 2: module.startNextActivity(DownloadActivity.this, data.get(position), @@ -87,7 +88,9 @@ public class DownloadActivity extends BaseActivity KotlinDownloadActivity.class); break; case 4: - // 服务中 + // 多fragment + module.startNextActivity(DownloadActivity.this, data.get(position), + MultiFragmentActivity.class); break; case 5: // 组件中使用 diff --git a/app/src/main/java/com/arialyy/simple/core/download/DownloadDialog.java b/app/src/main/java/com/arialyy/simple/core/download/DownloadDialog.java index d4075d0e..de3c5d70 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/DownloadDialog.java +++ b/app/src/main/java/com/arialyy/simple/core/download/DownloadDialog.java @@ -24,7 +24,7 @@ import android.widget.TextView; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.CommonUtil; import com.arialyy.frame.core.AbsDialog; import com.arialyy.simple.R; @@ -66,10 +66,7 @@ public class DownloadDialog extends AbsDialog implements View.OnClickListener { int p = (int) (entity.getCurrentProgress() * 100 / entity.getFileSize()); mPb.setProgress(p); int state = entity.getState(); - setBtState(state != DownloadEntity.STATE_RUNNING); mTaskId = entity.getId(); - } else { - setBtState(true); } mStart.setOnClickListener(this); mCancel.setOnClickListener(this); @@ -106,16 +103,13 @@ public class DownloadDialog extends AbsDialog implements View.OnClickListener { @Download.onTaskPre public void onTaskPre(DownloadTask task) { mSize.setText(CommonUtil.formatFileSize(task.getFileSize())); - setBtState(false); } @Download.onTaskStop public void onTaskStop(DownloadTask task) { - setBtState(true); mSpeed.setText(task.getConvertSpeed()); } @Download.onTaskCancel public void onTaskCancel(DownloadTask task) { - setBtState(true); mPb.setProgress(0); mSpeed.setText(task.getConvertSpeed()); } @@ -131,8 +125,4 @@ public class DownloadDialog extends AbsDialog implements View.OnClickListener { } - private void setBtState(boolean startEnable) { - mStart.setEnabled(startEnable); - mCancel.setEnabled(!startEnable); - } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/DownloadDialogFragment.java b/app/src/main/java/com/arialyy/simple/core/download/DownloadDialogFragment.java index b6755c18..2929a91f 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/DownloadDialogFragment.java +++ b/app/src/main/java/com/arialyy/simple/core/download/DownloadDialogFragment.java @@ -8,8 +8,8 @@ import android.widget.Toast; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.CommonUtil; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseDialog; @@ -33,8 +33,8 @@ import com.arialyy.simple.util.AppUtil; @Override protected void init(Bundle savedInstanceState) { super.init(savedInstanceState); - Aria.download(getContext()).register(); - mEntity = Aria.download(getContext()).getFirstDownloadEntity(DOWNLOAD_URL); + Aria.download(this).register(); + mEntity = Aria.download(this).getFirstDownloadEntity(DOWNLOAD_URL); if (mEntity != null) { if (mEntity.getState() == IEntity.STATE_RUNNING) { getBinding().setStateStr(getString(R.string.stop)); @@ -46,7 +46,6 @@ import com.arialyy.simple.util.AppUtil; getBinding().setProgress((int) (mEntity.getCurrentProgress() * 100 / mEntity.getFileSize())); } mRootView.findViewById(R.id.start).setOnClickListener(this); - mRootView.findViewById(R.id.stop).setOnClickListener(this); mRootView.findViewById(R.id.cancel).setOnClickListener(this); } @@ -56,7 +55,7 @@ import com.arialyy.simple.util.AppUtil; @Override public void onDestroy() { super.onDestroy(); - Aria.download(getContext()).unRegister(); + Aria.download(this).unRegister(); } @Download.onPre(DOWNLOAD_URL) protected void onPre(DownloadTask task) { @@ -103,7 +102,7 @@ import com.arialyy.simple.util.AppUtil; switch (view.getId()) { case R.id.start: if (!AppUtil.chekEntityValid(mEntity)) { - Aria.download(getContext()) + Aria.download(this) .load(DOWNLOAD_URL) .setFilePath(Environment.getExternalStorageDirectory().getPath() + "/放置江湖.apk") .create(); @@ -121,7 +120,7 @@ import com.arialyy.simple.util.AppUtil; case R.id.cancel: if (AppUtil.chekEntityValid(mEntity)) { - Aria.download(getContext()).load(mEntity.getId()).cancel(); + Aria.download(this).load(mEntity.getId()).cancel(); } break; } diff --git a/app/src/main/java/com/arialyy/simple/core/download/DownloadModule.java b/app/src/main/java/com/arialyy/simple/core/download/DownloadModule.java index 046eb007..4b87c0a8 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/DownloadModule.java +++ b/app/src/main/java/com/arialyy/simple/core/download/DownloadModule.java @@ -71,6 +71,29 @@ public class DownloadModule extends BaseModule { return list; } + /** + * 创建m3u8下载地址 + */ + public List createM3u8TestList(){ + String[] names = new String[]{"m3u8test1.ts", "m3u8test2.ts"}; + String[] urls = new String[]{ + "http://qn.shytong.cn/b83137769ff6b555/11b0c9970f9a3fa0.mp4.m3u8", + "http://qn.shytong.cn/8f4011f2a31bd347da42b54fe37a7ba8-transcode.m3u8" + }; + List list = new ArrayList<>(); + int i = 0; + for (String name : names) { + FileListEntity entity = new FileListEntity(); + entity.name = name; + entity.key = urls[i]; + entity.type = 2; + entity.downloadPath = Environment.getExternalStorageDirectory() + "/Download/" + name; + list.add(entity); + i++; + } + return list; + } + private String[] getStringArray(int array) { return getContext().getResources().getStringArray(array); } @@ -91,7 +114,7 @@ public class DownloadModule extends BaseModule { FileListEntity entity = new FileListEntity(); entity.urls = getStringArray(urls); entity.names = getStringArray(names); - entity.isGroup = true; + entity.type = 1; entity.name = alias; entity.key = CommonUtil.getMd5Code(Arrays.asList(entity.urls)); entity.downloadPath = Environment.getExternalStorageDirectory() + "/Download/" + alias; @@ -106,7 +129,7 @@ public class DownloadModule extends BaseModule { DownloadEntity entity = new DownloadEntity(); entity.setFileName(name); entity.setUrl(downloadUrl); - entity.setDownloadPath(path); + entity.setFilePath(path); return entity; } } \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/core/download/DownloadPopupWindow.java b/app/src/main/java/com/arialyy/simple/core/download/DownloadPopupWindow.java index 95e5856d..4c7e2bd6 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/DownloadPopupWindow.java +++ b/app/src/main/java/com/arialyy/simple/core/download/DownloadPopupWindow.java @@ -26,8 +26,8 @@ import android.widget.TextView; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.CommonUtil; import com.arialyy.frame.core.AbsPopupWindow; import com.arialyy.simple.R; diff --git a/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadActivity.java index 1c9ff07a..0eddb461 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadActivity.java @@ -18,37 +18,33 @@ package com.arialyy.simple.core.download; import android.os.Bundle; import android.util.Log; import android.view.View; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.common.ProtocolType; -import com.arialyy.aria.core.common.controller.ControllerType; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.FtpOption; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; -import com.arialyy.frame.util.show.L; -import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; -import com.arialyy.simple.common.DirChooseDialog; -import com.arialyy.simple.common.ModifyUrlDialog; -import com.arialyy.simple.databinding.ActivityFtpDownloadBinding; -import com.arialyy.simple.util.AppUtil; +import com.arialyy.simple.databinding.ActivitySingleBinding; +import com.arialyy.simple.widget.ProgressLayout; import java.io.File; -import java.io.IOException; /** * Created by lyy on 2017/7/25. * Ftp下载 */ -public class FtpDownloadActivity extends BaseActivity { +public class FtpDownloadActivity extends BaseActivity { private String mUrl, mFilePath; private FtpDownloadModule mModule; private long mTaskId; - private String user = "tester", passw = "password"; + private String user = "lao", passw = "123456"; @Override protected void init(Bundle savedInstanceState) { super.init(savedInstanceState); @@ -63,136 +59,123 @@ public class FtpDownloadActivity extends BaseActivity " + task.getDownloadEntity().getFileName()); + getBinding().pl.setInfo(task.getEntity()); + } } - @Download.onTaskPre() protected void onTaskPre(DownloadTask task) { - L.d(TAG, "ftp task pre"); - getBinding().setFileSize(task.getConvertFileSize()); + @Download.onPre + protected void onPre(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } } - @Download.onTaskStart() void taskStart(DownloadTask task) { - L.d(TAG, "ftp task create"); + @Download.onTaskStart + void taskStart(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); + } } - @Download.onTaskRunning() protected void running(DownloadTask task) { - getBinding().setProgress(task.getPercent()); - getBinding().setSpeed(task.getConvertSpeed()); + @Download.onTaskRunning + protected void running(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "isRunning" + "; state = " + task.getEntity().getState()); + getBinding().pl.setInfo(task.getEntity()); + } } - @Download.onTaskResume() void taskResume(DownloadTask task) { - L.d(TAG, "ftp task resume"); + @Download.onTaskResume + void taskResume(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "resume"); + getBinding().pl.setInfo(task.getEntity()); + } } - @Download.onTaskStop() void taskStop(DownloadTask task) { - L.d(TAG, "ftp task stop"); - getBinding().setSpeed(""); + @Download.onTaskStop + void taskStop(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "stop"); + getBinding().pl.setInfo(task.getEntity()); + } } - @Download.onTaskCancel() void taskCancel(DownloadTask task) { - getBinding().setSpeed(""); - getBinding().setProgress(0); + @Download.onTaskCancel + void taskCancel(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + mTaskId = -1; + Log.d(TAG, "cancel"); + getBinding().pl.setInfo(task.getEntity()); + } } - @Download.onTaskFail() void taskFail(DownloadTask task) { - L.d(TAG, "ftp task fail"); - getBinding().setStateStr(getString(R.string.resume)); + @Download.onTaskFail + void taskFail(DownloadTask task, Exception e) { + ALog.d(TAG, "下载失败"); + Toast.makeText(this, getString(R.string.download_fail), Toast.LENGTH_SHORT) + .show(); + if (task != null && task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } } - @Download.onTaskComplete() void taskComplete(DownloadTask task) { - getBinding().setSpeed(""); - getBinding().setProgress(100); - Log.d(TAG, "md5 ==> " + CommonUtil.getFileMD5(new File(task.getFilePath()))); - T.showShort(this, "文件:" + task.getEntity().getFileName() + ",下载完成"); + @Download.onTaskComplete + void taskComplete(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + Toast.makeText(this, getString(R.string.download_success), + Toast.LENGTH_SHORT).show(); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); + } } @Override protected int setLayoutId() { - return R.layout.activity_ftp_download; - } - - @Override protected void dataCallback(int result, Object data) { - super.dataCallback(result, data); - if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { - mModule.uploadUrl(this, String.valueOf(data)); - } else if (result == DirChooseDialog.DIR_CHOOSE_DIALOG_RESULT) { - mModule.updateFilePath(this, String.valueOf(data)); - } + return R.layout.activity_single; } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java b/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java index fbafda8f..97862a8b 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java +++ b/app/src/main/java/com/arialyy/simple/core/download/FtpDownloadModule.java @@ -33,7 +33,7 @@ public class FtpDownloadModule extends BaseViewModule { private final String FTP_URL_KEY = "FTP_URL_KEY"; private final String FTP_PATH_KEY = "FTP_PATH_KEY"; - private final String ftpDefUrl = "ftp://9.9.9.50:22/Cyberduck-6.9.4.30164.zip"; + private final String ftpDefUrl = "ftp://192.168.0.104:2121/qqqq.exe"; private final String ftpDefPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); @@ -41,12 +41,36 @@ public class FtpDownloadModule extends BaseViewModule { private DownloadEntity singDownloadInfo; /** - * xx * 单任务下载的信息 */ LiveData getFtpDownloadInfo(Context context) { - String url = AppUtil.getConfigValue(context, FTP_URL_KEY, ftpDefUrl); - String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, ftpDefPath); + //String url = AppUtil.getConfigValue(context, FTP_URL_KEY, ftpDefUrl); + //String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, ftpDefPath); + //String url = "ftp://9.9.9.72:2121/Cyberduck-6.9.4.30164.zip"; + + singDownloadInfo = Aria.download(context).getFirstDownloadEntity(ftpDefUrl); + if (singDownloadInfo == null) { + singDownloadInfo = new DownloadEntity(); + singDownloadInfo.setUrl(ftpDefUrl); + String name = getFileName(ftpDefUrl); + singDownloadInfo.setFileName(name); + singDownloadInfo.setFilePath(ftpDefPath + name); + } else { + AppUtil.setConfigValue(context, FTP_PATH_KEY, singDownloadInfo.getFilePath()); + AppUtil.setConfigValue(context, FTP_URL_KEY, singDownloadInfo.getUrl()); + } + liveData.postValue(singDownloadInfo); + + return liveData; + } + + /** + * 单任务下载的信息 + */ + LiveData getSftpDownloadInfo(Context context) { + //String url = AppUtil.getConfigValue(context, FTP_URL_KEY, ftpDefUrl); + //String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, ftpDefPath); + String url = "ftp://9.9.9.72:22/Cyberduck-6.9.4.30164.zip"; singDownloadInfo = Aria.download(context).getFirstDownloadEntity(url); if (singDownloadInfo == null) { @@ -54,7 +78,7 @@ public class FtpDownloadModule extends BaseViewModule { singDownloadInfo.setUrl(url); String name = getFileName(ftpDefUrl); singDownloadInfo.setFileName(name); - singDownloadInfo.setFilePath(filePath + name); + singDownloadInfo.setFilePath(ftpDefPath + name); } else { AppUtil.setConfigValue(context, FTP_PATH_KEY, singDownloadInfo.getFilePath()); AppUtil.setConfigValue(context, FTP_URL_KEY, singDownloadInfo.getUrl()); diff --git a/app/src/main/java/com/arialyy/simple/core/download/HighestPriorityActivity.java b/app/src/main/java/com/arialyy/simple/core/download/HighestPriorityActivity.java index dec8a60f..a20803b2 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/HighestPriorityActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/HighestPriorityActivity.java @@ -25,10 +25,10 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.frame.util.show.L; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; diff --git a/app/src/main/java/com/arialyy/simple/core/download/HttpDownloadModule.java b/app/src/main/java/com/arialyy/simple/core/download/HttpDownloadModule.java index 609ff721..b68dd6cb 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/HttpDownloadModule.java +++ b/app/src/main/java/com/arialyy/simple/core/download/HttpDownloadModule.java @@ -33,12 +33,13 @@ public class HttpDownloadModule extends BaseViewModule { private final String HTTP_PATH_KEY = "HTTP_PATH_KEY"; private final String defUrl = - "http://hzdown.muzhiwan.com/2017/05/08/nl.noio.kingdom_59104935e56f0.apk1"; + "http://hzdown.muzhiwan.com/2017/05/08/nl.noio.kingdom_59104935e56f0.apk"; + //"https://ss1.baidu.com/-4o3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/sign=a9e671b9a551f3dedcb2bf64a4eff0ec/4610b912c8fcc3cef70d70409845d688d53f20f7.jpg"; //"http://9.9.9.205:5000/download/Cyberduck-6.9.4.30164.zip"; //"http://202.98.201.103:7000/vrs/TPK/ZTC440402001Z.tpk"; private final String defFilePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath() - + "/ZTC440402001Z.tpk"; + + "/update.zip"; private MutableLiveData liveData = new MutableLiveData<>(); private DownloadEntity singDownloadInfo; @@ -47,16 +48,20 @@ public class HttpDownloadModule extends BaseViewModule { * 单任务下载的信息 */ LiveData getHttpDownloadInfo(Context context) { - String url = AppUtil.getConfigValue(context, HTTP_URL_KEY, defUrl); - //String url = - // "http://sdkdown.muzhiwan.com/openfile/2019/05/21/com.netease.tom.mzw_5ce3ef8754d05.apk"; - String filePath = AppUtil.getConfigValue(context, HTTP_PATH_KEY, defFilePath); + //String url = AppUtil.getConfigValue(context, HTTP_URL_KEY, defUrl); + //String url = "http://fdfs.speedata.cn:9989/group1/M00/00/05/rBGFrl3fdAKAVJwfMtSa9R18wLU139.zip"; + //String url = "http://9.9.9.28:8088/files/update.zip"; + String url = "https://y.qq.com/download/import/QQMusic-import-1.2.1.zip"; + //String url = "https://gitee.com/huang-junhua/iptv/raw/master/guonei.m3u8"; + //String url = "http://v.kjjl100.com/kz/zx/cj/2020cjswxdb/1.mp4"; + //String url = "https://static.runoob.com/images/demo/demo2.jpg"; + String filePath = "/mnt/sdcard/qq.zip"; singDownloadInfo = Aria.download(context).getFirstDownloadEntity(url); if (singDownloadInfo == null) { singDownloadInfo = new DownloadEntity(); singDownloadInfo.setUrl(url); - File file = new File(defFilePath); + File file = new File(filePath); singDownloadInfo.setFilePath(filePath); singDownloadInfo.setFileName(file.getName()); } else { diff --git a/app/src/main/java/com/arialyy/simple/core/download/KotlinDownloadActivity.kt b/app/src/main/java/com/arialyy/simple/core/download/KotlinDownloadActivity.kt index 1550420c..0afb5956 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/KotlinDownloadActivity.kt +++ b/app/src/main/java/com/arialyy/simple/core/download/KotlinDownloadActivity.kt @@ -30,9 +30,8 @@ import androidx.lifecycle.ViewModelProviders import com.arialyy.annotations.Download import com.arialyy.aria.core.Aria import com.arialyy.aria.core.download.DownloadEntity -import com.arialyy.aria.core.download.DownloadTask import com.arialyy.aria.core.inf.IEntity -import com.arialyy.aria.core.scheduler.ISchedulers +import com.arialyy.aria.core.listener.ISchedulers import com.arialyy.aria.util.ALog import com.arialyy.aria.util.CommonUtil import com.arialyy.frame.util.show.T @@ -51,7 +50,7 @@ class KotlinDownloadActivity : BaseActivity() { private var mUrl: String? = null private var mFilePath: String? = null private var mModule: HttpDownloadModule? = null - private val mTaskId: Long = -1 + private var mTaskId: Long = -1 internal var receiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive( @@ -59,10 +58,22 @@ class KotlinDownloadActivity : BaseActivity() { intent: Intent ) { if (intent.action == ISchedulers.ARIA_TASK_INFO_ACTION) { - ALog.d(TAG, "state = " + intent.getIntExtra(ISchedulers.TASK_STATE, -1)) - ALog.d(TAG, "type = " + intent.getIntExtra(ISchedulers.TASK_TYPE, -1)) - ALog.d(TAG, "speed = " + intent.getLongExtra(ISchedulers.TASK_SPEED, -1)) - ALog.d(TAG, "percent = " + intent.getIntExtra(ISchedulers.TASK_PERCENT, -1)) + ALog.d( + TAG, + "state = " + intent.getIntExtra(ISchedulers.TASK_STATE, -1) + ) + ALog.d( + TAG, "type = " + intent.getIntExtra(ISchedulers.TASK_TYPE, -1) + ) + ALog.d( + TAG, + "speed = " + intent.getLongExtra(ISchedulers.TASK_SPEED, -1) + ) + ALog.d( + TAG, "percent = " + intent.getIntExtra( + ISchedulers.TASK_PERCENT, -1 + ) + ) ALog.d( TAG, "entity = " + intent.getParcelableExtra( ISchedulers.TASK_ENTITY @@ -93,7 +104,7 @@ class KotlinDownloadActivity : BaseActivity() { .get(HttpDownloadModule::class.java) mModule!!.getHttpDownloadInfo(this) .observe(this, Observer { entity -> - if (entity == null || entity.id < 0) { + if (entity == null) { return@Observer } if (entity.state == IEntity.STATE_STOP) { @@ -167,28 +178,28 @@ class KotlinDownloadActivity : BaseActivity() { } @Download.onWait - fun onWait(task: DownloadTask) { + fun onWait(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { Log.d(TAG, "wait ==> " + task.downloadEntity.fileName) } } @Download.onPre - fun onPre(task: DownloadTask) { + fun onPre(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { binding.stateStr = getString(R.string.stop) } } @Download.onTaskStart - fun taskStart(task: DownloadTask) { + fun taskStart(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { binding.fileSize = task.convertFileSize } } @Download.onTaskRunning - fun running(task: DownloadTask) { + fun running(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { //Log.d(TAG, task.getKey()); val len = task.fileSize @@ -202,14 +213,14 @@ class KotlinDownloadActivity : BaseActivity() { } @Download.onTaskResume - fun taskResume(task: DownloadTask) { + fun taskResume(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { binding.stateStr = getString(R.string.stop) } } @Download.onTaskStop - fun taskStop(task: DownloadTask) { + fun taskStop(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { binding.stateStr = getString(R.string.resume) binding.speed = "" @@ -217,7 +228,7 @@ class KotlinDownloadActivity : BaseActivity() { } @Download.onTaskCancel - fun taskCancel(task: DownloadTask) { + fun taskCancel(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { binding.progress = 0 binding.stateStr = getString(R.string.start) @@ -231,7 +242,7 @@ class KotlinDownloadActivity : BaseActivity() { */ @Download.onTaskFail fun taskFail( - task: DownloadTask, + task: com.arialyy.aria.core.task.DownloadTask, e: Exception ) { if (task.key == mUrl) { @@ -242,7 +253,7 @@ class KotlinDownloadActivity : BaseActivity() { } @Download.onTaskComplete - fun taskComplete(task: DownloadTask) { + fun taskComplete(task: com.arialyy.aria.core.task.DownloadTask) { if (task.key == mUrl) { binding.progress = 100 @@ -275,15 +286,21 @@ class KotlinDownloadActivity : BaseActivity() { } private fun startD() { - Aria.download(this) - .load(mUrl!!) - //.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") - //.addHeader("Accept-Encoding", "gzip, deflate") - //.addHeader("DNT", "1") - //.addHeader("Cookie", "BAIDUID=648E5FF020CC69E8DD6F492D1068AAA9:FG=1; BIDUPSID=648E5FF020CC69E8DD6F492D1068AAA9; PSTM=1519099573; BD_UPN=12314753; locale=zh; BDSVRTM=0") - .useServerFileName(true) - .setFilePath(mFilePath!!, true) - .create() + if (mTaskId == -1L) { + + mTaskId = Aria.download(this) + .load(mUrl!!) + //.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") + //.addHeader("Accept-Encoding", "gzip, deflate") + //.addHeader("DNT", "1") + //.addHeader("Cookie", "BAIDUID=648E5FF020CC69E8DD6F492D1068AAA9:FG=1; BIDUPSID=648E5FF020CC69E8DD6F492D1068AAA9; PSTM=1519099573; BD_UPN=12314753; locale=zh; BDSVRTM=0") + .setFilePath(mFilePath!!, true) + .create() + } else { + Aria.download(this) + .load(mTaskId) + .resume() + } } override fun onStop() { diff --git a/app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java new file mode 100644 index 00000000..ef00d1b9 --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/core/download/SFtpDownloadActivity.java @@ -0,0 +1,233 @@ +/* + * 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.simple.core.download; + +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProviders; +import com.arialyy.annotations.Download; +import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.SFtpOption; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import com.arialyy.simple.R; +import com.arialyy.simple.base.BaseActivity; +import com.arialyy.simple.common.DirChooseDialog; +import com.arialyy.simple.common.ModifyUrlDialog; +import com.arialyy.simple.databinding.ActivitySingleBinding; +import com.arialyy.simple.widget.ProgressLayout; +import java.io.File; +import java.io.IOException; + +/** + * Created by lyy on 2017/7/25. + * Ftp下载 + */ +public class SFtpDownloadActivity extends BaseActivity { + private String mUrl, mFilePath; + private FtpDownloadModule mModule; + private long mTaskId; + private String user = "tester", passw = "password"; + private String prvKeyPath; + private String pubKeyPath; + + @Override protected void init(Bundle savedInstanceState) { + super.init(savedInstanceState); + setTitle("FTP文件下载"); + Aria.download(this).register(); + prvKeyPath = getFilesDir().getPath() + "/id_rsa"; + pubKeyPath = getFilesDir().getPath() + "/id_rsa.pub"; + copyKey(); + mModule = ViewModelProviders.of(this).get(FtpDownloadModule.class); + mModule.getSftpDownloadInfo(this).observe(this, new Observer() { + + @Override public void onChanged(@Nullable DownloadEntity entity) { + if (entity == null) { + return; + } + mTaskId = entity.getId(); + mUrl = entity.getUrl(); + mFilePath = entity.getFilePath(); + getBinding().pl.setInfo(entity); + } + }); + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + mTaskId = Aria.download(this).loadFtp(mUrl) + .setFilePath(mFilePath) + .ignoreFilePathOccupy() + .sftpOption(getFtpOption()) + .create(); + } + + @Override public void stop(View v, AbsEntity entity) { + Aria.download(this).loadFtp(mTaskId).stop(); + } + + @Override public void resume(View v, AbsEntity entity) { + Aria.download(this) + .loadFtp(mTaskId) + .sftpOption(getFtpOption()) + .resume(); + } + + @Override public void cancel(View v, AbsEntity entity) { + Aria.download(this).loadFtp(mTaskId).cancel(true); + mTaskId = -1; + } + }); + } + + private void copyKey() { + try { + // 为了测试方便,每次重新覆盖证书文件 + File prvKey = new File(prvKeyPath); + //FileUtil.deleteFile(prvKey); + if (!prvKey.exists()) { + FileUtil.createFileFormInputStream(getAssets().open("id_rsa"), prvKeyPath); + } + File pubKey = new File(pubKeyPath); + //FileUtil.deleteFile(pubKey); + if (!pubKey.exists()) { + FileUtil.createFileFormInputStream(getAssets().open("id_rsa.pub"), pubKeyPath); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private SFtpOption getFtpOption() { + SFtpOption option = new SFtpOption(); + option.login(user, passw); // 账号密码登录 + // 证书登录 + option.setPrvKey(prvKeyPath); // 设置私钥 + option.setPrvKeyPass("123456"); // 设置私钥密码(如果没有密码,可以不设置) + option.setPubKey(pubKeyPath); // 设置公钥 + option.setKnowHostPath(getFilesDir().getPath() + "/know_hosts"); + + //option.setServerIdentifier(FtpOption.FTPServerIdentifier.SYST_NT); + //option.setConnectionMode(FtpConnectionMode.DATA_CONNECTION_MODE_ACTIVITY); + return option; + } + + public void chooseUrl() { + ModifyUrlDialog dialog = + new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl); + dialog.show(getSupportFragmentManager(), "ModifyUrlDialog"); + } + + public void chooseFilePath() { + DirChooseDialog dirChooseDialog = new DirChooseDialog(this); + dirChooseDialog.show(getSupportFragmentManager(), "DirChooseDialog"); + } + + @Download.onWait + void onWait(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + Log.d(TAG, "wait ==> " + task.getDownloadEntity().getFileName()); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Download.onPre + protected void onPre(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Download.onTaskStart + void taskStart(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); + } + } + + @Download.onTaskRunning + protected void running(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "isRunning" + "; state = " + task.getEntity().getState()); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Download.onTaskResume + void taskResume(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "resume"); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Download.onTaskStop + void taskStop(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "stop"); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Download.onTaskCancel + void taskCancel(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + mTaskId = -1; + Log.d(TAG, "cancel"); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Download.onTaskFail + void taskFail(DownloadTask task, Exception e) { + ALog.d(TAG, "下载失败"); + Toast.makeText(this, getString(R.string.download_fail), Toast.LENGTH_SHORT) + .show(); + if (task != null && task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Download.onTaskComplete + void taskComplete(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + Toast.makeText(this, getString(R.string.download_success), + Toast.LENGTH_SHORT).show(); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Override protected int setLayoutId() { + return R.layout.activity_single; + } + + @Override protected void dataCallback(int result, Object data) { + super.dataCallback(result, data); + if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { + mModule.uploadUrl(this, String.valueOf(data)); + } else if (result == DirChooseDialog.DIR_CHOOSE_DIALOG_RESULT) { + mModule.updateFilePath(this, String.valueOf(data)); + } + } +} diff --git a/app/src/main/java/com/arialyy/simple/core/download/SimpleNotification.java b/app/src/main/java/com/arialyy/simple/core/download/SimpleNotification.java index f3ac70df..ab6e6043 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/SimpleNotification.java +++ b/app/src/main/java/com/arialyy/simple/core/download/SimpleNotification.java @@ -22,7 +22,7 @@ //import android.support.v4.app.NotificationCompat; //import com.arialyy.annotations.Download; //import com.arialyy.aria.core.Aria; -//import com.arialyy.aria.core.download.DownloadTask; +//import com.arialyy.aria.core.task.DownloadTask; //import com.arialyy.simple.R; // ///** diff --git a/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java index 151d7fa2..430e7ca5 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java @@ -31,23 +31,24 @@ import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.common.controller.ControllerType; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.HttpOption; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.aria.core.inf.IHttpFileLenAdapter; -import com.arialyy.aria.core.scheduler.ISchedulers; +import com.arialyy.aria.core.download.DownloadTaskListener; +import com.arialyy.aria.core.download.target.HttpNormalTarget; +import com.arialyy.aria.core.listener.ISchedulers; +import com.arialyy.aria.core.processor.IHttpFileLenAdapter; +import com.arialyy.aria.core.scheduler.NormalTaskListenerInterface; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; -import com.arialyy.simple.common.ModifyPathDialog; -import com.arialyy.simple.common.ModifyUrlDialog; import com.arialyy.simple.databinding.ActivitySingleBinding; -import com.arialyy.simple.util.AppUtil; +import com.arialyy.simple.widget.ProgressLayout; import java.io.File; -import java.io.IOException; import java.util.List; import java.util.Map; @@ -97,41 +98,51 @@ public class SingleTaskActivity extends BaseActivity { return; } mTaskId = entity.getId(); - if (entity.getState() == IEntity.STATE_STOP) { - getBinding().setStateStr(getString(R.string.resume)); - } else if (entity.getState() == IEntity.STATE_RUNNING) { - getBinding().setStateStr(getString(R.string.stop)); - } - - if (entity.getFileSize() != 0) { - getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize())); - getBinding().setProgress(entity.isComplete() ? 100 - : (int) (entity.getCurrentProgress() * 100 / entity.getFileSize())); - } - getBinding().setUrl(entity.getUrl()); - getBinding().setFilePath(entity.getFilePath()); mUrl = entity.getUrl(); mFilePath = entity.getFilePath(); + getBinding().pl.setInfo(entity); } }); - getBinding().setViewModel(this); - try { - getBinding().codeView.setSource(AppUtil.getHelpCode(this, "HttpDownload.java")); - } catch (IOException e) { - e.printStackTrace(); - } - } + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + startD(); + } - public void chooseUrl() { - ModifyUrlDialog dialog = - new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl); - dialog.show(getSupportFragmentManager(), "ModifyUrlDialog"); - } + @Override public void stop(View v, AbsEntity entity) { + Aria.download(this) + .load(mTaskId) + .stop(); + } + + @Override public void resume(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId) + //.updateUrl(mUrl) + .resume(); + } - public void chooseFilePath() { - ModifyPathDialog dialog = - new ModifyPathDialog(this, getString(R.string.modify_file_path), mFilePath); - dialog.show(getSupportFragmentManager(), "ModifyPathDialog"); + @Override public void cancel(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId).cancel(false); + // //1. 删除本地文件目录 + // File localDirFile = new File(mFilePath); + // if (localDirFile.exists()) { + // FileUtil.deleteDir(localDirFile.getParentFile()); + // } + // + // //2. 删除记录 + // HttpNormalTarget target = Aria.download(SingleTaskActivity.this).load(mTaskId); + // if (target != null) { + // // target.cancel(); + // target.removeRecord(); + // } + // + // List notCompleteTask = Aria.download(SingleTaskActivity.this).getAllNotCompleteTask(); + // if (notCompleteTask == null){ + // Log.d(TAG, "未完成的任务数:0"); + // return; + // } + // Log.d(TAG, "未完成的任务数:" + notCompleteTask.size()); + } + }); } @Override @@ -170,93 +181,95 @@ public class SingleTaskActivity extends BaseActivity { } if (speed > -1) { msg = item.getTitle().toString(); - Aria.download(this).setMaxSpeed(speed); T.showShort(this, msg); + Aria.get(this).getDownloadConfig().setMaxSpeed(speed); } return true; } @Download.onWait - void onWait(DownloadTask task) { + public void onWait(DownloadTask task) { if (task.getKey().equals(mUrl)) { Log.d(TAG, "wait ==> " + task.getDownloadEntity().getFileName()); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onPre - protected void onPre(DownloadTask task) { + public void onPre(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.stop)); + getBinding().pl.setInfo(task.getEntity()); } } + //@Override public void onTaskPre(DownloadTask task) { + // + //} + @Download.onTaskStart - void taskStart(DownloadTask task) { + public void onTaskStart(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setFileSize(task.getConvertFileSize()); + getBinding().pl.setInfo(task.getEntity()); ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); } } @Download.onTaskRunning - protected void running(DownloadTask task) { + public void onTaskRunning(DownloadTask task) { if (task.getKey().equals(mUrl)) { - ALog.d(TAG, "isRunning"); - //Log.d(TAG, task.getKey()); - long len = task.getFileSize(); - if (len == 0) { - getBinding().setProgress(0); - } else { - getBinding().setProgress(task.getPercent()); - } - getBinding().setSpeed(task.getConvertSpeed()); + //ALog.d(TAG, "isRunning" + "; state = " + task.getEntity().getState()); + getBinding().pl.setInfo(task.getEntity()); } } + //@Override public void onNoSupportBreakPoint(DownloadTask task) { + // + //} + @Download.onTaskResume - void taskResume(DownloadTask task) { + public void onTaskResume(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.stop)); + ALog.d(TAG, "resume"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskStop - void taskStop(DownloadTask task) { + public void onTaskStop(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.resume)); - getBinding().setSpeed(""); + ALog.d(TAG, "stop"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskCancel - void taskCancel(DownloadTask task) { + public void onTaskCancel(DownloadTask task) { if (task.getKey().equals(mUrl)) { mTaskId = -1; - getBinding().setProgress(0); - getBinding().setStateStr(getString(R.string.start)); - getBinding().setSpeed(""); Log.d(TAG, "cancel"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskFail - void taskFail(DownloadTask task, Exception e) { + public void onTaskFail(DownloadTask task, Exception e) { + ALog.d(TAG, "下载失败"); Toast.makeText(SingleTaskActivity.this, getString(R.string.download_fail), Toast.LENGTH_SHORT) .show(); if (task != null && task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.start)); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskComplete - void taskComplete(DownloadTask task) { + public void onTaskComplete(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setProgress(100); Toast.makeText(SingleTaskActivity.this, getString(R.string.download_success), Toast.LENGTH_SHORT).show(); - getBinding().setStateStr(getString(R.string.re_start)); - getBinding().setSpeed(""); - ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getFilePath()))); + ALog.d(TAG, "文件md5: e088677570afe2e9f847cc8159b932dd"); + ALog.d(TAG, "下载完成的文件md5: " + CommonUtil.getFileMD5(new File(task.getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); + getBinding().pl.setProgress(100); } } @@ -265,47 +278,18 @@ public class SingleTaskActivity extends BaseActivity { return R.layout.activity_single; } - public void onClick(View view) { - switch (view.getId()) { - case R.id.start: - if (mTaskId == -1) { - startD(); - break; - } - if (Aria.download(this).load(mTaskId).isRunning()) { - Aria.download(this).load(mTaskId).stop(); - } else { - Aria.download(this).load(mTaskId) - //.updateUrl("http://sdkdown.muzhiwan.com/openfile/2019/07/11/com.netease.syfz.mzw_5d26f8d9cee27.apk") - .resume(); - } - break; - case R.id.cancel: - Aria.download(this).load(mTaskId).cancel(true); - break; - } - } - private void startD() { + HttpOption option = new HttpOption(); + option.useServerFileName(true); + option.addHeader("1", "@") + .useServerFileName(true) + .setFileLenAdapter(new FileLenAdapter()); + //option.setRequestType(RequestEnum.POST); mTaskId = Aria.download(SingleTaskActivity.this) .load(mUrl) - .useServerFileName(true) .setFilePath(mFilePath, true) - .setFileLenAdapter(new IHttpFileLenAdapter() { - @Override public long handleFileLen(Map> headers) { - - List sLength = headers.get("Content-Length"); - if (sLength == null || sLength.isEmpty()) { - return -1; - } - String temp = sLength.get(0); - - return Long.parseLong(temp); - } - }) - .option() - .addHeader("1", "@") - .controller(ControllerType.CREATE_CONTROLLER) + .option(option) + .ignoreCheckPermissions() .create(); } @@ -319,12 +303,16 @@ public class SingleTaskActivity extends BaseActivity { return super.dispatchTouchEvent(ev); } - @Override protected void dataCallback(int result, Object data) { - super.dataCallback(result, data); - if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { - mModule.uploadUrl(this, String.valueOf(data)); - } else if (result == ModifyPathDialog.MODIFY_PATH_RESULT) { - mModule.updateFilePath(this, String.valueOf(data)); + static class FileLenAdapter implements IHttpFileLenAdapter { + @Override public long handleFileLen(Map> headers) { + + List sLength = headers.get("Content-Length"); + if (sLength == null || sLength.isEmpty()) { + return -1; + } + String temp = sLength.get(0); + + return Long.parseLong(temp); } } } \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/core/download/fragment/DownloadFragment.java b/app/src/main/java/com/arialyy/simple/core/download/fragment/DownloadFragment.java index 994d0295..c821a197 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/fragment/DownloadFragment.java +++ b/app/src/main/java/com/arialyy/simple/core/download/fragment/DownloadFragment.java @@ -19,24 +19,20 @@ package com.arialyy.simple.core.download.fragment; import android.os.Bundle; import android.os.Environment; import android.view.View; -import android.widget.Button; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.frame.core.AbsFragment; import com.arialyy.simple.R; import com.arialyy.simple.databinding.FragmentDownloadBinding; +import com.arialyy.simple.widget.ProgressLayout; /** * Created by lyy on 2017/1/4. */ -public class DownloadFragment extends AbsFragment - implements View.OnClickListener { - Button mStart; - Button mCancel; +public class DownloadFragment extends AbsFragment { private long mTaskId = -1; private static final String DOWNLOAD_URL = @@ -44,82 +40,58 @@ public class DownloadFragment extends AbsFragment private static final String FILE_NAME = "王者军团"; @Override protected void init(Bundle savedInstanceState) { - mStart = mRootView.findViewById(R.id.start); - mCancel = mRootView.findViewById(R.id.cancel); - mStart.setOnClickListener(this); - mCancel.setOnClickListener(this); - + Aria.download(this).register(); DownloadEntity entity = Aria.download(this).getFirstDownloadEntity(DOWNLOAD_URL); - if (entity != null) { - getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize())); - getBinding().setProgress(entity.getPercent()); - if (entity.getState() == IEntity.STATE_RUNNING) { - getBinding().setStateStr(getString(R.string.stop)); - } else { - getBinding().setStateStr(getString(R.string.resume)); - } - mTaskId = entity.getId(); - } else { - getBinding().setStateStr(getString(R.string.start)); + if (entity == null){ + entity = new DownloadEntity(); + entity.setUrl(DOWNLOAD_URL); } - getBinding().setUrl(DOWNLOAD_URL); - getBinding().setFileName(FILE_NAME); - Aria.download(this).register(); - } + getBinding().pl.setInfo(entity); + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + mTaskId = Aria.download(this) + .load(DOWNLOAD_URL) + .setFilePath( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + .getPath() + String.format("/%s.apk", + FILE_NAME)) + .create(); + } - public void onClick(View view) { - switch (view.getId()) { - case R.id.start: - if (mTaskId == -1) { - mTaskId = Aria.download(this) - .load(DOWNLOAD_URL) - .setFilePath( - Environment.getExternalStorageDirectory().getPath() + String.format("%s.apk", - FILE_NAME)) - .create(); - getBinding().setStateStr(getString(R.string.stop)); - break; - } - if (Aria.download(this).load(mTaskId).isRunning()) { - Aria.download(this).load(mTaskId).stop(); - getBinding().setStateStr(getString(R.string.resume)); - } else { - Aria.download(this).load(mTaskId).resume(); - getBinding().setStateStr(getString(R.string.stop)); - } - break; - - case R.id.cancel: - Aria.download(this).load(mTaskId).cancel(); - getBinding().setStateStr(getString(R.string.start)); + @Override public void stop(View v, AbsEntity entity) { + Aria.download(this) + .load(mTaskId) + .stop(); + } + + @Override public void resume(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId) + //.updateUrl(mUrl) + .resume(); + } + + @Override public void cancel(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId).cancel(false); mTaskId = -1; - break; - } + } + }); } @Download.onTaskPre public void onTaskPre(DownloadTask task) { - getBinding().setFileSize(task.getConvertFileSize()); + getBinding().pl.setInfo(task.getEntity()); } @Download.onTaskStop public void onTaskStop(DownloadTask task) { - getBinding().setSpeed(""); - getBinding().setStateStr(getString(R.string.resume)); + getBinding().pl.setInfo(task.getEntity()); } @Download.onTaskCancel public void onTaskCancel(DownloadTask task) { - getBinding().setProgress(0); - getBinding().setSpeed(""); - getBinding().setStateStr(getString(R.string.cancel)); + getBinding().pl.setInfo(task.getEntity()); } @Download.onTaskRunning public void onTaskRunning(DownloadTask task) { long len = task.getFileSize(); - if (len == 0) { - getBinding().setProgress(0); - } else { - getBinding().setProgress(task.getPercent()); - } - getBinding().setSpeed(task.getConvertSpeed()); + getBinding().pl.setInfo(task.getEntity()); } @Override protected void onDelayLoad() { diff --git a/app/src/main/java/com/arialyy/simple/core/download/fragment/FragmentActivity.java b/app/src/main/java/com/arialyy/simple/core/download/fragment/FragmentActivity.java index 483e0fb8..a3629fb0 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/fragment/FragmentActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/fragment/FragmentActivity.java @@ -38,6 +38,5 @@ public class FragmentActivity extends BaseActivity { final NormalTo to = getIntent().getParcelableExtra(MainActivity.KEY_MAIN_DATA); setTitle(to.title); - Aria.download(this).register(); } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/fragment/MultiFragmentActivity.kt b/app/src/main/java/com/arialyy/simple/core/download/fragment/MultiFragmentActivity.kt new file mode 100644 index 00000000..8f31bb12 --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/core/download/fragment/MultiFragmentActivity.kt @@ -0,0 +1,56 @@ +package com.arialyy.simple.core.download.fragment + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter +import com.arialyy.simple.R +import com.arialyy.simple.base.BaseActivity +import com.arialyy.simple.databinding.ActivityMultiFragmentActivityBinding + +class MultiFragmentActivity : BaseActivity() { + + private val urls = arrayListOf( + "http://cpsdown.muzhiwan.com/2020/07/23/com.suyou.xjhx.syad_5f195b471dff6.apk", + "http://60.174.135.183:88/sdkdown.muzhiwan.com/2020/01/13/com.supercell.clashroyale.ewan.mzw_5e1bc4eb4313e.apk", + "http://60.174.135.183:88/sdkdown.muzhiwan.com/openfile/2020/08/06/com.maoer.m282_5f2b79232a8b1.apk" + ) + private val names = arrayListOf( + "星界幻想", + "皇室战争", + "无双帝国" + ) + + override fun setLayoutId(): Int { + return R.layout.activity_multi_fragment_activity + } + + override fun init(savedInstanceState: Bundle?) { + super.init(savedInstanceState) + val fragmentList = arrayListOf() + urls.forEachIndexed { index, url -> + fragmentList.add(MultiItemFragment(url, names[index])) + } + binding.vpPage.adapter = PGAdapter(fragmentList, supportFragmentManager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) + binding.tabLayout.setupWithViewPager(binding.vpPage) + binding.tabLayout.getTabAt(0)!!.text = "fm1" + binding.tabLayout.getTabAt(1)!!.text = "fm2" + binding.tabLayout.getTabAt(2)!!.text = "fm3" + } + + private class PGAdapter( + val fragments: List, + fm: FragmentManager, + state: Int + ) : FragmentPagerAdapter(fm, state) { + override fun getItem(position: Int): Fragment { + return fragments[position] + } + + override fun getCount(): Int { + return fragments.size + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/core/download/fragment/MultiItemFragment.java b/app/src/main/java/com/arialyy/simple/core/download/fragment/MultiItemFragment.java new file mode 100644 index 00000000..ff5f544e --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/core/download/fragment/MultiItemFragment.java @@ -0,0 +1,124 @@ +/* + * 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.simple.core.download.fragment; + +import android.os.Bundle; +import android.os.Environment; +import android.view.View; +import com.arialyy.annotations.Download; +import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.task.DownloadTask; +import com.arialyy.frame.core.AbsFragment; +import com.arialyy.simple.R; +import com.arialyy.simple.databinding.FragmentDownloadBinding; +import com.arialyy.simple.widget.ProgressLayout; + +/** + * Created by lyy on 2017/1/4. + */ +public class MultiItemFragment extends AbsFragment { + private long mTaskId = -1; + + private String DOWNLOAD_URL; + private String FILE_NAME; + + public MultiItemFragment(String url, String fileName) { + DOWNLOAD_URL = url; + FILE_NAME = fileName; + } + + @Override protected void init(Bundle savedInstanceState) { + Aria.download(this).register(); + DownloadEntity entity = Aria.download(this).getFirstDownloadEntity(DOWNLOAD_URL); + if (entity == null) { + entity = new DownloadEntity(); + entity.setUrl(DOWNLOAD_URL); + } + getBinding().pl.setInfo(entity); + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + mTaskId = Aria.download(this) + .load(DOWNLOAD_URL) + .setFilePath( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + .getPath() + String.format("/%s.apk", + FILE_NAME)) + .create(); + } + + @Override public void stop(View v, AbsEntity entity) { + Aria.download(this) + .load(mTaskId) + .stop(); + } + + @Override public void resume(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId) + //.updateUrl(mUrl) + .resume(); + } + + @Override public void cancel(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId).cancel(false); + mTaskId = -1; + } + }); + } + + @Download.onTaskPre public void onTaskPre(DownloadTask task) { + if (!task.getKey().equals(DOWNLOAD_URL)){ + return; + } + getBinding().pl.setInfo(task.getEntity()); + } + + @Download.onTaskStop public void onTaskStop(DownloadTask task) { + if (!task.getKey().equals(DOWNLOAD_URL)){ + return; + } + getBinding().pl.setInfo(task.getEntity()); + } + + @Download.onTaskCancel public void onTaskCancel(DownloadTask task) { + if (!task.getKey().equals(DOWNLOAD_URL)){ + return; + } + getBinding().pl.setInfo(task.getEntity()); + } + + @Download.onTaskRunning public void onTaskRunning(DownloadTask task) { + if (!task.getKey().equals(DOWNLOAD_URL)){ + return; + } + long len = task.getFileSize(); + getBinding().pl.setInfo(task.getEntity()); + } + + @Override protected void onDelayLoad() { + + } + + @Override protected int setLayoutId() { + return R.layout.fragment_download; + } + + @Override protected void dataCallback(int result, Object obj) { + + } +} diff --git a/app/src/main/java/com/arialyy/simple/core/download/group/ChildHandleDialog.java b/app/src/main/java/com/arialyy/simple/core/download/group/ChildHandleDialog.java index c8c2c3c0..944df089 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/group/ChildHandleDialog.java +++ b/app/src/main/java/com/arialyy/simple/core/download/group/ChildHandleDialog.java @@ -27,7 +27,7 @@ import android.widget.TextView; import com.arialyy.annotations.DownloadGroup; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadGroupTask; +import com.arialyy.aria.core.task.DownloadGroupTask; import com.arialyy.frame.util.show.L; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseDialog; diff --git a/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java b/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java index db13ddc4..6620f35a 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/group/DownloadGroupActivity.java @@ -21,17 +21,18 @@ import android.util.Log; import android.view.View; import com.arialyy.annotations.DownloadGroup; import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.HttpOption; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadGroupEntity; -import com.arialyy.aria.core.download.DownloadGroupTask; -import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.aria.core.inf.IHttpFileLenAdapter; +import com.arialyy.aria.core.processor.IHttpFileLenAdapter; +import com.arialyy.aria.core.task.DownloadGroupTask; import com.arialyy.aria.util.ALog; -import com.arialyy.frame.util.show.L; import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.databinding.ActivityDownloadGroupBinding; +import com.arialyy.simple.widget.ProgressLayout; import com.arialyy.simple.widget.SubStateLinearLayout; import java.util.List; import java.util.Map; @@ -56,25 +57,50 @@ public class DownloadGroupActivity extends BaseActivity> headers) { - - List sLength = headers.get("Content-Length"); - if (sLength == null || sLength.isEmpty()) { - return -1; - } - String temp = sLength.get(0); - - return Long.parseLong(temp); - } - }) - .setFileSize(114981416) - //.updateUrls(temp) - .create(); - getBinding().setStateStr(getString(R.string.stop)); - break; - } - if (Aria.download(this).loadGroup(mTaskId).isRunning()) { - Aria.download(this).loadGroup(mTaskId).stop(); - getBinding().setStateStr(getString(R.string.resume)); - } else { - Aria.download(this).loadGroup(mTaskId).resume(); - getBinding().setStateStr(getString(R.string.stop)); - } - break; - case R.id.cancel: - Aria.download(this).loadGroup(mTaskId).cancel(true); - mTaskId = -1; - break; - } + private HttpOption getHttpOption() { + HttpOption option = new HttpOption(); + option.setFileLenAdapter(new HttpFileLenAdapter()); + return option; } @DownloadGroup.onWait void taskWait(DownloadGroupTask task) { - L.d(TAG, task.getTaskName() + "wait"); + ALog.d(TAG, task.getTaskName() + "wait"); } @DownloadGroup.onPre() protected void onPre(DownloadGroupTask task) { - L.d(TAG, "group pre"); + ALog.d(TAG, "group pre, p = " + task.getPercent()); } @DownloadGroup.onTaskPre() protected void onTaskPre(DownloadGroupTask task) { - L.d(TAG, "group task pre"); - getBinding().setFileSize(task.getConvertFileSize()); + ALog.d(TAG, "group task pre, p = " + task.getPercent()); if (mChildList.getSubData().size() <= 0) { mChildList.addData(task.getEntity().getSubEntities()); } + getBinding().pl.setInfo(task.getEntity()); } @DownloadGroup.onTaskStart() void taskStart(DownloadGroupTask task) { - getBinding().setFileSize(task.getConvertFileSize()); - L.d(TAG, "group task create"); + ALog.d(TAG, "group task create, p = " + task.getPercent()); + getBinding().pl.setInfo(task.getEntity()); } @DownloadGroup.onTaskRunning() protected void running(DownloadGroupTask task) { - Log.d(TAG, "group running, p = " + ALog.d(TAG, "group running, p = " + task.getPercent() + ", speed = " + task.getConvertSpeed() - + "current_p = " + + ", current_p = " + task.getCurrentProgress()); - getBinding().setProgress(task.getPercent()); - getBinding().setSpeed(task.getConvertSpeed()); + getBinding().pl.setInfo(task.getEntity()); //Log.d(TAG, "sub_len = " + task.getEntity().getSubEntities().size()); mChildList.updateChildProgress(task.getEntity().getSubEntities()); } @DownloadGroup.onTaskResume() void taskResume(DownloadGroupTask task) { - L.d(TAG, "group task resume"); + ALog.d(TAG, "group task resume, p = " + task.getPercent()); } @DownloadGroup.onTaskStop() void taskStop(DownloadGroupTask task) { - L.d(TAG, "group task stop"); - getBinding().setSpeed(""); - getBinding().setStateStr(getString(R.string.start)); + ALog.d(TAG, "group task stop"); + getBinding().pl.setInfo(task.getEntity()); } @DownloadGroup.onTaskCancel() void taskCancel(DownloadGroupTask task) { - L.d(TAG, "group task cancel"); - getBinding().setSpeed(""); - getBinding().setProgress(0); - getBinding().setStateStr(getString(R.string.start)); + ALog.d(TAG, "group task cancel"); + getBinding().pl.setInfo(task.getEntity()); } @DownloadGroup.onTaskFail() void taskFail(DownloadGroupTask task) { - L.d(TAG, "group task fail"); - getBinding().setStateStr(getString(R.string.resume)); - getBinding().setSpeed(""); + if (task != null){ + + ALog.d(TAG, "group task fail"); + getBinding().pl.setInfo(task.getEntity()); + } } @DownloadGroup.onTaskComplete() void taskComplete(DownloadGroupTask task) { - getBinding().setProgress(100); - getBinding().setSpeed(""); mChildList.updateChildProgress(task.getEntity().getSubEntities()); T.showShort(this, "任务组下载完成"); - L.d(TAG, "任务组下载完成"); - getBinding().setStateStr(getString(R.string.start)); + ALog.d(TAG, "任务组下载完成"); + getBinding().pl.setInfo(task.getEntity()); } @DownloadGroup.onSubTaskRunning void onSubTaskRunning(DownloadGroupTask groupTask, @@ -247,4 +230,17 @@ public class DownloadGroupActivity extends BaseActivity> headers) { + + List sLength = headers.get("Content-Length"); + if (sLength == null || sLength.isEmpty()) { + return -1; + } + String temp = sLength.get(0); + + return Long.parseLong(temp); + } + } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/group/FTPDirDownloadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/group/FTPDirDownloadActivity.java index 2eac991a..e6721987 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/group/FTPDirDownloadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/group/FTPDirDownloadActivity.java @@ -20,25 +20,27 @@ import android.os.Environment; import android.view.View; import com.arialyy.annotations.DownloadGroup; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.common.controller.ControllerType; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.FtpOption; import com.arialyy.aria.core.download.DownloadGroupEntity; -import com.arialyy.aria.core.download.DownloadGroupTask; -import com.arialyy.aria.core.inf.IEntity; -import com.arialyy.frame.util.show.L; +import com.arialyy.aria.core.task.DownloadGroupTask; +import com.arialyy.aria.util.ALog; import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.databinding.ActivityDownloadGroupBinding; +import com.arialyy.simple.widget.ProgressLayout; import com.arialyy.simple.widget.SubStateLinearLayout; /** * Created by lyy on 2017/7/6. */ public class FTPDirDownloadActivity extends BaseActivity { - private static final String dir = "ftp://9.9.9.205:2121/upload/测试"; + private static final String dir = "ftp://192.168.0.104:2121/aab/你好"; private SubStateLinearLayout mChildList; private long mTaskId = -1; + private String user = "lao", pwd = "123456"; @Override protected void init(Bundle savedInstanceState) { super.init(savedInstanceState); @@ -49,102 +51,97 @@ public class FTPDirDownloadActivity extends BaseActivity getUrls() { List urls = new ArrayList<>(); - String[] str = getContext().getResources().getStringArray(R.array.group_urls_1); - Collections.addAll(urls, str); + urls.add("http://storage.jd.com/apk/newchannel/jingdongdaojia-lechuang30.apk"); + urls.add("https://appdownload.alicdn.com/publish/cainiao4android/latest/cainiao4android_1598322688221.apk"); + urls.add("https://yanxuan-dl.nosdn.127.net/yan/android/yanxuan-aos_out_3547.apk"); return urls; } - List getUrls1() { List urls = new ArrayList<>(); //String[] str = getContext().getResources().getStringArray(R.array.group_urls); //Collections.addAll(urls, str); - urls.add("https://d.pcs.baidu.com/file/130335545f3f4d9cc38afe709c19af5a?fid=1411168371-250528-1010657263806840&dstime=1531134607&rt=sh&sign=FDtAERVY-DCb740ccc5511e5e8fedcff06b081203-sNCujT7lC42aMcfiHcgqAzYHuw4%3D&expires=8h&chkv=1&chkbd=0&chkpc=et&dp-logid=4401967667009194668&dp-callid=0&r=540192514"); + urls.add( + "https://d.pcs.baidu.com/file/130335545f3f4d9cc38afe709c19af5a?fid=1411168371-250528-1010657263806840&dstime=1531134607&rt=sh&sign=FDtAERVY-DCb740ccc5511e5e8fedcff06b081203-sNCujT7lC42aMcfiHcgqAzYHuw4%3D&expires=8h&chkv=1&chkbd=0&chkpc=et&dp-logid=4401967667009194668&dp-callid=0&r=540192514"); return urls; } List getSubName() { + List names = new ArrayList<>(); + //String[] str = getContext().getResources().getStringArray(R.array.group_names); + //Collections.addAll(names, str); + names.add("京东到家.apk"); + names.add("菜鸟.apk"); + names.add("网易严选.apk"); + //names.add("4.png"); + //names.add("5.png"); + return names; + } + + List getSubName1() { List names = new ArrayList<>(); String[] str = getContext().getResources().getStringArray(R.array.group_names); Collections.addAll(names, str); diff --git a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveDLoadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveDLoadActivity.java index 1067b633..256f946e 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveDLoadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveDLoadActivity.java @@ -26,11 +26,14 @@ import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import com.arialyy.annotations.Download; +import com.arialyy.annotations.M3U8; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.common.controller.ControllerType; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.download.m3u8.ILiveTsUrlConverter; +import com.arialyy.aria.core.download.m3u8.M3U8LiveOption; +import com.arialyy.aria.core.processor.IBandWidthUrlConverter; +import com.arialyy.aria.core.processor.ILiveTsUrlConverter; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import com.arialyy.frame.util.show.T; @@ -39,7 +42,7 @@ import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.common.ModifyPathDialog; import com.arialyy.simple.common.ModifyUrlDialog; import com.arialyy.simple.databinding.ActivityM3u8LiveBinding; -import com.arialyy.simple.util.AppUtil; +import com.arialyy.simple.widget.ProgressLayout; import java.io.File; public class M3U8LiveDLoadActivity extends BaseActivity { @@ -47,7 +50,7 @@ public class M3U8LiveDLoadActivity extends BaseActivity private String mUrl; private String mFilePath; private M3U8LiveModule mModule; - private DownloadEntity mEntity; + private long mTaskId; @Override protected void init(Bundle savedInstanceState) { @@ -61,27 +64,30 @@ public class M3U8LiveDLoadActivity extends BaseActivity if (entity == null) { return; } - mEntity = entity; - getBinding().setStateStr(getString(R.string.start)); - getBinding().setUrl(entity.getUrl()); - getBinding().setFilePath(entity.getFilePath()); + mTaskId = entity.getId(); mUrl = entity.getUrl(); mFilePath = entity.getFilePath(); + getBinding().pl.setInfo(entity); } }); - getBinding().setViewModel(this); - } + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + startD(); + } - public void chooseUrl() { - ModifyUrlDialog dialog = - new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl); - dialog.show(getSupportFragmentManager(), "ModifyUrlDialog"); - } + @Override public void stop(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId).stop(); + } - public void chooseFilePath() { - ModifyPathDialog dialog = - new ModifyPathDialog(this, getString(R.string.modify_file_path), mFilePath); - dialog.show(getSupportFragmentManager(), "ModifyPathDialog"); + @Override public void resume(View v, AbsEntity entity) { + ALog.d(TAG, "m3u8 live 不支持恢复"); + } + + @Override public void cancel(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId).cancel(true); + mTaskId = -1; + } + }); } @Override @@ -126,6 +132,22 @@ public class M3U8LiveDLoadActivity extends BaseActivity return true; } + @M3U8.onPeerStart + void onPeerStart(String m3u8Url, String peerPath, int peerIndex) { + //ALog.d(TAG, "peer create, path: " + peerPath + ", index: " + peerIndex); + } + + @M3U8.onPeerComplete + void onPeerComplete(String m3u8Url, String peerPath, int peerIndex) { + //ALog.d(TAG, "peer complete, path: " + peerPath + ", index: " + peerIndex); + //mVideoFragment.addPlayer(peerIndex, peerPath); + } + + @M3U8.onPeerFail + void onPeerFail(String m3u8Url, String peerPath, int peerIndex) { + //ALog.d(TAG, "peer fail, path: " + peerPath + ", index: " + peerIndex); + } + @Download.onWait void onWait(DownloadTask task) { if (task.getKey().equals(mUrl)) { @@ -136,71 +158,70 @@ public class M3U8LiveDLoadActivity extends BaseActivity @Download.onPre protected void onPre(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.stop)); + ALog.d(TAG, "pre"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskStart void taskStart(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setFileSize(task.getConvertFileSize()); ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskRunning protected void running(DownloadTask task) { if (task.getKey().equals(mUrl)) { - ALog.d(TAG, "isRunning"); - getBinding().setProgress(task.getPercent()); - getBinding().setSpeed(task.getConvertSpeed()); + ALog.d(TAG, + "m3u8 void running, p = " + task.getPercent() + ", speed = " + task.getConvertSpeed()); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskResume void taskResume(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.stop)); + ALog.d(TAG, "m3u8 vod resume"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskStop void taskStop(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.resume)); - getBinding().setSpeed(""); + ALog.d(TAG, "stop"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskCancel void taskCancel(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setProgress(0); - getBinding().setStateStr(getString(R.string.start)); - getBinding().setSpeed(""); Log.d(TAG, "cancel"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskFail void taskFail(DownloadTask task, Exception e) { if (task.getKey().equals(mUrl)) { - Toast.makeText(M3U8LiveDLoadActivity.this, getString(R.string.download_fail), + Toast.makeText(this, getString(R.string.download_fail), Toast.LENGTH_SHORT) .show(); - getBinding().setStateStr(getString(R.string.start)); + Log.d(TAG, "fail"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskComplete void taskComplete(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setProgress(100); - Toast.makeText(M3U8LiveDLoadActivity.this, getString(R.string.download_success), + Toast.makeText(this, getString(R.string.download_success), Toast.LENGTH_SHORT).show(); - getBinding().setStateStr(getString(R.string.re_start)); - getBinding().setSpeed(""); - ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getDownloadPath()))); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); } } @@ -209,63 +230,24 @@ public class M3U8LiveDLoadActivity extends BaseActivity return R.layout.activity_m3u8_live; } - public void onClick(View view) { - switch (view.getId()) { - case R.id.start: - if (!AppUtil.chekEntityValid(mEntity)) { - startD(); - break; - } - if (Aria.download(this).load(mEntity.getId()).isRunning()) { - Aria.download(this).load(mEntity.getId()).stop(); - } else { - Aria.download(this).load(mEntity.getId()).resume(); - } - break; - case R.id.cancel: - if (AppUtil.chekEntityValid(mEntity)) { - Aria.download(this).load(mEntity.getId()).cancel(true); - } - break; - } - } - private void startD() { - Aria.download(M3U8LiveDLoadActivity.this) + mTaskId = Aria.download(M3U8LiveDLoadActivity.this) .load(mUrl) - .useServerFileName(true) - .setFilePath(mFilePath, true) - .asM3U8() - //.setBandWidthUrlConverter(new IBandWidthUrlConverter() { - // @Override public String convert(String bandWidthUrl) { - // int peerIndex = mUrl.lastIndexOf("/"); - // return mUrl.substring(0, peerIndex + 1) + bandWidthUrl; - // } - //}) - .asLive() - .setLiveTsUrlConvert(new ILiveTsUrlConverter() { - @Override public String convert(String m3u8Url, String tsUrl) { - int index = m3u8Url.lastIndexOf("/"); - String parentUrl = m3u8Url.substring(0, index + 1); - return parentUrl + tsUrl; - } - }) - .controller(ControllerType.CREATE_CONTROLLER) - //.setLiveTsUrlConvert(new IVodTsUrlConverter() { - // @Override public List convert(String m3u8Url, List tsUrls) { - // int peerIndex = m3u8Url.lastIndexOf("/"); - // String parentUrl = m3u8Url.substring(0, peerIndex + 1); - // List newUrls = new ArrayList<>(); - // for (String url : tsUrls) { - // newUrls.add(parentUrl + url); - // } - // - // return newUrls; - // } - //}) + .setFilePath(mFilePath) + .ignoreFilePathOccupy() + .m3u8LiveOption(getLiveoption()) .create(); } + private M3U8LiveOption getLiveoption() { + M3U8LiveOption option = new M3U8LiveOption(); + option + //.generateIndexFile() + .setLiveTsUrlConvert(new LiveTsUrlConverter()); + //.setBandWidthUrlConverter(new BandWidthUrlConverter(mUrl)); + return option; + } + @Override protected void dataCallback(int result, Object data) { super.dataCallback(result, data); if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { @@ -274,4 +256,20 @@ public class M3U8LiveDLoadActivity extends BaseActivity mModule.updateFilePath(this, String.valueOf(data)); } } + + static class LiveTsUrlConverter implements ILiveTsUrlConverter { + @Override public String convert(String m3u8Url, String tsUrl) { + int index = m3u8Url.lastIndexOf("/"); + String parentUrl = m3u8Url.substring(0, index + 1); + return parentUrl + tsUrl; + } + } + + static class BandWidthUrlConverter implements IBandWidthUrlConverter { + + @Override public String convert(String m3u8Url, String bandWidthUrl) { + int peerIndex = m3u8Url.lastIndexOf("/"); + return m3u8Url.substring(0, peerIndex + 1) + bandWidthUrl; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveModule.java b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveModule.java index 899c39d3..5a86ca0c 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveModule.java +++ b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveModule.java @@ -55,7 +55,7 @@ public class M3U8LiveModule extends BaseViewModule { singDownloadInfo.setFilePath(filePath); singDownloadInfo.setFileName(temp.getName()); } else { - AppUtil.setConfigValue(context, M3U8_LIVE_PATH_KEY, singDownloadInfo.getDownloadPath()); + AppUtil.setConfigValue(context, M3U8_LIVE_PATH_KEY, singDownloadInfo.getFilePath()); AppUtil.setConfigValue(context, M3U8_LIVE_URL_KEY, singDownloadInfo.getUrl()); } liveData.postValue(singDownloadInfo); diff --git a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodDLoadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodDLoadActivity.java index 53d746e2..55096d12 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodDLoadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodDLoadActivity.java @@ -22,22 +22,24 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.SeekBar; import android.widget.Toast; import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import com.arialyy.annotations.Download; import com.arialyy.annotations.M3U8; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.common.controller.BuilderController; -import com.arialyy.aria.core.common.controller.ControllerType; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.HttpOption; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; -import com.arialyy.aria.core.download.m3u8.ITsMergeHandler; -import com.arialyy.aria.core.download.m3u8.IVodTsUrlConverter; -import com.arialyy.aria.core.download.m3u8.M3U8Entity; -import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.download.M3U8Entity; +import com.arialyy.aria.core.download.m3u8.M3U8VodOption; +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.core.processor.IVodTsUrlConverter; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; import com.arialyy.frame.util.show.T; @@ -47,6 +49,7 @@ import com.arialyy.simple.common.ModifyPathDialog; import com.arialyy.simple.common.ModifyUrlDialog; import com.arialyy.simple.databinding.ActivityM3u8VodBinding; import com.arialyy.simple.to.PeerIndex; +import com.arialyy.simple.widget.ProgressLayout; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -74,28 +77,53 @@ public class M3U8VodDLoadActivity extends BaseActivity { if (entity == null) { return; } - mTaskId = entity.getId(); - if (entity.getState() == IEntity.STATE_STOP) { - getBinding().setStateStr(getString(R.string.resume)); - } else if (entity.getState() == IEntity.STATE_RUNNING) { - getBinding().setStateStr(getString(R.string.stop)); - } - - if (entity.getFileSize() != 0) { - getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize())); + if (entity.getM3U8Entity() != null) { + getBinding().seekBar.setMax(entity.getM3U8Entity().getPeerNum()); } - getBinding().setProgress(entity.getPercent()); - getBinding().setUrl(entity.getUrl()); - getBinding().setFilePath(entity.getFilePath()); + mTaskId = entity.getId(); mUrl = entity.getUrl(); mFilePath = entity.getFilePath(); - mVideoFragment = new VideoPlayerFragment(0, entity); - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.add(R.id.video_content, mVideoFragment); - ft.commit(); + getBinding().pl.setInfo(entity); + //mVideoFragment = new VideoPlayerFragment(0, entity); + //FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + //ft.add(R.id.video_content, mVideoFragment); + //ft.commit(); + } + }); + getBinding().seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + + } + + @Override public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override public void onStopTrackingTouch(SeekBar seekBar) { + Aria.download(this).load(mTaskId).m3u8VodOption().jumPeerIndex(seekBar.getProgress()); + } + }); + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + startD(); + } + + @Override public void stop(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId).stop(); + } + + @Override public void resume(View v, AbsEntity entity) { + Aria.download(this) + .load(mTaskId) + .m3u8VodOption(getM3U8Option()) + .resume(); + } + + @Override public void cancel(View v, AbsEntity entity) { + Aria.download(this).load(mTaskId).cancel(true); + mTaskId = -1; } }); - getBinding().setViewModel(this); } public void chooseUrl() { @@ -122,7 +150,7 @@ public class M3U8VodDLoadActivity extends BaseActivity { @Subscribe(threadMode = ThreadMode.MAIN) public void jumpIndex(PeerIndex index) { - Aria.download(this).load(mUrl).asM3U8().asVod().jumPeerIndex(index.index); + Aria.download(this).load(mTaskId).m3u8VodOption().jumPeerIndex(index.index); } @Override @@ -175,7 +203,7 @@ public class M3U8VodDLoadActivity extends BaseActivity { @M3U8.onPeerComplete void onPeerComplete(String m3u8Url, String peerPath, int peerIndex) { //ALog.d(TAG, "peer complete, path: " + peerPath + ", index: " + peerIndex); - mVideoFragment.addPlayer(peerIndex, peerPath); + //mVideoFragment.addPlayer(peerIndex, peerPath); } @M3U8.onPeerFail @@ -193,73 +221,71 @@ public class M3U8VodDLoadActivity extends BaseActivity { @Download.onPre protected void onPre(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.stop)); + ALog.d(TAG, "pre"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskStart void taskStart(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setFileSize(task.getConvertFileSize()); ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); + getBinding().seekBar.setMax(task.getEntity().getM3U8Entity().getPeerNum()); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskRunning protected void running(DownloadTask task) { if (task.getKey().equals(mUrl)) { - //ALog.d(TAG, "isRunning"); - getBinding().setProgress(task.getPercent()); - getBinding().setSpeed(task.getConvertSpeed()); + ALog.d(TAG, + "m3u8 void running, p = " + task.getPercent() + ", speed = " + task.getConvertSpeed()); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskResume void taskResume(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.stop)); + ALog.d(TAG, "m3u8 vod resume"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskStop void taskStop(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setStateStr(getString(R.string.resume)); - getBinding().setSpeed(""); + ALog.d(TAG, "stop"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskCancel void taskCancel(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setProgress(0); - getBinding().setStateStr(getString(R.string.start)); - getBinding().setSpeed(""); Log.d(TAG, "cancel"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskFail void taskFail(DownloadTask task, Exception e) { - if (task.getKey().equals(mUrl)) { + if (task != null && task.getKey().equals(mUrl)) { Toast.makeText(M3U8VodDLoadActivity.this, getString(R.string.download_fail), Toast.LENGTH_SHORT) .show(); - getBinding().setStateStr(getString(R.string.start)); - getBinding().setSpeed(""); Log.d(TAG, "fail"); + getBinding().pl.setInfo(task.getEntity()); } } @Download.onTaskComplete void taskComplete(DownloadTask task) { if (task.getKey().equals(mUrl)) { - getBinding().setProgress(100); Toast.makeText(M3U8VodDLoadActivity.this, getString(R.string.download_success), Toast.LENGTH_SHORT).show(); - getBinding().setStateStr(getString(R.string.re_start)); - getBinding().setSpeed(""); - ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getDownloadPath()))); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); } } @@ -268,91 +294,30 @@ public class M3U8VodDLoadActivity extends BaseActivity { return R.layout.activity_m3u8_vod; } - public void onClick(View view) { - switch (view.getId()) { - case R.id.start: - if (mTaskId == -1) { - startD(); - break; - } - if (Aria.download(this).load(mTaskId).isRunning()) { - Aria.download(this).load(mTaskId).stop(); - } else { - Aria.download(this) - .load(mTaskId) - .asM3U8() - //.setBandWidthUrlConverter(new IBandWidthUrlConverter() { - // @Override public String convert(String bandWidthUrl) { - // int index = mUrl.lastIndexOf("/"); - // return mUrl.substring(0, index + 1) + bandWidthUrl; - // } - //}) - .setTsUrlConvert(new IVodTsUrlConverter() { - @Override public List convert(String m3u8Url, List tsUrls) { - - Uri uri = Uri.parse(m3u8Url); - String parentUrl = uri.getAuthority() + "://" + uri.getHost(); - List newUrls = new ArrayList<>(); - for (String url : tsUrls) { - newUrls.add(parentUrl + url); - } - - return newUrls; - } - }) - .controller(ControllerType.TASK_CONTROLLER) - .resume(); - } - break; - case R.id.cancel: - Aria.download(this).load(mTaskId).cancel(true); - mTaskId = -1; - break; - } - } - private void startD() { mTaskId = Aria.download(M3U8VodDLoadActivity.this) .load(mUrl) - .useServerFileName(true) - .setFilePath(mFilePath, true) - .asM3U8() - //.setBandWidthUrlConverter(new IBandWidthUrlConverter() { - // @Override public String convert(String bandWidthUrl) { - // int index = mUrl.lastIndexOf("/"); - // return mUrl.substring(0, index + 1) + bandWidthUrl; - // } - //}) - .setTsUrlConvert(new IVodTsUrlConverter() { - @Override public List convert(String m3u8Url, List tsUrls) { - //int index = m3u8Url.lastIndexOf("/"); - //String parentUrl = m3u8Url.substring(0, index + 1); - //List newUrls = new ArrayList<>(); - //for (String url : tsUrls) { - // newUrls.add(parentUrl + url); - //} - Uri uri = Uri.parse(m3u8Url); - String parentUrl = uri.getScheme() + "://" + uri.getHost(); - List newUrls = new ArrayList<>(); - for (String url : tsUrls) { - newUrls.add(parentUrl + url); - } - - return newUrls; - } - }) - .setMergeHandler(new ITsMergeHandler() { - public boolean merge(@Nullable M3U8Entity m3U8Entity, List tsPath) { - ALog.d(TAG, "合并TS...."); - return false; - } - }) - .generateIndexFile() - .controller(ControllerType.CREATE_CONTROLLER) + .setFilePath(mFilePath) + .ignoreFilePathOccupy() + .m3u8VodOption(getM3U8Option()) .create(); } - private Class c = BuilderController.class; + private M3U8VodOption getM3U8Option() { + M3U8VodOption option = new M3U8VodOption(); + //option.setBandWidth(200000); + //.generateIndexFile() + //.merge(true) + //.setVodTsUrlConvert(new VodTsUrlConverter()); + //.setMergeHandler(new TsMergeHandler()); + option.setUseDefConvert(true); + option.generateIndexFile(); + //option.setKeyUrlConverter(new KeyUrlConverter()); + //option.setVodTsUrlConvert(new VodTsUrlConverter()); + option.setBandWidthUrlConverter(new BandWidthUrlConverter()); + //option.setUseDefConvert(true); + return option; + } @Override protected void dataCallback(int result, Object data) { super.dataCallback(result, data); @@ -362,4 +327,48 @@ public class M3U8VodDLoadActivity extends BaseActivity { mModule.updateFilePath(this, String.valueOf(data)); } } + + static class VodTsUrlConverter implements IVodTsUrlConverter { + @Override public List convert(String m3u8Url, List tsUrls) { + Uri uri = Uri.parse(m3u8Url); + //String parentUrl = "http://devimages.apple.com/iphone/samples/bipbop/gear1/"; + //String parentUrl = "http://youku.cdn7-okzy.com/20200123/16815_fbe419ed/1000k/hls/"; + //String parentUrl = "http://" + uri.getHost() + "/gear1/"; + //int index = m3u8Url.lastIndexOf("/"); + //String parentUrl = m3u8Url.substring(0, index + 1); + //String parentUrl = "https://v1.szjal.cn/20190819/Ql6UD1od/"; + //String parentUrl = "http://" + uri.getHost() + "/"; + //List newUrls = new ArrayList<>(); + //for (String url : tsUrls) { + // newUrls.add(parentUrl + url); + //} + + //return newUrls; + return tsUrls; + } + } + + static class TsMergeHandler implements ITsMergeHandler { + public boolean merge(@Nullable M3U8Entity m3U8Entity, List tsPath) { + ALog.d("TsMergeHandler", "合并TS...."); + return false; + } + } + + static class BandWidthUrlConverter implements IBandWidthUrlConverter { + + + @Override public String convert(String m3u8Url, String bandWidthUrl) { + int index = m3u8Url.lastIndexOf("/"); + return m3u8Url.substring(0, index + 1) + bandWidthUrl; + } + } + + static class KeyUrlConverter implements IKeyUrlConverter { + + @Override public String convert(String m3u8Url, String tsListUrl, String keyUrl) { + ALog.d("TAG", "convertUrl...."); + return null; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodModule.java b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodModule.java index d1fec3c2..8466be2d 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodModule.java +++ b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodModule.java @@ -35,10 +35,20 @@ public class M3U8VodModule extends BaseViewModule { //private final String defUrl = "https://www.gaoya123.cn/2019/1557993797897.m3u8"; // 多码率地址: //private final String defUrl = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"; - private final String defUrl = "http://cn1.fa1244.cn/hls/20190516/6d271eaa73b2e4cb51d13831b0c1ab4c/1557976262/index.m3u8"; + private final String defUrl = "https://5.voooe.cn/cache/youku/4e00b49a6e4f11155967c2cb3385a2ab.m3u8"; + //private final String defUrl = "http://pp3zvsk2n.bkt.clouddn.com/20200806/sd/15967206011811803/38475fadd55e4ecea3.m3u8"; + //private final String defUrl = "http://youku.cdn7-okzy.com/20200123/16815_fbe419ed/index.m3u8"; + + //private final String defUrl = "https://cn7.kankia.com/hls/20200108/e1eaec074274c64fe46a3bdb5d2ba487/1578488360/index.m3u8"; + //private final String defUrl = "https://youku.cdn7-okzy.com/20191213/16167_c3592a02/index.m3u8"; + //private final String defUrl = "http://qn.shytong.cn/b83137769ff6b555/11b0c9970f9a3fa0.mp4.m3u8"; + //private final String defUrl = "https://135zyv5.xw0371.com/2018/10/29/X05c7CG3VB91gi1M/playlist.m3u8"; + //private final String defUrl = "https://fangao.qfxmj.com/video/20191111/dbf7e2aa0c5f42a8b040442b54c13e3a/cloudv-transfer/555555556nr593o75556r165q86n82n0_eb3da35b8f4442808756a6ddc8ec1372_0_3.m3u8?wsSecret=d5fb403512d4cd427f18858086b35ce4&wsTime=1582197537"; + //private final String defUrl = "https://v1.szjal.cn/20190819/Ql6UD1od/index.m3u8"; private final String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath() - + "/道士下山.ts"; + //+ "/道士下山.ts"; + + "/bb1.ts"; private MutableLiveData liveData = new MutableLiveData<>(); private DownloadEntity singDownloadInfo; diff --git a/app/src/main/java/com/arialyy/simple/core/download/m3u8/VideoPlayerFragment.java b/app/src/main/java/com/arialyy/simple/core/download/m3u8/VideoPlayerFragment.java index d5d001c3..0d281500 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/m3u8/VideoPlayerFragment.java +++ b/app/src/main/java/com/arialyy/simple/core/download/m3u8/VideoPlayerFragment.java @@ -1,6 +1,8 @@ package com.arialyy.simple.core.download.m3u8; import android.annotation.SuppressLint; +import android.graphics.Canvas; +import android.graphics.Color; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Build; @@ -13,7 +15,7 @@ import android.widget.SeekBar; import androidx.annotation.RequiresApi; import androidx.lifecycle.ViewModelProviders; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.m3u8.M3U8Entity; +import com.arialyy.aria.core.download.M3U8Entity; import com.arialyy.aria.util.ALog; import com.arialyy.frame.base.BaseFragment; import com.arialyy.simple.R; @@ -37,6 +39,7 @@ public class VideoPlayerFragment extends BaseFragment temps = Aria.download(this).getTotalTaskList(); if (temps != null && !temps.isEmpty()) { - for (AbsEntity temp : temps){ + for (AbsEntity temp : temps) { ALog.d(TAG, "state = " + temp.getState()); } mData.addAll(temps); @@ -100,9 +101,16 @@ public class MultiDownloadActivity extends BaseActivity tasks = Aria.download(this).getAllNotCompleteTask(); + if (tasks != null){ + ALog.d(TAG, "未完成的任务数:" + tasks.size()); + } } @Download.onTaskFail void taskFail(DownloadTask task) { + if (task == null || task.getEntity() == null){ + return; + } mAdapter.updateState(task.getEntity()); } @@ -110,7 +118,7 @@ public class MultiDownloadActivity extends BaseActivity { +public class MultiTaskListActivity extends BaseActivity { RecyclerView mList; Toolbar mBar; private FileListAdapter mAdapter; @@ -57,6 +58,7 @@ public class MultiTaskActivity extends BaseActivity { setTitle("多任务下载"); mData.addAll(getModule(DownloadModule.class).createGroupTestList()); mData.addAll(getModule(DownloadModule.class).createMultiTestList()); + mData.addAll(getModule(DownloadModule.class).createM3u8TestList()); mAdapter = new FileListAdapter(this, mData); mList = getBinding().list; mBar = findViewById(R.id.toolbar); @@ -71,7 +73,11 @@ public class MultiTaskActivity extends BaseActivity { dialog.show(getSupportFragmentManager(), "download_num"); break; case R.id.stop_all: - Aria.download(this).stopAllTask(); + //Aria.download(this).stopAllTask(); + List list = Aria.download(this).getTotalTaskList(); + for (AbsEntity entity : list){ + Aria.download(this).load(entity.getId()).cancel(true); + } //Aria.download(this).removeAllTask(false); break; case R.id.turn: @@ -101,11 +107,14 @@ public class MultiTaskActivity extends BaseActivity { } @Download.onTaskFail void taskFail(DownloadTask task) { + if (task == null || task.getEntity() == null){ + return; + } mAdapter.updateBtState(task.getKey(), true); } @Download.onTaskComplete void taskComplete(DownloadTask task) { - Log.d(TAG, FileUtil.getFileMD5(new File(task.getDownloadPath()))); + Log.d(TAG, FileUtil.getFileMD5(new File(task.getFilePath()))); } //############################### 任务组 ############################## diff --git a/app/src/main/java/com/arialyy/simple/core/download/service/DownloadService.java b/app/src/main/java/com/arialyy/simple/core/download/service/DownloadService.java index 29a54e98..8d7745a4 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/service/DownloadService.java +++ b/app/src/main/java/com/arialyy/simple/core/download/service/DownloadService.java @@ -22,7 +22,7 @@ import android.os.IBinder; import androidx.annotation.Nullable; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.download.DownloadTask; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.frame.util.show.T; /** diff --git a/app/src/main/java/com/arialyy/simple/core/test/AnyRunActivity.java b/app/src/main/java/com/arialyy/simple/core/test/AnyRunActivity.java index 073f3b4a..1333f538 100644 --- a/app/src/main/java/com/arialyy/simple/core/test/AnyRunActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/test/AnyRunActivity.java @@ -1,7 +1,6 @@ package com.arialyy.simple.core.test; import android.os.Bundle; -import android.util.Log; import android.view.View; import com.arialyy.aria.core.Aria; import com.arialyy.aria.core.download.DTaskWrapper; diff --git a/app/src/main/java/com/arialyy/simple/core/upload/FtpUploadActivity.java b/app/src/main/java/com/arialyy/simple/core/upload/FtpUploadActivity.java index a6535705..6fcac396 100644 --- a/app/src/main/java/com/arialyy/simple/core/upload/FtpUploadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/upload/FtpUploadActivity.java @@ -15,51 +15,48 @@ */ package com.arialyy.simple.core.upload; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.util.Log; import android.view.View; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import com.arialyy.annotations.Upload; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.common.controller.ControllerType; -import com.arialyy.aria.core.common.ftp.FtpInterceptHandler; -import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor; -import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.FtpOption; +import com.arialyy.aria.core.processor.FtpInterceptHandler; +import com.arialyy.aria.core.processor.IFtpUploadInterceptor; +import com.arialyy.aria.core.task.UploadTask; import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.core.upload.UploadTask; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; -import com.arialyy.frame.util.FileUtil; -import com.arialyy.frame.util.show.T; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; -import com.arialyy.simple.common.ModifyUrlDialog; -import com.arialyy.simple.databinding.ActivityFtpUploadBinding; -import com.arialyy.simple.util.AppUtil; +import com.arialyy.simple.databinding.ActivitySingleBinding; +import com.arialyy.simple.widget.ProgressLayout; import java.io.File; -import java.io.IOException; +import java.util.ArrayList; import java.util.List; /** * Created by lyy on 2017/7/28. Ftp 文件上传demo */ -public class FtpUploadActivity extends BaseActivity { +public class FtpUploadActivity extends BaseActivity { private final int OPEN_FILE_MANAGER_CODE = 0xB1; private String mFilePath; private String mUrl; private UploadModule mModule; private long mTaskId = -1; + private String user = "lao", pwd = "123456"; @Override protected void init(Bundle savedInstanceState) { setTile("D_FTP 文件上传"); super.init(savedInstanceState); Aria.upload(this).register(); - getBinding().setViewModel(this); setUI(); } @@ -68,159 +65,157 @@ public class FtpUploadActivity extends BaseActivity { mModule.getFtpInfo(this).observe(this, new Observer() { @Override public void onChanged(@Nullable UploadEntity entity) { if (entity != null) { - mTaskId = entity.getId(); - if (entity.getFileSize() != 0) { - getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize())); - getBinding().setProgress(entity.isComplete() ? 100 - : (int) (entity.getCurrentProgress() * 100 / entity.getFileSize())); - } - getBinding().setUrl(entity.getUrl()); - getBinding().setFilePath(entity.getFilePath()); - mUrl = entity.getUrl(); mFilePath = entity.getFilePath(); - getBinding().setStateStr(getString( - entity.getState() == IEntity.STATE_RUNNING ? R.string.stop : R.string.start)); - } else { - getBinding().setStateStr(getString(R.string.resume)); + mUrl = entity.getUrl(); + mTaskId = entity.getId(); + getBinding().pl.setInfo(entity); } } }); - setHelpCode(); - } - - private void setHelpCode() { - try { - getBinding().codeView.setSource(AppUtil.getHelpCode(this, "FtpUpload.java")); - } catch (IOException e) { - e.printStackTrace(); - } - } + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + mTaskId = Aria.upload(this) + .loadFtp(mFilePath) + .setUploadUrl(mUrl) + .option(getOption()) + .ignoreFilePathOccupy() + .create(); + } - @Override protected int setLayoutId() { - return R.layout.activity_ftp_upload; - } + @Override public void stop(View v, AbsEntity entity) { + Aria.upload(this).loadFtp(mTaskId).stop(); + } - public void chooseUrl() { - ModifyUrlDialog dialog = - new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl); - dialog.show(getSupportFragmentManager(), "ModifyUrlDialog"); - } + @Override public void resume(View v, AbsEntity entity) { + Aria.upload(this) + .loadFtp(mTaskId) + .option(getOption()) + .resume(); + } - public void chooseFilePath() { - AppUtil.chooseFile(this, new File(mFilePath), null, OPEN_FILE_MANAGER_CODE); + @Override public void cancel(View v, AbsEntity entity) { + Aria.upload(this).loadFtp(mTaskId).cancel(true); + mTaskId = -1; + } + }); } - public void onClick(View view) { - switch (view.getId()) { - case R.id.start: - if (mTaskId == -1) { - mTaskId = Aria.upload(this) - .loadFtp(mFilePath) - .setUploadUrl(mUrl) - .setUploadInterceptor( - new IFtpUploadInterceptor() { - - @Override - public FtpInterceptHandler onIntercept(UploadEntity entity, - List fileList) { - FtpInterceptHandler.Builder builder = new FtpInterceptHandler.Builder(); - //builder.coverServerFile(); - builder.resetFileName("test.zip"); - return builder.build(); - } - }) - .option() - .login("8L8e", "8guD") - .controller(ControllerType.CREATE_CONTROLLER) - .create(); - getBinding().setStateStr(getString(R.string.stop)); - break; - } - if (Aria.upload(this).loadFtp(mTaskId).isRunning()) { - Aria.upload(this).loadFtp(mTaskId).stop(); - getBinding().setStateStr(getString(R.string.resume)); - } else { - Aria.upload(this) - .loadFtp(mTaskId) - .option() - .login("8L8e", "8guD") - .controller(ControllerType.TASK_CONTROLLER) - .resume(); - getBinding().setStateStr(getString(R.string.stop)); - } - - break; - case R.id.cancel: - Aria.upload(this).loadFtp(mTaskId).cancel(); - mTaskId = -1; - getBinding().setStateStr(getString(R.string.start)); - break; + @Override protected int setLayoutId() { + return R.layout.activity_single; + } + + private void upload() { + List paths = new ArrayList<>(); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/1.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/2.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/3.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/4.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/5.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/6.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/7.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/8.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/9.jpg"); + paths.add(Environment.getExternalStorageDirectory().getPath() + "/Download/img/img/10.jpg"); + for (String path : paths) { + Aria.upload(this) + .loadFtp(path) + .setUploadUrl(mUrl) + .option(getOption()) + .forceUpload() + .create(); } } - @Upload.onWait void onWait(UploadTask task) { - Log.d(TAG, task.getTaskName() + "_wait"); + private FtpOption getOption() { + FtpOption option = new FtpOption(); + option.login(user, pwd); + option.setUploadInterceptor(new FtpUploadInterceptor()); + return option; } - @Upload.onPre public void onPre(UploadTask task) { - getBinding().setFileSize(task.getConvertFileSize()); + @Upload.onWait + void onWait(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + Log.d(TAG, "wait ==> " + task.getEntity().getFileName()); + getBinding().pl.setInfo(task.getEntity()); + } } - @Upload.onTaskStart public void taskStart(UploadTask task) { - Log.d(TAG, "开始上传,md5:" + FileUtil.getFileMD5(new File(task.getEntity().getFilePath()))); + @Upload.onPre + protected void onPre(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } } - @Upload.onTaskResume public void taskResume(UploadTask task) { - Log.d(TAG, "恢复上传"); + @Upload.onTaskStart + void taskStart(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); + } } - @Upload.onTaskStop public void taskStop(UploadTask task) { - getBinding().setSpeed(""); - Log.d(TAG, "停止上传"); + @Upload.onTaskRunning + protected void running(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + ALog.d(TAG, "isRunning" + "; state = " + task.getEntity().getState()); + getBinding().pl.setInfo(task.getEntity()); + } } - @Upload.onTaskCancel public void taskCancel(UploadTask task) { - getBinding().setSpeed(""); - getBinding().setFileSize(""); - getBinding().setProgress(0); - Log.d(TAG, "删除任务"); + @Upload.onTaskResume + void taskResume(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + ALog.d(TAG, "resume"); + getBinding().pl.setInfo(task.getEntity()); + } } - @Upload.onTaskFail public void taskFail(UploadTask task) { - Log.d(TAG, "上传失败"); - getBinding().setStateStr(getString(R.string.resume)); + @Upload.onTaskStop + void taskStop(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + ALog.d(TAG, "stop"); + getBinding().pl.setInfo(task.getEntity()); + } } - @Upload.onTaskRunning public void taskRunning(UploadTask task) { - Log.d(TAG, "PP = " + task.getPercent()); - getBinding().setProgress(task.getPercent()); - getBinding().setSpeed(task.getConvertSpeed()); + @Upload.onTaskCancel + void taskCancel(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + mTaskId = -1; + Log.d(TAG, "cancel"); + getBinding().pl.setInfo(task.getEntity()); + } } - @Upload.onTaskComplete public void taskComplete(UploadTask task) { - getBinding().setProgress(100); - getBinding().setSpeed(""); - T.showShort(this, "文件:" + task.getEntity().getFileName() + ",上传完成"); - getBinding().setStateStr(getString(R.string.re_start)); + @Upload.onTaskFail + void taskFail(UploadTask task, Exception e) { + ALog.d(TAG, "fail"); + Toast.makeText(this, getString(R.string.upload_fail), Toast.LENGTH_SHORT) + .show(); + if (task != null && task.getEntity().getUrl().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } } - @Override protected void dataCallback(int result, Object data) { - super.dataCallback(result, data); - if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { - mModule.updateFtpUrl(this, String.valueOf(data)); + @Upload.onTaskComplete + void taskComplete(UploadTask task) { + if (task.getEntity().getUrl().equals(mUrl)) { + Toast.makeText(this, getString(R.string.upload_success), + Toast.LENGTH_SHORT).show(); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getEntity().getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); } } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == OPEN_FILE_MANAGER_CODE && resultCode == RESULT_OK) { - Uri uri = data.getData(); - if (uri != null) { - mModule.updateFtpFilePath(this, uri.getPath()); - ALog.d(TAG, String.format("选择的文件路径:%s", uri.getPath())); - } else { - ALog.d(TAG, "没有选择文件"); - } + private static class FtpUploadInterceptor implements IFtpUploadInterceptor { + @Override public FtpInterceptHandler onIntercept(UploadEntity entity, List fileList) { + FtpInterceptHandler.Builder builder = new FtpInterceptHandler.Builder(); + //builder.coverServerFile(); // 覆盖远端同名文件 + builder.resetFileName("test12.zip"); //修改上传到远端服务器的文件名 + return builder.build(); } } } diff --git a/app/src/main/java/com/arialyy/simple/core/upload/HttpUploadActivity.java b/app/src/main/java/com/arialyy/simple/core/upload/HttpUploadActivity.java index 992d83f5..a8224140 100644 --- a/app/src/main/java/com/arialyy/simple/core/upload/HttpUploadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/upload/HttpUploadActivity.java @@ -20,68 +20,80 @@ import android.os.Bundle; import android.view.View; import com.arialyy.annotations.Upload; import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.HttpOption; import com.arialyy.aria.core.common.RequestEnum; -import com.arialyy.aria.core.common.controller.ControllerType; +import com.arialyy.aria.core.task.UploadTask; import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.core.upload.UploadTask; +import com.arialyy.aria.util.ALog; import com.arialyy.frame.util.FileUtil; -import com.arialyy.frame.util.show.L; import com.arialyy.simple.R; import com.arialyy.simple.base.BaseActivity; -import com.arialyy.simple.databinding.ActivityUploadBinding; +import com.arialyy.simple.databinding.ActivitySingleBinding; import com.arialyy.simple.util.AppUtil; -import com.arialyy.simple.widget.HorizontalProgressBarWithNumber; +import com.arialyy.simple.widget.ProgressLayout; import java.io.File; /** * Created by lyy on 2017/2/9. */ -public class HttpUploadActivity extends BaseActivity { +public class HttpUploadActivity extends BaseActivity { private static final String TAG = "HttpUploadActivity"; - HorizontalProgressBarWithNumber mPb; - private final String FILE_PATH = "/mnt/sdcard/ggsg14.apk"; + //private final String FILE_PATH = "/mnt/sdcard/QQMusic-import-1.2.1.zip"; + private final String FILE_PATH = "/mnt/sdcard/update.jpg"; + //private final String FILE_PATH = + // Environment.getExternalStorageDirectory().getPath() + "/Download/QQMusic-import-1.2.1.zip"; private UploadEntity mEntity; @Override protected int setLayoutId() { - return R.layout.activity_upload; + return R.layout.activity_single; } @Override protected void init(Bundle savedInstanceState) { setTile("D_HTTP 上传"); super.init(savedInstanceState); + Aria.upload(this).getTaskList(); mEntity = Aria.upload(this).getFirstUploadEntity(FILE_PATH); - + if (mEntity == null) { + mEntity = new UploadEntity(); + mEntity.setFilePath(FILE_PATH); + } + getBinding().pl.setInfo(mEntity); Aria.upload(this).register(); - getBinding().upload.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { + + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { upload(); } - }); - getBinding().stop.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { - stop(); + + @Override public void stop(View v, AbsEntity entity) { + HttpUploadActivity.this.stop(); } - }); - getBinding().remove.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { + + @Override public void resume(View v, AbsEntity entity) { + upload(); + } + + @Override public void cancel(View v, AbsEntity entity) { remove(); } }); - mPb = getBinding().pb; } void upload() { + HttpOption option = new HttpOption(); + option.setRequestType(RequestEnum.POST); + option.setParam("params", "bbbbbbbb"); + option.setAttachment("file"); Aria.upload(HttpUploadActivity.this).load(FILE_PATH) //.setUploadUrl("http://lib-test.xzxyun.com:8042/Api/upload?data={\"type\":\"1\",\"fileType\":\".apk\"}") - .setUploadUrl("http://9.9.9.205:5000/upload/") + .setUploadUrl("http://172.16.0.91:5000/upload/") + .ignoreFilePathOccupy() //.setTempUrl("http://192.168.1.6:8080/upload/sign_file/").setAttachment("file") //.addHeader("iplanetdirectorypro", "11a09102fb934ad0bc206f9c611d7933") - .option() - .setRequestType(RequestEnum.POST) - .setParam("params", "bbbbbbbb") - .controller(ControllerType.CREATE_CONTROLLER) + .option(option) .create(); } @@ -101,32 +113,29 @@ public class HttpUploadActivity extends BaseActivity { } @Upload.onTaskStart public void taskStart(UploadTask task) { - L.d(TAG, "upload create,md5:" + FileUtil.getFileMD5(new File(task.getEntity().getFilePath()))); - getBinding().setFileSize(task.getConvertFileSize()); + ALog.d(TAG, + "upload create,md5:" + FileUtil.getFileMD5(new File(task.getEntity().getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); } @Upload.onTaskStop public void taskStop(UploadTask task) { - L.d(TAG, "upload stop"); - getBinding().setSpeed(""); - getBinding().setProgress(0); + ALog.d(TAG, "upload stop"); + getBinding().pl.setInfo(task.getEntity()); } @Upload.onTaskCancel public void taskCancel(UploadTask task) { - L.d(TAG, "upload cancel"); - getBinding().setSpeed(""); - getBinding().setProgress(0); + ALog.d(TAG, "upload cancel"); + getBinding().pl.setInfo(task.getEntity()); } @Upload.onTaskRunning public void taskRunning(UploadTask task) { - getBinding().setSpeed(task.getConvertSpeed()); - getBinding().setProgress(task.getPercent()); - L.d(TAG, "P => " + task.getPercent()); + ALog.d(TAG, "running, P = " + task.getPercent()); + getBinding().pl.setInfo(task.getEntity()); } @Upload.onTaskComplete public void taskComplete(UploadTask task) { - L.d(TAG, "上传完成"); - L.d(TAG, "上传成功服务端返回数据(如果有的话):" + task.getEntity().getResponseStr()); - getBinding().setSpeed(""); - getBinding().setProgress(100); + ALog.d(TAG, "上传完成"); + ALog.d(TAG, "上传成功服务端返回数据(如果有的话):" + task.getEntity().getResponseStr()); + getBinding().pl.setInfo(task.getEntity()); } } diff --git a/app/src/main/java/com/arialyy/simple/core/upload/SFtpUploadActivity.java b/app/src/main/java/com/arialyy/simple/core/upload/SFtpUploadActivity.java new file mode 100644 index 00000000..d208dc8b --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/core/upload/SFtpUploadActivity.java @@ -0,0 +1,183 @@ +/* + * 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.simple.core.upload; + +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProviders; +import com.arialyy.annotations.Upload; +import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.SFtpOption; +import com.arialyy.aria.core.task.UploadTask; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.simple.R; +import com.arialyy.simple.base.BaseActivity; +import com.arialyy.simple.databinding.ActivitySingleBinding; +import com.arialyy.simple.widget.ProgressLayout; +import java.io.File; + +/** + * Created by lyy on 2017/7/28. Ftp 文件上传demo + */ +public class SFtpUploadActivity extends BaseActivity { + private final int OPEN_FILE_MANAGER_CODE = 0xB1; + private String mFilePath; + private String mUrl; + private UploadModule mModule; + private long mTaskId = -1; + private String user = "tester", pwd = "password"; + + @Override protected void init(Bundle savedInstanceState) { + setTile("D_FTP 文件上传"); + super.init(savedInstanceState); + Aria.upload(this).register(); + + setUI(); + } + + private void setUI() { + mModule = ViewModelProviders.of(this).get(UploadModule.class); + mModule.getSFtpInfo(this).observe(this, new Observer() { + @Override public void onChanged(@Nullable UploadEntity entity) { + if (entity != null) { + mTaskId = entity.getId(); + mUrl = entity.getUrl(); + mFilePath = entity.getFilePath(); + getBinding().pl.setInfo(entity); + } + } + }); + getBinding().pl.setBtListener(new ProgressLayout.OnProgressLayoutBtListener() { + @Override public void create(View v, AbsEntity entity) { + mTaskId = Aria.upload(this) + .loadFtp(mFilePath) + .setUploadUrl(mUrl) + .sftpOption(getOption()) + .ignoreFilePathOccupy() + .create(); + } + + @Override public void stop(View v, AbsEntity entity) { + Aria.upload(this).loadFtp(mTaskId).stop(); + } + + @Override public void resume(View v, AbsEntity entity) { + Aria.upload(this) + .loadFtp(mTaskId) + .sftpOption(getOption()) + .resume(); + } + + @Override public void cancel(View v, AbsEntity entity) { + Aria.upload(this).loadFtp(mTaskId).cancel(false); + mTaskId = -1; + } + }); + } + + @Override protected int setLayoutId() { + return R.layout.activity_single; + } + + private SFtpOption getOption() { + SFtpOption option = new SFtpOption(); + option.login(user, pwd); + return option; + } + + @Upload.onWait + void onWait(UploadTask task) { + if (task.getKey().equals(mUrl)) { + Log.d(TAG, "wait ==> " + task.getEntity().getFileName()); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Upload.onPre + protected void onPre(UploadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Upload.onTaskStart + void taskStart(UploadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); + } + } + + @Upload.onTaskRunning + protected void running(UploadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "isRunning" + "; state = " + task.getEntity().getState()); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Upload.onTaskResume + void taskResume(UploadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "resume"); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Upload.onTaskStop + void taskStop(UploadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "stop"); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Upload.onTaskCancel + void taskCancel(UploadTask task) { + if (task.getKey().equals(mUrl)) { + mTaskId = -1; + Log.d(TAG, "cancel"); + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Upload.onTaskFail + void taskFail(UploadTask task, Exception e) { + ALog.d(TAG, "fail"); + Toast.makeText(this, getString(R.string.download_fail), Toast.LENGTH_SHORT) + .show(); + if (task != null && task.getKey().equals(mUrl)) { + getBinding().pl.setInfo(task.getEntity()); + } + } + + @Upload.onTaskComplete + void taskComplete(UploadTask task) { + if (task.getKey().equals(mUrl)) { + Toast.makeText(this, getString(R.string.download_success), + Toast.LENGTH_SHORT).show(); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getEntity().getFilePath()))); + getBinding().pl.setInfo(task.getEntity()); + } + } +} diff --git a/app/src/main/java/com/arialyy/simple/core/upload/UploadModule.java b/app/src/main/java/com/arialyy/simple/core/upload/UploadModule.java index 7c5994c0..b55ed3d2 100644 --- a/app/src/main/java/com/arialyy/simple/core/upload/UploadModule.java +++ b/app/src/main/java/com/arialyy/simple/core/upload/UploadModule.java @@ -35,9 +35,36 @@ public class UploadModule extends BaseViewModule { * 获取Ftp上传信息 */ LiveData getFtpInfo(Context context) { - String url = AppUtil.getConfigValue(context, FTP_URL_KEY, "ftp://9.9.9.205:2121/aa/你好"); - String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, - Environment.getExternalStorageDirectory().getPath() + "/AriaPrj.rar"); + //String url = AppUtil.getConfigValue(context, FTP_URL_KEY, "ftp://9.9.9.72:2121/aab/你好"); + //String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, + // Environment.getExternalStorageDirectory().getPath() + "/Download/AndroidAria.db"); + String url = "ftp://192.168.0.105:2121/aab/你好"; + String filePath = "/mnt/sdcard/QQMusic-import-1.2.1.zip"; + + UploadEntity entity = Aria.upload(context).getFirstUploadEntity(filePath); + if (entity != null) { + uploadInfo = entity; + AppUtil.setConfigValue(context, FTP_URL_KEY, uploadInfo.getUrl()); + AppUtil.setConfigValue(context, FTP_PATH_KEY, uploadInfo.getFilePath()); + } else { + uploadInfo = new UploadEntity(); + uploadInfo.setUrl(url); + uploadInfo.setFilePath(filePath); + } + + liveData.postValue(uploadInfo); + return liveData; + } + + /** + * 获取Ftp上传信息 + */ + LiveData getSFtpInfo(Context context) { + //String url = AppUtil.getConfigValue(context, FTP_URL_KEY, "ftp://9.9.9.72:2121/aab/你好"); + //String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, + // Environment.getExternalStorageDirectory().getPath() + "/Download/AndroidAria.db"); + String url = "ftp://9.9.9.72:22/aab/你好"; + String filePath = "/mnt/sdcard/QQMusic-import-1.2.1.zip"; UploadEntity entity = Aria.upload(context).getFirstUploadEntity(filePath); if (entity != null) { diff --git a/app/src/main/java/com/arialyy/simple/modlue/AnyRunnModule.java b/app/src/main/java/com/arialyy/simple/modlue/AnyRunnModule.java index 6b87c9b6..8bc2331f 100644 --- a/app/src/main/java/com/arialyy/simple/modlue/AnyRunnModule.java +++ b/app/src/main/java/com/arialyy/simple/modlue/AnyRunnModule.java @@ -20,9 +20,9 @@ import android.os.Environment; import android.util.Log; import com.arialyy.annotations.Download; import com.arialyy.aria.core.Aria; -import com.arialyy.aria.core.common.controller.ControllerType; +import com.arialyy.aria.core.common.FtpOption; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DownloadTask; +import com.arialyy.aria.core.task.DownloadTask; import com.arialyy.aria.util.CommonUtil; import com.arialyy.frame.util.show.L; import com.arialyy.simple.util.AppUtil; @@ -76,8 +76,8 @@ public class AnyRunnModule { } @Download.onTaskComplete void taskComplete(DownloadTask task) { - L.d(TAG, "path ==> " + task.getDownloadEntity().getDownloadPath()); - L.d(TAG, "md5Code ==> " + CommonUtil.getFileMD5(new File(task.getDownloadPath()))); + L.d(TAG, "path ==> " + task.getDownloadEntity().getFilePath()); + L.d(TAG, "md5Code ==> " + CommonUtil.getFileMD5(new File(task.getFilePath()))); } public void start(String url) { @@ -108,18 +108,21 @@ public class AnyRunnModule { Aria.download(this) .loadFtp(url) .setFilePath(Environment.getExternalStorageDirectory().getPath() + "/Download/") - .option() - .asFtps() - .setStorePath("/mnt/sdcard/Download/server.crt") - .setAlias("www.laoyuyu.me") - .setStorePass("123456") - .controller(ControllerType.CREATE_CONTROLLER) + .option(getFtpOption()) .create(); } else { Aria.download(this).load(mEntity.getId()).resume(); } } + private FtpOption getFtpOption() { + FtpOption option = new FtpOption(); + option.setStorePath("/mnt/sdcard/Download/server.crt") + .setAlias("www.laoyuyu.me") + .setStorePass("123456"); + return option; + } + public void stop(String url) { if (AppUtil.chekEntityValid(mEntity)) { Aria.download(this).load(mEntity.getId()).stop(); diff --git a/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java b/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java index 467af13f..2312a69a 100644 --- a/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java +++ b/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java @@ -48,7 +48,8 @@ public class CommonModule extends BaseViewModule { String[] titles = context.getResources().getStringArray(R.array.component_items); int[] icons = new int[] { R.drawable.ic_fragment, - R.drawable.ic_dialog + R.drawable.ic_dialog, + R.drawable.ic_fragment }; int i = 0; for (String title : titles) { @@ -103,7 +104,9 @@ public class CommonModule extends BaseViewModule { R.drawable.ic_ftp_dir, R.drawable.ic_ftp, R.drawable.ic_ts, - R.drawable.ic_live + R.drawable.ic_live, + R.drawable.ic_sftp, + R.drawable.ic_sftp }; int i = 0; for (String title : titles) { diff --git a/app/src/main/java/com/arialyy/simple/util/AppUtil.java b/app/src/main/java/com/arialyy/simple/util/AppUtil.java index e22e75c7..96054d2b 100644 --- a/app/src/main/java/com/arialyy/simple/util/AppUtil.java +++ b/app/src/main/java/com/arialyy/simple/util/AppUtil.java @@ -24,9 +24,8 @@ import android.net.Uri; import android.os.Build; import android.text.TextUtils; import androidx.core.content.FileProvider; -import com.arialyy.aria.core.inf.AbsEntity; +import com.arialyy.aria.core.common.AbsEntity; import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CommonUtil; import com.arialyy.aria.util.FileUtil; import com.arialyy.simple.BuildConfig; import java.io.File; @@ -52,8 +51,8 @@ public class AppUtil { String path = String.format("%s/code/%s", context.getFilesDir().getPath(), fileName); File ftpCode = new File(path); if (!ftpCode.exists()) { - FileUtil.createFile(path); - CommonUtil.createFileFormInputStream(context.getAssets() + FileUtil.createFile(ftpCode); + FileUtil.createFileFormInputStream(context.getAssets() .open(String.format("help_code/%s", fileName)), path); } diff --git a/app/src/main/java/com/arialyy/simple/widget/ProgressLayout.java b/app/src/main/java/com/arialyy/simple/widget/ProgressLayout.java new file mode 100644 index 00000000..64aa49a9 --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/widget/ProgressLayout.java @@ -0,0 +1,267 @@ +/* + * 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.simple.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.RelativeLayout; +import android.widget.TextView; +import androidx.appcompat.widget.AppCompatImageButton; +import com.arialyy.aria.core.common.AbsEntity; +import com.arialyy.aria.core.common.AbsGroupEntity; +import com.arialyy.aria.core.common.AbsNormalEntity; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.simple.R; +import java.math.BigDecimal; + +/** + * 统一的进度布局 + */ +public class ProgressLayout extends RelativeLayout implements View.OnClickListener { + private final String TAG = "ProgressLayout"; + private TextView speedOrState, fileName, leftTime, fileSize; + private HorizontalProgressBarWithNumber pb; + private AppCompatImageButton delBt; + private Button handleBt; + private OnProgressLayoutBtListener listener; + private AbsEntity entity; + private int currentState; + + public interface OnProgressLayoutBtListener { + /** + * 处理创建操作 + */ + void create(View v, AbsEntity entity); + + /** + * 处理任务暂停的操作 + */ + void stop(View v, AbsEntity entity); + + /** + * 处理恢复任务的操作 + */ + void resume(View v, AbsEntity entity); + + /** + * 处理任务删除的操作 + */ + void cancel(View v, AbsEntity entity); + } + + public ProgressLayout(Context context) { + this(context, null); + } + + public ProgressLayout(Context context, AttributeSet attrs) { + this(context, attrs, -1); + } + + public ProgressLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + LayoutInflater.from(context).inflate(R.layout.layout_progress_content, this, true); + speedOrState = findViewById(R.id.speed_or_state); + fileName = findViewById(R.id.file_name); + leftTime = findViewById(R.id.left_time); + fileSize = findViewById(R.id.file_size); + pb = findViewById(R.id.pb); + delBt = findViewById(R.id.del_bt); + handleBt = findViewById(R.id.handle_bt); + delBt.setOnClickListener(this); + handleBt.setOnClickListener(this); + } + + public void setBtListener(OnProgressLayoutBtListener listener) { + this.listener = listener; + } + + @Override public void onClick(View v) { + if (listener == null) { + ALog.e(TAG, "没有设置OnProgressLayoutBtListener"); + return; + } + if (entity == null) { + ALog.d(TAG, "entity 为空,请设置信息"); + return; + } + + switch (v.getId()) { + case R.id.del_bt: + listener.cancel(v, entity); + initState(); + break; + case R.id.handle_bt: + handleTask(v); + break; + } + } + + private void initState() { + fileName.setText("-"); + leftTime.setText(""); + speedOrState.setText(""); + fileSize.setText("-/-"); + pb.setProgress(0); + } + + private void handleTask(View v) { + switch (entity.getState()) { + case IEntity.STATE_OTHER: + case IEntity.STATE_FAIL: + case IEntity.STATE_STOP: + listener.resume(v, entity); + break; + case IEntity.STATE_COMPLETE: + case IEntity.STATE_WAIT: + if (entity.getId() != -1) { + listener.resume(v, entity); + } else { + listener.create(v, entity); + } + break; + case IEntity.STATE_PRE: + case IEntity.STATE_POST_PRE: + case IEntity.STATE_RUNNING: + listener.stop(v, entity); + break; + default: + listener.create(v, entity); + break; + } + } + + public void setInfo(AbsEntity entity) { + this.entity = entity; + this.currentState = entity.getState(); + if (entity instanceof AbsNormalEntity) { + AbsNormalEntity normalEntity = (AbsNormalEntity) entity; + //ALog.d(TAG, "fileName = " + ((AbsNormalEntity) entity).getFileName()); + fileName.setText(normalEntity.getFileName()); + } else if (entity instanceof AbsGroupEntity) { + AbsGroupEntity groupEntity = (AbsGroupEntity) entity; + fileName.setText( + groupEntity.getAlias() == null ? groupEntity.getKey() : groupEntity.getAlias()); + } + + String str = + formatFileSize(entity.getCurrentProgress()) + "/" + formatFileSize(entity.getFileSize()); + fileSize.setText(str); + leftTime.setText(CommonUtil.formatTime(entity.getTimeLeft())); + String btStr = getResources().getString(R.string.start); + String stateStr = ""; + switch (entity.getState()) { + case IEntity.STATE_WAIT: + btStr = getResources().getString(R.string.start); + stateStr = getResources().getString(R.string.waiting); + break; + case IEntity.STATE_OTHER: + case IEntity.STATE_FAIL: + btStr = getResources().getString(R.string.start); + stateStr = getResources().getString(R.string.state_error); + break; + case IEntity.STATE_STOP: + btStr = getResources().getString(R.string.resume); + stateStr = getResources().getString(R.string.stopped); + break; + case IEntity.STATE_PRE: + case IEntity.STATE_POST_PRE: + case IEntity.STATE_RUNNING: + btStr = getResources().getString(R.string.stop); + stateStr = entity.getConvertSpeed(); + leftTime.setText(CommonUtil.formatTime(entity.getTimeLeft())); + break; + case IEntity.STATE_COMPLETE: + btStr = getResources().getString(R.string.re_start); + stateStr = getResources().getString(R.string.completed); + break; + case IEntity.STATE_CANCEL: + initState(); + break; + default: + btStr = getResources().getString(R.string.start); + stateStr = ""; + leftTime.setText(""); + } + this.handleBt.setText(btStr); + this.speedOrState.setText(stateStr); + if (entity.getState() != IEntity.STATE_CANCEL){ + this.pb.setProgress(entity.getPercent()); + } + } + + public void setFileName(Character fileName) { + this.fileName.setText(fileName); + } + + public void setLeftTime(Character leftTime) { + this.leftTime.setText(leftTime); + } + + public void setFileSize(Character fileSize) { + this.fileSize.setText(fileSize); + } + + public void setProgress(int progress) { + this.pb.setProgress(progress); + } + + public void setSpeed(Character speed) { + this.speedOrState.setText(speed); + } + + public void setState(Character state) { + this.speedOrState.setText(state); + } + + public String formatFileSize(double size) { + if (size < 0) { + return "0k"; + } + double kiloByte = size / 1024; + if (kiloByte < 1) { + return size + "b"; + } + + double megaByte = kiloByte / 1024; + if (megaByte < 1) { + BigDecimal result1 = new BigDecimal(Double.toString(kiloByte)); + return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "k"; + } + + double gigaByte = megaByte / 1024; + if (gigaByte < 1) { + BigDecimal result2 = new BigDecimal(Double.toString(megaByte)); + return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "m"; + } + + double teraBytes = gigaByte / 1024; + if (teraBytes < 1) { + BigDecimal result3 = new BigDecimal(Double.toString(gigaByte)); + return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "g"; + } + BigDecimal result4 = new BigDecimal(teraBytes); + return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "t"; + } +} diff --git a/app/src/main/java/com/arialyy/simple/widget/SubStateLinearLayout.java b/app/src/main/java/com/arialyy/simple/widget/SubStateLinearLayout.java index 0c42cfcc..9ee9644f 100644 --- a/app/src/main/java/com/arialyy/simple/widget/SubStateLinearLayout.java +++ b/app/src/main/java/com/arialyy/simple/widget/SubStateLinearLayout.java @@ -74,7 +74,7 @@ public class SubStateLinearLayout extends LinearLayout implements View.OnClickLi int i = 1; for (DownloadEntity entity : datas) { TextView view = createView(i - 1, entity); - mPosition.put(entity.getDownloadPath(), i); + mPosition.put(entity.getFilePath(), i); addView(view, i); i++; } diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 00000000..b2fcd6e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sftp.xml b/app/src/main/res/drawable/ic_sftp.xml new file mode 100644 index 00000000..e479aa64 --- /dev/null +++ b/app/src/main/res/drawable/ic_sftp.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/activity_download_group.xml b/app/src/main/res/layout/activity_download_group.xml index 5191f4d5..7b2e0072 100644 --- a/app/src/main/res/layout/activity_download_group.xml +++ b/app/src/main/res/layout/activity_download_group.xml @@ -1,26 +1,5 @@ - - - - - - - - + - + - diff --git a/app/src/main/res/layout/activity_ftp_download.xml b/app/src/main/res/layout/activity_ftp_download.xml deleted file mode 100644 index b6f161b1..00000000 --- a/app/src/main/res/layout/activity_ftp_download.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_ftp_upload.xml b/app/src/main/res/layout/activity_ftp_upload.xml deleted file mode 100644 index 174e9ab4..00000000 --- a/app/src/main/res/layout/activity_ftp_upload.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_m3u8_live.xml b/app/src/main/res/layout/activity_m3u8_live.xml index d1b9bb02..2c3c0a51 100644 --- a/app/src/main/res/layout/activity_m3u8_live.xml +++ b/app/src/main/res/layout/activity_m3u8_live.xml @@ -1,46 +1,7 @@ - + - - - - - - - - - - - - - - + - - - - - - - - -