重构线程任务模块

v3.6.6
laoyuyu 6 years ago
parent 39a43c836d
commit d3fb30d8b9
  1. 81
      AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java
  2. 56
      AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java
  3. 951
      AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java
  4. 77
      AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java
  5. 194
      AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java
  6. 7
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  7. 551
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
  8. 360
      Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java
  9. 44
      Aria/src/main/java/com/arialyy/aria/core/common/BaseListener.java
  10. 6
      Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java
  11. 67
      Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java
  12. 190
      Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java
  13. 2
      Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java
  14. 419
      Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java
  15. 82
      Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java
  16. 29
      Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java
  17. 8
      Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java
  18. 11
      Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java
  19. 223
      Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java
  20. 4
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java
  21. 11
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java
  22. 88
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskDelegate.java
  23. 144
      Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java
  24. 3
      Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java
  25. 199
      Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java
  26. 159
      Aria/src/main/java/com/arialyy/aria/core/download/AbsGroupDelegate.java
  27. 26
      Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java
  28. 19
      Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java
  29. 201
      Aria/src/main/java/com/arialyy/aria/core/download/DNormalDelegate.java
  30. 10
      Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java
  31. 39
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java
  32. 6
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java
  33. 21
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java
  34. 91
      Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDelegate.java
  35. 247
      Aria/src/main/java/com/arialyy/aria/core/download/HttpGroupDelegate.java
  36. 11
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java
  37. 34
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java
  38. 69
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java
  39. 15
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java
  40. 79
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java
  41. 31
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java
  42. 5
      Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java
  43. 6
      Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java
  44. 32
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IM3U8UrlExtInfHandler.java
  45. 17
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/LiveProtocol.java
  46. 62
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java
  47. 158
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileInfoThread.java
  48. 325
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileLoader.java
  49. 136
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileUtil.java
  50. 22
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java
  51. 63
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java
  52. 140
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadStateManager.java
  53. 247
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java
  54. 58
      Aria/src/main/java/com/arialyy/aria/core/inf/IGroupTarget.java
  55. 66
      Aria/src/main/java/com/arialyy/aria/core/inf/INormalTarget.java
  56. 4
      Aria/src/main/java/com/arialyy/aria/core/manager/DTaskWrapperFactory.java
  57. 21
      Aria/src/main/java/com/arialyy/aria/core/upload/BaseUListener.java
  58. 155
      Aria/src/main/java/com/arialyy/aria/core/upload/UNormalDelegate.java
  59. 3
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java
  60. 38
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java
  61. 14
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java
  62. 4
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java
  63. 16
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java
  64. 28
      Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java
  65. 2
      Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java
  66. 61
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  67. 79
      Aria/src/main/java/com/arialyy/aria/util/IdGenerator.java
  68. 1
      DEV_LOG.md
  69. 4
      app/build.gradle
  70. 3
      app/src/main/AndroidManifest.xml
  71. 47
      app/src/main/java/com/arialyy/simple/MainActivity.java
  72. 75
      app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java
  73. 4
      app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java
  74. 268
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8DownloadActivity.java
  75. 95
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8Module.java
  76. 52
      app/src/main/java/com/arialyy/simple/modlue/CommonModule.java
  77. 33
      app/src/main/res/drawable/ic_ts.xml
  78. 80
      app/src/main/res/layout/activity_m3u8.xml
  79. 2
      app/src/main/res/values/strings.xml

@ -1,81 +0,0 @@
package com.arialyy.frame.cache;
import android.text.TextUtils;
import com.arialyy.frame.util.show.L;
import java.util.Map;
import java.util.Set;
/**
* Created by AriaLyy@outlook.com on 2015/4/9.
*/
public class CacheHelp {
/**
* 设置缓存有效时间
*/
public static void setCacheAvailableTime(long availableTime) {
CacheObj.CACHE_INTERVAL = availableTime;
}
/**
* 储存缓存
*/
public static void saveCache(CacheUtil util, String key, String value) {
if (TextUtils.isEmpty(value)) {
L.e("缓存数据位null");
return;
}
CacheObj cache = util.getObjectCache(CacheObj.class, key);
if (cache == null) {
cache = new CacheObj();
}
cache.setCacheTime(System.currentTimeMillis());
cache.setAvailableNum(cache.getAvailableNum() + 1);
cache.setData(value);
util.putObjectCache(CacheObj.class, key, cache);
}
/**
* 获取有效的缓存
*/
public static CacheObj getAvailableCache(CacheUtil util, String url, Map<String, String> param) {
if (TextUtils.isEmpty(url)) {
return null;
}
String key = getKey(url, param);
CacheObj cache = util.getObjectCache(CacheObj.class, key);
if (cache == null) {
return null;
}
if (cache.isAvailable()) {
return cache;
}
return null;
}
/**
* 获取缓存有效或无效
*/
public static CacheObj getCache(CacheUtil util, String url, Map<String, String> param) {
if (TextUtils.isEmpty(url)) {
return null;
}
String key = getKey(url, param);
return util.getObjectCache(CacheObj.class, key);
}
/**
* 通过url和请求参数获取key
*/
public static String getKey(String url, Map<String, String> param) {
if (param == null || param.size() == 0) {
return url;
}
String p = "";
for (Map.Entry<String, String> entry : param.entrySet()) {
p += "[" + entry.getKey() + "," + entry.getValue() + "];";
}
url += ";" + p;
return url;
}
}

@ -1,56 +0,0 @@
package com.arialyy.frame.cache;
/**
* Created by AriaLyy@outlook.com on 2015/4/9.
* 缓存对象
*/
class CacheObj {
/**
* 缓存有效请求时间
*/
public static long CACHE_INTERVAL = 120 * 1000;
/**
* 缓存有效请求次数
*/
public static final int CACHE_NUM_INTERVAL = 5;
private long cacheTime = 0;
private long availableNum = 0;
private String data;
/**
* 缓存是否有效
*/
public boolean isAvailable() {
if (System.currentTimeMillis() - cacheTime > CACHE_INTERVAL) {
return false;
} else if (availableNum >= CACHE_NUM_INTERVAL) {
return false;
}
return true;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public long getCacheTime() {
return cacheTime;
}
public void setCacheTime(long cacheTime) {
this.cacheTime = cacheTime;
}
public long getAvailableNum() {
return availableNum;
}
public void setAvailableNum(long availableNum) {
this.availableNum = availableNum;
}
}

@ -1,951 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* 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.frame.cache.diskcache;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A cache that uses a bounded amount of space on a filesystem. Each cache
* entry has a string key and a fixed number of values. Each key must match
* the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,
* accessible as streams or files. Each value must be between {@code 0} and
* {@code Integer.MAX_VALUE} bytes in length.
*
* <p>The cache stores its data in a directory on the filesystem. This
* directory must be exclusive to the cache; the cache may delete or overwrite
* files from its directory. It is an error for multiple processes to use the
* same cache directory at the same time.
*
* <p>This cache limits the number of bytes that it will store on the
* filesystem. When the number of stored bytes exceeds the limit, the cache will
* remove entries in the background until the limit is satisfied. The limit is
* not strict: the cache may temporarily exceed it while waiting for files to be
* deleted. The limit does not include filesystem overhead or the cache
* journal so space-sensitive applications should set a conservative limit.
*
* <p>Clients call {@link #edit} to create or update the values of an entry. An
* entry may have only one editor at one time; if a value is not available to be
* edited then {@link #edit} will return null.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to
* supply a full set of values; the empty value should be used as a
* placeholder if necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary
* to supply data for every value; values default to their previous
* value.
* </ul>
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
* of values as they were before or after the commit, but never a mix of values.
*
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
* observe the value at the time that {@link #get} was called. Updates and
* removals after the call do not impact ongoing reads.
*
* <p>This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If
* an error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
public final class DiskLruCache implements Closeable {
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TEMP = "journal.tmp";
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
static final String MAGIC = "libcore.io.DiskLruCache";
static final String VERSION_1 = "1";
static final long ANY_SEQUENCE_NUMBER = -1;
static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final File journalFileBackup;
private final int appVersion;
private long maxSize;
private final int valueCount;
private long size = 0;
private Writer journalWriter;
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);
private int redundantOpCount;
/**
* To differentiate between old and current snapshots, each entry is given
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0;
/** This cache uses a single background thread to evict entries. */
final ThreadPoolExecutor executorService =
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private final Callable<Void> cleanupCallable = new Callable<Void>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws IOException if reading or writing the cache directory fails
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// If a bkp file exists, use it instead.
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
File journalFile = new File(directory, JOURNAL_FILE);
// If journal file also exists just delete backup file.
if (journalFile.exists()) {
backupFile.delete();
} else {
renameTo(backupFile, journalFile, false);
}
}
// Prefer to pick up where we left off.
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal();
cache.processJournal();
return cache;
} catch (IOException journalIsCorrupt) {
System.out.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete();
}
}
// Create a new empty cache.
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
private void readJournal() throws IOException {
StrictLineReader reader =
new StrictLineReader(new FileInputStream(journalFile), DiskLruCacheUtil.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
if (!MAGIC.equals(magic) || !VERSION_1.equals(version) || !Integer.toString(appVersion)
.equals(appVersionString) || !Integer.toString(valueCount).equals(valueCountString) || !""
.equals(blank)) {
throw new IOException("unexpected journal header: ["
+ magic
+ ", "
+ version
+ ", "
+ valueCountString
+ ", "
+ blank
+ "]");
}
int lineCount = 0;
while (true) {
try {
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
// If we ended on a truncated line, rebuild the journal before appending to it.
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true),
DiskLruCacheUtil.US_ASCII));
}
} finally {
DiskLruCacheUtil.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private void processJournal() throws IOException {
deleteIfExists(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
deleteIfExists(entry.getCleanFile(t));
deleteIfExists(entry.getDirtyFile(t));
}
i.remove();
}
}
}
/**
* Creates a new journal that omits redundant information. This replaces the
* current journal if it exists.
*/
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
Writer writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFileTmp), DiskLruCacheUtil.US_ASCII));
try {
writer.write(MAGIC);
writer.write("\n");
writer.write(VERSION_1);
writer.write("\n");
writer.write(Integer.toString(appVersion));
writer.write("\n");
writer.write(Integer.toString(valueCount));
writer.write("\n");
writer.write("\n");
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.write(DIRTY + ' ' + entry.key + '\n');
} else {
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
}
}
} finally {
writer.close();
}
if (journalFile.exists()) {
renameTo(journalFile, journalFileBackup, true);
}
renameTo(journalFileTmp, journalFile, false);
journalFileBackup.delete();
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true), DiskLruCacheUtil.US_ASCII));
}
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
if (deleteDestination) {
deleteIfExists(to);
}
if (!from.renameTo(to)) {
throw new IOException();
}
}
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
InputStream[] ins = new InputStream[valueCount];
try {
for (int i = 0; i < valueCount; i++) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// A file must have been deleted manually!
for (int i = 0; i < valueCount; i++) {
if (ins[i] != null) {
DiskLruCacheUtil.closeQuietly(ins[i]);
} else {
break;
}
}
return null;
}
redundantOpCount++;
journalWriter.append(READ + ' ' + key + '\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null; // Snapshot is stale.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
} else if (entry.currentEditor != null) {
return null; // Another edit is in progress.
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// Flush the journal before creating files to prevent file leaks.
journalWriter.write(DIRTY + ' ' + key + '\n');
journalWriter.flush();
return editor;
}
/** Returns the directory where this cache stores its data. */
public File getDirectory() {
return directory;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public synchronized long getMaxSize() {
return maxSize;
}
/**
* Changes the maximum number of bytes the cache can store and queues a job
* to trim the existing store, if necessary.
*/
public synchronized void setMaxSize(long maxSize) {
this.maxSize = maxSize;
executorService.submit(cleanupCallable);
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public synchronized long size() {
return size;
}
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
// If this edit is creating the entry for the first time, every index must have a value.
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
}
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
entry.readable = true;
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
/**
* We only rebuild the journal when it will halve the size of the journal
* and eliminate at least 2000 ops.
*/
private boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold //
&& redundantOpCount >= lruEntries.size();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
journalWriter.append(REMOVE + ' ' + key + '\n');
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
/** Returns true if this cache has been closed. */
public synchronized boolean isClosed() {
return journalWriter == null;
}
private void checkNotClosed() {
if (journalWriter == null) {
throw new IllegalStateException("cache is closed");
}
}
/** Force buffered operations to the filesystem. */
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}
/** Closes this cache. Stored values will remain on the filesystem. */
public synchronized void close() throws IOException {
if (journalWriter == null) {
return; // Already closed.
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
remove(toEvict.getKey());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public void delete() throws IOException {
close();
DiskLruCacheUtil.deleteContents(directory);
}
private void validateKey(String key) {
Matcher matcher = LEGAL_KEY_PATTERN.matcher(key);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"keys must match regex " + STRING_KEY_PATTERN + ": \"" + key + "\"");
}
}
private static String inputStreamToString(InputStream in) throws IOException {
return DiskLruCacheUtil.readFully(new InputStreamReader(in, DiskLruCacheUtil.UTF_8));
}
/** A snapshot of the values for an entry. */
public final class Snapshot implements Closeable {
private final String key;
private final long sequenceNumber;
private final InputStream[] ins;
private final long[] lengths;
private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) {
this.key = key;
this.sequenceNumber = sequenceNumber;
this.ins = ins;
this.lengths = lengths;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public Editor edit() throws IOException {
return DiskLruCache.this.edit(key, sequenceNumber);
}
/** Returns the unbuffered stream with the value for {@code index}. */
public InputStream getInputStream(int index) {
return ins[index];
}
/** Returns the string value for {@code index}. */
public String getString(int index) throws IOException {
return inputStreamToString(getInputStream(index));
}
/** Returns the byte length of the value for {@code index}. */
public long getLength(int index) {
return lengths[index];
}
public void close() {
for (InputStream in : ins) {
DiskLruCacheUtil.closeQuietly(in);
}
}
}
private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
@Override public void write(int b) throws IOException {
// Eat all writes silently. Nom nom.
}
};
/** Edits the values for an entry. */
public final class Editor {
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public InputStream newInputStream(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
return null;
}
try {
return new FileInputStream(entry.getCleanFile(index));
} catch (FileNotFoundException e) {
return null;
}
}
}
/**
* Returns the last committed value as a string, or null if no value
* has been committed.
*/
public String getString(int index) throws IOException {
InputStream in = newInputStream(index);
return in != null ? inputStreamToString(in) : null;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public OutputStream newOutputStream(int index) throws IOException {
if (index < 0 || index >= valueCount) {
throw new IllegalArgumentException("Expected index "
+ index
+ " to "
+ "be greater than 0 and less than the maximum value count "
+ "of "
+ valueCount);
}
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.getDirtyFile(index);
FileOutputStream outputStream;
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e) {
// Attempt to recreate the cache directory.
directory.mkdirs();
try {
outputStream = new FileOutputStream(dirtyFile);
} catch (FileNotFoundException e2) {
// We are unable to recover. Silently eat the writes.
return NULL_OUTPUT_STREAM;
}
}
return new FaultHidingOutputStream(outputStream);
}
}
/** Sets the value at {@code index} to {@code value}. */
public void set(int index, String value) throws IOException {
Writer writer = null;
try {
writer = new OutputStreamWriter(newOutputStream(index), DiskLruCacheUtil.UTF_8);
writer.write(value);
} finally {
DiskLruCacheUtil.closeQuietly(writer);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public void commit() throws IOException {
if (hasErrors) {
completeEdit(this, false);
remove(entry.key); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public void abort() throws IOException {
completeEdit(this, false);
}
public void abortUnlessCommitted() {
if (!committed) {
try {
abort();
} catch (IOException ignored) {
}
}
}
private class FaultHidingOutputStream extends FilterOutputStream {
private FaultHidingOutputStream(OutputStream out) {
super(out);
}
@Override public void write(int oneByte) {
try {
out.write(oneByte);
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void write(byte[] buffer, int offset, int length) {
try {
out.write(buffer, offset, length);
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void close() {
try {
out.close();
} catch (IOException e) {
hasErrors = true;
}
}
@Override public void flush() {
try {
out.flush();
} catch (IOException e) {
hasErrors = true;
}
}
}
}
private final class Entry {
private final String key;
/** Lengths of this entry's files. */
private final long[] lengths;
/** True if this entry has ever been published. */
private boolean readable;
/** The ongoing edit or null if this entry is not being edited. */
private Editor currentEditor;
/** The sequence number of the most recently committed edit to this entry. */
private long sequenceNumber;
private Entry(String key) {
this.key = key;
this.lengths = new long[valueCount];
}
public String getLengths() throws IOException {
StringBuilder result = new StringBuilder();
for (long size : lengths) {
result.append(' ').append(size);
}
return result.toString();
}
/** Set lengths using decimal numbers like "10123". */
private void setLengths(String[] strings) throws IOException {
if (strings.length != valueCount) {
throw invalidLengths(strings);
}
try {
for (int i = 0; i < strings.length; i++) {
lengths[i] = Long.parseLong(strings[i]);
}
} catch (NumberFormatException e) {
throw invalidLengths(strings);
}
}
private IOException invalidLengths(String[] strings) throws IOException {
throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings));
}
public File getCleanFile(int i) {
return new File(directory, key + "." + i);
}
public File getDirtyFile(int i) {
return new File(directory, key + "." + i + ".tmp");
}
}
}

@ -1,77 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* 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.frame.cache.diskcache;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.charset.Charset;
/** Junk drawer of utility methods. */
final class DiskLruCacheUtil {
static final Charset US_ASCII = Charset.forName("US-ASCII");
static final Charset UTF_8 = Charset.forName("UTF-8");
private DiskLruCacheUtil() {
}
static String readFully(Reader reader) throws IOException {
try {
StringWriter writer = new StringWriter();
char[] buffer = new char[1024];
int count;
while ((count = reader.read(buffer)) != -1) {
writer.write(buffer, 0, count);
}
return writer.toString();
} finally {
reader.close();
}
}
/**
* Deletes the contents of {@code dir}. Throws an IOException if any file
* could not be deleted, or if {@code dir} is not a readable directory.
*/
static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
throw new IOException("not a readable directory: " + dir);
}
for (File file : files) {
if (file.isDirectory()) {
deleteContents(file);
}
if (!file.delete()) {
throw new IOException("failed to delete file: " + file);
}
}
}
static void closeQuietly(/*Auto*/Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
}

@ -1,194 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.frame.cache.diskcache;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
/**
* Buffers input from an {@link InputStream} for reading lines.
*
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
* line at end of input is invalid and will be ignored, the caller may use {@code
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
*
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
* end-of-input reporting and a more restrictive definition of a line.
*
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
* and 10, respectively, and the representation of no other character contains these values.
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
* The default charset is US_ASCII.
*/
class StrictLineReader implements Closeable {
private static final byte CR = (byte) '\r';
private static final byte LF = (byte) '\n';
private final InputStream in;
private final Charset charset;
/*
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
*/
private byte[] buf;
private int pos;
private int end;
/**
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
*
* @param in the {@code InputStream} to read data from.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if the specified charset is not supported.
*/
public StrictLineReader(InputStream in, Charset charset) {
this(in, 8192, charset);
}
/**
* Constructs a new {@code LineReader} with the specified capacity and charset.
*
* @param in the {@code InputStream} to read data from.
* @param capacity the capacity of the buffer.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if {@code capacity} is negative or zero
* or the specified charset is not supported.
*/
public StrictLineReader(InputStream in, int capacity, Charset charset) {
if (in == null || charset == null) {
throw new NullPointerException();
}
if (capacity < 0) {
throw new IllegalArgumentException("capacity <= 0");
}
if (!(charset.equals(DiskLruCacheUtil.US_ASCII))) {
throw new IllegalArgumentException("Unsupported encoding");
}
this.in = in;
this.charset = charset;
buf = new byte[capacity];
}
/**
* Closes the reader by closing the underlying {@code InputStream} and
* marking this reader as closed.
*
* @throws IOException for errors when closing the underlying {@code InputStream}.
*/
public void close() throws IOException {
synchronized (in) {
if (buf != null) {
buf = null;
in.close();
}
}
}
/**
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
* this end of line marker is not included in the result.
*
* @return the next line from the input.
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
*/
public String readLine() throws IOException {
synchronized (in) {
if (buf == null) {
throw new IOException("LineReader is closed");
}
// Read more data if we are at the end of the buffered data.
// Though it's an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
if (pos >= end) {
fillBuf();
}
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
String res = new String(buf, pos, lineEnd - pos, charset.name());
pos = i + 1;
return res;
}
}
// Let's anticipate up to 80 characters on top of those already read.
ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
@Override public String toString() {
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
try {
return new String(buf, 0, length, charset.name());
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e); // Since we control the charset this will never happen.
}
}
};
while (true) {
out.write(buf, pos, end - pos);
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end = -1;
fillBuf();
// Try to find LF in the buffered data and return the line if successful.
for (int i = pos; i != end; ++i) {
if (buf[i] == LF) {
if (i != pos) {
out.write(buf, pos, i - pos);
}
pos = i + 1;
return out.toString();
}
}
}
}
}
public boolean hasUnterminatedLine() {
return end == -1;
}
/**
* Reads new input data into the buffer. Call only with pos == end or end == -1,
* depending on the desired outcome if the function throws.
*/
private void fillBuf() throws IOException {
int result = in.read(buf, 0, buf.length);
if (result == -1) {
throw new EOFException();
}
pos = 0;
end = result;
}
}

@ -30,6 +30,7 @@ import android.support.v4.app.Fragment;
import android.widget.PopupWindow;
import com.arialyy.aria.core.command.ICmd;
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.DGroupConfig;
import com.arialyy.aria.core.config.DownloadConfig;
@ -145,7 +146,7 @@ import org.xml.sax.SAXException;
}
public synchronized Handler getAriaHandler() {
if (mAriaHandler == null){
if (mAriaHandler == null) {
mAriaHandler = new Handler(Looper.getMainLooper());
}
return mAriaHandler;
@ -293,13 +294,13 @@ import org.xml.sax.SAXException;
public void delRecord(int type, String key, boolean removeFile) {
switch (type) {
case 1: // 删除普通任务记录
CommonUtil.delTaskRecord(key, 1, removeFile, true);
CommonUtil.delTaskRecord(key, RecordHandler.TYPE_DOWNLOAD, removeFile, true);
break;
case 2:
CommonUtil.delGroupTaskRecord(key, removeFile);
break;
case 3:
CommonUtil.delTaskRecord(key, 2);
CommonUtil.delTaskRecord(key, RecordHandler.TYPE_UPLOAD);
break;
}
}

@ -16,81 +16,57 @@
package com.arialyy.aria.core.common;
import android.content.Context;
import android.os.Looper;
import android.util.SparseArray;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.BaseDListener;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DTaskWrapper;
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.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 java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by AriaL on 2017/7/1. 任务处理器
* Created by AriaL on 2017/7/1.
* 控制线程任务状态开始停止取消重试
*/
public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>>
implements Runnable {
private static final String STATE = "_state_";
private static final String RECORD = "_record_";
/**
* 分块文件路径
*/
public static final String SUB_PATH = "%s.%s.part";
/**
* 小于1m的文件不启用多线程
*/
protected static final long SUB_LEN = 1024 * 1024;
protected static final int DOWNLOAD = 0x1;
protected static final int UPLOAD = 0x2;
private final String TAG = "AbsFileer";
protected final String TAG;
protected IEventListener mListener;
protected TASK_WRAPPER mTaskWrapper;
protected ENTITY mEntity;
protected Context mContext;
protected File mTempFile; //文件
protected StateConstance mConstance;
//总线程数
protected int mTotalThreadNum;
//已完成的线程数
private int mCompleteThreadNum;
private SparseArray<AbsThreadTask> mTask = new SparseArray<>();
private SparseArray<AbsThreadTask> mTask = new SparseArray<>();
private ScheduledThreadPoolExecutor mTimer;
@Deprecated private File mConfigFile;
/**
* 进度刷新间隔
*/
private long mUpdateInterval = 1000;
protected TaskRecord mRecord;
private IThreadState mStateManager;
private boolean isCancel = false, isStop = false;
protected AbsFileer(IEventListener listener, TASK_WRAPPER taskEntity) {
protected AbsFileer(IEventListener listener, TASK_WRAPPER wrapper) {
mListener = listener;
mTaskWrapper = taskEntity;
mTaskWrapper = wrapper;
mEntity = mTaskWrapper.getEntity();
mContext = AriaManager.APP;
mConstance = new StateConstance();
TAG = CommonUtil.getClassName(getClass());
}
protected abstract int getType();
protected abstract IThreadState getStateManager(Looper looper);
public void setNewTask(boolean newTask) {
mTaskWrapper.setNewTask(newTask);
}
/**
* 处理任务
*/
protected abstract void handleTask();
public String getKey() {
return mTaskWrapper.getKey();
@ -100,18 +76,8 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
return mEntity;
}
/**
* 设置最大下载/上传速度
*
* @param maxSpeed 单位为kb
*/
public void setMaxSpeed(int maxSpeed) {
for (int i = 0; i < mTotalThreadNum; i++) {
AbsThreadTask task = mTask.get(i);
if (task != null) {
task.setMaxSpeed(maxSpeed);
}
}
public SparseArray<AbsThreadTask> getTaskList() {
return mTask;
}
/**
@ -119,8 +85,6 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
*/
private void resetState() {
closeTimer();
mCompleteThreadNum = 0;
mTotalThreadNum = 0;
if (mTask != null && mTask.size() != 0) {
for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i);
@ -140,44 +104,14 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
return;
}
resetState();
mConstance.resetState();
ALog.d(TAG, "path: " + getFilePath());
checkRecord();
mConstance.TASK_RECORD = mRecord;
if (!mTaskWrapper.isSupportBP() || mTaskWrapper.asHttp().isChunked()) {
mTotalThreadNum = 1;
} else {
mTotalThreadNum =
mTaskWrapper.isNewTask() ? setNewTaskThreadNum() : mTotalThreadNum;
}
mConstance.START_THREAD_NUM = mTotalThreadNum;
// 处理分块和动态文件参数
if (mTaskWrapper.isNewTask() && mTaskWrapper instanceof DTaskWrapper) {
AriaManager manager = AriaManager.getInstance(AriaManager.APP);
mRecord.isBlock = mTotalThreadNum > 1 && manager.getDownloadConfig().isUseBlock();
// 线程数不等1并且没有使用块下载,则认为没有使用动态文件
mRecord.isOpenDynamicFile = mTotalThreadNum == 1 || mRecord.isBlock;
}
/*
* mTaskWrapper.getEntity().getFileSize() != mTempFile.length()为兼容以前老版本代码
* 动态长度条件
* 1总线程数为1并且是新任务
* 2总线程数为1不是新任务但是长度不是文件全长度
*/
if (mTotalThreadNum == 1 && (mTaskWrapper.getEntity().getFileSize() != mTempFile.length())) {
mRecord.isOpenDynamicFile = true;
}
mRecord = new RecordHandler(mTaskWrapper).getRecord();
Looper.prepare();
Looper looper = Looper.myLooper();
mStateManager = getStateManager(looper);
onPostPre();
if (!mTaskWrapper.isSupportBP()) {
handleNoSupportBP();
} else {
handleBreakpoint();
}
startTimer();
handleTask();
Looper.loop();
}
@Override public void run() {
@ -195,9 +129,11 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
}
/**
* 设置新任务的最大线程数
* 延迟启动定时器
*/
protected abstract int setNewTaskThreadNum();
protected long delayTimer() {
return 1000;
}
/**
* 启动进度获取定时器
@ -207,23 +143,24 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
mTimer = new ScheduledThreadPoolExecutor(1);
mTimer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() {
if (mConstance.isComplete()
|| mConstance.isStop()
|| mConstance.isCancel()
|| mConstance.isFail()
if (mStateManager.isComplete()
|| mStateManager.isStop()
|| mStateManager.isCancel()
|| mStateManager.isFail()
|| !isRunning()) {
if (mConstance.isComplete() || mConstance.isFail()) {
if (mStateManager.isComplete() || mStateManager.isFail()) {
ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey());
}
closeTimer();
} else if (mConstance.CURRENT_LOCATION >= 0) {
mListener.onProgress(mConstance.CURRENT_LOCATION);
} else if (mStateManager.getCurrentProgress() >= 0) {
mListener.onProgress(mStateManager.getCurrentProgress());
}
}
}, 0, mUpdateInterval, TimeUnit.MILLISECONDS);
}, delayTimer(), mUpdateInterval, TimeUnit.MILLISECONDS);
}
public synchronized void closeTimer() {
ALog.d(TAG, "关闭定时器");
if (mTimer != null && !mTimer.isShutdown()) {
mTimer.shutdown();
}
@ -250,21 +187,22 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
* 获取当前任务位置
*/
public long getCurrentLocation() {
return mConstance.CURRENT_LOCATION;
return mStateManager.getCurrentProgress();
}
public synchronized boolean isRunning() {
return ThreadTaskManager.getInstance().taskIsRunning(mTaskWrapper.getKey());
boolean b = ThreadTaskManager.getInstance().taskIsRunning(mTaskWrapper.getKey());
//ALog.d(TAG, "isRunning = " + b);
return b;
}
public synchronized void cancel() {
ALog.d(TAG, "constance hash: " + mConstance.hashCode());
if (mConstance.isCancel) {
if (isCancel) {
ALog.d(TAG, String.format("任务【%s】正在删除,删除任务失败", mTaskWrapper.getKey()));
return;
}
closeTimer();
mConstance.isCancel = true;
isCancel = true;
for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i);
if (task != null) {
@ -275,12 +213,12 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
}
public synchronized void stop() {
if (mConstance.isStop) {
if (isStop) {
return;
}
closeTimer();
mConstance.isStop = true;
if (mConstance.isComplete()) return;
isStop = true;
if (mStateManager.isComplete()) return;
for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i);
if (task != null && !task.isThreadComplete()) {
@ -305,355 +243,6 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
start();
}
/**
* 检查记录 对于分块任务 子分块不存在或被删除子线程将重新下载 对于普通任务 预下载文件不存在则任务任务呗删除
*/
private void checkRecord() {
mConfigFile = new File(CommonUtil.getFileConfigPath(false, mEntity.getFileName()));
if (mConfigFile.exists()) {
convertDb();
} else {
mRecord = DbDataHelper.getTaskRecord(getFilePath());
if (mRecord == null) {
initRecord(true);
} else {
if (mRecord.threadRecords == null || mRecord.threadRecords.isEmpty()) {
initRecord(true);
} else if (mRecord.isBlock) {
handleBlockRecord();
} else {
handleNormalRecord();
}
}
}
}
/**
* 处理普通任务的记录
*/
private void handleNormalRecord() {
File file = new File(mRecord.filePath);
if (!file.exists()) {
ALog.w(TAG, String.format("文件【%s】不存在,任务将重新开始", file.getPath()));
mRecord.deleteData();
initRecord(true);
return;
}
if (mRecord.isOpenDynamicFile) {
ThreadRecord tr = mRecord.threadRecords.get(0);
if (tr == null) {
ALog.d(TAG, "线程记录为空,任务为新任务");
mTaskWrapper.setNewTask(true);
mTotalThreadNum = 1;
return;
}
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;
mCompleteThreadNum++;
} else {
if (file.length() != tr.startLocation) {
ALog.i(TAG, String.format("修正【%s】的进度记录为:%s", file.getPath(), file.length()));
tr.startLocation = file.length();
tr.isComplete = false;
}
}
} else {
for (ThreadRecord tr : mRecord.threadRecords) {
if (tr.isComplete) {
mCompleteThreadNum++;
}
}
}
mTotalThreadNum = mRecord.threadRecords.size();
mTaskWrapper.setNewTask(false);
}
/**
* 处理分块任务的记录分块文件blockFileLen长度必须需要小于等于线程区间threadRectLen的长度
*/
private void handleBlockRecord() {
// 默认线程分块长度
long normalRectLen = mEntity.getFileSize() / mRecord.threadRecords.size();
for (ThreadRecord tr : mRecord.threadRecords) {
long threadRect = tr.blockLen;
File temp = new File(String.format(SUB_PATH, mRecord.filePath, tr.threadId));
if (!temp.exists()) {
ALog.i(TAG, String.format("分块文件【%s】不存在,该分块将重新开始", temp.getPath()));
tr.isComplete = false;
tr.startLocation = -1;
} else {
if (tr.isComplete) {
mCompleteThreadNum++;
} else {
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;
mCompleteThreadNum++;
} else if (tr.startLocation != realLocation) { // 处理记录小于分块文件长度的情况
ALog.i(TAG, String.format("修正分块【%s】的进度记录为:%s", temp.getPath(), realLocation));
tr.startLocation = realLocation;
}
}
}
}
mTotalThreadNum = mRecord.threadRecords.size();
mTaskWrapper.setNewTask(false);
}
/**
* convertDb 是兼容性代码 从3.4.1开始线程配置信息将存储在数据库中 将配置文件的内容复制到数据库中并将配置文件删除
*/
private void convertDb() {
List<RecordWrapper> records =
DbEntity.findRelationData(RecordWrapper.class, "TaskRecord.filePath=?",
getFilePath());
if (records == null || records.size() == 0) {
Properties pro = CommonUtil.loadConfig(mConfigFile);
if (pro.isEmpty()) {
ALog.d(TAG, "老版本的线程记录为空,任务为新任务");
mTaskWrapper.setNewTask(true);
return;
}
initRecord(false);
Set<Object> keys = pro.keySet();
// 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录...
// 第一步应该是record 和 state去重取正确的线程数
Set<Integer> set = new HashSet<>();
for (Object key : keys) {
String str = String.valueOf(key);
int i = Integer.parseInt(str.substring(str.length() - 1, str.length()));
set.add(i);
}
int threadNum = set.size();
if (threadNum == 0) {
ALog.d(TAG, "线程数为空,任务为新任务");
mTaskWrapper.setNewTask(true);
return;
}
mRecord.threadNum = threadNum;
mTotalThreadNum = threadNum;
for (int i = 0; i < threadNum; i++) {
ThreadRecord tRecord = new ThreadRecord();
tRecord.key = mRecord.filePath;
Object state = pro.getProperty(mTempFile.getName() + STATE + i);
Object record = pro.getProperty(mTempFile.getName() + RECORD + i);
if (state != null && Integer.parseInt(String.valueOf(state)) == 1) {
mCompleteThreadNum++;
tRecord.isComplete = true;
continue;
}
if (record != null) {
long temp = Long.parseLong(String.valueOf(record));
tRecord.startLocation = temp > 0 ? temp : 0;
} else {
tRecord.startLocation = 0;
}
mRecord.threadRecords.add(tRecord);
}
mConfigFile.delete();
}
}
/**
* 初始化记录
*/
private void initRecord(boolean isNewTask) {
mRecord = new TaskRecord();
mRecord.fileName = mEntity.getFileName();
mRecord.filePath = getFilePath();
mRecord.threadRecords = new ArrayList<>();
mRecord.isGroupRecord = mTaskWrapper.getEntity().isGroupChild();
if (mRecord.isGroupRecord) {
if (mTaskWrapper.getEntity() instanceof DownloadEntity) {
mRecord.dGroupHash = ((DownloadEntity) mTaskWrapper.getEntity()).getGroupHash();
}
}
ALog.d(TAG, String.format("初始化记录,任务为%s任务", isNewTask ? "新" : "旧"));
mTaskWrapper.setNewTask(isNewTask);
}
private String getFilePath() {
if (getType() == DOWNLOAD) {
return ((DownloadEntity) mTaskWrapper.getEntity()).getDownloadPath();
} else {
return ((UploadEntity) mTaskWrapper.getEntity()).getFilePath();
}
}
/**
* 保存任务记录
*/
private void saveRecord() {
mRecord.threadNum = mRecord.threadRecords.size();
mRecord.save();
ALog.d(TAG, String.format("保存记录,线程记录数:%s", mRecord.threadRecords.size()));
DbEntity.saveAll(mRecord.threadRecords);
}
/**
* 恢复记录地址
*
* @return {@code true}任务已完成
*/
private boolean resumeRecordLocation(int i, long startL, long endL) {
mConstance.CURRENT_LOCATION += endL - startL;
ALog.d(TAG, String.format("任务【%s】线程__%s__已完成", mTaskWrapper.getEntity().getFileName(), i));
mConstance.COMPLETE_THREAD_NUM = mCompleteThreadNum;
mConstance.STOP_NUM++;
mConstance.CANCEL_NUM++;
if (mConstance.isComplete()) {
mRecord.deleteData();
mListener.onComplete();
return true;
}
return false;
}
/**
* 启动断点任务时创建单线程任务
*
* @param i 线程id
* @param startL 该任务起始位置
* @param endL 该任务结束位置
* @param fileLength 该任务需要处理的文件长度
*/
private AbsThreadTask createSingThreadTask(int i, long startL, long endL, long fileLength,
ThreadRecord record) {
SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>();
config.TOTAL_FILE_SIZE = fileLength;
config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.TEMP_FILE =
mRecord.isBlock ? new File(String.format(SUB_PATH, mTempFile.getPath(), i)) : mTempFile;
config.THREAD_ID = i;
config.START_LOCATION = startL;
config.END_LOCATION = endL;
config.SUPPORT_BP = mTaskWrapper.isSupportBP();
config.TASK_WRAPPER = mTaskWrapper;
config.THREAD_RECORD = record;
return selectThreadTask(config);
}
private void handleBreakpoint() {
long fileLength = mEntity.getFileSize();
long blockSize = fileLength / mTotalThreadNum;
Set<Integer> threads = new HashSet<>();
//// 如果是新任务,检查下历史记录,如果有遗留的记录,删除并删除分块文件
//if (mTaskWrapper.isNewTask()) {
// CommonUtil.delTaskRecord(getFilePath(), 1, true);
//}
mRecord.fileLength = fileLength;
if (mTaskWrapper.isNewTask() && !handleNewTask()) {
return;
}
for (int i = 0; i < mTotalThreadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
ThreadRecord tr;
boolean isNewTr = false; // 是否是新的线程记录
if (mTaskWrapper.isNewTask()) {
tr = new ThreadRecord();
tr.key = mRecord.filePath;
tr.threadId = i;
isNewTr = true;
} else {
tr = mRecord.threadRecords.get(i);
}
if (tr.blockLen == 0) {
tr.blockLen = CommonUtil.getBlockLen(fileLength, i, mTotalThreadNum);
}
if (tr.isComplete) {//该线程已经完成
if (resumeRecordLocation(i, startL, endL)) return;
continue;
}
//如果有记录,则恢复任务
if (tr.startLocation > 0) {
long r = tr.startLocation;
//记录的位置需要在线程区间中
if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) {
mConstance.CURRENT_LOCATION += r - startL;
startL = r;
}
ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", mEntity.getFileName(), i));
} else {
tr.startLocation = startL;
}
//最后一个线程的结束位置即为文件的总长度
if (i == (mTotalThreadNum - 1)) {
endL = fileLength;
}
if (tr.endLocation <= 0) {
tr.endLocation = endL;
}
if (isNewTr) {
mRecord.threadRecords.add(tr);
}
//ALog.d(TAG, String.format("创建任务线程:%s,线程总数:%s", i, mTotalThreadNum));
AbsThreadTask task = createSingThreadTask(i, startL, endL, fileLength, tr);
if (task == null) return;
mTask.put(i, task);
threads.add(i);
}
if (mConstance.CURRENT_LOCATION != 0
&& mConstance.CURRENT_LOCATION != mEntity.getCurrentProgress()) {
ALog.d(TAG, String.format("进度修正,当前进度:%s", mConstance.CURRENT_LOCATION));
mEntity.setCurrentProgress(mConstance.CURRENT_LOCATION);
}
saveRecord();
startThreadTask(threads);
}
/**
* 启动单线程任务
*/
private void startThreadTask(Set<Integer> recordL) {
if (isBreak()) {
return;
}
if (mConstance.CURRENT_LOCATION > 0) {
mListener.onResume(mConstance.CURRENT_LOCATION);
} else {
mListener.onStart(mConstance.CURRENT_LOCATION);
}
for (int l : recordL) {
if (l == -1) continue;
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), mTask.get(l));
}
}
/**
* 重试任务
*/
@ -662,59 +251,13 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
startFlow();
}
/**
* 处理新任务
*
* @return {@code true}创建新任务失败
*/
protected abstract boolean handleNewTask();
/**
* 处理不支持断点的任务
*/
private void handleNoSupportBP() {
if (mListener instanceof BaseDListener) {
((BaseDListener) mListener).supportBreakpoint(false);
}
SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>();
config.TOTAL_FILE_SIZE = mEntity.getFileSize();
config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.TEMP_FILE = mTempFile;
config.THREAD_ID = 0;
config.START_LOCATION = 0;
config.END_LOCATION = config.TOTAL_FILE_SIZE;
config.SUPPORT_BP = mTaskWrapper.isSupportBP();
config.TASK_WRAPPER = mTaskWrapper;
ThreadRecord record = DbEntity.findFirst(ThreadRecord.class, "key=?", mRecord.filePath);
if (record != null) {
record.deleteData();
}
record = new ThreadRecord();
record.startLocation = 0;
record.endLocation = config.TOTAL_FILE_SIZE;
record.key = mTempFile.getPath();
mRecord.threadRecords.add(record);
config.THREAD_RECORD = record;
AbsThreadTask task = selectThreadTask(config);
if (task == null) return;
mTask.put(0, task);
saveRecord();
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task);
mListener.onStart(0);
}
/**
* 选择单任务线程的类型
*/
protected abstract AbsThreadTask selectThreadTask(SubThreadConfig<TASK_WRAPPER> config);
/**
* 任务是否已经中断
*
* @return {@code true}中断
*/
public boolean isBreak() {
if (mConstance.isCancel || mConstance.isStop) {
if (isCancel || isStop) {
closeTimer();
ALog.d(TAG, String.format("任务【%s】已停止或取消了", mEntity.getFileName()));
return true;

@ -16,29 +16,26 @@
package com.arialyy.aria.core.common;
import android.net.TrafficStats;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.support.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.download.DownloadEntity;
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.inf.ITaskWrapper;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.exception.FileException;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.BufferedRandomAccessFile;
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.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
@ -59,20 +56,20 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* 当前子线程相对于总长度的位置
*/
protected long mChildCurrentLocation = 0;
protected IEventListener mListener;
private StateConstance sState;
private SubThreadConfig<TASK_WRAPPER> sConfig;
private ENTITY mEntity;
private TASK_WRAPPER mTaskWrapper;
protected TASK_WRAPPER mTaskWrapper;
private int mFailTimes = 0;
private long mLastSaveTime;
private ExecutorService mConfigThreadPool;
private boolean isNotNetRetry; //断网情况是否重试
private boolean taskBreak = false; //任务跳出
private int mThreadNum;
protected BandwidthLimiter mSpeedBandUtil; //速度限制工具
protected AriaManager mAridManager;
private boolean isInterrupted = false;
protected boolean isCancel = false, isStop = false;
protected ThreadRecord mRecord;
private Handler mStateHandler;
private SubThreadConfig<TASK_WRAPPER> mConfig;
private Thread mConfigThread = new Thread(new Runnable() {
@Override public void run() {
@ -82,21 +79,31 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
}
});
protected AbsThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<TASK_WRAPPER> config) {
sState = constance;
sConfig = config;
mListener = listener;
mTaskWrapper = getConfig().TASK_WRAPPER;
protected AbsThreadTask(SubThreadConfig<TASK_WRAPPER> config) {
mConfig = config;
mTaskWrapper = config.taskWrapper;
mRecord = config.record;
mStateHandler = config.stateHandler;
mEntity = mTaskWrapper.getEntity();
mLastSaveTime = System.currentTimeMillis();
mConfigThreadPool = Executors.newCachedThreadPool();
mThreadNum = getState().TASK_RECORD.threadRecords.size();
mAridManager = AriaManager.getInstance(AriaManager.APP);
if (getMaxSpeed() > 0) {
mSpeedBandUtil = new BandwidthLimiter(getMaxSpeed(), mThreadNum);
mSpeedBandUtil = new BandwidthLimiter(getMaxSpeed(), config.startThreadNum);
}
isNotNetRetry = mAridManager.getAppConfig().isNotNetRetry();
mChildCurrentLocation = mRecord.startLocation;
}
/**
* 当前线程处理的文件名
*/
protected String getFileName() {
return mConfig.tempFile.getName();
}
protected SubThreadConfig<TASK_WRAPPER> getConfig() {
return mConfig;
}
/**
@ -121,14 +128,7 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* 当前线程是否完成对于不支持断点的任务一律未完成 {@code true} 完成{@code false} 未完成
*/
boolean isThreadComplete() {
return getConfig().THREAD_RECORD.isComplete;
}
/**
* 获取状态信息
*/
protected StateConstance getState() {
return sState;
return mRecord.isComplete;
}
/**
@ -145,20 +145,6 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
return mTaskWrapper;
}
/**
* 获取任务记录
*/
private TaskRecord getTaskRecord() {
return getState().TASK_RECORD;
}
/**
* 获取线程记录
*/
protected ThreadRecord getThreadRecord() {
return getConfig().THREAD_RECORD;
}
/**
* 获取配置的最大上传/下载速度
*
@ -180,7 +166,7 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
*/
public void setMaxSpeed(int speed) {
if (mSpeedBandUtil != null) {
mSpeedBandUtil.setMaxRate(speed / mThreadNum);
mSpeedBandUtil.setMaxRate(speed / mConfig.startThreadNum);
}
}
@ -190,30 +176,35 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
void breakTask() {
synchronized (AriaManager.LOCK) {
taskBreak = true;
if (getConfig().SUPPORT_BP) {
if (mTaskWrapper.isSupportBP()) {
final long currentTemp = mChildCurrentLocation;
getState().STOP_NUM++;
ALog.d(TAG, String.format("任务【%s】thread__%s__中断【停止位置:%s】", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID, currentTemp));
sendState(IThreadState.STATE_STOP, null);
ALog.d(TAG, String.format("任务【%s】thread__%s__中断【停止位置:%s】", getFileName(),
mRecord.threadId, currentTemp));
writeConfig(false, currentTemp);
if (getState().isStop()) {
ALog.i(TAG, String.format("任务【%s】已中断", getConfig().TEMP_FILE.getName()));
}
} else {
ALog.i(TAG, String.format("任务【%s】已中断", getConfig().TEMP_FILE.getName()));
ALog.i(TAG, String.format("任务【%s】已中断", getFileName()));
}
}
}
public boolean isInterrupted() {
return Thread.currentThread().isInterrupted();
}
/**
* 获取线程配置信息
* 发送状态给状态处理器
*
* @param state {@link IThreadState#STATE_STOP}..
* @param bundle 而外数据
*/
protected SubThreadConfig<TASK_WRAPPER> getConfig() {
return sConfig;
void sendState(int state, @Nullable Bundle bundle) {
Message msg = mStateHandler.obtainMessage();
msg.what = state;
if (bundle != null) {
msg.setData(bundle);
}
msg.sendToTarget();
}
public boolean isInterrupted() {
return Thread.currentThread().isInterrupted();
}
@Override protected void finalize() throws Throwable {
@ -229,37 +220,7 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* @return {@code true} 中断{@code false} 不是中断
*/
protected boolean isBreak() {
return getState().isCancel || getState().isStop || taskBreak;
}
/**
* 合并文件
*
* @return {@code true} 合并成功{@code false}合并失败
*/
protected boolean mergeFile() {
List<String> partPath = new ArrayList<>();
for (int i = 0, len = getState().TASK_RECORD.threadNum; i < len; i++) {
partPath.add(String.format(AbsFileer.SUB_PATH, getState().TASK_RECORD.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(getState().TASK_RECORD.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
File targetFile = new File(getState().TASK_RECORD.filePath);
if (targetFile.exists() && targetFile.length() > getEntity().getFileSize()) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s",
getConfig().TEMP_FILE.getName(), targetFile.length(), getEntity().getFileSize()));
return false;
}
return true;
} else {
return false;
}
return isCancel || isStop || taskBreak;
}
/**
@ -268,16 +229,15 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* @return {@code true} 分块分大小正常{@code false} 分块大小错误
*/
protected boolean checkBlock() {
if (!getTaskRecord().isBlock) {
if (!mConfig.isBlock) {
return true;
}
ThreadRecord tr = getThreadRecord();
File blockFile = getBockFile();
if (!blockFile.exists() || blockFile.length() != tr.blockLen) {
File blockFile = mConfig.tempFile;
if (!blockFile.exists() || blockFile.length() != mRecord.blockLen) {
ALog.i(TAG,
String.format("分块【%s】错误,blockFileLen: %s, threadRect: %s; 即将重新下载该分块,开始位置:%s,结束位置:%s",
blockFile.getName(), blockFile.length(), tr.blockLen, tr.startLocation,
tr.endLocation));
blockFile.getName(), blockFile.length(), mRecord.blockLen, mRecord.startLocation,
mRecord.endLocation));
if (blockFile.exists()) {
blockFile.delete();
ALog.i(TAG, String.format("删除分块【%s】成功", blockFile.getName()));
@ -293,31 +253,17 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
*/
public void stop() {
synchronized (AriaManager.LOCK) {
if (getConfig().SUPPORT_BP) {
final long stopLocation;
if (getTaskRecord().isBlock) {
File blockFile = getBockFile();
ThreadRecord tr = getThreadRecord();
long block = getEntity().getFileSize() / getTaskRecord().threadRecords.size();
stopLocation =
blockFile.exists() ? (tr.threadId * block + blockFile.length()) : tr.threadId * block;
} else {
stopLocation = mChildCurrentLocation;
}
getState().STOP_NUM++;
isStop = true;
if (mTaskWrapper.isSupportBP()) {
final long stopLocation = mChildCurrentLocation;
sendState(IThreadState.STATE_STOP, null);
ALog.d(TAG,
String.format("任务【%s】thread__%s__停止【当前线程停止位置:%s】", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID, stopLocation));
String.format("任务【%s】thread__%s__停止【当前线程停止位置:%s】", getFileName(),
mRecord.threadId, stopLocation));
writeConfig(false, stopLocation);
//ALog.d(TAG, String.format("stop_thread_num=%s; start_thread_num=%s; complete_thread_num=%s",
// getState().STOP_NUM, getState().START_THREAD_NUM, getState().COMPLETE_THREAD_NUM));
if (getState().isStop()) {
ALog.i(TAG, String.format("任务【%s】已停止", getConfig().TEMP_FILE.getName()));
mListener.onStop(getState().CURRENT_LOCATION);
}
} else {
ALog.i(TAG, String.format("任务【%s】已停止", getConfig().TEMP_FILE.getName()));
mListener.onStop(getState().CURRENT_LOCATION);
ALog.i(TAG, String.format("任务【%s】已停止", getFileName()));
sendState(IThreadState.STATE_STOP, null);
}
}
}
@ -327,20 +273,23 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
*/
protected void progress(long len) {
synchronized (AriaManager.LOCK) {
if (getState().CURRENT_LOCATION > 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);
//if (getState().CURRENT_LOCATION > 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;
if (!mStateHandler.getLooper().getThread().isAlive()) {
return;
}
mChildCurrentLocation += len;
getState().CURRENT_LOCATION += len;
mStateHandler.obtainMessage(IThreadState.STATE_RUNNING, len).sendToTarget();
if (System.currentTimeMillis() - mLastSaveTime > 5000
&& mChildCurrentLocation < getConfig().END_LOCATION) {
&& mChildCurrentLocation < mRecord.endLocation) {
mLastSaveTime = System.currentTimeMillis();
if (!mConfigThreadPool.isShutdown()) {
mConfigThreadPool.execute(mConfigThread);
@ -354,26 +303,10 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
*/
public void cancel() {
synchronized (AriaManager.LOCK) {
if (getConfig().SUPPORT_BP) {
getState().CANCEL_NUM++;
isCancel = true;
sendState(IThreadState.STATE_CANCEL, null);
ALog.d(TAG,
String.format("任务【%s】thread__%s__取消", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID));
//ALog.d(TAG, "cancel_num = "
// + getState().CANCEL_NUM
// + ", start_thread_num = "
// + getState().START_THREAD_NUM);
if (getState().isCancel()) {
if (getConfig().TEMP_FILE.exists() && !(getEntity() instanceof UploadEntity)) {
getConfig().TEMP_FILE.delete();
}
ALog.d(TAG, String.format("任务【%s】已取消", getConfig().TEMP_FILE.getName()));
mListener.onCancel();
}
} else {
ALog.d(TAG, String.format("任务【%s】已取消", getConfig().TEMP_FILE.getName()));
mListener.onCancel();
}
String.format("任务【%s】thread__%s__取消", getFileName(), mRecord.threadId));
}
}
@ -396,13 +329,17 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
if (ex != null) {
ALog.e(TAG, ALog.getExceptionString(ex));
}
if (getConfig().SUPPORT_BP) {
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
writeConfig(false, 0);
} else {
if (mTaskWrapper.isSupportBP()) {
writeConfig(false, subCurrentLocation);
retryThis(needRetry && getState().START_THREAD_NUM != 1);
retryThis(needRetry && mConfig.startThreadNum != 1);
} else {
ALog.e(TAG, String.format("任务【%s】执行失败", getConfig().TEMP_FILE.getName()));
ALog.e(TAG, String.format("任务【%s】执行失败", getFileName()));
ErrorHelp.saveError(TAG, "", ALog.getExceptionString(ex));
handleFailState(!isBreak());
sendFailMsg(null);
}
}
}
@ -413,38 +350,35 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
*/
private void retryThis(boolean needRetry) {
if (!NetUtils.isConnected(AriaManager.APP) && !isNotNetRetry) {
ALog.w(TAG, String.format("任务【%s】重试失败,网络未连接", getConfig().TEMP_FILE.getName()));
ALog.w(TAG, String.format("任务【%s】重试失败,网络未连接", getFileName()));
}
if (mFailTimes < RETRY_NUM && needRetry && (NetUtils.isConnected(AriaManager.APP)
|| isNotNetRetry) && !isBreak()) {
ALog.d(TAG, String.format("isCancel: %s, isStop: %s, isBreak: %s", getState().isCancel,
getState().isStop, taskBreak));
ALog.w(TAG, String.format("任务【%s】正在重试", getConfig().TEMP_FILE.getName()));
ALog.w(TAG, String.format("任务【%s】正在重试", getFileName()));
mFailTimes++;
handleRetryRecord();
ThreadTaskManager.getInstance().retryThread(AbsThreadTask.this);
} else {
handleFailState(!isBreak());
sendFailMsg(null);
}
}
/**
* 处理线程重试的记录只有多线程任务才会执行
* 如果是以前版本{@link BufferedRandomAccessFile}创建的下载那么 record.startLocation不用修改
*/
private void handleRetryRecord() {
if (getTaskRecord().isBlock) {
ThreadRecord tr = getThreadRecord();
if (mConfig.isBlock) {
// 默认线程分块长度
long normalRectLen = getEntity().getFileSize() / getTaskRecord().threadRecords.size();
File temp = getBockFile();
File temp = mConfig.tempFile;
long blockFileLen = temp.length(); // 磁盘中的分块文件长度
long threadRect = tr.blockLen; // 当前线程的区间
long threadRect = mRecord.blockLen; // 当前线程的区间
if (!temp.exists()) {
ALog.i(TAG, String.format("分块文件【%s】不存在,该分块将重新开始", temp.getName()));
tr.isComplete = false;
getConfig().START_LOCATION = tr.startLocation;
mRecord.isComplete = false;
mRecord.startLocation = mRecord.endLocation - threadRect;
} else {
/*
* 检查磁盘中的分块文件
@ -452,27 +386,21 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
if (blockFileLen > threadRect) {
ALog.i(TAG, String.format("分块【%s】错误,将重新下载该分块", temp.getName()));
temp.delete();
tr.startLocation = normalRectLen * tr.threadId;
tr.isComplete = false;
getConfig().START_LOCATION = tr.startLocation;
} else if (blockFileLen < tr.blockLen) {
tr.startLocation = normalRectLen * tr.threadId + blockFileLen;
tr.isComplete = false;
getConfig().START_LOCATION = tr.startLocation;
getState().CURRENT_LOCATION = getBlockRealTotalSize();
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);
ALog.i(TAG,
String.format("修正分块【%s】,开始位置:%s,当前进度:%s", temp.getName(), tr.startLocation,
getState().CURRENT_LOCATION));
String.format("修正分块【%s】记录,开始位置:%s,结束位置:%s", temp.getName(), mRecord.startLocation,
mRecord.endLocation));
} else {
ALog.i(TAG, String.format("分块【%s】已完成,更新记录", temp.getName()));
getState().COMPLETE_THREAD_NUM++;
tr.isComplete = true;
mRecord.isComplete = true;
}
}
tr.update();
} else {
getConfig().START_LOCATION = mChildCurrentLocation == 0 ? getConfig().START_LOCATION
: getConfig().THREAD_RECORD.startLocation;
mRecord.update();
}
}
@ -480,51 +408,20 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* 发送任务完成的消息并删除任务记录
*/
protected synchronized void sendCompleteMsg() {
ALog.i(TAG, String.format("任务【%s】完成", mEntity.getFileName()));
CommonUtil.delTaskRecord(mEntity.getKey(), mEntity instanceof DownloadEntity ? 1 : 2, false,
false);
mListener.onComplete();
ALog.i(TAG, String.format("任务【%s】完成", getFileName()));
sendState(IThreadState.STATE_COMPLETE, null);
}
/**
* 获取分块文件
*
* @return 分块文件
* 发送失败信息
*/
private File getBockFile() {
return new File(String.format(AbsFileer.SUB_PATH, getState().TASK_RECORD.filePath,
getThreadRecord().threadId));
}
/**
* 获取分块任务真实的进度
*
* @return 进度
*/
private long getBlockRealTotalSize() {
long size = 0;
for (int i = 0, len = getTaskRecord().threadRecords.size(); i < len; i++) {
File temp = new File(String.format(AbsFileer.SUB_PATH, getTaskRecord().filePath, i));
if (temp.exists()) {
size += temp.length();
}
}
return size;
}
/**
* 处理失败状态
*
* @param taskNeedReTry 任务是否需要重试{@code true} 需要
*/
private void handleFailState(boolean taskNeedReTry) {
getState().FAIL_NUM++;
if (getState().isFail()) {
// 手动停止不进行fail回调
if (!getState().isStop) {
String errorMsg = String.format("任务【%s】执行失败", getConfig().TEMP_FILE.getName());
mListener.onFail(taskNeedReTry, new TaskException(TAG, errorMsg));
}
protected void sendFailMsg(@Nullable BaseException e) {
if (e != null) {
Bundle b = new Bundle();
b.putSerializable(IThreadState.KEY_ERROR_INFO, e);
sendState(IThreadState.STATE_FAIL, b);
} else {
sendState(IThreadState.STATE_FAIL, null);
}
}
@ -535,19 +432,18 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* @param record 当前下载进度
*/
protected void writeConfig(boolean isComplete, final long record) {
ThreadRecord tr = getThreadRecord();
if (tr != null) {
tr.isComplete = isComplete;
if (getTaskRecord().isBlock) {
tr.startLocation = record;
} else if (getTaskRecord().isOpenDynamicFile) {
tr.startLocation = getConfig().TEMP_FILE.length();
if (mRecord != null) {
mRecord.isComplete = isComplete;
if (mConfig.isBlock) {
mRecord.startLocation = record;
} else if (mConfig.isOpenDynamicFile) {
mRecord.startLocation = mConfig.tempFile.length();
} else {
if (0 < record && record < getConfig().END_LOCATION) {
tr.startLocation = record;
if (0 < record && record < mRecord.endLocation) {
mRecord.startLocation = record;
}
}
tr.update();
mRecord.update();
}
}

@ -16,6 +16,8 @@
package com.arialyy.aria.core.common;
import android.os.Handler;
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;
@ -23,6 +25,7 @@ import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
@ -32,7 +35,7 @@ import java.lang.ref.WeakReference;
public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>,
TASK extends AbsTask<ENTITY, TASK_WRAPPER>>
implements IEventListener {
private static final String TAG = "BaseListener";
protected static String TAG;
private static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度
protected WeakReference<Handler> outHandler;
private long mLastLen; //上一次发送长度
@ -53,6 +56,7 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
mUpdateInterval = mTaskWrapper.getConfig().getUpdateInterval();
mLastLen = mEntity.getCurrentProgress();
mLastSaveTime = System.currentTimeMillis();
TAG = CommonUtil.getClassName(getClass());
}
protected TASK getTask() {
@ -120,9 +124,11 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
mTask.setNeedRetry(needRetry);
mTask.putExpand(AbsTask.ERROR_INFO_KEY, e);
sendInState2Target(ISchedulers.FAIL);
if (e != null) {
e.printStackTrace();
ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e));
}
}
private void handleSpeed(long speed) {
if (mUpdateInterval != 1000) {
@ -137,14 +143,30 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
: mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()));
}
protected void handleComplete() {
/**
* 处理任务完成后的情况
*/
private void handleComplete() {
mEntity.setComplete(true);
mEntity.setCompleteTime(System.currentTimeMillis());
mEntity.setCurrentProgress(mEntity.getFileSize());
mEntity.setPercent(100);
handleSpeed(0);
ALog.i(TAG, String.format("任务【%s】完成,将删除线程任务记录", mEntity.getKey()));
if (mEntity instanceof DownloadGroupEntity) {
CommonUtil.delGroupTaskRecord((DownloadGroupEntity) mEntity, false, false);
} else if (mEntity instanceof DownloadEntity) {
CommonUtil.delTaskRecord(mEntity.getKey(), RecordHandler.TYPE_DOWNLOAD, false, false);
} else if (mEntity instanceof UploadEntity) {
CommonUtil.delTaskRecord(mEntity.getKey(), RecordHandler.TYPE_UPLOAD, false, false);
}
}
/**
* 处理任务取消
*/
protected abstract void handleCancel();
/**
* 将任务状态发送给下载器
*
@ -156,5 +178,21 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
}
}
protected abstract void saveData(int state, long location);
protected void saveData(int state, long location) {
mEntity.setState(state);
mEntity.setComplete(false);
if (state == IEntity.STATE_CANCEL) {
handleCancel();
return;
} else if (state == IEntity.STATE_STOP) {
mEntity.setStopTime(System.currentTimeMillis());
} else if (state == IEntity.STATE_COMPLETE) {
handleComplete();
}
if (location > 0) {
mEntity.setCurrentProgress(location);
}
mEntity.update();
}
}

@ -29,6 +29,12 @@ public class CompleteInfo {
public AbsTaskWrapper wrapper;
public Object obj;
public CompleteInfo() {
}
public CompleteInfo(int code, AbsTaskWrapper wrapper) {
this.code = code;
this.wrapper = wrapper;

@ -0,0 +1,67 @@
/*
* 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;
/**
* 线程任务状态
*/
public interface IThreadState extends Handler.Callback {
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";
/**
* 任务是否已经停止
*
* @return true 任务已停止
*/
boolean isStop();
/**
* 任务是否已经失败
*
* @return true 任务已失败
*/
boolean isFail();
/**
* 任务是否已经完成
*
* @return true 任务已完成
*/
boolean isComplete();
/**
* 任务是否已经取消
*
* @return true 任务已取消
*/
boolean isCancel();
/**
* 获取当前任务进度
*
* @return 任务当前进度
*/
long getCurrentProgress();
}

@ -0,0 +1,190 @@
/*
* 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.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.util.ALog;
import java.io.File;
public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>>
extends AbsFileer<ENTITY, TASK_WRAPPER> {
private ThreadStateManager mStateManager;
private Handler mStateHandler;
protected int mTotalThreadNum; //总线程数
protected NormalFileer(IEventListener listener, TASK_WRAPPER wrapper) {
super(listener, wrapper);
}
/**
* 处理新任务
*
* @return {@code true}创建新任务成功
*/
protected abstract boolean handleNewTask();
/**
* 选择单任务线程的类型
*/
protected abstract AbsThreadTask selectThreadTask(SubThreadConfig<TASK_WRAPPER> config);
/**
* 设置最大下载/上传速度
*
* @param maxSpeed 单位为kb
*/
public void setMaxSpeed(int maxSpeed) {
for (int i = 0; i < mTotalThreadNum; i++) {
AbsThreadTask task = getTaskList().get(i);
if (task != null) {
task.setMaxSpeed(maxSpeed);
}
}
}
@Override protected void onPostPre() {
super.onPostPre();
mTotalThreadNum = mRecord.threadNum;
}
@Override protected IThreadState getStateManager(Looper looper) {
mStateManager = new ThreadStateManager(looper, mRecord, mListener);
mStateHandler = new Handler(looper, mStateManager);
return mStateManager;
}
@Override protected void handleTask() {
if (mTaskWrapper.isSupportBP()) {
handleBreakpoint();
} else {
handleNoSupportBP();
}
}
/**
* 启动断点任务时创建单线程任务
*
* @param record 线程记录
* @param startNum 启动的线程数
*/
private AbsThreadTask createSingThreadTask(ThreadRecord record, int startNum) {
SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>();
config.url = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.tempFile =
mRecord.isBlock ? new File(
String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), record.threadId))
: mTempFile;
config.isBlock = mRecord.isBlock;
config.isOpenDynamicFile = mRecord.isOpenDynamicFile;
config.startThreadNum = startNum;
config.taskWrapper = mTaskWrapper;
config.record = record;
config.stateHandler = mStateHandler;
return selectThreadTask(config);
}
private void handleBreakpoint() {
long fileLength = mEntity.getFileSize();
long blockSize = fileLength / mTotalThreadNum;
long currentProgress = 0;
mRecord.fileLength = fileLength;
if (mTaskWrapper.isNewTask() && !handleNewTask()) {
return;
}
int startNum = mRecord.threadNum;
for (ThreadRecord tr : mRecord.threadRecords) {
if (!tr.isComplete) {
startNum++;
}
}
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, startNum);
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().get(i));
}
}
/**
* 处理不支持断点的任务
*/
private void handleNoSupportBP() {
if (mListener instanceof BaseDListener) {
((BaseDListener) mListener).supportBreakpoint(false);
}
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);
}
}

@ -24,7 +24,7 @@ public interface OnFileInfoCallback {
*
* @param info 一些回调的信息
*/
void onComplete(String url, CompleteInfo info);
void onComplete(String key, CompleteInfo info);
/**
* 请求失败

@ -0,0 +1,419 @@
/*
* 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.M3U8FileLoader;
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 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;
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 mRecord;
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 {
mRecord = DbDataHelper.getTaskRecord(getFilePath());
if (mRecord == null) {
initRecord(true);
} else {
if (mRecord.threadRecords == null || mRecord.threadRecords.isEmpty()) {
initRecord(false);
}
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
handleM3U8Record();
} else {
if (mRecord.isBlock) {
handleBlockRecord();
} else if (!mTaskWrapper.isSupportBP()) {
handleNoSupportBPRecord();
} else {
handleSingleThreadRecord();
}
}
}
}
saveRecord();
return mRecord;
}
/**
* 处理m3u8记录
* 1如果分片文件存在并且分片文件的记录没有完成则需要删除该分片文件
* 2如果记录显示已完成但是分片文件不存在则重新开始该分片
* 3如果记录显示已完成并且文件存在记录当前任务进度
*/
private void handleM3U8Record() {
DTaskWrapper wrapper = (DTaskWrapper) mTaskWrapper;
String cacheDir = wrapper.asM3U8().getCacheDir();
long currentProgress = 0;
for (ThreadRecord record : mRecord.threadRecords) {
File temp = new File(M3U8FileLoader.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 {
currentProgress += temp.length();
}
}
}
wrapper.getEntity().setCurrentProgress(currentProgress);
}
/**
* 处理不支持断点的记录
*/
private void handleNoSupportBPRecord() {
ThreadRecord tr = mRecord.threadRecords.get(0);
tr.startLocation = 0;
tr.endLocation = mEntity.getFileSize();
tr.key = mRecord.filePath;
tr.blockLen = tr.endLocation;
tr.isComplete = false;
}
/**
* 处理单线程的任务的记录
*/
private void handleSingleThreadRecord() {
File file = new File(mRecord.filePath);
ThreadRecord tr = mRecord.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 (mRecord.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() / mRecord.threadRecords.size();
for (ThreadRecord tr : mRecord.threadRecords) {
long threadRect = tr.blockLen;
File temp = new File(String.format(SUB_PATH, mRecord.filePath, tr.threadId));
if (!temp.exists()) {
ALog.i(TAG, String.format("分块文件【%s】不存在,该分块将重新开始", temp.getPath()));
tr.isComplete = false;
tr.startLocation = tr.threadId * normalRectLen;
} else {
if (!tr.isComplete) {
ALog.i(TAG, String.format(
"startLocation = %s; endLocation = %s; block = %s; tempLen = %s; threadId = %s",
tr.startLocation, tr.endLocation, threadRect, temp.length(), tr.threadId));
long blockFileLen = temp.length(); // 磁盘中的分块文件长度
/*
* 检查磁盘中的分块文件
*/
if (blockFileLen > threadRect) {
ALog.i(TAG, String.format("分块【%s】错误,分块长度【%s】 > 线程区间长度【%s】,将重新开始该分块",
tr.threadId, blockFileLen, threadRect));
temp.delete();
tr.startLocation = tr.threadId * threadRect;
continue;
}
long realLocation =
tr.threadId * normalRectLen + blockFileLen; //正常情况下,该线程的startLocation的位置
/*
* 检查记录文件
*/
if (blockFileLen == threadRect) {
ALog.i(TAG, String.format("分块【%s】已完成,更新记录", temp.getPath()));
tr.startLocation = blockFileLen;
tr.isComplete = true;
} else if (tr.startLocation != realLocation) { // 处理记录小于分块文件长度的情况
ALog.i(TAG, String.format("修正分块【%s】的进度记录为:%s", temp.getPath(), realLocation));
tr.startLocation = realLocation;
}
} else {
ALog.i(TAG, String.format("分块【%s】已完成", temp.getPath()));
}
}
}
mTaskWrapper.setNewTask(false);
}
/**
* convertDb 是兼容性代码 从3.4.1开始线程配置信息将存储在数据库中 将配置文件的内容复制到数据库中并将配置文件删除
*/
private void convertDb() {
List<RecordWrapper> records =
DbEntity.findRelationData(RecordWrapper.class, "TaskRecord.filePath=?",
getFilePath());
if (records == null || records.size() == 0) {
Properties pro = CommonUtil.loadConfig(mConfigFile);
if (pro.isEmpty()) {
ALog.d(TAG, "老版本的线程记录为空,任务为新任务");
initRecord(true);
return;
}
Set<Object> keys = pro.keySet();
// 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录...
// 第一步应该是record 和 state去重取正确的线程数
Set<Integer> set = new HashSet<>();
for (Object key : keys) {
String str = String.valueOf(key);
int i = Integer.parseInt(str.substring(str.length() - 1));
set.add(i);
}
int threadNum = set.size();
if (threadNum == 0) {
ALog.d(TAG, "线程数为空,任务为新任务");
initRecord(true);
return;
}
mTaskWrapper.setNewTask(false);
mRecord = createTaskRecord(threadNum);
mRecord.isOpenDynamicFile = false;
mRecord.isBlock = false;
File tempFile = new File(getFilePath());
for (int i = 0; i < threadNum; i++) {
ThreadRecord tRecord = new ThreadRecord();
tRecord.key = mRecord.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;
}
mRecord.threadRecords.add(tRecord);
}
mConfigFile.delete();
}
}
/**
* 初始化任务记录分配线程区间
*
* @param newRecord {@code true} 需要创建新{@link TaskRecord}
*/
private void initRecord(boolean newRecord) {
if (newRecord) {
mRecord = createTaskRecord(getNewTaskThreadNum());
}
mTaskWrapper.setNewTask(true);
// 处理线程区间记录
int requestType = mTaskWrapper.getRequestType();
long blockSize = mEntity.getFileSize() / mRecord.threadNum;
for (int i = 0; i < mRecord.threadNum; i++) {
long startL = i * blockSize, endL = (i + 1) * blockSize;
ThreadRecord tr;
tr = new ThreadRecord();
tr.key = mRecord.filePath;
tr.threadId = i;
tr.startLocation = startL;
tr.isComplete = false;
if (requestType == ITaskWrapper.M3U8_FILE) {
tr.startLocation = 0;
tr.threadType = TaskRecord.TYPE_M3U8;
tr.m3u8url = ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().get(i);
} else {
tr.threadType = TaskRecord.TYPE_HTTP_FTP;
//最后一个线程的结束位置即为文件的总长度
if (i == (mRecord.threadNum - 1)) {
endL = mEntity.getFileSize();
}
tr.endLocation = endL;
tr.blockLen = CommonUtil.getBlockLen(mEntity.getFileSize(), i, mRecord.threadNum);
}
mRecord.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;
// 处理分块和动态文件参数
if (getRecordType() == TYPE_DOWNLOAD) {
record.isBlock = threadNum > 1 && Configuration.getInstance().downloadCfg.isUseBlock();
// 线程数为1,或者使用了分块,则认为是使用动态长度文件
record.isOpenDynamicFile = threadNum == 1 || record.isBlock;
} else {
record.isBlock = false;
}
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
record.taskType = TaskRecord.TYPE_M3U8;
record.isOpenDynamicFile = true;
} else {
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() {
mRecord.threadNum = mRecord.threadRecords.size();
mRecord.save();
DbEntity.saveAll(mRecord.threadRecords);
ALog.d(TAG, String.format("保存记录,线程记录数:%s", mRecord.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_FILE) {
return ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().size();
}
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;
}
}
}

@ -1,82 +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.util.ALog;
/**
* Created by lyy on 2017/1/18.
* 状态常量
*/
public class StateConstance {
private static final String TAG = "StateConstance";
public int CANCEL_NUM = 0;
public int STOP_NUM = 0;
public int FAIL_NUM = 0;
public int COMPLETE_THREAD_NUM = 0;
public int START_THREAD_NUM; //启动的线程数
public long CURRENT_LOCATION = 0; //当前下载进度
public boolean isCancel = false;
public boolean isStop = false;
public TaskRecord TASK_RECORD;
StateConstance() {
}
public void resetState() {
isCancel = false;
isStop = false;
CANCEL_NUM = 0;
STOP_NUM = 0;
FAIL_NUM = 0;
COMPLETE_THREAD_NUM = 0;
START_THREAD_NUM = 0;
CURRENT_LOCATION = 0;
}
/**
* 所有子线程是否都已经停止
*/
public boolean isStop() {
//ALog.d(TAG, String.format("stop_thread_num=%s; start_thread_num=%s; complete_thread_num=%s",
// STOP_NUM, START_THREAD_NUM, COMPLETE_THREAD_NUM));
return STOP_NUM == START_THREAD_NUM || STOP_NUM + COMPLETE_THREAD_NUM == START_THREAD_NUM;
}
/**
* 所有子线程是否都已经失败
*/
public boolean isFail() {
//ALog.d(TAG, String.format("fail_thread_num=%s; start_thread_num=%s; complete_thread_num=%s",
// FAIL_NUM, START_THREAD_NUM, COMPLETE_THREAD_NUM));
return COMPLETE_THREAD_NUM != START_THREAD_NUM
&& (FAIL_NUM == START_THREAD_NUM || FAIL_NUM + COMPLETE_THREAD_NUM == START_THREAD_NUM);
}
/**
* 所有子线程是否都已经完成
*/
public boolean isComplete() {
return COMPLETE_THREAD_NUM == START_THREAD_NUM;
}
/**
* 所有子线程是否都已经取消
*/
public boolean isCancel() {
return CANCEL_NUM == START_THREAD_NUM;
}
}

@ -15,6 +15,7 @@
*/
package com.arialyy.aria.core.common;
import android.os.Handler;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import java.io.File;
@ -22,19 +23,17 @@ import java.io.File;
* 子线程下载信息类
*/
public class SubThreadConfig<TASK_WRAPPER extends AbsTaskWrapper> {
//线程Id
public int THREAD_ID;
//文件总长度
public long TOTAL_FILE_SIZE;
//子线程启动下载位置
public long START_LOCATION;
//子线程结束下载位置
public long END_LOCATION;
//下载文件或上传的文件路径
public File TEMP_FILE;
//服务器地址
public String URL;
public TASK_WRAPPER TASK_WRAPPER;
public boolean SUPPORT_BP = true;
public ThreadRecord THREAD_RECORD;
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;
}

@ -29,6 +29,8 @@ import java.util.List;
* 任务上传或下载的任务记录
*/
public class TaskRecord extends DbEntity {
public static final int TYPE_HTTP_FTP = 0;
public static final int TYPE_M3U8 = 1;
@Ignore
public List<ThreadRecord> threadRecords;
@ -84,4 +86,10 @@ public class TaskRecord extends DbEntity {
* {@code true}{@code false}不是
*/
public boolean isOpenDynamicFile = false;
/**
* 线程类型
* {@link #TYPE_HTTP_FTP}{@link #TYPE_M3U8}
*/
public int taskType = 0;
}

@ -53,4 +53,15 @@ public class ThreadRecord extends DbEntity {
* 分块长度
*/
public long blockLen = 0;
/**
* 线程类型
* {@link TaskRecord#TYPE_HTTP_FTP}{@link TaskRecord#TYPE_M3U8}
*/
public int threadType = 0;
/**
* m3u8文件的下载地址
*/
public String m3u8url;
}

@ -0,0 +1,223 @@
/*
* 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.Handler;
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()) {
mListener.onStop(mProgress);
quitLooper();
}
break;
case STATE_CANCEL:
mCancelNum++;
if (isCancel()) {
ALog.d(TAG, "icCancel");
mListener.onCancel();
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:
mProgress += (long) msg.obj;
break;
case STATE_UPDATE_PROGRESS:
if (msg.obj == null) {
mProgress = updateBlockProgress();
} else {
mProgress = (long) msg.obj;
}
break;
}
return false;
}
/**
* 退出looper循环
*/
private void quitLooper() {
mLooper.quit();
}
/**
* 获取当前任务下载进度
*
* @return 当前任务下载进度
*/
@Override
public long getCurrentProgress() {
return mProgress;
}
/**
* 所有子线程是否都已经停止
*/
@Override
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;
}
/**
* 所有子线程是否都已经取消
*/
@Override
public boolean isCancel() {
//ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCancelNum == mThreadNum;
}
/**
* 更新分块任务s的真实进度
*/
private long updateBlockProgress() {
long size = 0;
for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) {
File temp = new File(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i));
if (temp.exists()) {
size += temp.length();
}
}
return size;
}
/**
* 合并文件
*
* @return {@code true} 合并成功{@code false}合并失败
*/
private boolean mergeFile() {
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s",
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength));
return false;
}
return true;
} else {
ALog.e(TAG, "合并失败");
return false;
}
}
}

@ -192,10 +192,6 @@ public abstract class AbsFtpInfoThread<ENTITY extends AbsEntity, TASK_WRAPPER ex
return false;
}
public void start() {
new Thread(this).start();
}
protected void onPreComplete(int code) {
}

@ -23,11 +23,9 @@ import aria.apache.commons.net.ftp.FTPSClient;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.ProtocolType;
import com.arialyy.aria.core.common.StateConstance;
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.core.inf.IEventListener;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.SSLContextUtil;
@ -38,14 +36,13 @@ import javax.net.ssl.SSLContext;
/**
* Created by lyy on 2017/9/26. FTP单任务父类
*/
public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_ENTITY extends AbsTaskWrapper<ENTITY>>
extends AbsThreadTask<ENTITY, TASK_ENTITY> {
public abstract class AbsFtpThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>>
extends AbsThreadTask<ENTITY, TASK_WRAPPER> {
private final String TAG = "AbsFtpThreadTask";
protected String charSet;
protected AbsFtpThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<TASK_ENTITY> info) {
super(constance, listener, info);
protected AbsFtpThreadTask(SubThreadConfig<TASK_WRAPPER> config) {
super(config);
}
/**

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

@ -1,144 +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.support.annotation.NonNull;
import android.text.TextUtils;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
import com.arialyy.aria.core.inf.IHttpHeaderDelegate;
import com.arialyy.aria.util.ALog;
import java.net.Proxy;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* Created by laoyuyu on 2018/3/9.
* D_HTTP header参数设置委托类
*/
public class HttpHeaderDelegate<TARGET extends AbsTarget>
implements IHttpHeaderDelegate<TARGET> {
private static final String TAG = "HttpHeaderDelegate";
private TARGET mTarget;
public HttpHeaderDelegate(TARGET target) {
mTarget = target;
}
/**
* 给url请求添加Header数据
* 如果新的header数据和数据保存的不一致则更新数据库中对应的header数据
*
* @param key header对应的key
* @param value header对应的value
*/
@Override
public TARGET addHeader(@NonNull String key, @NonNull String value) {
if (TextUtils.isEmpty(key)) {
ALog.w(TAG, "设置header失败,header对应的key不能为null");
return mTarget;
} else if (TextUtils.isEmpty(value)) {
ALog.w(TAG, "设置header失败,header对应的value不能为null");
return mTarget;
}
addHeader(mTarget.getTaskWrapper(), key, value);
return mTarget;
}
/**
* 给url请求添加一组header数据
* 如果新的header数据和数据保存的不一致则更新数据库中对应的header数据
*
* @param headers 一组http header数据
*/
@Override
public TARGET addHeaders(@NonNull Map<String, String> headers) {
if (headers.size() == 0) {
ALog.w(TAG, "设置header失败,map没有header数据");
return mTarget;
}
addHeaders(mTarget.getTaskWrapper(), headers);
return mTarget;
}
@Override public TARGET setUrlProxy(Proxy proxy) {
mTarget.getTaskWrapper().asHttp().setProxy(proxy);
return mTarget;
}
public TARGET setFileLenAdapter(IHttpFileLenAdapter adapter) {
if (adapter == null) {
throw new IllegalArgumentException("adapter为空");
}
mTarget.getTaskWrapper().asHttp().setFileLenAdapter(adapter);
return mTarget;
}
private void addHeader(AbsTaskWrapper taskWrapper, String key, String value) {
HttpTaskDelegate taskDelegate = taskWrapper.asHttp();
if (taskDelegate.getHeaders().get(key) == null) {
taskDelegate.getHeaders().put(key, value);
} else if (!taskDelegate.getHeaders().get(key).equals(value)) {
taskDelegate.getHeaders().put(key, value);
}
}
private void addHeaders(AbsTaskWrapper taskWrapper, Map<String, String> headers) {
HttpTaskDelegate taskDelegate = taskWrapper.asHttp();
/*
两个map比较逻辑
1比对key是否相同
2如果key相同比对value是否相同
3只有当上面两个步骤中key value都相同时才能任务两个map数据一致
*/
boolean mapEquals = false;
if (taskDelegate.getHeaders().size() == headers.size()) {
int i = 0;
Set<String> keys = taskDelegate.getHeaders().keySet();
for (String key : keys) {
if (headers.containsKey(key)) {
i++;
} else {
break;
}
}
if (i == taskDelegate.getHeaders().size()) {
int j = 0;
Collection<String> values = taskDelegate.getHeaders().values();
for (String value : values) {
if (headers.containsValue(value)) {
j++;
} else {
break;
}
}
if (j == taskDelegate.getHeaders().size()) {
mapEquals = true;
}
}
}
if (!mapEquals) {
taskDelegate.getHeaders().clear();
Set<String> keys = headers.keySet();
for (String key : keys) {
taskDelegate.getHeaders().put(key, headers.get(key));
}
}
}
}

@ -88,6 +88,9 @@ public class HttpTaskConfig implements ITaskConfig {
return fileLenAdapter;
}
/**
* 如果是匿名内部类完成后需要将adapter设置为空否则会出现内存泄漏
*/
public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) {
this.fileLenAdapter = fileLenAdapter;
}

@ -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.http;
import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
import com.arialyy.aria.core.inf.ITargetHeadDelegate;
import java.lang.ref.WeakReference;
import java.net.CookieManager;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
/**
* Http任务设置的信息cookie请求参数
*/
public class HttpTaskDelegate implements ITargetHeadDelegate {
private CookieManager cookieManager;
/**
* 请求参数
*/
private Map<String, String> params;
/**
* http 请求头
*/
private Map<String, String> headers = new HashMap<>();
/**
* 字符编码默认为"utf-8"
*/
private String charSet = "utf-8";
/**
* 网络请求类型
*/
private RequestEnum requestEnum = RequestEnum.GET;
/**
* 是否使用服务器通过content-disposition传递的文件名内容格式{@code attachment; filename="filename.jpg"} {@code true}
* 使用
*/
private boolean useServerFileName = false;
/**
* 重定向链接
*/
private String redirectUrl = "";
/**
* 是否是chunk模式
*/
private boolean isChunked = false;
/**
* 文件上传需要的key
*/
private String attachment;
/**
* 上传的文件类型
*/
private String contentType = "multipart/form-data";
private String userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)";
private Proxy proxy;
/**
* 文件上传表单
*/
private Map<String, String> formFields = new HashMap<>();
private IHttpFileLenAdapter fileLenAdapter;
public IHttpFileLenAdapter getFileLenAdapter() {
return fileLenAdapter;
}
public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) {
this.fileLenAdapter = fileLenAdapter;
}
public Map<String, String> getFormFields() {
return formFields;
}
public void setFormFields(Map<String, String> formFields) {
this.formFields = formFields;
}
public String getAttachment() {
return attachment;
}
public void setAttachment(String attachment) {
this.attachment = attachment;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public boolean isChunked() {
return isChunked;
}
public void setChunked(boolean chunked) {
isChunked = chunked;
}
public CookieManager getCookieManager() {
return cookieManager;
}
public void setCookieManager(CookieManager cookieManager) {
this.cookieManager = cookieManager;
}
public Proxy getProxy() {
return proxy;
}
public void setProxy(Proxy proxy) {
this.proxy = proxy;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public String getCharSet() {
return charSet;
}
public void setCharSet(String charSet) {
this.charSet = charSet;
}
public RequestEnum getRequestEnum() {
return requestEnum;
}
public void setRequestEnum(RequestEnum requestEnum) {
this.requestEnum = requestEnum;
}
public boolean isUseServerFileName() {
return useServerFileName;
}
public void setUseServerFileName(boolean useServerFileName) {
this.useServerFileName = useServerFileName;
}
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
}

@ -1,159 +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;
import android.support.annotation.CheckResult;
import android.text.TextUtils;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.inf.IGroupTarget;
import com.arialyy.aria.core.queue.DownloadGroupTaskQueue;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lyy on 2019/4/9.
* 下载组合任务功能
*/
abstract class AbsGroupDelegate<TARGET extends AbsDGTarget> implements IGroupTarget {
protected String TAG;
private TARGET mTarget;
private DGTaskWrapper mWrapper;
/**
* 组任务名
*/
private String mGroupHash;
/**
* 文件夹临时路径
*/
private String mDirPathTemp;
/**
* 是否需要修改路径
*/
private boolean needModifyPath = false;
AbsGroupDelegate(TARGET target, DGTaskWrapper wrapper) {
TAG = CommonUtil.getClassName(getClass());
mTarget = target;
mWrapper = wrapper;
setGroupHash(wrapper.getKey());
mTarget.setTaskWrapper(wrapper);
if (getEntity() != null) {
mDirPathTemp = getEntity().getDirPath();
}
}
@Override public boolean isRunning() {
DownloadGroupTask task = DownloadGroupTaskQueue.getInstance().getTask(getEntity().getKey());
return task != null && task.isRunning();
}
@CheckResult
TARGET setDirPath(String dirPath) {
mDirPathTemp = dirPath;
return mTarget;
}
/**
* 改变任务组文件夹路径修改文件夹路径会将子任务所有路径更换
*
* @param newDirPath 新的文件夹路径
*/
void reChangeDirPath(String newDirPath) {
ALog.d(TAG, String.format("修改新路径为:%s", newDirPath));
List<DTaskWrapper> subTasks = mWrapper.getSubTaskWrapper();
if (subTasks != null && !subTasks.isEmpty()) {
List<DownloadEntity> des = new ArrayList<>();
for (DTaskWrapper dte : subTasks) {
DownloadEntity de = dte.getEntity();
String oldPath = de.getDownloadPath();
String newPath = newDirPath + "/" + de.getFileName();
File file = new File(oldPath);
if (file.exists()) {
file.renameTo(new File(newPath));
}
de.setDownloadPath(newPath);
des.add(de);
}
}
}
/**
* 检查并设置文件夹路径
*
* @return {@code true} 合法
*/
@Override public boolean checkDirPath() {
if (TextUtils.isEmpty(mDirPathTemp)) {
ALog.e(TAG, "文件夹路径不能为null");
return false;
} else if (!mDirPathTemp.startsWith("/")) {
ALog.e(TAG, "文件夹路径【" + mDirPathTemp + "】错误");
return false;
}
File file = new File(mDirPathTemp);
if (file.isFile()) {
ALog.e(TAG, "路径【" + mDirPathTemp + "】是文件,请设置文件夹路径");
return false;
}
if (TextUtils.isEmpty(getEntity().getDirPath()) || !getEntity().getDirPath()
.equals(mDirPathTemp)) {
if (!file.exists()) {
file.mkdirs();
}
needModifyPath = true;
getEntity().setDirPath(mDirPathTemp);
ALog.i(TAG, String.format("文件夹路径改变,将更新文件夹路径为:%s", mDirPathTemp));
}
return true;
}
@Override public DownloadGroupEntity getEntity() {
return mWrapper.getEntity();
}
@Override public boolean taskExists() {
return DbEntity.checkDataExist(DownloadGroupEntity.class, "groupHash=?", mWrapper.getKey());
}
DGTaskWrapper getTaskWrapper() {
return mWrapper;
}
boolean isNeedModifyPath() {
return needModifyPath;
}
String getDirPathTemp() {
return mDirPathTemp;
}
TARGET getTarget() {
return mTarget;
}
public String getGroupHash() {
return mGroupHash;
}
public void setGroupHash(String groupHash) {
this.mGroupHash = groupHash;
}
}

@ -18,6 +18,7 @@ package com.arialyy.aria.core.download;
import android.os.Handler;
import com.arialyy.aria.core.common.BaseListener;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
@ -29,7 +30,6 @@ import com.arialyy.aria.util.CommonUtil;
*/
public class BaseDListener extends BaseListener<DownloadEntity, DTaskWrapper, DownloadTask>
implements IDownloadListener {
private static final String TAG = "BaseDListener";
BaseDListener(DownloadTask task, Handler outHandler) {
super(task, outHandler);
@ -50,30 +50,16 @@ public class BaseDListener extends BaseListener<DownloadEntity, DTaskWrapper, Do
}
}
@Override
protected void saveData(int state, long location) {
mTaskWrapper.setState(state);
mEntity.setState(state);
if (state == IEntity.STATE_CANCEL) {
@Override protected void handleCancel() {
int sType = getTask().getSchedulerType();
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
CommonUtil.delTaskRecord(mEntity.getFilePath(), 1, mTaskWrapper.isRemoveFile(), false);
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
mTaskWrapper.isRemoveFile(), false);
} else {
CommonUtil.delTaskRecord(mEntity.getFilePath(), 1, mTaskWrapper.isRemoveFile(), true);
}
return;
} else if (state == IEntity.STATE_STOP) {
mEntity.setStopTime(System.currentTimeMillis());
} else if (state == IEntity.STATE_COMPLETE) {
handleComplete();
}
if (location > 0) {
mEntity.setCurrentProgress(location);
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
mTaskWrapper.isRemoveFile(), true);
}
mEntity.update();
}
}

@ -16,8 +16,10 @@
package com.arialyy.aria.core.download;
import android.text.TextUtils;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.inf.ITargetHandler;
import com.arialyy.aria.core.inf.IConfigHandler;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.DownloadTaskQueue;
import com.arialyy.aria.orm.DbEntity;
@ -100,6 +102,10 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
if (b) {
mEntity.save();
}
if (mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_FILE
&& mEntity.getFileSize() == 0) {
ALog.e(TAG, "由于m3u8协议的特殊性质,你需要设置文件长度才能获取到正确的下载进度百分比。设置方法:asM3U8().setFileSize(xxx)");
}
return b;
}
@ -109,13 +115,14 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
ALog.e(TAG, "下载失败,文件保存路径为null");
return false;
} else if (!filePath.startsWith("/")) {
ALog.e(TAG, "下载失败,文件保存路径【" + filePath + "】错误");
ALog.e(TAG, String.format("下载失败,文件保存路径【%s】错误", filePath));
return false;
}
File file = new File(filePath);
if (file.isDirectory()) {
if (mTarget.getTargetType() == ITargetHandler.D_HTTP) {
ALog.e(TAG, "下载失败,保存路径【" + filePath + "】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip");
ALog.e(TAG,
String.format("下载失败,保存路径【%s】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip", filePath));
return false;
} else if (mTarget.getTargetType() == ITargetHandler.D_FTP) {
filePath += mEntity.getFileName();
@ -132,11 +139,11 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
// 检查路径冲突
if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", filePath)) {
if (!forceDownload) {
ALog.e(TAG, "下载失败,保存路径【" + filePath + "】已经被其它任务占用,请设置其它保存路径");
ALog.e(TAG, String.format("下载失败,保存路径【%s】已经被其它任务占用,请设置其它保存路径", filePath));
return false;
} else {
ALog.w(TAG, "保存路径【" + filePath + "】已经被其它任务占用,当前任务将覆盖该路径的文件");
CommonUtil.delTaskRecord(filePath, 1);
ALog.w(TAG, String.format("保存路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath));
CommonUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD);
mTarget.setTaskWrapper(
TaskWrapperManager.getInstance()
.getHttpTaskWrapper(DTaskWrapper.class, mUrl));
@ -144,7 +151,7 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
}
File oldFile = new File(mEntity.getDownloadPath());
File newFile = new File(filePath);
mEntity.setDownloadPath(filePath);
mEntity.setFilePath(filePath);
mEntity.setFileName(newFile.getName());
// 如过使用Content-Disposition中的文件名,将不会执行重命名工作
if (mTarget.getTaskWrapper().asHttp().isUseServerFileName()) {

@ -1,201 +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;
import android.text.TextUtils;
import com.arialyy.aria.core.inf.ITargetHandler;
import com.arialyy.aria.core.inf.INormalTarget;
import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.DownloadTaskQueue;
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 java.io.File;
/**
* Created by AriaL on 2019/4/5.
* 普通下载任务通用功能处理
*/
class DNormalDelegate<TARGET extends AbsDTarget> implements INormalTarget {
private final String TAG = "DNormalDelegate";
private DownloadEntity mEntity;
private TARGET mTarget;
private String mNewUrl;
/**
* 设置的文件保存路径的临时变量
*/
private String mTempFilePath;
/**
* {@code true}强制下载不考虑文件路径是否被占用
*/
private boolean forceDownload = false;
/**
* 资源地址
*/
private String mUrl;
DNormalDelegate(TARGET target, String url, String targetName) {
this.mTarget = target;
initTarget(url, targetName);
}
private void initTarget(String url, String targetName) {
DTaskWrapper taskWrapper =
TaskWrapperManager.getInstance().getHttpTaskWrapper(DTaskWrapper.class, url);
mEntity = taskWrapper.getEntity();
mUrl = url;
mTarget.setTargetName(targetName);
mTarget.setTaskWrapper(taskWrapper);
if (mEntity != null) {
mTempFilePath = mEntity.getDownloadPath();
}
}
TARGET updateUrl(String newUrl) {
if (TextUtils.isEmpty(newUrl)) {
ALog.e(TAG, "url更新失败,newUrl为null");
return mTarget;
}
if (mUrl.equals(newUrl)) {
ALog.e(TAG, "url更新失败,新的下载url和旧的url一致");
return mTarget;
}
mNewUrl = newUrl;
mTarget.getTaskWrapper().setRefreshInfo(true);
return mTarget;
}
@Override public DownloadEntity getEntity() {
return mTarget.getEntity();
}
@Override public boolean taskExists() {
return DbEntity.checkDataExist(DownloadEntity.class, "url=?", mUrl);
}
@Override public boolean isRunning() {
DownloadTask task = DownloadTaskQueue.getInstance().getTask(mEntity.getKey());
return task != null && task.isRunning();
}
@Override public boolean checkEntity() {
boolean b = checkUrl() && checkFilePath();
if (b) {
mEntity.save();
}
return b;
}
@Override public boolean checkFilePath() {
String filePath = mTempFilePath;
if (TextUtils.isEmpty(filePath)) {
ALog.e(TAG, "下载失败,文件保存路径为null");
return false;
} else if (!filePath.startsWith("/")) {
ALog.e(TAG, "下载失败,文件保存路径【" + filePath + "】错误");
return false;
}
File file = new File(filePath);
if (file.isDirectory()) {
if (mTarget.getTargetType() == ITargetHandler.D_HTTP) {
ALog.e(TAG, "下载失败,保存路径【" + filePath + "】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip");
return false;
} else if (mTarget.getTargetType() == ITargetHandler.D_FTP) {
filePath += mEntity.getFileName();
}
} else {
// http文件名设置
if (TextUtils.isEmpty(mEntity.getFileName())) {
mEntity.setFileName(file.getName());
}
}
//设置文件保存路径,如果新文件路径和旧文件路径不同,则修改路径
if (!filePath.equals(mEntity.getFilePath())) {
// 检查路径冲突
if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", filePath)) {
if (!forceDownload) {
ALog.e(TAG, "下载失败,保存路径【" + filePath + "】已经被其它任务占用,请设置其它保存路径");
return false;
} else {
ALog.w(TAG, "保存路径【" + filePath + "】已经被其它任务占用,当前任务将覆盖该路径的文件");
CommonUtil.delTaskRecord(filePath, 1);
mTarget.setTaskWrapper(
TaskWrapperManager.getInstance()
.getHttpTaskWrapper(DTaskWrapper.class, mUrl));
}
}
File oldFile = new File(mEntity.getDownloadPath());
File newFile = new File(filePath);
mEntity.setDownloadPath(filePath);
mEntity.setFileName(newFile.getName());
// 如过使用Content-Disposition中的文件名,将不会执行重命名工作
if (mTarget.getTaskWrapper().asHttp().isUseServerFileName()) {
return true;
}
if (oldFile.exists()) {
// 处理普通任务的重命名
CommonUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath());
ALog.i(TAG, String.format("将任务重命名为:%s", newFile.getName()));
} else if (CommonUtil.blockTaskExists(oldFile.getPath())) {
// 处理分块任务的重命名
CommonUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath());
ALog.i(TAG, String.format("将分块任务重命名为:%s", newFile.getName()));
}
}
return true;
}
@Override public boolean checkUrl() {
final String url = mEntity.getUrl();
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "下载失败,url为null");
return false;
} else if (!CheckUtil.checkUrlNotThrow(url)) {
ALog.e(TAG, "下载失败,url【" + url + "】错误");
return false;
}
int index = url.indexOf("://");
if (index == -1) {
ALog.e(TAG, "下载失败,url【" + url + "】不合法");
return false;
}
if (!TextUtils.isEmpty(mNewUrl)) {
mEntity.setUrl(mNewUrl);
}
return true;
}
void setForceDownload(boolean forceDownload) {
this.forceDownload = forceDownload;
}
void setUrl(String url) {
this.mUrl = url;
}
String getUrl() {
return mUrl;
}
void setTempFilePath(String mTempFilePath) {
this.mTempFilePath = mTempFilePath;
}
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.download;
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;
/**
@ -34,10 +35,19 @@ public class DTaskWrapper extends AbsTaskWrapper<DownloadEntity> {
*/
private boolean isGroupTask = false;
private M3U8TaskConfig m3u8TaskConfig;
public DTaskWrapper(DownloadEntity entity) {
super(entity);
}
public M3U8TaskConfig asM3U8() {
if (m3u8TaskConfig == null) {
m3u8TaskConfig = new M3U8TaskConfig();
}
return m3u8TaskConfig;
}
/**
* Task实体对应的key下载url
*/

@ -18,11 +18,13 @@ package com.arialyy.aria.core.download;
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.IDownloadGroupListener;
import com.arialyy.aria.core.inf.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.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
@ -34,7 +36,6 @@ import com.arialyy.aria.util.ErrorHelp;
class DownloadGroupListener
extends BaseListener<DownloadGroupEntity, DGTaskWrapper, DownloadGroupTask>
implements IDownloadGroupListener {
private final String TAG = "DownloadGroupListener";
private GroupSendParams<DownloadGroupTask, DownloadEntity> mSeedEntity;
DownloadGroupListener(DownloadGroupTask task, Handler outHandler) {
@ -74,9 +75,11 @@ class DownloadGroupListener
public void onSubFail(DownloadEntity subEntity, BaseException e) {
saveCurrentLocation();
sendInState2Target(ISchedulers.SUB_FAIL, subEntity);
if (e != null) {
e.printStackTrace();
ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e));
}
}
@Override
public void onSubCancel(DownloadEntity subEntity) {
@ -99,6 +102,24 @@ class DownloadGroupListener
mSeedEntity.entity = subEntity;
outHandler.get().obtainMessage(state, ISchedulers.IS_SUB_TASK, 0, mSeedEntity).sendToTarget();
}
saveSubState(state, subEntity);
}
private void saveSubState(int state, DownloadEntity subEntity) {
subEntity.setState(state);
if (state == IEntity.STATE_STOP) {
subEntity.setStopTime(System.currentTimeMillis());
} else if (state == IEntity.STATE_COMPLETE) {
subEntity.setComplete(true);
subEntity.setCompleteTime(System.currentTimeMillis());
subEntity.setCurrentProgress(subEntity.getFileSize());
subEntity.setPercent(100);
subEntity.setConvertSpeed("0kb/s");
subEntity.setSpeed(0);
ALog.i(TAG, String.format("任务【%s】完成,将删除线程任务记录", mEntity.getKey()));
CommonUtil.delTaskRecord(subEntity.getKey(), RecordHandler.TYPE_DOWNLOAD, false, false);
}
subEntity.update();
}
private void saveCurrentLocation() {
@ -130,11 +151,7 @@ class DownloadGroupListener
}
@Override
protected void saveData(int state, long location) {
mTaskWrapper.setState(state);
mEntity.setState(state);
if (state == IEntity.STATE_CANCEL) {
@Override protected void handleCancel() {
int sType = getTask().getSchedulerType();
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
@ -143,15 +160,5 @@ class DownloadGroupListener
} else {
CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), true);
}
return;
} else if (state == IEntity.STATE_STOP) {
mEntity.setStopTime(System.currentTimeMillis());
} else if (state == IEntity.STATE_COMPLETE) {
handleComplete();
}
if (location > 0) {
mEntity.setCurrentProgress(location);
}
mEntity.update();
}
}

@ -20,6 +20,7 @@ import android.support.annotation.NonNull;
import com.arialyy.aria.core.common.http.GetDelegate;
import com.arialyy.aria.core.common.http.HttpDelegate;
import com.arialyy.aria.core.common.http.PostDelegate;
import com.arialyy.aria.core.download.m3u8.M3U8Delegate;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
import java.net.Proxy;
import java.util.Map;
@ -59,6 +60,11 @@ public class DownloadTarget extends AbsDTarget<DownloadTarget> {
return (GetDelegate) mHttpDelegate;
}
@CheckResult
public M3U8Delegate<DownloadTarget> asM3U8() {
return new M3U8Delegate<>(this);
}
/**
* 是否使用服务器通过content-disposition传递的文件名内容格式{@code attachment;filename=***}
* 如果获取不到服务器文件名则使用用户设置的文件名

@ -21,10 +21,11 @@ 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.M3U8FileUtil;
import com.arialyy.aria.core.inf.AbsNormalTask;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.core.scheduler.ISchedulers;
import java.io.File;
/**
* Created by lyy on 2016/8/11.
@ -44,14 +45,18 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity, DTaskWrapper> {
/**
* 获取文件保存路径
*
* @return 如果路径不存在返回null
* @deprecated 后续版本将删除该方法请使用{@link #getFilePath()}
*/
@Deprecated
public String getDownloadPath() {
File file = new File(mEntity.getDownloadPath());
if (!file.exists()) {
return null;
return getFilePath();
}
return mEntity.getDownloadPath();
/**
* 获取文件保存路径
*/
public String getFilePath() {
return mEntity.getFilePath();
}
public DownloadEntity getEntity() {
@ -84,7 +89,11 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity, DTaskWrapper> {
}
@Override protected synchronized IUtil createUtil() {
if (mTaskWrapper.getRequestType() == ITaskWrapper.D_HTTP){
return new SimpleDownloadUtil(mTaskWrapper, (IDownloadListener) mListener);
}else {
return new M3U8FileUtil(mTaskWrapper, (IDownloadListener) mListener);
}
}
public static class Builder {

@ -1,91 +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;
import android.text.TextUtils;
import com.arialyy.aria.core.FtpUrlEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
/**
* Created by lyy on 2017/4/9.
* ftp文件夹下载功能代理
*/
class FtpDirDelegate extends AbsGroupDelegate<FtpDirDownloadTarget> {
FtpDirDelegate(FtpDirDownloadTarget target, DGTaskWrapper wrapper) {
super(target, wrapper);
wrapper.setRequestType(AbsTaskWrapper.D_FTP_DIR);
}
@Override public boolean checkEntity() {
boolean b = checkDirPath() && checkUrl();
if (b) {
getEntity().save();
if (getTaskWrapper().getSubTaskWrapper() != null) {
//初始化子项的登录信息
FtpUrlEntity tUrlEntity = getTaskWrapper().asFtp().getUrlEntity();
for (DTaskWrapper wrapper : getTaskWrapper().getSubTaskWrapper()) {
FtpUrlEntity urlEntity = wrapper.asFtp().getUrlEntity();
urlEntity.needLogin = tUrlEntity.needLogin;
urlEntity.account = tUrlEntity.account;
urlEntity.user = tUrlEntity.user;
urlEntity.password = tUrlEntity.password;
// 处理ftps详细
if (tUrlEntity.isFtps) {
urlEntity.isFtps = true;
urlEntity.protocol = tUrlEntity.protocol;
urlEntity.storePath = tUrlEntity.storePath;
urlEntity.storePass = tUrlEntity.storePass;
urlEntity.keyAlias = tUrlEntity.keyAlias;
}
}
}
}
if (getTaskWrapper().asFtp().getUrlEntity().isFtps) {
if (TextUtils.isEmpty(getTaskWrapper().asFtp().getUrlEntity().storePath)) {
ALog.e(TAG, "证书路径为空");
return false;
}
if (TextUtils.isEmpty(getTaskWrapper().asFtp().getUrlEntity().keyAlias)) {
ALog.e(TAG, "证书别名为空");
return false;
}
}
return b;
}
/**
* 检查普通任务的下载地址
*
* @return {@code true}地址合法
*/
private boolean checkUrl() {
final String url = getGroupHash();
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "下载失败,url为null");
return false;
} else if (!url.startsWith("ftp")) {
ALog.e(TAG, "下载失败,url【" + url + "】错误");
return false;
}
int index = url.indexOf("://");
if (index == -1) {
ALog.e(TAG, "下载失败,url【" + url + "】不合法");
return false;
}
return true;
}
}

@ -1,247 +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;
import android.support.annotation.CheckResult;
import android.text.TextUtils;
import com.arialyy.aria.core.common.RequestEnum;
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.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Created by lyy on 2019/4/9.
*
* http组合任务功能代理
*/
class HttpGroupDelegate extends AbsGroupDelegate<DownloadGroupTarget> {
/**
* 子任务下载地址
*/
private List<String> mUrls = new ArrayList<>();
/**
* 子任务文件名
*/
private List<String> mSubNameTemp = new ArrayList<>();
HttpGroupDelegate(DownloadGroupTarget target, DGTaskWrapper wrapper) {
super(target, wrapper);
mUrls.addAll(wrapper.getEntity().getUrls());
}
@CheckResult
DownloadGroupTarget setGroupUrl(List<String> urls) {
mUrls.clear();
mUrls.addAll(urls);
return getTarget();
}
/**
* 设置子任务文件名该方法必须在{@link #setDirPath(String)}之后调用否则不生效
*/
@CheckResult
DownloadGroupTarget setSubFileName(List<String> subTaskFileName) {
if (subTaskFileName == null || subTaskFileName.isEmpty()) {
ALog.w(TAG, "修改子任务的文件名失败:列表为null");
return getTarget();
}
if (subTaskFileName.size() != getTaskWrapper().getSubTaskWrapper().size()) {
ALog.w(TAG, "修改子任务的文件名失败:子任务文件名列表数量和子任务的数量不匹配");
return getTarget();
}
mSubNameTemp.clear();
mSubNameTemp.addAll(subTaskFileName);
return getTarget();
}
/**
* 更新组合任务下载地址
*
* @param urls 新的组合任务下载地址列表
*/
@CheckResult
DownloadGroupTarget updateUrls(List<String> urls) {
if (urls == null || urls.isEmpty()) {
throw new NullPointerException("下载地址列表为空");
}
if (urls.size() != mUrls.size()) {
throw new IllegalArgumentException("新下载地址数量和旧下载地址数量不一致");
}
mUrls.clear();
mUrls.addAll(urls);
String newHash = CommonUtil.getMd5Code(urls);
setGroupHash(newHash);
getEntity().setGroupHash(newHash);
getEntity().update();
if (getEntity().getSubEntities() != null && !getEntity().getSubEntities().isEmpty()) {
for (DownloadEntity de : getEntity().getSubEntities()) {
de.setGroupHash(newHash);
de.update();
}
}
return getTarget();
}
@Override public boolean checkEntity() {
if (!checkDirPath()) {
return false;
}
if (!checkSubName()) {
return false;
}
if (!checkUrls()) {
return false;
}
if (!getTaskWrapper().isUnknownSize() && getTaskWrapper().getEntity().getFileSize() == 0) {
ALog.e(TAG, "组合任务必须设置文件文件大小,默认需要强制设置文件大小。如果无法获取到总长度,请调用#unknownSize()来标志该组合任务");
return false;
}
if (getTaskWrapper().asHttp().getRequestEnum() == RequestEnum.POST) {
for (DTaskWrapper subTask : getTaskWrapper().getSubTaskWrapper()) {
subTask.asHttp().setRequestEnum(RequestEnum.POST);
}
}
if (isNeedModifyPath()) {
reChangeDirPath(getDirPathTemp());
}
if (!mSubNameTemp.isEmpty()) {
updateSingleSubFileName();
}
saveEntity();
return true;
}
private void saveEntity() {
getEntity().save();
DbEntity.saveAll(getEntity().getSubEntities());
}
/**
* 更新所有改动的子任务文件名
*/
private void updateSingleSubFileName() {
List<DTaskWrapper> entities = getTaskWrapper().getSubTaskWrapper();
int i = 0;
for (DTaskWrapper taskWrapper : entities) {
if (i < mSubNameTemp.size()) {
String newName = mSubNameTemp.get(i);
DownloadEntity entity = taskWrapper.getEntity();
if (!newName.equals(entity.getFileName())) {
String oldPath = getEntity().getDirPath() + "/" + entity.getFileName();
String newPath = getEntity().getDirPath() + "/" + newName;
if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=? or isComplete='true'",
newPath)) {
ALog.w(TAG, String.format("更新文件名失败,路径【%s】已存在或文件已下载", newPath));
return;
}
CommonUtil.modifyTaskRecord(oldPath, newPath);
entity.setDownloadPath(newPath);
entity.setFileName(newName);
}
}
i++;
}
}
/**
* 检查urls是否合法并删除不合法的子任务
*
* @return {@code true} 合法
*/
private boolean checkUrls() {
if (mUrls.isEmpty()) {
ALog.e(TAG, "下载失败,子任务下载列表为null");
return false;
}
Set<String> repeated = new HashSet<>();
List<String> results = new ArrayList<>();
for (String url : mUrls) {
if (!repeated.add(url)) {
results.add(url);
}
}
if (!results.isEmpty()) {
ALog.e(TAG, String.format("组合任务中有url重复,重复的url:%s", Arrays.toString(results.toArray())));
return false;
}
Set<Integer> delItem = new HashSet<>();
int i = 0;
for (String url : mUrls) {
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "子任务url为null,即将删除该子任务。");
delItem.add(i);
continue;
} else if (!url.startsWith("http")) {
ALog.e(TAG, "子任务url【" + url + "】错误,即将删除该子任务。");
delItem.add(i);
continue;
}
int index = url.indexOf("://");
if (index == -1) {
ALog.e(TAG, "子任务url【" + url + "】不合法,即将删除该子任务。");
delItem.add(i);
continue;
}
i++;
}
for (int index : delItem) {
mUrls.remove(index);
if (mSubNameTemp != null && !mSubNameTemp.isEmpty()) {
mSubNameTemp.remove(index);
}
}
getEntity().setGroupHash(CommonUtil.getMd5Code(mUrls));
return true;
}
/**
* 如果用户设置了子任务文件名检查子任务文件名
*
* @return {@code true} 合法
*/
private boolean checkSubName() {
if (mSubNameTemp == null || mSubNameTemp.isEmpty()) {
return true;
}
if (mUrls.size() != mSubNameTemp.size()) {
ALog.e(TAG, "子任务文件名必须和子任务数量一致");
return false;
}
return true;
}
}

@ -44,7 +44,7 @@ import javax.net.ssl.SSLSocketFactory;
/**
* Created by lyy on 2017/1/18. 链接帮助类
*/
class ConnectionHelp {
public class ConnectionHelp {
private static final String TAG = "ConnectionHelp";
/**
@ -52,7 +52,8 @@ class ConnectionHelp {
*
* @throws MalformedURLException
*/
static URL handleUrl(String url, HttpTaskConfig taskDelegate) throws MalformedURLException {
public static URL handleUrl(String url, HttpTaskConfig taskDelegate)
throws MalformedURLException {
Map<String, String> params = taskDelegate.getParams();
if (params != null && taskDelegate.getRequestEnum() == RequestEnum.GET) {
if (url.contains("?")) {
@ -79,7 +80,7 @@ class ConnectionHelp {
* @return {@link GZIPInputStream}{@link InflaterInputStream}
* @throws IOException
*/
static InputStream convertInputStream(HttpURLConnection connection) throws IOException {
public static InputStream convertInputStream(HttpURLConnection connection) throws IOException {
String encoding = connection.getHeaderField("Content-Encoding");
if (TextUtils.isEmpty(encoding)) {
return connection.getInputStream();
@ -98,7 +99,7 @@ class ConnectionHelp {
*
* @throws IOException
*/
static HttpURLConnection handleConnection(URL url, HttpTaskConfig taskDelegate)
public static HttpURLConnection handleConnection(URL url, HttpTaskConfig taskDelegate)
throws IOException {
HttpURLConnection conn;
URLConnection urlConn;
@ -130,7 +131,7 @@ class ConnectionHelp {
*
* @throws ProtocolException
*/
static HttpURLConnection setConnectParam(HttpTaskConfig delegate, HttpURLConnection conn) {
public static HttpURLConnection setConnectParam(HttpTaskConfig delegate, HttpURLConnection conn) {
if (delegate.getRequestEnum() == RequestEnum.POST) {
conn.setDoInput(true);
conn.setDoOutput(true);

@ -15,8 +15,9 @@
*/
package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.common.AbsFileer;
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.DownloadEntity;
import com.arialyy.aria.core.download.DTaskWrapper;
@ -26,34 +27,21 @@ 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 com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.io.IOException;
/**
* Created by AriaL on 2017/7/1. 文件下载器
*/
public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
public class Downloader extends NormalFileer<DownloadEntity, DTaskWrapper> {
private String TAG = "Downloader";
public Downloader(IDownloadListener listener, DTaskWrapper taskWrapper) {
super(listener, taskWrapper);
mTempFile = new File(mEntity.getDownloadPath());
mTempFile = new File(mEntity.getFilePath());
setUpdateInterval(taskWrapper.getConfig().getUpdateInterval());
}
/**
* 小于1m的文件或是任务组的子任务线程数都是1
*/
@Override protected int setNewTaskThreadNum() {
int threadNum = mTaskWrapper.getConfig().getThreadNum();
return mEntity.getFileSize() <= SUB_LEN
|| mTaskWrapper.isGroupTask()
|| threadNum == 1
? 1
: threadNum;
}
@Override protected boolean handleNewTask() {
if (!mRecord.isBlock) {
if (mTempFile.exists()) {
@ -62,7 +50,7 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
//CommonUtil.createFile(mTempFile.getPath());
} else {
for (int i = 0; i < mTotalThreadNum; i++) {
File blockFile = new File(String.format(AbsFileer.SUB_PATH, mTempFile.getPath(), 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();
@ -97,9 +85,9 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
* 如果使用"Content-Disposition"中的文件名需要更新{@link #mTempFile}的路径
*/
void updateTempFile() {
if (!mTempFile.getPath().equals(mEntity.getDownloadPath())) {
if (!mTempFile.getPath().equals(mEntity.getFilePath())) {
if (!mTempFile.exists()) {
mTempFile = new File(mEntity.getDownloadPath());
mTempFile = new File(mEntity.getFilePath());
} else {
boolean b = mTempFile.renameTo(new File(mEntity.getDownloadPath()));
ALog.d(TAG, String.format("更新tempFile文件名%s", b ? "成功" : "失败"));
@ -107,10 +95,6 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
}
}
@Override protected int getType() {
return DOWNLOAD;
}
@Override protected void onPostPre() {
super.onPostPre();
((IDownloadListener) mListener).onPostPre(mEntity.getFileSize());
@ -124,9 +108,9 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
switch (mTaskWrapper.getRequestType()) {
case AbsTaskWrapper.D_FTP:
case AbsTaskWrapper.D_FTP_DIR:
return new FtpThreadTask(mConstance, (IDownloadListener) mListener, config);
return new FtpThreadTask(config);
case AbsTaskWrapper.D_HTTP:
return new HttpThreadTask(mConstance, (IDownloadListener) mListener, config);
return new HttpThreadTask(config);
}
return null;
}

@ -17,14 +17,11 @@ package com.arialyy.aria.core.download.downloader;
import aria.apache.commons.net.ftp.FTPClient;
import aria.apache.commons.net.ftp.FTPReply;
import com.arialyy.aria.core.common.StateConstance;
import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask;
import com.arialyy.aria.core.config.BaseTaskConfig;
import com.arialyy.aria.core.config.DownloadConfig;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
@ -43,37 +40,31 @@ import java.nio.channels.ReadableByteChannel;
*/
class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
private final String TAG = "FtpThreadTask";
private boolean isOpenDynamicFile;
private boolean isBlock;
FtpThreadTask(StateConstance constance, IDownloadListener listener,
SubThreadConfig<DTaskWrapper> downloadInfo) {
super(constance, listener, downloadInfo);
isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile;
isBlock = getState().TASK_RECORD.isBlock;
FtpThreadTask(SubThreadConfig<DTaskWrapper> config) {
super(config);
}
@Override public FtpThreadTask call() throws Exception {
super.call();
if (getConfig().THREAD_RECORD.isComplete) {
if (mRecord.isComplete) {
handleComplete();
return this;
}
mChildCurrentLocation = getConfig().START_LOCATION;
FTPClient client = null;
InputStream is = null;
try {
ALog.d(TAG,
String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION));
String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(),
mRecord.threadId, mRecord.startLocation, mRecord.endLocation));
client = createClient();
if (client == null) {
fail(mChildCurrentLocation, new TaskException(TAG, "ftp client 创建失败"));
return this;
}
if (getConfig().START_LOCATION > 0) {
client.setRestartOffset(getConfig().START_LOCATION);
if (mRecord.startLocation > 0) {
client.setRestartOffset(mRecord.startLocation);
}
//发送第二次指令时,还需要再做一次判断
int reply = client.getReplyCode();
@ -97,7 +88,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
return this;
}
if (isOpenDynamicFile) {
if (getConfig().isOpenDynamicFile) {
readDynamicFile(is);
} else {
readNormal(is);
@ -105,10 +96,10 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
}
} catch (IOException e) {
fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e));
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} catch (Exception e) {
fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e));
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} finally {
try {
if (is != null) {
@ -134,28 +125,10 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
if (!checkBlock()) {
return;
}
ALog.i(TAG,
String.format("任务【%s】线程__%s__下载完毕", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID));
writeConfig(true, getConfig().END_LOCATION);
getState().COMPLETE_THREAD_NUM++;
if (getState().isComplete()) {
if (isBlock) {
boolean success = mergeFile();
if (!success) {
mListener.onFail(false,
new TaskException(TAG,
String.format("任务【%s】分块文件合并失败", getConfig().TEMP_FILE.getName())));
return;
}
}
ALog.i(TAG, String.format("任务【%s】线程__%s__下载完毕", getFileName(), mRecord.threadId));
writeConfig(true, mRecord.endLocation);
sendCompleteMsg();
}
if (getState().isFail()) {
mListener.onFail(false,
new TaskException(TAG, String.format("任务【%s】下载失败", getConfig().TEMP_FILE.getName())));
}
}
/**
* 动态长度文件读取方式
@ -166,7 +139,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
ReadableByteChannel fic = null;
try {
int len;
fos = new FileOutputStream(getConfig().TEMP_FILE, true);
fos = new FileOutputStream(getConfig().tempFile, true);
foc = fos.getChannel();
fic = Channels.newChannel(is);
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize());
@ -177,8 +150,8 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
if (mChildCurrentLocation + len >= getConfig().END_LOCATION) {
len = (int) (getConfig().END_LOCATION - mChildCurrentLocation);
if (mChildCurrentLocation + len >= mRecord.endLocation) {
len = (int) (mRecord.endLocation - mChildCurrentLocation);
bf.flip();
fos.write(bf.array(), 0, len);
bf.compact();
@ -194,7 +167,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
handleComplete();
} catch (IOException e) {
fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e));
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} finally {
try {
if (fos != null) {
@ -219,8 +192,8 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
BufferedRandomAccessFile file = null;
try {
file =
new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", getTaskConfig().getBuffSize());
file.seek(getConfig().START_LOCATION);
new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize());
file.seek(mRecord.startLocation);
byte[] buffer = new byte[getTaskConfig().getBuffSize()];
int len;
while (isLive() && (len = is.read(buffer)) != -1) {
@ -230,8 +203,8 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
if (mChildCurrentLocation + len >= getConfig().END_LOCATION) {
len = (int) (getConfig().END_LOCATION - mChildCurrentLocation);
if (mChildCurrentLocation + len >= mRecord.endLocation) {
len = (int) (mRecord.endLocation - mChildCurrentLocation);
file.write(buffer, 0, len);
progress(len);
break;
@ -242,7 +215,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
}
} catch (IOException e) {
fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e));
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} finally {
try {
if (file != null) {

@ -90,9 +90,12 @@ public class HttpFileInfoThread implements Runnable {
conn.disconnect();
}
// 销毁文件长度适配器
IHttpFileLenAdapter lenAdapter = mTaskWrapper.asHttp().getFileLenAdapter();
if (lenAdapter != null && lenAdapter.getClass().isAnonymousClass()) {
mTaskWrapper.asHttp().setFileLenAdapter(null);
}
}
}
private void handleConnect(HttpURLConnection conn) throws IOException {
if (mTaskDelegate.getRequestEnum() == RequestEnum.POST) {
@ -141,9 +144,14 @@ public class HttpFileInfoThread implements Runnable {
}
Map<String, List<String>> headers = conn.getHeaderFields();
String disposition = conn.getHeaderField("Content-Disposition");
if (mTaskDelegate.isUseServerFileName() && !TextUtils.isEmpty(disposition)) {
if (mTaskDelegate.isUseServerFileName()) {
if (!TextUtils.isEmpty(disposition)) {
mEntity.setDisposition(CommonUtil.encryptBASE64(disposition));
handleContentDisposition(disposition);
} else {
ALog.w(TAG, "Content-Disposition对于端字段为空,使用服务器端文件名失败");
}
}
CookieManager msCookieManager = new CookieManager();
List<String> cookiesHeader = headers.get("Set-Cookie");
@ -264,14 +272,15 @@ public class HttpFileInfoThread implements Runnable {
return;
}
ALog.d(TAG, String.format("文件重命名为:%s", newName));
File oldFile = new File(mEntity.getDownloadPath());
File oldFile = new File(mEntity.getFilePath());
String newPath = oldFile.getParent() + "/" + newName;
if (oldFile.exists()) {
boolean b = oldFile.renameTo(new File(newPath));
ALog.d(TAG, String.format("文件重命名%s", b ? "成功" : "失败"));
}
mEntity.setFileName(newName);
mEntity.setDownloadPath(newPath);
mEntity.setFilePath(newPath);
CommonUtil.modifyTaskRecord(oldFile.getPath(), newPath);
}
/**

@ -17,13 +17,11 @@ package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.core.common.StateConstance;
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.core.inf.IDownloadListener;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
@ -49,37 +47,30 @@ import java.util.Set;
*/
final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
private final String TAG = "HttpThreadTask";
private boolean isOpenDynamicFile;
private boolean isBlock;
HttpThreadTask(StateConstance constance, IDownloadListener listener,
SubThreadConfig<DTaskWrapper> downloadInfo) {
super(constance, listener, downloadInfo);
isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile;
isBlock = getState().TASK_RECORD.isBlock;
HttpThreadTask(SubThreadConfig<DTaskWrapper> config) {
super(config);
}
@Override public HttpThreadTask call() throws Exception {
super.call();
if (getThreadRecord().isComplete) {
if (mRecord.isComplete) {
handleComplete();
return this;
}
HttpURLConnection conn = null;
BufferedInputStream is = null;
BufferedRandomAccessFile file = null;
//当前子线程的下载位置
mChildCurrentLocation = getConfig().START_LOCATION;
try {
HttpTaskConfig taskDelegate = getTaskWrapper().asHttp();
URL url = ConnectionHelp.handleUrl(getConfig().URL, taskDelegate);
URL url = ConnectionHelp.handleUrl(getConfig().url, taskDelegate);
conn = ConnectionHelp.handleConnection(url, taskDelegate);
if (getConfig().SUPPORT_BP) {
if (mTaskWrapper.isSupportBP()) {
ALog.d(TAG,
String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION));
conn.setRequestProperty("Range", String.format("bytes=%s-%s", getConfig().START_LOCATION,
(getConfig().END_LOCATION - 1)));
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)));
} else {
ALog.w(TAG, "该下载不支持断点");
}
@ -112,29 +103,29 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn));
if (taskDelegate.isChunked()) {
readChunked(is);
} else if (isOpenDynamicFile) {
} else if (getConfig().isOpenDynamicFile) {
readDynamicFile(is);
} else {
//创建可设置位置的文件
file =
new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd",
new BufferedRandomAccessFile(getConfig().tempFile, "rwd",
getTaskConfig().getBuffSize());
//设置每条线程写入文件的位置
file.seek(getConfig().START_LOCATION);
file.seek(mRecord.startLocation);
readNormal(is, file);
handleComplete();
}
} catch (MalformedURLException e) {
fail(mChildCurrentLocation, new TaskException(TAG,
String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(),
String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
getEntity().getDownloadPath(), getEntity().getUrl()), e));
} catch (IOException e) {
fail(mChildCurrentLocation, new TaskException(TAG,
String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(),
String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
getEntity().getDownloadPath(), getEntity().getUrl()), e));
} catch (Exception e) {
fail(mChildCurrentLocation, new TaskException(TAG,
String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(),
String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(),
getEntity().getDownloadPath(), getEntity().getUrl()), e));
} finally {
try {
@ -160,7 +151,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
private void readChunked(InputStream is) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(getConfig().TEMP_FILE, true);
fos = new FileOutputStream(getConfig().tempFile, true);
byte[] buffer = new byte[getTaskConfig().getBuffSize()];
int len;
while (isLive() && (len = is.read(buffer)) != -1) {
@ -177,7 +168,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
} catch (IOException e) {
fail(mChildCurrentLocation, new AriaIOException(TAG,
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(),
getConfig().URL),
getConfig().url),
e));
} finally {
if (fos != null) {
@ -199,7 +190,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
ReadableByteChannel fic = null;
try {
int len;
fos = new FileOutputStream(getConfig().TEMP_FILE, true);
fos = new FileOutputStream(getConfig().tempFile, true);
foc = fos.getChannel();
fic = Channels.newChannel(is);
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize());
@ -212,8 +203,8 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
if (mChildCurrentLocation + len >= getConfig().END_LOCATION) {
len = (int) (getConfig().END_LOCATION - mChildCurrentLocation);
if (mChildCurrentLocation + len >= mRecord.endLocation) {
len = (int) (mRecord.endLocation - mChildCurrentLocation);
bf.flip();
fos.write(bf.array(), 0, len);
bf.compact();
@ -230,7 +221,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
} catch (IOException e) {
fail(mChildCurrentLocation, new AriaIOException(TAG,
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(),
getConfig().URL),
getConfig().url),
e));
} finally {
try {
@ -284,37 +275,13 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
return;
}
if (mChildCurrentLocation == getConfig().END_LOCATION) {
//支持断点的处理
if (getConfig().SUPPORT_BP) {
ALog.i(TAG,
String.format("任务【%s】线程__%s__下载完毕", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID));
writeConfig(true, getConfig().END_LOCATION);
getState().COMPLETE_THREAD_NUM++;
if (getState().isComplete()) {
if (isBlock) {
boolean success = mergeFile();
if (!success) {
mListener.onFail(false, new TaskException(TAG,
String.format("任务【%s】分块文件合并失败", getConfig().TEMP_FILE.getName())));
return;
}
}
if (mTaskWrapper.isSupportBP()) {
writeConfig(true, mRecord.endLocation);
sendCompleteMsg();
}
if (getState().isFail()) {
mListener.onFail(false,
new TaskException(TAG,
String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(),
getEntity().getDownloadPath(), getEntity().getUrl())));
}
} else {
sendCompleteMsg();
}
} else {
getState().FAIL_NUM++;
}
}
@Override public int getMaxSpeed() {

@ -29,7 +29,7 @@ import com.arialyy.aria.exception.BaseException;
* Created by lyy on 2015/8/25.
* D_HTTP\FTP单任务下载工具
*/
public class SimpleDownloadUtil implements IUtil, Runnable {
public class SimpleDownloadUtil implements IUtil {
private String TAG = "SimpleDownloadUtil";
private IDownloadListener mListener;
private Downloader mDownloader;
@ -84,7 +84,17 @@ public class SimpleDownloadUtil implements IUtil, Runnable {
if (isStop || isCancel) {
return;
}
new Thread(this).start();
mListener.onPre();
// 如果网址没有变,而服务器端端文件改变,以下代码就没有用了
//if (mTaskWrapper.getEntity().getFileSize() <= 1
// || mTaskWrapper.isRefreshInfo()
// || mTaskWrapper.getRequestType() == AbsTaskWrapper.D_FTP
// || mTaskWrapper.getState() == IEntity.STATE_FAIL) {
// new Thread(createInfoThread()).start();
//} else {
// mDownloader.start();
//}
new Thread(createInfoThread()).start();
}
@Override public void resume() {
@ -102,23 +112,6 @@ public class SimpleDownloadUtil implements IUtil, Runnable {
mListener.onFail(needRetry, e);
}
@Override public void run() {
mListener.onPre();
if (isStop || isCancel) {
return;
}
// 如果网址没有变,而服务器端端文件改变,以下代码就没有用了
//if (mTaskWrapper.getEntity().getFileSize() <= 1
// || mTaskWrapper.isRefreshInfo()
// || mTaskWrapper.getRequestType() == AbsTaskWrapper.D_FTP
// || mTaskWrapper.getState() == IEntity.STATE_FAIL) {
// new Thread(createInfoThread()).start();
//} else {
// mDownloader.start();
//}
new Thread(createInfoThread()).start();
}
/**
* 通过链接类型创建不同的获取文件信息的线程
*/

@ -25,6 +25,7 @@ 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.core.inf.IHttpFileLenAdapter;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
@ -145,8 +146,10 @@ public class DownloadGroupUtil extends AbsGroupUtil implements IUtil {
} else if (failCount == mGTWrapper.getSubTaskWrapper().size()) {
mListener.onFail(true, new AriaIOException(TAG, "获取子任务长度失败"));
}
IHttpFileLenAdapter lenAdapter = mGTWrapper.asHttp().getFileLenAdapter();
if (lenAdapter != null && lenAdapter.getClass().isAnonymousClass()) {
mGTWrapper.asHttp().setFileLenAdapter(null);
}
synchronized (LOCK) {
LOCK.notifyAll();
}

@ -43,7 +43,8 @@ public class FtpDirDownloadUtil extends AbsGroupUtil {
if (mGTWrapper.getEntity().getFileSize() > 1) {
startDownload();
} else {
new FtpDirInfoThread(mGTWrapper, new OnFileInfoCallback() {
FtpDirInfoThread infoThread = new FtpDirInfoThread(mGTWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String url, CompleteInfo info) {
if (info.code >= 200 && info.code < 300) {
startDownload();
@ -53,7 +54,8 @@ public class FtpDirDownloadUtil extends AbsGroupUtil {
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
mListener.onFail(needRetry, e);
}
}).start();
});
new Thread(infoThread).start();
}
}

@ -0,0 +1,32 @@
/*
* 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 java.util.List;
/**
* M3U8 #EXTINF 信息处理器
*/
public interface IM3U8UrlExtInfHandler {
/**
* 处理#EXTINF信息对于某些服务器返回的切片信息有可能是相对地址因此你需要自行转换为可下载http连接
*
* @param extInf #EXTINF 切片信息列表
* @return 根据切片信息转换后的http连接列表如果你的切片信息是可以直接下载的http连接直接返回extInf便可
*/
List<String> handler(List<String> extInf);
}

@ -13,12 +13,19 @@
* 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.m3u8;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by AriaL on 2017/6/29.
* 处理任务头部信息等设置等接口
* 直播协议
*/
public interface ITargetHeadDelegate {
@IntDef({
LiveProtocol.HLS
})
@Retention(RetentionPolicy.SOURCE)
public @interface LiveProtocol {
int HLS = 1;
}

@ -16,20 +16,74 @@
package com.arialyy.aria.core.download.m3u8;
import com.arialyy.aria.core.common.BaseDelegate;
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.inf.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import java.io.File;
/**
* m3u8 委托
*/
public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> {
private DTaskWrapper mTaskWrapper;
public M3U8Delegate(TARGET target) {
super(target);
mTarget.getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_FILE);
mTaskWrapper = (DTaskWrapper) mTarget.getTaskWrapper();
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_FILE);
String filePath = mTaskWrapper.getEntity().getFilePath();
File file = new File(filePath);
if (!file.isDirectory()) {
mTaskWrapper.asM3U8().setCacheDir(file.getParent() + "/." + file.getName());
}
}
/**
* 设置文件长度由于m3u8协议的特殊性你需要设置文件长度才能获取到正确的下载进度百分比
*
* @param fileSize 文件长度
*/
public M3U8Delegate setFileSize(long fileSize) {
if (fileSize <= 0) {
ALog.e(TAG, "文件长度错误");
return this;
}
mTaskWrapper.getEntity().setFileSize(fileSize);
return this;
}
/**
* 如果你的#EXTINF信息是相对路径或有其它需求你需要设置extinf处理器#EXTINF中的切片信息转换为可下载的http连接
*
* @param handler {@link IM3U8UrlExtInfHandler}
*/
public M3U8Delegate setExtInfHandler(IM3U8UrlExtInfHandler handler) {
mTaskWrapper.asM3U8().setExtInfHandler(handler);
return this;
}
//public M3U8LiveDelegate asLive(){
// return
//}
/**
* 选择需要下载的码率默认下载最大码率
*
* @param bandWidth 指定的码率
*/
public M3U8Delegate setBandWidth(int bandWidth) {
return this;
}
/**
* 处理直播类的下载
*/
public M3U8LiveDelegate<TARGET> asLive() {
return new M3U8LiveDelegate<>(mTarget);
}
/**
* 处理需要解码的ts文件
*/
public M3U8Delegate setDecodeAdapter() {
return this;
}
}

@ -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.core.download.m3u8;
import android.net.TrafficStats;
import android.os.Process;
import android.text.TextUtils;
import com.arialyy.aria.core.AriaManager;
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.exception.M3U8Exception;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* 解析url中获取到到m3u信息
* https://www.cnblogs.com/renhui/p/10351870.html
*/
public class M3U8FileInfoThread implements Runnable {
private final String TAG = "M3U8FileInfoThread";
private DownloadEntity mEntity;
private DTaskWrapper mTaskWrapper;
private int mConnectTimeOut;
private OnFileInfoCallback onFileInfoCallback;
private HttpTaskConfig mTaskDelegate;
public M3U8FileInfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) {
this.mTaskWrapper = taskWrapper;
mEntity = taskWrapper.getEntity();
mConnectTimeOut =
AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getConnectTimeOut();
onFileInfoCallback = callback;
mTaskDelegate = taskWrapper.asHttp();
}
@Override public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
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);
conn.setConnectTimeout(mConnectTimeOut);
conn.connect();
handleConnect(conn);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private void handleConnect(HttpURLConnection conn) throws IOException {
int code = conn.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = reader.readLine();
if (TextUtils.isEmpty(line) || !line.equalsIgnoreCase("#EXTM3U")) {
failDownload("读取M3U8信息失败,读取不到#EXTM3U标签", false);
return;
}
List<String> extInf = new ArrayList<>();
boolean isDes = false;
while ((line = reader.readLine()) != null) {
if (line.startsWith("#EXT-X-ENDLIST")) {
break;
}
if (line.startsWith("#EXTINF")) {
isDes = true;
} else if (isDes) {
extInf.add(line);
isDes = false;
}
}
if (extInf.isEmpty()) {
failDownload("获取M3U8下载地址列表失败", false);
return;
}
CompleteInfo info = new CompleteInfo();
info.obj = extInf;
onFileInfoCallback.onComplete(mEntity.getKey(), info);
} 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 {
failDownload(String.format("不支持的响应,code: %s", code), true);
}
}
/**
* 处理30x跳转
*/
private void handleUrlReTurn(HttpURLConnection conn, String newUrl) throws IOException {
ALog.d(TAG, "30x跳转,新url为【" + newUrl + "】");
if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null") || !newUrl.startsWith(
"http")) {
if (onFileInfoCallback != null) {
onFileInfoCallback.onFail(mEntity, new TaskException(TAG, "获取重定向链接失败"), false);
}
return;
}
if (!CheckUtil.checkUrlNotThrow(newUrl)) {
failDownload("下载失败,重定向url错误", false);
return;
}
mTaskDelegate.setRedirectUrl(newUrl);
mEntity.setRedirect(true);
mEntity.setRedirectUrl(newUrl);
String cookies = conn.getHeaderField("Set-Cookie");
URL url = ConnectionHelp.handleUrl(newUrl, mTaskDelegate);
conn = ConnectionHelp.handleConnection(url, mTaskDelegate);
ConnectionHelp.setConnectParam(mTaskDelegate, conn);
conn.setRequestProperty("Cookie", cookies);
conn.setConnectTimeout(mConnectTimeOut);
conn.connect();
handleConnect(conn);
conn.disconnect();
}
private void failDownload(String errorInfo, boolean needRetry) {
onFileInfoCallback.onFail(mEntity, new M3U8Exception(TAG, errorInfo), needRetry);
}
}

@ -0,0 +1,325 @@
/*
* 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.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.arialyy.aria.core.common.AbsFileer;
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.download.DownloadEntity;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.FileUtil;
import com.arialyy.aria.util.IdGenerator;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* M3U8点播文件下载器
*/
public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
/**
* 最大执行数
*/
private static final int EXEC_MAX_NUM = 4;
private Handler mStateHandler;
private ArrayBlockingQueue<Long> mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM);
private M3U8ThreadStateManager mManager;
private ReentrantLock LOCK = new ReentrantLock();
private Condition mCondition = LOCK.newCondition();
protected M3U8FileLoader(IEventListener listener, DTaskWrapper wrapper) {
super(listener, wrapper);
}
@Override protected IThreadState getStateManager(Looper looper) {
mManager = new M3U8ThreadStateManager(looper, mRecord, mListener);
mStateHandler = new Handler(looper, mManager);
return mManager;
}
@Override protected long delayTimer() {
return 2000;
}
/**
* 获取ts文件保存路径
*
* @param dirCache 缓存目录
* @param threadId ts文件名
*/
public static String getTsFilePath(String dirCache, int threadId) {
return String.format("%s/%s.ts", dirCache, threadId);
}
private String getCacheDir() {
String cacheDir = mTaskWrapper.asM3U8().getCacheDir();
if (!new File(cacheDir).exists()) {
CommonUtil.createDir(cacheDir);
}
return cacheDir;
}
@Override protected void handleTask() {
new Thread(new Runnable() {
@Override public void run() {
int index = 0;
while (!isBreak()) {
try {
LOCK.lock();
while (mFlagQueue.size() < EXEC_MAX_NUM) {
if (index == mRecord.threadRecords.size()) {
break;
}
ThreadRecord tr = mRecord.threadRecords.get(index);
index++;
if (tr.isComplete) {
continue;
}
M3U8ThreadTask task = createThreadTask(getCacheDir(), tr);
getTaskList().put(tr.threadId, task);
mFlagQueue.offer(startThreadTask(task));
}
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, ThreadRecord record) {
SubThreadConfig<DTaskWrapper> config = new SubThreadConfig<>();
config.url = record.m3u8url;
config.tempFile = new File(getTsFilePath(cacheDir, record.threadId));
config.isBlock = mRecord.isBlock;
config.isOpenDynamicFile = mRecord.isOpenDynamicFile;
config.taskWrapper = mTaskWrapper;
config.record = record;
config.stateHandler = mStateHandler;
if (!config.tempFile.exists()) {
CommonUtil.createFile(config.tempFile.getPath());
}
return new M3U8ThreadTask(config);
}
/**
* M3U8线程状态管理
*/
private class M3U8ThreadStateManager implements IThreadState {
private final String TAG = "M3U8ThreadStateManager";
/**
* 任务状态回调
*/
private IEventListener mListener;
private int mStartThreadNum; // 启动的线程总数
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 任务事件
*/
M3U8ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) {
mLooper = looper;
mTaskRecord = taskRecord;
for (ThreadRecord record : taskRecord.threadRecords) {
if (!record.isComplete) {
mStartThreadNum++;
}
}
mListener = listener;
}
/**
* 退出looper循环
*/
private void quitLooper() {
ALog.d(TAG, "quitLooper");
mLooper.quit();
}
@Override public boolean handleMessage(Message msg) {
switch (msg.what) {
case STATE_STOP:
mStopNum++;
if (isStop()) {
mListener.onStop(mProgress);
quitLooper();
}
break;
case STATE_CANCEL:
mCancelNum++;
if (isCancel()) {
ALog.d(TAG, "icCancel");
mListener.onCancel();
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++;
ALog.d(TAG, "完成");
notifyLock();
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:
mProgress += (long) msg.obj;
break;
}
return false;
}
@Override public boolean isStop() {
printInfo("isStop");
return mStartThreadNum == mStopNum + mFailNum + mCompleteNum;
}
@Override public boolean isFail() {
printInfo("isFail");
return mStartThreadNum == mStopNum + mFailNum + mCompleteNum + mCancelNum;
}
@Override public boolean isComplete() {
printInfo("isComplete");
return mStartThreadNum == mCompleteNum;
}
@Override public boolean isCancel() {
printInfo("isCancel");
return mStartThreadNum == mCancelNum + mFailNum + mCompleteNum;
}
@Override public long getCurrentProgress() {
return mProgress;
}
private void printInfo(String tag) {
if (false) {
ALog.d(tag, String.format(
"startThreadNum = %s, stopNum = %s, cancelNum = %s, failNum = %s, completeNum = %s",
mStartThreadNum, mStopNum, mCancelNum, mFailNum, mCompleteNum));
}
}
/**
* 合并文件
*
* @return {@code true} 合并成功{@code false}合并失败
*/
private boolean mergeFile() {
String cacheDir = getCacheDir();
List<String> partPath = new ArrayList<>();
for (ThreadRecord tr : mTaskRecord.threadRecords) {
partPath.add(getTsFilePath(cacheDir, tr.threadId));
}
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;
}
}
}
}

@ -0,0 +1,136 @@
/*
* 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.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;
import com.arialyy.aria.exception.M3U8Exception;
import com.arialyy.aria.util.ALog;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* M3U8点播文件下载工具
* 工作流程
* 1创建一个和文件同父路径并且同名隐藏文件夹
* 2将所有m3u8的ts文件下载到该文件夹中
* 3完成所有分片下载后合并ts文件
* 4删除该隐藏文件夹
*/
public class M3U8FileUtil implements IUtil {
private final String TAG = "M3U8DownloadUtil";
private DTaskWrapper mWrapper;
private IDownloadListener mListener;
private boolean isStop = false, isCancel = false;
private List<String> mUrls = new ArrayList<>();
public M3U8FileUtil(DTaskWrapper wrapper, IDownloadListener listener) {
mWrapper = wrapper;
mListener = listener;
}
@Override public String getKey() {
return mWrapper.getKey();
}
@Override public long getFileSize() {
return 0;
}
@Override public long getCurrentLocation() {
return 0;
}
@Override public boolean isRunning() {
return false;
}
@Override public void cancel() {
isCancel = true;
}
@Override public void stop() {
isStop = true;
}
@Override public void start() {
if (isStop || isCancel) {
return;
}
mListener.onPre();
new Thread(createInfoThread()).start();
}
@Override public void resume() {
start();
}
@Override public void setMaxSpeed(int speed) {
}
private Runnable createInfoThread() {
if (mWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
return new M3U8FileInfoThread(mWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String key, CompleteInfo info) {
IM3U8UrlExtInfHandler handler = mWrapper.asM3U8().getExtInfHandler();
if (handler != null) {
mUrls.addAll(handler.handler((List<String>) info.obj));
if (handler.getClass().isAnonymousClass()) {
mWrapper.asM3U8().setExtInfHandler(null);
}
} else {
mUrls.addAll((Collection<? extends String>) info.obj);
}
if (mUrls.isEmpty()) {
failDownload(new M3U8Exception(TAG, "获取地址失败"), false);
return;
} else if (!mUrls.get(0).startsWith("http")) {
failDownload(new M3U8Exception(TAG, "地址错误,请使用IM3U8UrlExtInfHandler处理你的url信息"), false);
return;
}
mWrapper.asM3U8().setUrls(mUrls);
new M3U8FileLoader(mListener, mWrapper).start();
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
failDownload(e, needRetry);
}
});
} else if (mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
return null;
} else {
ALog.e(TAG, "不支持的类型");
return null;
}
}
private void failDownload(BaseException e, boolean needRetry) {
if (isStop || isCancel) {
return;
}
mListener.onFail(needRetry, e);
}
}

@ -15,22 +15,34 @@
*/
package com.arialyy.aria.core.download.m3u8;
import com.arialyy.aria.core.common.BaseDelegate;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
/**
* m3u8直播参数设置
*/
public class M3U8LiveDelegate {
M3U8LiveDelegate(M3U8Delegate delegate){
public class M3U8LiveDelegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET> {
M3U8LiveDelegate(TARGET target) {
super(target);
mTarget.getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE);
}
/**
* 设置缓存目录
*
* @param cacheDir 缓存目录路径
* @return
*/
public M3U8LiveDelegate setCacheDir(String cacheDir){
public M3U8LiveDelegate setCacheDir(String cacheDir) {
return this;
}
/**
* 设置直播传输协议{@link LiveProtocol}
*/
public M3U8LiveDelegate setProtocol(@LiveProtocol int protocol) {
return this;
}
}

@ -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.core.download.m3u8;
import java.util.List;
/**
* m3u8任务配信息
*/
public class M3U8TaskConfig {
/**
* 所有ts文件的下载地址
*/
private List<String> urls;
/**
* #EXTINF 标签信息处理器
*/
private IM3U8UrlExtInfHandler extInfHandler;
/**
* 缓存目录
*/
private String cacheDir;
public IM3U8UrlExtInfHandler getExtInfHandler() {
return extInfHandler;
}
public void setExtInfHandler(IM3U8UrlExtInfHandler extInfHandler) {
this.extInfHandler = extInfHandler;
}
public List<String> getUrls() {
return urls;
}
public void setUrls(List<String> urls) {
this.urls = urls;
}
public String getCacheDir() {
return cacheDir;
}
public void setCacheDir(String cacheDir) {
this.cacheDir = cacheDir;
}
}

@ -0,0 +1,140 @@
///*
// * 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.Bundle;
//import android.os.Looper;
//import android.os.Message;
//import com.arialyy.aria.core.common.IThreadState;
//import com.arialyy.aria.core.common.TaskRecord;
//import com.arialyy.aria.core.download.DTaskWrapper;
//import com.arialyy.aria.core.inf.IEventListener;
//import com.arialyy.aria.exception.BaseException;
//import com.arialyy.aria.util.ALog;
//
///**
// * M3U8线程状态管理
// */
//public class M3U8ThreadStateManager implements IThreadState {
// private final String TAG = "M3U8ThreadStateManager";
//
// /**
// * 任务状态回调
// */
// 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 任务事件
// */
// M3U8ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) {
// mLooper = looper;
// mTaskRecord = taskRecord;
// mThreadNum = mTaskRecord.threadNum;
// mListener = listener;
// }
//
// /**
// * 退出looper循环
// */
// private void quitLooper() {
// mLooper.quit();
// }
//
// @Override public boolean handleMessage(Message msg) {
// switch (msg.what) {
// case STATE_STOP:
// mStopNum++;
// if (isStop()) {
// mListener.onStop(mProgress);
// quitLooper();
// }
// break;
// case STATE_CANCEL:
// mCancelNum++;
// if (isCancel()) {
// ALog.d(TAG, "icCancel");
// mListener.onCancel();
// 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:
// mProgress += (long) msg.obj;
// break;
// case STATE_UPDATE_PROGRESS:
// if (msg.obj == null) {
// mProgress = updateBlockProgress();
// } else {
// mProgress = (long) msg.obj;
// }
// break;
// }
// return false;
// }
//
// @Override public boolean isStop() {
// return false;
// }
//
// @Override public boolean isFail() {
// return false;
// }
//
// @Override public boolean isComplete() {
// return false;
// }
//
// @Override public boolean isCancel() {
// return false;
// }
//
// @Override public long getCurrentProgress() {
// return mProgress;
// }
//}

@ -0,0 +1,247 @@
/*
* 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.common.AbsThreadTask;
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.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Created by lyy on 2017/1/18. 下载线程
*/
final class M3U8ThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
private final String TAG = "HttpThreadTask";
M3U8ThreadTask(SubThreadConfig<DTaskWrapper> config) {
super(config);
}
@Override public M3U8ThreadTask call() throws Exception {
super.call();
if (mRecord.isComplete) {
handleComplete();
return this;
}
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()) {
conn.setDoInput(true);
conn.setChunkedStreamingMode(0);
}
conn.connect();
//Map<String, List<String>> header = conn.getHeaderFields();
//int code = conn.getResponseCode();
//String msg = conn.getResponseMessage();
// 传递参数
if (taskDelegate.getRequestEnum() == RequestEnum.POST) {
Map<String, String> params = taskDelegate.getParams();
if (params != null) {
OutputStreamWriter dos = new OutputStreamWriter(conn.getOutputStream());
Set<String> keys = params.keySet();
StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(URLEncoder.encode(params.get(key))).append("&");
}
String paramStr = sb.toString();
paramStr = paramStr.substring(0, paramStr.length() - 1);
dos.write(paramStr);
dos.flush();
dos.close();
}
}
is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn));
if (taskDelegate.isChunked()) {
readChunked(is);
} else if (getConfig().isOpenDynamicFile) {
readDynamicFile(is);
}
} catch (MalformedURLException e) {
fail(mChildCurrentLocation, new TaskException(TAG,
String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId,
getConfig().tempFile.getPath(), getEntity().getUrl()), e));
} catch (IOException e) {
fail(mChildCurrentLocation, new TaskException(TAG,
String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId,
getConfig().tempFile.getPath(), getEntity().getUrl()), e));
} catch (Exception e) {
fail(mChildCurrentLocation, new TaskException(TAG,
String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId,
getConfig().tempFile.getPath(), getEntity().getUrl()), e));
} finally {
try {
if (is != null) {
is.close();
}
if (conn != null) {
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return this;
}
/**
* 读取chunked数据
*/
private void readChunked(InputStream is) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(getConfig().tempFile, true);
byte[] buffer = new byte[getTaskConfig().getBuffSize()];
int len;
while (isLive() && (len = is.read(buffer)) != -1) {
if (isBreak()) {
break;
}
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
fos.write(buffer, 0, len);
progress(len);
}
handleComplete();
} catch (IOException e) {
fail(mChildCurrentLocation, new AriaIOException(TAG,
String.format("文件下载失败,savePath: %s, url: %s", getConfig().tempFile.getPath(),
getConfig().url),
e));
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 动态长度文件读取方式
*/
private void readDynamicFile(InputStream is) {
FileOutputStream fos = null;
FileChannel foc = null;
ReadableByteChannel fic = null;
try {
int len;
fos = new FileOutputStream(getConfig().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()) {
break;
}
if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len);
}
bf.flip();
foc.write(bf);
bf.compact();
progress(len);
}
handleComplete();
} catch (IOException e) {
fail(mChildCurrentLocation, new AriaIOException(TAG,
String.format("文件下载失败,savePath: %s, url: %s", getConfig().tempFile.getPath(),
getConfig().url),
e));
} finally {
try {
if (fos != null) {
fos.flush();
fos.close();
}
if (foc != null) {
foc.close();
}
if (fic != null) {
fic.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 处理完成配置文件的更新或事件回调
*/
private void handleComplete() {
if (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();
}
}

@ -1,58 +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 2019/4/5.
* 组合任务接收器功能接口
*/
public interface IGroupTarget {
/**
* 获取实体
*/
AbsEntity getEntity();
/**
* 任务是否存在
*
* @return {@code true}任务存在{@code false} 任务不存在
*/
boolean taskExists();
/**
* 任务是否在执行
*
* @return {@code true} 任务正在执行{@code false} 任务没有执行
*/
boolean isRunning();
/**
* 检查实体是否合法
*
* @return {@code true}合法
*/
boolean checkEntity();
/**
* 检查文件夹路径
* 1文件夹路径不能为空
* 2文件夹路径不能是文件
*
* @return {@code true} 合法
*/
boolean checkDirPath();
}

@ -1,66 +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 2019/4/5.
* 普通任务接收器功能接口
*/
public interface INormalTarget {
/**
* 获取实体
*/
AbsEntity getEntity();
/**
* 任务是否存在
*
* @return {@code true}任务存在{@code false} 任务不存在
*/
boolean taskExists();
/**
* 任务是否在执行
*
* @return {@code true} 任务正在执行{@code false} 任务没有执行
*/
boolean isRunning();
/**
* 检查下载实体判断实体是否合法 合法标准为
* 1下载路径不为null并且下载路径是正常的http或ftp路径
* 2保存路径不为null并且保存路径是android文件系统路径
* 3保存路径不能重复
*
* @return {@code true}合法
*/
boolean checkEntity();
/**
* 检查并设置普通任务的文件保存路径
*
* @return {@code true}保存路径合法
*/
boolean checkFilePath();
/**
* 检查普通任务的下载地址
*
* @return {@code true}地址合法
*/
boolean checkUrl();
}

@ -15,7 +15,7 @@
*/
package com.arialyy.aria.core.manager;
import com.arialyy.aria.core.common.AbsFileer;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.TaskRecord;
import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity;
@ -83,7 +83,7 @@ class DTaskWrapperFactory implements INormalTEFactory<DownloadEntity, DTaskWrapp
if (record.isBlock) {
int count = 0;
for (int i = 0, len = record.threadNum; i < len; i++) {
File temp = new File(String.format(AbsFileer.SUB_PATH, record.filePath, i));
File temp = new File(String.format(RecordHandler.SUB_PATH, record.filePath, i));
if (!temp.exists()) {
count++;
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.upload;
import android.os.Handler;
import com.arialyy.aria.core.common.BaseListener;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IUploadListener;
import com.arialyy.aria.core.inf.TaskSchedulerType;
@ -32,26 +33,16 @@ class BaseUListener extends BaseListener<UploadEntity, UTaskWrapper, UploadTask>
super(task, outHandler);
}
@Override
protected void saveData(int state, long location) {
mTaskWrapper.setState(state);
mEntity.setState(state);
if (state == IEntity.STATE_CANCEL) {
@Override protected void handleCancel() {
int sType = getTask().getSchedulerType();
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
CommonUtil.delTaskRecord(mEntity.getFilePath(), 2, mTaskWrapper.isRemoveFile(), false);
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD,
mTaskWrapper.isRemoveFile(), false);
} else {
CommonUtil.delTaskRecord(mEntity.getFilePath(), 2, mTaskWrapper.isRemoveFile(), true);
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD,
mTaskWrapper.isRemoveFile(), true);
}
} else if (state == IEntity.STATE_STOP) {
mEntity.setStopTime(System.currentTimeMillis());
} else if (state == IEntity.STATE_COMPLETE) {
handleComplete();
} else if (location > 0) {
mEntity.setCurrentProgress(location);
}
mEntity.update();
}
}

@ -1,155 +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 android.text.TextUtils;
import com.arialyy.aria.core.inf.AbsEntity;
import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor;
import com.arialyy.aria.core.inf.INormalTarget;
import com.arialyy.aria.core.manager.TaskWrapperManager;
import com.arialyy.aria.core.queue.UploadTaskQueue;
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 java.io.File;
/**
* Created by Aria.Lao on 2019/4/5.
* 普通上传任务通用功能处理
*/
class UNormalDelegate<TARGET extends AbsUploadTarget> implements INormalTarget {
private String TAG = "UNormalDelegate";
private UploadEntity mEntity;
private TARGET mTarget;
/**
* 上传路径
*/
private String mTempUrl;
UNormalDelegate(TARGET target, String filePath, String targetName) {
mTarget = target;
initTarget(filePath, targetName);
}
private void initTarget(String filePath, String targetName) {
UTaskWrapper taskWrapper =
TaskWrapperManager.getInstance().getHttpTaskWrapper(UTaskWrapper.class, filePath);
mEntity = taskWrapper.getEntity();
File file = new File(filePath);
mEntity.setFileName(file.getName());
mEntity.setFileSize(file.length());
mTarget.setTargetName(targetName);
mTarget.setTaskWrapper(taskWrapper);
mTempUrl = mEntity.getUrl();
}
TARGET updateUrl(String newUrl) {
mTempUrl = newUrl;
return mTarget;
}
TARGET setUploadInterceptor(IFtpUploadInterceptor uploadInterceptor) {
if (uploadInterceptor == null) {
throw new NullPointerException("ftp拦截器为空");
}
mTarget.getTaskWrapper().asFtp().setUploadInterceptor(uploadInterceptor);
return mTarget;
}
@Override public AbsEntity getEntity() {
return mEntity;
}
@Override public boolean taskExists() {
return DbEntity.checkDataExist(UploadEntity.class, "key=?", mEntity.getFilePath());
}
@Override public boolean isRunning() {
UploadTask task = UploadTaskQueue.getInstance().getTask(mEntity.getKey());
return task != null && task.isRunning();
}
@Override public boolean checkEntity() {
boolean b = checkUrl() && checkFilePath();
if (b) {
mEntity.save();
}
if (mTarget.getTaskWrapper().asFtp().getUrlEntity() != null && mTarget.getTaskWrapper()
.asFtp()
.getUrlEntity().isFtps) {
if (TextUtils.isEmpty(mTarget.getTaskWrapper().asFtp().getUrlEntity().storePath)) {
ALog.e(TAG, "证书路径为空");
return false;
}
if (TextUtils.isEmpty(mTarget.getTaskWrapper().asFtp().getUrlEntity().keyAlias)) {
ALog.e(TAG, "证书别名为空");
return false;
}
}
return b;
}
@Override public boolean checkFilePath() {
String filePath = mEntity.getFilePath();
if (TextUtils.isEmpty(filePath)) {
ALog.e(TAG, "上传失败,文件路径为null");
return false;
} else if (!filePath.startsWith("/")) {
ALog.e(TAG, "上传失败,文件路径【" + filePath + "】不合法");
return false;
}
File file = new File(mEntity.getFilePath());
if (!file.exists()) {
ALog.e(TAG, "上传失败,文件【" + filePath + "】不存在");
return false;
}
if (file.isDirectory()) {
ALog.e(TAG, "上传失败,文件【" + filePath + "】不能是文件夹");
return false;
}
return true;
}
@Override public boolean checkUrl() {
final String url = mTempUrl;
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "上传失败,url为null");
return false;
} else if (!CheckUtil.checkUrlNotThrow(url)) {
ALog.e(TAG, "上传失败,url【" + url + "】错误");
return false;
}
int index = url.indexOf("://");
if (index == -1) {
ALog.e(TAG, "上传失败,url【" + url + "】不合法");
return false;
}
mEntity.setUrl(url);
return true;
}
void setTempUrl(String tempUrl) {
this.mTempUrl = tempUrl;
mTarget.getTaskWrapper().asFtp().setUrlEntity(CommonUtil.getFtpUrlInfo(tempUrl));
}
public String getTempUrl() {
return mTempUrl;
}
}

@ -103,8 +103,11 @@ class FtpFileInfoThread extends AbsFtpInfoThread<UploadEntity, UTaskWrapper> {
e.printStackTrace();
return false;
} finally {
IFtpUploadInterceptor interceptor = mTaskWrapper.asFtp().getUploadInterceptor();
if (interceptor != null && interceptor.getClass().isAnonymousClass()) {
mTaskWrapper.asFtp().setUploadInterceptor(null);
}
}
return true;
}

@ -19,16 +19,13 @@ 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.StateConstance;
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.inf.IEventListener;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil;
@ -42,9 +39,8 @@ class FtpThreadTask extends AbsFtpThreadTask<UploadEntity, UTaskWrapper> {
private final String TAG = "FtpThreadTask";
private String dir, remotePath;
FtpThreadTask(StateConstance constance, IEventListener listener,
SubThreadConfig<UTaskWrapper> info) {
super(constance, listener, info);
FtpThreadTask(SubThreadConfig<UTaskWrapper> config) {
super(config);
}
@Override public int getMaxSpeed() {
@ -57,14 +53,12 @@ class FtpThreadTask extends AbsFtpThreadTask<UploadEntity, UTaskWrapper> {
@Override public FtpThreadTask call() throws Exception {
super.call();
//当前子线程的下载位置
mChildCurrentLocation = getConfig().START_LOCATION;
FTPClient client = null;
BufferedRandomAccessFile file = null;
try {
ALog.d(TAG,
String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION));
String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getFileName(),
mRecord.threadId, mRecord.startLocation, mRecord.endLocation));
client = createClient();
if (client == null) {
return this;
@ -72,7 +66,7 @@ class FtpThreadTask extends AbsFtpThreadTask<UploadEntity, UTaskWrapper> {
initPath();
client.makeDirectory(dir);
client.changeWorkingDirectory(dir);
client.setRestartOffset(getConfig().START_LOCATION);
client.setRestartOffset(mRecord.startLocation);
int reply = client.getReplyCode();
if (!FTPReply.isPositivePreliminary(reply) && reply != FTPReply.FILE_ACTION_OK) {
fail(mChildCurrentLocation,
@ -84,32 +78,22 @@ class FtpThreadTask extends AbsFtpThreadTask<UploadEntity, UTaskWrapper> {
}
file =
new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", getTaskConfig().getBuffSize());
if (getConfig().START_LOCATION != 0) {
new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize());
if (mRecord.startLocation != 0) {
//file.skipBytes((int) getConfig().START_LOCATION);
file.seek(getConfig().START_LOCATION);
file.seek(mRecord.startLocation);
}
boolean complete = upload(client, file);
if (!complete || isBreak()) {
return this;
}
ALog.i(TAG,
String.format("任务【%s】线程__%s__上传完毕", getConfig().TEMP_FILE.getName(),
getConfig().THREAD_ID));
writeConfig(true, getConfig().END_LOCATION);
getState().COMPLETE_THREAD_NUM++;
if (getState().isComplete()) {
ALog.i(TAG, String.format("任务【%s】线程__%s__上传完毕", getFileName(), mRecord.threadId));
writeConfig(true, mRecord.endLocation);
sendCompleteMsg();
}
if (getState().isFail()) {
mListener.onFail(false, new TaskException(TAG,
String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(),
getConfig().URL)));
}
} catch (IOException e) {
fail(mChildCurrentLocation, new AriaIOException(TAG,
String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(),
getConfig().URL)));
getConfig().url)));
} catch (Exception e) {
fail(mChildCurrentLocation, new AriaIOException(TAG, null, e));
} finally {

@ -16,11 +16,9 @@
package com.arialyy.aria.core.upload.uploader;
import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.StateConstance;
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.inf.IUploadListener;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException;
@ -51,9 +49,8 @@ class HttpThreadTask extends AbsThreadTask<UploadEntity, UTaskWrapper> {
private HttpURLConnection mHttpConn;
private OutputStream mOutputStream;
HttpThreadTask(StateConstance constance, IUploadListener listener,
SubThreadConfig<UTaskWrapper> uploadInfo) {
super(constance, listener, uploadInfo);
HttpThreadTask(SubThreadConfig<UTaskWrapper> config) {
super(config);
}
@Override public HttpThreadTask call() throws Exception {
@ -65,7 +62,6 @@ class HttpThreadTask extends AbsThreadTask<UploadEntity, UTaskWrapper> {
getEntity().getUrl())));
return this;
}
mListener.onPre();
URL url;
try {
url = new URL(getEntity().getUrl());
@ -112,7 +108,7 @@ class HttpThreadTask extends AbsThreadTask<UploadEntity, UTaskWrapper> {
private void fail(BaseException e1) {
try {
mListener.onFail(true, e1);
sendFailMsg(e1);
if (mOutputStream != null) {
mOutputStream.close();
}
@ -164,9 +160,9 @@ class HttpThreadTask extends AbsThreadTask<UploadEntity, UTaskWrapper> {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
getState().CURRENT_LOCATION += bytesRead;
progress(bytesRead);
mOutputStream.write(buffer, 0, bytesRead);
if (getState().isCancel) {
if (isCancel) {
break;
}
if (mSpeedBandUtil != null) {

@ -54,6 +54,7 @@ public class SimpleUploadUtil implements IUtil, Runnable {
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) {
@ -66,7 +67,8 @@ public class SimpleUploadUtil implements IUtil, Runnable {
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
mListener.onFail(needRetry, e);
}
}).start();
});
new Thread(infoThread).start();
break;
case AbsTaskWrapper.U_HTTP:
mUploader.start();

@ -16,8 +16,8 @@
package com.arialyy.aria.core.upload.uploader;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.common.AbsFileer;
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.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IUploadListener;
@ -29,7 +29,7 @@ import java.io.File;
* Created by Aria.Lao on 2017/7/27.
* 文件上传器
*/
class Uploader extends AbsFileer<UploadEntity, UTaskWrapper> {
class Uploader extends NormalFileer<UploadEntity, UTaskWrapper> {
Uploader(IUploadListener listener, UTaskWrapper taskEntity) {
super(listener, taskEntity);
@ -42,20 +42,12 @@ class Uploader extends AbsFileer<UploadEntity, UTaskWrapper> {
return true;
}
@Override protected int getType() {
return UPLOAD;
}
@Override protected int setNewTaskThreadNum() {
return 1;
}
@Override protected AbsThreadTask selectThreadTask(SubThreadConfig<UTaskWrapper> config) {
switch (mTaskWrapper.getRequestType()) {
case AbsTaskWrapper.U_FTP:
return new FtpThreadTask(mConstance, mListener, config);
return new FtpThreadTask(config);
case AbsTaskWrapper.U_HTTP:
return new HttpThreadTask(mConstance, (IUploadListener) mListener, config);
return new HttpThreadTask(config);
}
return null;
}

@ -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 M3U8Exception extends BaseException{
private static final String M3U8_EXCEPTION = "M3U8 Exception:";
public M3U8Exception(String tag, String message) {
super(tag, String.format("%s%s", M3U8_EXCEPTION, message));
}
public M3U8Exception(String tag, String message, Exception e){
super(tag, message, e);
}
}

@ -36,7 +36,7 @@ class DBConfig {
static boolean DEBUG = false;
static Map<String, Class> mapping = new HashMap<>();
static String DB_NAME;
static int VERSION = 46;
static int VERSION = 47;
/**
* 是否将数据库保存在Sd卡{@code true}

@ -31,7 +31,7 @@ import com.arialyy.aria.core.command.group.AbsGroupCmd;
import com.arialyy.aria.core.command.group.GroupCmdFactory;
import com.arialyy.aria.core.command.normal.AbsNormalCmd;
import com.arialyy.aria.core.command.normal.NormalCmdFactory;
import com.arialyy.aria.core.common.AbsFileer;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.RecordWrapper;
import com.arialyy.aria.core.common.TaskRecord;
import com.arialyy.aria.core.common.ThreadRecord;
@ -99,7 +99,7 @@ public class CommonUtil {
* @return {@code true} 分块文件存在
*/
public static boolean blockTaskExists(String filePath) {
return new File(String.format(AbsFileer.SUB_PATH, filePath, 0)).exists();
return new File(String.format(RecordHandler.SUB_PATH, filePath, 0)).exists();
}
/**
@ -604,7 +604,8 @@ public class CommonUtil {
/**
* 删除任务组记录
*
* @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经删除完成的文件 {@code false}如果任务已经完成只删除任务数据库记录
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
*/
public static void delGroupTaskRecord(String groupHash, boolean removeFile) {
if (TextUtils.isEmpty(groupHash)) {
@ -619,7 +620,8 @@ public class CommonUtil {
/**
* 删除任务组记录
*
* @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经删除完成的文件 {@code false}如果任务已经完成只删除任务数据库记录
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
*/
public static void delGroupTaskRecord(DownloadGroupEntity groupEntity, boolean removeFile,
boolean removeEntity) {
@ -641,7 +643,7 @@ public class CommonUtil {
if (record.taskRecord.isBlock) {
for (int i = 0, len = record.taskRecord.threadNum; i < len; i++) {
File partFile =
new File(String.format(AbsFileer.SUB_PATH, record.taskRecord.filePath, i));
new File(String.format(RecordHandler.SUB_PATH, record.taskRecord.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
@ -655,13 +657,14 @@ public class CommonUtil {
List<DownloadEntity> subs = groupEntity.getSubEntities();
if (subs != null) {
for (DownloadEntity sub : subs) {
File file = new File(sub.getDownloadPath());
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())) {
@ -677,17 +680,19 @@ public class CommonUtil {
/**
* 删除任务记录默认删除任务实体
*
* @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经完成的文件 {@code false}如果任务已经完成只删除任务数据库记录
* @param removeFile{@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果是下载任务并且任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
* 如果是上传任务无论任务是否完成都只删除记录
*/
public static void delTaskRecord(AbsNormalEntity dEntity, boolean removeFile) {
if (dEntity == null) return;
String filePath;
int type;
if (dEntity instanceof DownloadEntity) {
type = 1;
type = RecordHandler.TYPE_DOWNLOAD;
filePath = ((DownloadEntity) dEntity).getDownloadPath();
} else if (dEntity instanceof UploadEntity) {
type = 2;
type = RecordHandler.TYPE_UPLOAD;
filePath = ((UploadEntity) dEntity).getFilePath();
} else {
ALog.w(TAG, "删除记录失败,未知类型");
@ -700,8 +705,11 @@ public class CommonUtil {
* 删除任务记录默认删除文件
*
* @param filePath 文件路径
* @param removeFile {@code true} 不仅删除任务数据库记录还会删除已经删除完成的文件 {@code false}如果任务已经完成只删除任务数据库记录
* @param type {@code 1}下载任务的记录{@code 2} 上传任务的记录 {@code false}如果任务已经完成只删除任务数据库记录
* @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,
@ -714,32 +722,44 @@ public class CommonUtil {
}
List<RecordWrapper> recordWrapper =
DbEntity.findRelationData(RecordWrapper.class, "filePath=?", filePath);
DbEntity.deleteData(ThreadRecord.class, "key=?", filePath); // 必须先获取完成数据再删除线程记录
File file = new File(filePath);
if (recordWrapper == null
|| recordWrapper.isEmpty()
|| recordWrapper.get(0) == null
|| recordWrapper.get(0).taskRecord == null) {
ALog.w(TAG, "记录为空");
ALog.w(TAG, String.format("记录为空,filePath: %s", filePath));
} else {
TaskRecord record = recordWrapper.get(0).taskRecord;
// 删除分块文件
if (record.isBlock) {
for (int i = 0, len = record.threadNum; i < len; i++) {
File partFile = new File(String.format(AbsFileer.SUB_PATH, record.filePath, i));
File partFile = new File(String.format(RecordHandler.SUB_PATH, record.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
} else if (type == RecordHandler.TYPE_DOWNLOAD) { // 处理单线程任务没有完成的情况
if (record.threadRecords != null && !record.threadRecords.isEmpty()) {
ThreadRecord tr = record.threadRecords.get(0);
if (!tr.isComplete && file.exists()) {
file.delete();
}
// 删除任务记录
record.deleteData();
} else {
if (file.exists()) {
file.delete();
}
}
} else { // 处理上传的情况
if (file.exists() && removeFile) {
file.delete();
}
}
// 删除任务记录
DbEntity.deleteData(ThreadRecord.class, "key=?", filePath); // 必须先获取完成数据再删除线程记录
record.deleteData();
}
if (removeEntity) {
if (type == 1) {
@ -754,7 +774,8 @@ public class CommonUtil {
* 删除任务记录默认删除文件删除任务实体
*
* @param filePath 文件路径
* @param type {@code 1}下载任务的记录{@code 2} 上传任务的记录 {@code false}如果任务已经完成只删除任务数据库记录
* @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录{@link RecordHandler#TYPE_UPLOAD}
* 上传任务的记录
*/
public static void delTaskRecord(String filePath, int type) {
delTaskRecord(filePath, type, false, true);
@ -1208,9 +1229,9 @@ public class CommonUtil {
if (record.threadRecords != null && !record.threadRecords.isEmpty()) {
for (ThreadRecord tr : record.threadRecords) {
tr.key = newPath;
File blockFile = new File(String.format(AbsFileer.SUB_PATH, oldPath, tr.threadId));
File blockFile = new File(String.format(RecordHandler.SUB_PATH, oldPath, tr.threadId));
if (blockFile.exists()) {
blockFile.renameTo(new File(String.format(AbsFileer.SUB_PATH, newPath, tr.threadId)));
blockFile.renameTo(new File(String.format(RecordHandler.SUB_PATH, newPath, tr.threadId)));
}
}
DbEntity.updateManyData(record.threadRecords);

@ -0,0 +1,79 @@
package com.arialyy.aria.util;
public class IdGenerator {
/**
* SnowFlake算法 64位Long类型生成唯一ID 第一位0表明正数 2-4241位表示毫秒时间戳差值起始值自定义
* 43-5210位机器编号5位数据中心编号5位进程编号 53-6412位毫秒内计数器 本机内存生成性能高
* <p>
* 主要就是三部分 时间戳进程id序列号 时间戳41id10位序列号12位
*
* @author chiwei
* @param args
* @since JDK 1.6
*/
private static volatile IdGenerator INSTANCE = null;
private final static long beginTs = 1483200000000L;
private long lastTs = 0L;
private long processId;
private int processIdBits = 10;
private long sequence = 0L;
private int sequenceBits = 12;
private IdGenerator() {
}
public static synchronized IdGenerator getInstance() {
if (INSTANCE == null) {
INSTANCE = new IdGenerator();
}
return INSTANCE;
}
// 10位进程ID标识
public IdGenerator(long processId) {
if (processId > ((1 << processIdBits) - 1)) {
throw new RuntimeException("进程ID超出范围,设置位数" + processIdBits + ",最大"
+ ((1 << processIdBits) - 1));
}
this.processId = processId;
}
private long timeGen() {
return System.currentTimeMillis();
}
public synchronized long nextId() {
long ts = timeGen();
if (ts < lastTs) {// 刚刚生成的时间戳比上次的时间戳还小,出错
throw new RuntimeException("时间戳顺序错误");
}
if (ts == lastTs) {// 刚刚生成的时间戳跟上次的时间戳一样,则需要生成一个sequence序列号
// sequence循环自增
sequence = (sequence + 1) & ((1 << sequenceBits) - 1);
// 如果sequence=0则需要重新生成时间戳
if (sequence == 0) {
// 且必须保证时间戳序列往后
ts = nextTs(lastTs);
}
} else {// 如果ts>lastTs,时间戳序列已经不同了,此时可以不必生成sequence了,直接取0
sequence = 0L;
}
lastTs = ts;// 更新lastTs时间戳
return ((ts - beginTs) << (processIdBits + sequenceBits)) | (processId << sequenceBits)
| sequence;
}
private long nextTs(long lastTs) {
long ts = timeGen();
while (ts <= lastTs) {
ts = timeGen();
}
return ts;
}
}

@ -2,6 +2,7 @@
+ v_3.6.5
- fix bug https://github.com/AriaLyy/Aria/issues/403
- 新增ftp上传拦截器 https://github.com/AriaLyy/Aria/issues/402
- 重构线程任务模块
+ v_3.6.4 (2019/5/16)
- 优化任务接收器的代码结构
- 修复`DbEntity.saveAll()`失败的问题

@ -50,8 +50,8 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:${rootProject.ext.kotlin_version}"
api project(':Aria')
kapt project(':AriaCompiler')
// compile 'com.arialyy.aria:aria-core:3.4.0'
// annotationProcessor 'com.arialyy.aria:aria-compiler:3.4'
// api 'com.arialyy.aria:aria-core:3.6.5_dev_4'
// kapt 'com.arialyy.aria:aria-compiler:3.6.5_dev_4'
// compile 'com.arialyy.aria:aria-core:3.3.14'
// annotationProcessor 'com.arialyy.aria:aria-compiler:3.3.14'

@ -44,9 +44,10 @@
<activity android:name=".core.download.KotlinDownloadActivity"/>
<activity
android:name=".core.FullScreenCodeActivity"
android:theme="@style/FullScreen"
android:screenOrientation="landscape"
android:theme="@style/FullScreen"
android:windowSoftInputMode="stateHidden|adjustResize"/>
<activity android:name=".core.download.m3u8.M3U8DownloadActivity"/>
<service android:name=".core.download.service_download.DownloadService"/>

@ -17,15 +17,16 @@
package com.arialyy.simple;
import android.Manifest;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.arialyy.frame.permission.OnPermissionCallback;
@ -35,6 +36,7 @@ import com.arialyy.simple.base.BaseActivity;
import com.arialyy.simple.base.adapter.AbsHolder;
import com.arialyy.simple.base.adapter.AbsRVAdapter;
import com.arialyy.simple.base.adapter.RvItemClickSupport;
import com.arialyy.simple.core.download.m3u8.M3U8DownloadActivity;
import com.arialyy.simple.databinding.ActivityMainBinding;
import com.arialyy.simple.core.download.DownloadActivity;
import com.arialyy.simple.core.download.FtpDownloadActivity;
@ -44,6 +46,7 @@ import com.arialyy.simple.core.upload.FtpUploadActivity;
import com.arialyy.simple.core.upload.HttpUploadActivity;
import com.arialyy.simple.modlue.CommonModule;
import com.arialyy.simple.to.NormalTo;
import java.util.ArrayList;
import java.util.List;
/**
@ -60,33 +63,53 @@ public class MainActivity extends BaseActivity<ActivityMainBinding> {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getBinding().list.setLayoutManager(new LinearLayoutManager(this));
final List<NormalTo> data = getModule(CommonModule.class).getMainData();
getBinding().list.setAdapter(
new Adapter(this, data));
final List<NormalTo> data = new ArrayList<>();
final Adapter adapter = new Adapter(this, data);
getBinding().list.setAdapter(adapter);
getBinding().list.addItemDecoration(
new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
final CommonModule module = ViewModelProviders.of(this).get(CommonModule.class);
module.getMainData(this).observe(this, new Observer<List<NormalTo>>() {
@Override public void onChanged(@Nullable List<NormalTo> normalTos) {
if (normalTos != null) {
data.addAll(normalTos);
adapter.notifyDataSetChanged();
}
}
});
RvItemClickSupport.addTo(getBinding().list).setOnItemClickListener(
new RvItemClickSupport.OnItemClickListener() {
@Override public void onItemClicked(RecyclerView recyclerView, int position, View v) {
CommonModule module = getModule(CommonModule.class);
switch (position) {
case 0:
module.startNextActivity(data.get(position), DownloadActivity.class);
module.startNextActivity(MainActivity.this, data.get(position),
DownloadActivity.class);
break;
case 1:
module.startNextActivity(data.get(position), HttpUploadActivity.class);
module.startNextActivity(MainActivity.this, data.get(position),
HttpUploadActivity.class);
break;
case 2:
module.startNextActivity(data.get(position), DownloadGroupActivity.class);
module.startNextActivity(MainActivity.this, data.get(position),
DownloadGroupActivity.class);
break;
case 3:
module.startNextActivity(data.get(position), FtpDownloadActivity.class);
module.startNextActivity(MainActivity.this, data.get(position),
FtpDownloadActivity.class);
break;
case 4:
module.startNextActivity(data.get(position), FTPDirDownloadActivity.class);
module.startNextActivity(MainActivity.this, data.get(position),
FTPDirDownloadActivity.class);
break;
case 5:
module.startNextActivity(data.get(position), FtpUploadActivity.class);
module.startNextActivity(MainActivity.this, data.get(position),
FtpUploadActivity.class);
break;
case 6:
module.startNextActivity(MainActivity.this, data.get(position),
M3U8DownloadActivity.class);
break;
}
}

@ -16,14 +16,14 @@
package com.arialyy.simple.core.download;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import com.arialyy.simple.MainActivity;
@ -33,11 +33,10 @@ import com.arialyy.simple.base.adapter.AbsHolder;
import com.arialyy.simple.base.adapter.AbsRVAdapter;
import com.arialyy.simple.base.adapter.RvItemClickSupport;
import com.arialyy.simple.databinding.ActivityDownloadMeanBinding;
import com.arialyy.simple.core.download.fragment_download.FragmentActivity;
import com.arialyy.simple.core.download.multi_download.MultiTaskActivity;
import com.arialyy.simple.core.download.service_download.DownloadService;
import com.arialyy.simple.modlue.CommonModule;
import com.arialyy.simple.to.NormalTo;
import java.util.ArrayList;
import java.util.List;
/**
@ -54,25 +53,42 @@ public class DownloadActivity extends BaseActivity<ActivityDownloadMeanBinding>
super.init(savedInstanceState);
mTo = getIntent().getParcelableExtra(MainActivity.KEY_MAIN_DATA);
setTitle(mTo.title);
final List<NormalTo> data = getModule(CommonModule.class).getDownloadData();
final List<NormalTo> data = new ArrayList<>();
getBinding().list.setLayoutManager(new GridLayoutManager(this, 2));
getBinding().list.setAdapter(new Adapter(this, data));
final Adapter adapter = new Adapter(this, data);
getBinding().list.setAdapter(adapter);
final CommonModule module = ViewModelProviders.of(this).get(CommonModule.class);
module.getDownloadData(this).observe(this,
new Observer<List<NormalTo>>() {
@Override public void onChanged(@Nullable List<NormalTo> normalTos) {
if (normalTos != null) {
data.addAll(normalTos);
adapter.notifyDataSetChanged();
}
}
});
RvItemClickSupport.addTo(getBinding().list).setOnItemClickListener(
new RvItemClickSupport.OnItemClickListener() {
@Override public void onItemClicked(RecyclerView recyclerView, int position, View v) {
CommonModule module = getModule(CommonModule.class);
switch (position) {
case 0:
module.startNextActivity(data.get(position), SingleTaskActivity.class);
module.startNextActivity(DownloadActivity.this, data.get(position),
SingleTaskActivity.class);
break;
case 1:
module.startNextActivity(data.get(position), MultiTaskActivity.class);
module.startNextActivity(DownloadActivity.this, data.get(position),
MultiTaskActivity.class);
break;
case 2:
module.startNextActivity(data.get(position), HighestPriorityActivity.class);
module.startNextActivity(DownloadActivity.this, data.get(position),
HighestPriorityActivity.class);
break;
case 3:
module.startNextActivity(data.get(position), KotlinDownloadActivity.class);
module.startNextActivity(DownloadActivity.this, data.get(position),
KotlinDownloadActivity.class);
break;
case 4:
break;
@ -81,41 +97,6 @@ public class DownloadActivity extends BaseActivity<ActivityDownloadMeanBinding>
});
}
//public void onClick(View view) {
// switch (view.getId()) {
// case R.id.highest_priority:
// startActivity(new Intent(this, HighestPriorityActivity.class));
// break;
// case R.id.service:
// startService(new Intent(this, DownloadService.class));
// break;
// case R.id.single_task:
// startActivity(new Intent(this, SingleTaskActivity.class));
// break;
// case R.id.multi_task:
// startActivity(new Intent(this, MultiTaskActivity.class));
// break;
// case R.id.dialog_task:
// DownloadDialog dialog = new DownloadDialog(this);
//
// dialog.show();
// //DownloadDialogFragment dialog = new DownloadDialogFragment(this);
// //dialog.show(getSupportFragmentManager(), "dialog");
// break;
// case R.id.pop_task:
// DownloadPopupWindow pop = new DownloadPopupWindow(this);
// pop.showAtLocation(mRootView, Gravity.CENTER_VERTICAL, 0, 0);
// break;
// case R.id.fragment_task:
// startActivity(new Intent(this, FragmentActivity.class));
// break;
// case R.id.notification:
// //SimpleNotification notification = new SimpleNotification(this);
// //notification.start();
// break;
// }
//}
private static class Adapter extends AbsRVAdapter<NormalTo, Adapter.Holder> {
Adapter(Context context, List<NormalTo> data) {

@ -47,6 +47,7 @@ import com.arialyy.simple.common.ModifyUrlDialog;
import com.arialyy.simple.databinding.ActivitySingleBinding;
import com.arialyy.simple.util.AppUtil;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@ -201,6 +202,7 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
@Download.onTaskRunning
protected void running(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
ALog.d(TAG, "isRunning");
//Log.d(TAG, task.getKey());
long len = task.getFileSize();
if (len == 0) {
@ -248,13 +250,13 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
@Download.onTaskComplete
void taskComplete(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.getDownloadPath())));
}
}

@ -0,0 +1,268 @@
/*
* 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.m3u8;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
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.DownloadTarget;
import com.arialyy.aria.core.download.DownloadTask;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
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.ActivityM3u8Binding;
import java.io.File;
import java.util.List;
import java.util.Map;
public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
private String mUrl;
private String mFilePath;
private M3U8Module mModule;
private DownloadTarget mTarget;
@Override
protected void init(Bundle savedInstanceState) {
super.init(savedInstanceState);
setTitle("单任务下载");
Aria.download(this).register();
mModule = ViewModelProviders.of(this).get(M3U8Module.class);
mModule.getHttpDownloadInfo(this).observe(this, new Observer<DownloadEntity>() {
@Override public void onChanged(@Nullable DownloadEntity entity) {
if (entity == null) {
return;
}
mTarget = Aria.download(M3U8DownloadActivity.this).load(entity.getUrl());
if (mTarget.getTaskState() == IEntity.STATE_STOP) {
getBinding().setStateStr(getString(R.string.resume));
} else if (mTarget.isRunning()) {
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().setViewModel(this);
}
public void chooseUrl() {
ModifyUrlDialog dialog =
new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl);
dialog.show(getSupportFragmentManager(), "ModifyUrlDialog");
}
public void chooseFilePath() {
ModifyPathDialog dialog =
new ModifyPathDialog(this, getString(R.string.modify_file_path), mFilePath);
dialog.show(getSupportFragmentManager(), "ModifyPathDialog");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_single_task_activity, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
int speed = -1;
String msg = "";
switch (item.getItemId()) {
case R.id.help:
msg = "一些小知识点:\n"
+ "1、你可以在注解中增加链接,用于指定被注解的方法只能被特定的下载任务回调,以防止progress乱跳\n"
+ "2、当遇到网络慢的情况时,你可以先使用onPre()更新UI界面,待连接成功时,再在onTaskPre()获取完整的task数据,然后给UI界面设置正确的数据\n"
+ "3、你可以在界面初始化时通过Aria.download(this).load(URL).getPercent()等方法快速获取相关任务的一些数据";
showMsgDialog("tip", msg);
break;
case R.id.speed_0:
speed = 0;
break;
case R.id.speed_128:
speed = 128;
break;
case R.id.speed_256:
speed = 256;
break;
case R.id.speed_512:
speed = 512;
break;
case R.id.speed_1m:
speed = 1024;
break;
}
if (speed > -1) {
msg = item.getTitle().toString();
Aria.download(this).setMaxSpeed(speed);
T.showShort(this, msg);
}
return true;
}
@Download.onWait
void onWait(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
Log.d(TAG, "wait ==> " + task.getDownloadEntity().getFileName());
}
}
@Download.onPre
protected void onPre(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
getBinding().setStateStr(getString(R.string.stop));
}
}
@Download.onTaskStart
void taskStart(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
getBinding().setFileSize(task.getConvertFileSize());
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");
//Log.d(TAG, task.getKey());
long len = task.getFileSize();
if (len == 0) {
getBinding().setProgress(0);
} else {
getBinding().setProgress(task.getPercent());
}
getBinding().setSpeed(task.getConvertSpeed());
}
}
@Download.onTaskResume
void taskResume(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
getBinding().setStateStr(getString(R.string.stop));
}
}
@Download.onTaskStop
void taskStop(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
getBinding().setStateStr(getString(R.string.resume));
getBinding().setSpeed("");
}
}
@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");
}
}
@Download.onTaskFail
void taskFail(DownloadTask task, Exception e) {
if (task.getKey().equals(mUrl)) {
Toast.makeText(M3U8DownloadActivity.this, getString(R.string.download_fail),
Toast.LENGTH_SHORT)
.show();
getBinding().setStateStr(getString(R.string.start));
}
}
@Download.onTaskComplete
void taskComplete(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
getBinding().setProgress(100);
Toast.makeText(M3U8DownloadActivity.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())));
}
}
@Override
protected int setLayoutId() {
return R.layout.activity_m3u8;
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.start:
if (mTarget.isRunning()) {
Aria.download(this).load(mUrl).stop();
} else {
startD();
}
break;
case R.id.cancel:
Aria.download(this).load(mUrl).cancel(true);
break;
}
}
private void startD() {
Aria.download(M3U8DownloadActivity.this)
.load(mUrl)
.useServerFileName(true)
.setFilePath(mFilePath, true)
.asM3U8()
.start();
}
@Override
protected void onStop() {
super.onStop();
//Aria.download(this).unRegister();
}
@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));
}
}
}

@ -0,0 +1,95 @@
/*
* 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.m3u8;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.frame.base.BaseViewModule;
import com.arialyy.simple.util.AppUtil;
import java.io.File;
public class M3U8Module extends BaseViewModule {
private final String M3U8_URL_KEY = "M3U8_URL_KEY";
private final String M3U8_PATH_KEY = "M3U8_PATH_KEY";
private final String defUrl = "https://www.gaoya123.cn/2019/1557993797897.m3u8";
private final String filePath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()
+ "/1557993797897.m3u8";
private MutableLiveData<DownloadEntity> liveData = new MutableLiveData<>();
private DownloadEntity singDownloadInfo;
/**
* 单任务下载的信息
*/
LiveData<DownloadEntity> getHttpDownloadInfo(Context context) {
String url = AppUtil.getConfigValue(context, M3U8_URL_KEY, defUrl);
String filePath = AppUtil.getConfigValue(context, M3U8_PATH_KEY, this.filePath);
singDownloadInfo = Aria.download(context).getDownloadEntity(url);
if (singDownloadInfo == null) {
singDownloadInfo = new DownloadEntity();
singDownloadInfo.setUrl(url);
File temp = new File(this.filePath);
singDownloadInfo.setDownloadPath(filePath);
singDownloadInfo.setFileName(temp.getName());
} else {
AppUtil.setConfigValue(context, M3U8_PATH_KEY, singDownloadInfo.getDownloadPath());
AppUtil.setConfigValue(context, M3U8_URL_KEY, singDownloadInfo.getUrl());
}
liveData.postValue(singDownloadInfo);
return liveData;
}
/**
* 更新文件保存路径
*
* @param filePath 文件保存路径
*/
void updateFilePath(Context context, String filePath) {
if (TextUtils.isEmpty(filePath)) {
ALog.e(TAG, "文件保存路径为空");
return;
}
File temp = new File(filePath);
AppUtil.setConfigValue(context, M3U8_PATH_KEY, filePath);
singDownloadInfo.setFileName(temp.getName());
singDownloadInfo.setDownloadPath(filePath);
liveData.postValue(singDownloadInfo);
}
/**
* 更新url
*/
void uploadUrl(Context context, String url) {
if (TextUtils.isEmpty(url)) {
ALog.e(TAG, "下载地址为空");
return;
}
AppUtil.setConfigValue(context, M3U8_URL_KEY, url);
singDownloadInfo.setUrl(url);
liveData.postValue(singDownloadInfo);
}
}

@ -15,52 +15,37 @@
*/
package com.arialyy.simple.modlue;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.content.Context;
import android.content.Intent;
import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.frame.module.AbsModule;
import com.arialyy.frame.base.BaseViewModule;
import com.arialyy.simple.MainActivity;
import com.arialyy.simple.R;
import com.arialyy.simple.to.NormalTo;
import com.github.mikephil.charting.data.Entry;
import java.util.ArrayList;
import java.util.List;
/**
* 通用Modle块
*/
public class CommonModule extends AbsModule {
public class CommonModule extends BaseViewModule {
private int mPosition;
private MutableLiveData<List<NormalTo>> mLiveData = new MutableLiveData<>();
public CommonModule(Context context) {
super(context);
public CommonModule() {
}
public void startNextActivity(NormalTo to, Class clazz) {
Intent intent = new Intent(getContext(), clazz);
public void startNextActivity(Context context, NormalTo to, Class clazz) {
Intent intent = new Intent(context, clazz);
intent.putExtra(MainActivity.KEY_MAIN_DATA, to);
getContext().startActivity(intent);
context.startActivity(intent);
}
/**
* 创建图表实体
*/
public Entry createNewEntry(DownloadEntity entity) {
long speed = entity.getSpeed() / 1024;
boolean isKb = true;
if (speed > 1024) {
isKb = false;
speed /= 1024;
}
Entry entry = new Entry(mPosition, speed, isKb);
mPosition++;
return entry;
}
public List<NormalTo> getDownloadData() {
public LiveData<List<NormalTo>> getDownloadData(Context context) {
List<NormalTo> list = new ArrayList<>();
String[] titles = getContext().getResources().getStringArray(R.array.download_items);
String[] titles = context.getResources().getStringArray(R.array.download_items);
int[] icons = new int[] {
R.drawable.ic_http,
R.drawable.ic_http_group,
@ -77,20 +62,22 @@ public class CommonModule extends AbsModule {
i++;
list.add(to);
}
return list;
mLiveData.postValue(list);
return mLiveData;
}
public List<NormalTo> getMainData() {
public LiveData<List<NormalTo>> getMainData(Context context) {
List<NormalTo> list = new ArrayList<>();
String[] titles = getContext().getResources().getStringArray(R.array.main_items);
String[] descs = getContext().getResources().getStringArray(R.array.main_items_desc);
String[] titles = context.getResources().getStringArray(R.array.main_items);
String[] descs = context.getResources().getStringArray(R.array.main_items_desc);
int[] icons = new int[] {
R.drawable.ic_http,
R.drawable.ic_http,
R.drawable.ic_http_group,
R.drawable.ic_ftp,
R.drawable.ic_ftp_dir,
R.drawable.ic_ftp
R.drawable.ic_ftp,
R.drawable.ic_ts
};
int i = 0;
for (String title : titles) {
@ -101,6 +88,7 @@ public class CommonModule extends AbsModule {
i++;
list.add(to);
}
return list;
mLiveData.postValue(list);
return mLiveData;
}
}

@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="1024"
android:viewportWidth="1024"
android:width="24dp">
<path
android:fillColor="#F4F4F4"
android:pathData="M891.9,307.2v634.88c0,15.87 -12.8,28.16 -28.16,28.67H206.34c-11.78,0 -21.5,-9.22 -22.02,-21.5V90.62c0,-8.7 7.17,-15.87 15.87,-15.87h442.88c0.51,0 1.02,0.51 1.02,1.02s0,0.51 -0.51,0.51c-0.51,0.51 -0.51,1.02 0,1.54l247.81,228.86c0.51,0 0.51,0.51 0.51,0.51z"/>
<path
android:fillColor="#6200AF"
android:pathData="M643.58,291.84L643.58,74.75l248.32,231.94h-233.98c-7.68,0 -14.34,-6.66 -14.34,-14.85zM420.35,366.08L202.24,366.08c-58.37,0 -105.47,-47.1 -105.47,-105.47S143.36,154.62 202.24,154.62h218.11c58.37,0 105.47,47.1 105.47,105.47s-47.1,105.98 -105.47,105.98zM658.43,771.58h-235.52c-21.5,0 -38.4,-17.41 -38.91,-38.4v-178.69c0,-21.5 17.41,-38.4 38.4,-38.4h235.52c21.5,0 38.4,17.41 38.4,38.4v178.69c0,20.99 -16.9,38.4 -37.89,38.4zM422.91,529.41c-13.82,0 -24.58,11.26 -24.58,24.58v178.69c0,13.82 11.26,24.58 24.58,24.58h235.52c13.82,0 24.58,-11.26 24.58,-24.58v-178.69c0,-13.82 -11.26,-24.58 -24.58,-24.58h-235.52z"/>
<path
android:fillColor="#6200AF"
android:pathData="M437.76,771.58c-4.1,0 -7.17,-3.07 -7.17,-7.17L430.59,522.24c0,-4.1 3.07,-7.17 7.17,-7.17s7.17,3.07 7.17,7.17v242.18c0,4.1 -3.07,7.17 -7.17,7.17zM643.07,771.58c-4.1,0 -7.17,-3.07 -7.17,-7.17L635.9,522.24c0,-4.1 3.07,-7.17 7.17,-7.17s7.17,3.07 7.17,7.17v242.18c0,4.1 -3.07,7.17 -7.17,7.17z"/>
<path
android:fillColor="#6200AF"
android:pathData="M437.76,595.97h-46.59c-4.1,0 -7.17,-3.07 -7.17,-7.17s3.07,-7.17 7.17,-7.17h46.59c4.1,0 7.17,3.07 7.17,7.17s-3.58,7.17 -7.17,7.17zM437.76,650.75h-46.59c-4.1,0 -7.17,-3.07 -7.17,-7.17s3.07,-7.17 7.17,-7.17h46.59c4.1,0 7.17,3.07 7.17,7.17s-3.58,7.17 -7.17,7.17zM437.76,712.19h-46.59c-4.1,0 -7.17,-3.07 -7.17,-7.17s3.07,-7.17 7.17,-7.17h46.59c4.1,0 7.17,3.07 7.17,7.17s-3.58,7.17 -7.17,7.17zM689.66,595.97h-46.59c-4.1,0 -7.17,-3.07 -7.17,-7.17s3.07,-7.17 7.17,-7.17h46.59c4.1,0 7.17,3.07 7.17,7.17s-3.07,7.17 -7.17,7.17zM689.66,650.75h-46.59c-4.1,0 -7.17,-3.07 -7.17,-7.17s3.07,-7.17 7.17,-7.17h46.59c4.1,0 7.17,3.07 7.17,7.17s-3.07,7.17 -7.17,7.17zM689.66,712.19h-46.59c-4.1,0 -7.17,-3.07 -7.17,-7.17s3.07,-7.17 7.17,-7.17h46.59c4.1,0 7.17,3.07 7.17,7.17s-3.07,7.17 -7.17,7.17zM509.44,693.25c-1.02,0 -2.56,-0.51 -3.58,-1.02 -2.05,-1.02 -3.58,-3.58 -3.58,-6.14v-86.02c0,-2.56 1.54,-4.61 3.58,-6.14 2.05,-1.02 4.61,-1.02 7.17,0l74.24,43.01c3.58,2.05 4.61,6.14 2.56,9.73 -0.51,1.02 -1.54,2.05 -2.56,2.56l-74.24,43.01c-1.02,1.02 -2.05,1.02 -3.58,1.02zM516.61,612.86v61.44l53.25,-30.72 -53.25,-30.72z"/>
<path
android:fillColor="#FFFFFF"
android:pathData="M284.16,301.57h-15.36L261.12,279.55h-33.79L220.16,301.57h-15.36l31.74,-84.99h15.87l31.74,84.99zM257.54,268.29L245.76,233.98c-0.51,-1.54 -1.02,-3.58 -1.02,-5.12 -0.51,1.54 -0.51,3.58 -1.02,5.12l-11.78,33.79h25.6zM360.96,216.06l-30.72,85.5h-15.87l-30.21,-85.5h15.36l20.99,64.51c0.51,2.56 1.02,4.61 1.54,7.17 0.51,-2.56 1.02,-5.12 1.54,-7.17l21.5,-64.51h15.87zM391.68,301.57h-14.34L377.34,216.06h14.34v85.5z"/>
<path
android:fillColor="#F4F4F4"
android:pathData="M891.9,307.2v634.88c0,15.87 -12.8,28.16 -28.16,28.67H206.34c-11.78,0 -21.5,-9.22 -22.02,-21.5V90.62c0,-8.7 7.17,-15.87 15.87,-15.87h442.88c0.51,0 1.02,0.51 1.02,1.02s0,0.51 -0.51,0.51c-0.51,0.51 -0.51,1.02 0,1.54l247.81,228.86c0.51,0 0.51,0.51 0.51,0.51z"/>
<path
android:fillColor="@color/icon_color"
android:pathData="M643.58,291.84V74.75l248.32,231.94h-233.98c-7.68,0 -14.34,-6.66 -14.34,-14.85zM420.35,366.08H202.24c-58.37,0 -105.47,-47.1 -105.47,-105.47S143.36,154.62 202.24,154.62h218.11c58.37,0 105.47,47.1 105.47,105.47s-47.1,105.98 -105.47,105.98z"/>
<path
android:fillColor="#FFFFFF"
android:pathData="M308.22,231.94h-24.06v69.63h-19.46V231.94H240.64v-15.87h67.58v15.87zM321.02,297.98v-18.94c3.58,3.07 7.17,5.12 11.26,6.66 4.1,1.54 8.19,2.05 12.29,2.05 2.56,0 4.61,0 6.14,-0.51s3.07,-1.02 4.61,-2.05c1.02,-1.02 2.05,-1.54 2.56,-2.56 0.51,-1.02 1.02,-2.05 1.02,-3.58 0,-1.54 -0.51,-3.07 -1.54,-4.61 -1.02,-1.54 -2.05,-2.56 -4.1,-3.58 -1.54,-1.02 -3.58,-2.05 -5.63,-3.07 -2.05,-1.02 -4.61,-2.05 -7.17,-3.07 -6.66,-2.56 -11.78,-6.14 -14.85,-10.24 -3.07,-4.1 -5.12,-8.7 -5.12,-14.34 0,-4.61 1.02,-8.19 2.56,-11.26 1.54,-3.07 4.1,-5.63 7.17,-7.68s6.66,-3.58 10.75,-4.61 8.19,-1.54 12.8,-1.54c4.61,0 8.19,0.51 11.78,1.02 3.58,0.51 6.66,1.54 9.73,2.56v17.92c-1.54,-1.02 -3.07,-2.05 -4.61,-2.56 -1.54,-0.51 -3.58,-1.54 -5.12,-2.05s-3.58,-1.02 -5.12,-1.02c-1.54,0 -3.58,-0.51 -5.12,-0.51 -2.05,0 -4.1,0 -6.14,0.51l-4.61,1.54c-1.02,0.51 -2.05,1.54 -3.07,2.56 -0.51,1.02 -1.02,2.05 -1.02,3.58 0,1.54 0.51,2.56 1.02,3.58 0.51,1.02 2.05,2.05 3.07,3.07 1.54,1.02 3.07,2.05 5.12,3.07 2.05,1.02 4.1,2.05 6.66,3.07 3.58,1.54 6.66,3.07 9.22,4.61 2.56,1.54 5.12,3.58 7.17,5.63 2.05,2.05 3.58,4.1 4.61,7.17s1.54,5.63 1.54,9.22c0,4.61 -1.02,8.7 -2.56,11.78s-4.1,5.63 -7.17,7.68 -6.66,3.58 -10.75,4.1c-4.1,1.02 -8.19,1.54 -12.8,1.54 -4.61,0 -9.22,-0.51 -13.31,-1.02 -4.61,-1.02 -8.19,-2.56 -11.26,-4.1z"/>
<path
android:fillColor="@color/icon_color"
android:pathData="M504.83,708.61l75.26,-43.01c5.12,-3.58 6.66,-10.75 3.58,-15.87 -1.02,-1.54 -2.05,-2.56 -3.58,-3.58l-75.26,-43.01c-4.61,-2.05 -10.24,0 -12.8,4.61 -0.51,1.54 -1.02,3.07 -1.02,5.12v85.5c-0.51,5.12 3.58,9.73 8.7,10.24 2.05,1.02 3.58,0.51 5.12,0zM660.48,527.36L416.26,527.36c-15.36,0 -28.16,14.34 -28.16,32.26v192.51c0,17.92 12.29,32.26 28.16,32.26L660.48,784.38c15.36,0 28.16,-14.34 28.16,-32.26v-192c0,-17.92 -12.8,-32.77 -28.16,-32.77zM444.42,752.13c0,5.63 -4.1,10.24 -9.22,10.75h-18.43c-5.63,-0.51 -9.73,-5.12 -9.22,-10.75v-20.99c-0.51,-5.63 4.1,-10.24 9.22,-10.75L435.2,720.38c5.63,0.51 9.73,5.12 9.22,10.75v20.99zM444.42,666.62c0.51,5.63 -4.1,10.24 -9.22,10.75h-18.43c-5.63,-0.51 -9.73,-5.12 -9.22,-10.75v-20.99c0,-5.63 4.1,-10.24 9.22,-10.75L435.2,634.88c5.63,0.51 9.73,5.12 9.22,10.75v20.99zM444.42,581.12c0.51,5.63 -4.1,10.24 -9.22,10.75h-18.43c-5.63,-0.51 -9.73,-5.12 -9.22,-10.75v-20.99c-0.51,-5.63 4.1,-10.24 9.22,-10.75L435.2,549.38c5.63,0.51 9.73,5.12 9.22,10.75v20.99zM613.38,762.88L463.36,762.88v-214.02h150.02L613.38,762.88zM669.7,752.13c0,5.63 -3.58,10.24 -9.22,10.75h-18.43c-5.63,-0.51 -9.73,-5.12 -9.22,-10.75v-20.99c-0.51,-5.63 4.1,-10.24 9.22,-10.75h18.43c5.63,0.51 9.73,5.12 9.73,10.75l-0.51,20.99zM669.7,666.62c0.51,5.63 -4.1,10.24 -9.73,10.75h-18.43c-5.63,-0.51 -9.73,-5.12 -9.22,-10.75v-20.99c-0.51,-5.63 4.1,-10.24 9.22,-10.75h18.94c5.63,0.51 9.73,5.12 9.73,10.75l-0.51,20.99zM669.7,581.12c0.51,5.63 -4.1,10.24 -9.73,10.75h-18.43c-5.63,-0.51 -9.73,-5.12 -9.22,-10.75v-20.99c-0.51,-5.63 4.1,-10.24 9.22,-10.75h18.94c5.63,0.51 9.73,5.12 9.73,10.75l-0.51,20.99z"/>
</vector>

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="fileSize"
type="String"
/>
<variable
name="speed"
type="String"
/>
<variable
name="progress"
type="int"
/>
<variable
name="stateStr"
type="String"
/>
<variable
name="url"
type="String"
/>
<variable
name="filePath"
type="String"
/>
<variable
name="viewModel"
type="com.arialyy.simple.core.download.m3u8.M3U8DownloadActivity"
/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".core.download.SingleTaskActivity"
>
<include layout="@layout/layout_bar"/>
<com.arialyy.simple.widget.SvgTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
bind:iconClickListener="@{() -> viewModel.chooseUrl()}"
bind:svg_text_view_icon="@drawable/ic_modify"
bind:text="@{@string/url(url)}"
/>
<com.arialyy.simple.widget.SvgTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
bind:iconClickListener="@{() -> viewModel.chooseFilePath()}"
bind:svg_text_view_icon="@drawable/ic_choose_file"
bind:text="@{@string/file_path(filePath)}"
/>
<include
layout="@layout/layout_content_single"
bind:fileSize="@{fileSize}"
bind:progress="@{progress}"
bind:speed="@{speed}"
bind:stateStr="@{stateStr}"
/>
</LinearLayout>
</layout>

@ -35,6 +35,7 @@
<item>FTP 下载</item>
<item>FTP 文件夹下载</item>
<item>FTP 文件上传</item>
<item>M3U8 点播文件下载</item>
</string-array>
<string-array name="main_items_desc">
@ -44,6 +45,7 @@
<item>单文件下载、多文件队列下载</item>
<item>下载服务器上的某个FTP文件夹</item>
<item>上传单个文件到服务器</item>
<item>下载M3U8点播文件</item>
</string-array>
<string-array name="download_items">

Loading…
Cancel
Save