重构线程任务模块

v3.6.6
laoyuyu 6 years ago
parent 39a43c836d
commit 6550647a38
  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. 502
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
  8. 357
      Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java
  9. 48
      Aria/src/main/java/com/arialyy/aria/core/common/BaseListener.java
  10. 361
      Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java
  11. 82
      Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java
  12. 29
      Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java
  13. 226
      Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java
  14. 11
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpThreadTask.java
  15. 88
      Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskDelegate.java
  16. 144
      Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java
  17. 199
      Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java
  18. 159
      Aria/src/main/java/com/arialyy/aria/core/download/AbsGroupDelegate.java
  19. 36
      Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java
  20. 5
      Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java
  21. 201
      Aria/src/main/java/com/arialyy/aria/core/download/DNormalDelegate.java
  22. 31
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java
  23. 6
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java
  24. 16
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java
  25. 91
      Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDelegate.java
  26. 247
      Aria/src/main/java/com/arialyy/aria/core/download/HttpGroupDelegate.java
  27. 30
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java
  28. 71
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java
  29. 16
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java
  30. 85
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java
  31. 17
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/LiveProtocol.java
  32. 25
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java
  33. 48
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileInfothread.java
  34. 21
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java
  35. 58
      Aria/src/main/java/com/arialyy/aria/core/inf/IGroupTarget.java
  36. 66
      Aria/src/main/java/com/arialyy/aria/core/inf/INormalTarget.java
  37. 4
      Aria/src/main/java/com/arialyy/aria/core/manager/DTaskWrapperFactory.java
  38. 31
      Aria/src/main/java/com/arialyy/aria/core/upload/BaseUListener.java
  39. 155
      Aria/src/main/java/com/arialyy/aria/core/upload/UNormalDelegate.java
  40. 40
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java
  41. 14
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java
  42. 12
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java
  43. 63
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  44. 1
      DEV_LOG.md
  45. 4
      app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java

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

@ -16,26 +16,17 @@
package com.arialyy.aria.core.common; package com.arialyy.aria.core.common;
import android.content.Context; import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray; import android.util.SparseArray;
import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.download.BaseDListener; 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.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.manager.ThreadTaskManager; 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.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DbDataHelper;
import java.io.File; 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.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -44,52 +35,31 @@ import java.util.concurrent.TimeUnit;
*/ */
public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>> public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>>
implements Runnable { 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"; private final String TAG = "AbsFileer";
protected IEventListener mListener; protected IEventListener mListener;
protected TASK_WRAPPER mTaskWrapper; protected TASK_WRAPPER mTaskWrapper;
protected ENTITY mEntity; protected ENTITY mEntity;
protected Context mContext; protected Context mContext;
protected File mTempFile; //文件 protected File mTempFile; //文件
protected StateConstance mConstance; protected int mTotalThreadNum; //总线程数
//总线程数
protected int mTotalThreadNum;
//已完成的线程数
private int mCompleteThreadNum;
private SparseArray<AbsThreadTask> mTask = new SparseArray<>();
private SparseArray<AbsThreadTask> mTask = new SparseArray<>();
private ScheduledThreadPoolExecutor mTimer; private ScheduledThreadPoolExecutor mTimer;
@Deprecated private File mConfigFile;
/** /**
* 进度刷新间隔 * 进度刷新间隔
*/ */
private long mUpdateInterval = 1000; private long mUpdateInterval = 1000;
protected TaskRecord mRecord; protected TaskRecord mRecord;
private ThreadStateManager mStateManager;
private Handler mStateHandler;
private boolean isCancel = false, isStop = false;
protected AbsFileer(IEventListener listener, TASK_WRAPPER taskEntity) { protected AbsFileer(IEventListener listener, TASK_WRAPPER taskEntity) {
mListener = listener; mListener = listener;
mTaskWrapper = taskEntity; mTaskWrapper = taskEntity;
mEntity = mTaskWrapper.getEntity(); mEntity = mTaskWrapper.getEntity();
mContext = AriaManager.APP; mContext = AriaManager.APP;
mConstance = new StateConstance();
}
protected abstract int getType();
public void setNewTask(boolean newTask) {
mTaskWrapper.setNewTask(newTask);
} }
public String getKey() { public String getKey() {
@ -119,7 +89,6 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
*/ */
private void resetState() { private void resetState() {
closeTimer(); closeTimer();
mCompleteThreadNum = 0;
mTotalThreadNum = 0; mTotalThreadNum = 0;
if (mTask != null && mTask.size() != 0) { if (mTask != null && mTask.size() != 0) {
for (int i = 0; i < mTask.size(); i++) { for (int i = 0; i < mTask.size(); i++) {
@ -140,44 +109,20 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
return; return;
} }
resetState(); resetState();
mConstance.resetState(); mRecord = new RecordHandler(mTaskWrapper).getRecord();
ALog.d(TAG, "path: " + getFilePath()); mTotalThreadNum = mRecord.threadNum;
checkRecord(); Looper.prepare();
mConstance.TASK_RECORD = mRecord; Looper looper = Looper.myLooper();
if (!mTaskWrapper.isSupportBP() || mTaskWrapper.asHttp().isChunked()) { mStateManager = new ThreadStateManager(looper, mRecord, mListener);
mTotalThreadNum = 1; mStateHandler = new Handler(looper, mStateManager);
} 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;
}
onPostPre(); onPostPre();
if (!mTaskWrapper.isSupportBP()) { if (!mTaskWrapper.isSupportBP()) {
handleNoSupportBP(); handleNoSupportBP();
} else { } else {
handleBreakpoint(); handleBreakpoint();
} }
startTimer(); startTimer();
Looper.loop();
} }
@Override public void run() { @Override public void run() {
@ -194,11 +139,6 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
} }
/**
* 设置新任务的最大线程数
*/
protected abstract int setNewTaskThreadNum();
/** /**
* 启动进度获取定时器 * 启动进度获取定时器
*/ */
@ -207,17 +147,17 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
mTimer = new ScheduledThreadPoolExecutor(1); mTimer = new ScheduledThreadPoolExecutor(1);
mTimer.scheduleWithFixedDelay(new Runnable() { mTimer.scheduleWithFixedDelay(new Runnable() {
@Override public void run() { @Override public void run() {
if (mConstance.isComplete() if (mStateManager.isComplete()
|| mConstance.isStop() || mStateManager.isStop()
|| mConstance.isCancel() || mStateManager.isCancel()
|| mConstance.isFail() || mStateManager.isFail()
|| !isRunning()) { || !isRunning()) {
if (mConstance.isComplete() || mConstance.isFail()) { if (mStateManager.isComplete() || mStateManager.isFail()) {
ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey()); ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey());
} }
closeTimer(); closeTimer();
} else if (mConstance.CURRENT_LOCATION >= 0) { } else if (mStateManager.getCurrentProgress() >= 0) {
mListener.onProgress(mConstance.CURRENT_LOCATION); mListener.onProgress(mStateManager.getCurrentProgress());
} }
} }
}, 0, mUpdateInterval, TimeUnit.MILLISECONDS); }, 0, mUpdateInterval, TimeUnit.MILLISECONDS);
@ -250,7 +190,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
* 获取当前任务位置 * 获取当前任务位置
*/ */
public long getCurrentLocation() { public long getCurrentLocation() {
return mConstance.CURRENT_LOCATION; return mStateManager.getCurrentProgress();
} }
public synchronized boolean isRunning() { public synchronized boolean isRunning() {
@ -258,13 +198,12 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
} }
public synchronized void cancel() { public synchronized void cancel() {
ALog.d(TAG, "constance hash: " + mConstance.hashCode()); if (isCancel) {
if (mConstance.isCancel) {
ALog.d(TAG, String.format("任务【%s】正在删除,删除任务失败", mTaskWrapper.getKey())); ALog.d(TAG, String.format("任务【%s】正在删除,删除任务失败", mTaskWrapper.getKey()));
return; return;
} }
closeTimer(); closeTimer();
mConstance.isCancel = true; isCancel = true;
for (int i = 0; i < mTask.size(); i++) { for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i); AbsThreadTask task = mTask.get(i);
if (task != null) { if (task != null) {
@ -275,12 +214,12 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
} }
public synchronized void stop() { public synchronized void stop() {
if (mConstance.isStop) { if (isStop) {
return; return;
} }
closeTimer(); closeTimer();
mConstance.isStop = true; isStop = true;
if (mConstance.isComplete()) return; if (mStateManager.isComplete()) return;
for (int i = 0; i < mTask.size(); i++) { for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i); AbsThreadTask task = mTask.get(i);
if (task != null && !task.isThreadComplete()) { if (task != null && !task.isThreadComplete()) {
@ -305,352 +244,96 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
start(); 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 record 线程记录
* @param startL 该任务起始位置 * @param startNum 启动的线程数
* @param endL 该任务结束位置
* @param fileLength 该任务需要处理的文件长度
*/ */
private AbsThreadTask createSingThreadTask(int i, long startL, long endL, long fileLength, private AbsThreadTask createSingThreadTask(ThreadRecord record, int startNum) {
ThreadRecord record) {
SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>(); SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>();
config.TOTAL_FILE_SIZE = fileLength; config.url = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl();
config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); config.tempFile =
config.TEMP_FILE = mRecord.isBlock ? new File(
mRecord.isBlock ? new File(String.format(SUB_PATH, mTempFile.getPath(), i)) : mTempFile; String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), record.threadId))
config.THREAD_ID = i; : mTempFile;
config.START_LOCATION = startL; config.isBlock = mRecord.isBlock;
config.END_LOCATION = endL; config.isOpenDynamicFile = mRecord.isOpenDynamicFile;
config.SUPPORT_BP = mTaskWrapper.isSupportBP(); config.startThreadNum = startNum;
config.TASK_WRAPPER = mTaskWrapper; config.taskWrapper = mTaskWrapper;
config.THREAD_RECORD = record; config.record = record;
config.stateHandler = mStateHandler;
return selectThreadTask(config); return selectThreadTask(config);
} }
private void handleBreakpoint() { private void handleBreakpoint() {
long fileLength = mEntity.getFileSize(); long fileLength = mEntity.getFileSize();
long blockSize = fileLength / mTotalThreadNum; long blockSize = fileLength / mTotalThreadNum;
Set<Integer> threads = new HashSet<>(); long currentProgress = 0;
//// 如果是新任务,检查下历史记录,如果有遗留的记录,删除并删除分块文件
//if (mTaskWrapper.isNewTask()) {
// CommonUtil.delTaskRecord(getFilePath(), 1, true);
//}
mRecord.fileLength = fileLength; mRecord.fileLength = fileLength;
if (mTaskWrapper.isNewTask() && !handleNewTask()) { if (mTaskWrapper.isNewTask() && !handleNewTask()) {
return; 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) { int startNum = mRecord.threadNum;
tr.blockLen = CommonUtil.getBlockLen(fileLength, i, mTotalThreadNum); 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) {//该线程已经完成 if (tr.isComplete) {//该线程已经完成
if (resumeRecordLocation(i, startL, endL)) return; currentProgress += endL - startL;
ALog.d(TAG, String.format("任务【%s】线程__%s__已完成", mTaskWrapper.getEntity().getFileName(), i));
mStateHandler.obtainMessage(ThreadStateManager.STATE_COMPLETE).sendToTarget();
if (mStateManager.isComplete()) {
mRecord.deleteData();
mListener.onComplete();
return;
}
continue; continue;
} }
//如果有记录,则恢复任务 //如果有记录,则恢复任务
if (tr.startLocation > 0) { long r = tr.startLocation;
long r = tr.startLocation; //记录的位置需要在线程区间中
//记录的位置需要在线程区间中 if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) {
if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) { currentProgress += r - startL;
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) { ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", mEntity.getFileName(), i));
mRecord.threadRecords.add(tr);
} AbsThreadTask task = createSingThreadTask(tr, startNum);
//ALog.d(TAG, String.format("创建任务线程:%s,线程总数:%s", i, mTotalThreadNum));
AbsThreadTask task = createSingThreadTask(i, startL, endL, fileLength, tr);
if (task == null) return; if (task == null) return;
mTask.put(i, task); mTask.put(tr.threadId, task);
threads.add(i);
} }
if (mConstance.CURRENT_LOCATION != 0 if (currentProgress != 0 && currentProgress != mEntity.getCurrentProgress()) {
&& mConstance.CURRENT_LOCATION != mEntity.getCurrentProgress()) { ALog.d(TAG, String.format("进度修正,当前进度:%s", currentProgress));
ALog.d(TAG, String.format("进度修正,当前进度:%s", mConstance.CURRENT_LOCATION)); mEntity.setCurrentProgress(currentProgress);
mEntity.setCurrentProgress(mConstance.CURRENT_LOCATION);
} }
saveRecord(); mStateHandler.obtainMessage(ThreadStateManager.STATE_UPDATE_PROGRESS, currentProgress)
startThreadTask(threads); .sendToTarget();
startThreadTask();
} }
/** /**
* 启动单线程任务 * 启动单线程任务
*/ */
private void startThreadTask(Set<Integer> recordL) { private void startThreadTask() {
if (isBreak()) { if (isBreak()) {
return; return;
} }
if (mConstance.CURRENT_LOCATION > 0) { if (mStateManager.getCurrentProgress() > 0) {
mListener.onResume(mConstance.CURRENT_LOCATION); mListener.onResume(mStateManager.getCurrentProgress());
} else { } else {
mListener.onStart(mConstance.CURRENT_LOCATION); mListener.onStart(mStateManager.getCurrentProgress());
} }
for (int l : recordL) { for (int i = 0; i < mTask.size(); i++) {
if (l == -1) continue; ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), mTask.get(i));
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), mTask.get(l));
} }
} }
@ -676,29 +359,10 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
if (mListener instanceof BaseDListener) { if (mListener instanceof BaseDListener) {
((BaseDListener) mListener).supportBreakpoint(false); ((BaseDListener) mListener).supportBreakpoint(false);
} }
SubThreadConfig<TASK_WRAPPER> config = new SubThreadConfig<>();
config.TOTAL_FILE_SIZE = mEntity.getFileSize(); AbsThreadTask task = createSingThreadTask(mRecord.threadRecords.get(0), 1);
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; if (task == null) return;
mTask.put(0, task); mTask.put(0, task);
saveRecord();
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task); ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task);
mListener.onStart(0); mListener.onStart(0);
} }
@ -714,7 +378,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
* @return {@code true}中断 * @return {@code true}中断
*/ */
public boolean isBreak() { public boolean isBreak() {
if (mConstance.isCancel || mConstance.isStop) { if (isCancel || isStop) {
closeTimer(); closeTimer();
ALog.d(TAG, String.format("任务【%s】已停止或取消了", mEntity.getFileName())); ALog.d(TAG, String.format("任务【%s】已停止或取消了", mEntity.getFileName()));
return true; return true;

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

@ -16,6 +16,8 @@
package com.arialyy.aria.core.common; package com.arialyy.aria.core.common;
import android.os.Handler; 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.AbsEntity;
import com.arialyy.aria.core.inf.AbsTask; import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper; 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.IEventListener;
import com.arialyy.aria.core.inf.TaskSchedulerType; import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.scheduler.ISchedulers; import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException; import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil; 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>, public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>,
TASK extends AbsTask<ENTITY, TASK_WRAPPER>> TASK extends AbsTask<ENTITY, TASK_WRAPPER>>
implements IEventListener { implements IEventListener {
private static final String TAG = "BaseListener"; protected static String TAG;
private static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度 private static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度
protected WeakReference<Handler> outHandler; protected WeakReference<Handler> outHandler;
private long mLastLen; //上一次发送长度 private long mLastLen; //上一次发送长度
@ -53,6 +56,7 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
mUpdateInterval = mTaskWrapper.getConfig().getUpdateInterval(); mUpdateInterval = mTaskWrapper.getConfig().getUpdateInterval();
mLastLen = mEntity.getCurrentProgress(); mLastLen = mEntity.getCurrentProgress();
mLastSaveTime = System.currentTimeMillis(); mLastSaveTime = System.currentTimeMillis();
TAG = CommonUtil.getClassName(getClass());
} }
protected TASK getTask() { protected TASK getTask() {
@ -120,8 +124,10 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
mTask.setNeedRetry(needRetry); mTask.setNeedRetry(needRetry);
mTask.putExpand(AbsTask.ERROR_INFO_KEY, e); mTask.putExpand(AbsTask.ERROR_INFO_KEY, e);
sendInState2Target(ISchedulers.FAIL); sendInState2Target(ISchedulers.FAIL);
e.printStackTrace(); if (e != null) {
ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e)); e.printStackTrace();
ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e));
}
} }
private void handleSpeed(long speed) { private void handleSpeed(long speed) {
@ -137,14 +143,30 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
: mEntity.getCurrentProgress() * 100 / mEntity.getFileSize())); : mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()));
} }
protected void handleComplete() { /**
* 处理任务完成后的情况
*/
private void handleComplete() {
mEntity.setComplete(true); mEntity.setComplete(true);
mEntity.setCompleteTime(System.currentTimeMillis()); mEntity.setCompleteTime(System.currentTimeMillis());
mEntity.setCurrentProgress(mEntity.getFileSize()); mEntity.setCurrentProgress(mEntity.getFileSize());
mEntity.setPercent(100); mEntity.setPercent(100);
handleSpeed(0); 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();
}
} }

@ -0,0 +1,361 @@
/*
* 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.DownloadEntity;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity;
import 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 {
public static final int TYPE_DOWNLOAD = 1;
public static final int TYPE_UPLOAD = 2;
private final String TAG = "RecordHandler";
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);
} else if (mRecord.isBlock) {
handleBlockRecord();
} else if (!mTaskWrapper.isSupportBP()) {
handleNoSupportBPRecord();
} else {
handleSingleThreadRecord();
}
}
}
saveRecord();
return mRecord;
}
/**
* 处理不支持断点的记录
*/
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);
// 处理线程区间记录
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;
//最后一个线程的结束位置即为文件的总长度
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;
}
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.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; package com.arialyy.aria.core.common;
import android.os.Handler;
import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.inf.AbsTaskWrapper;
import java.io.File; import java.io.File;
@ -22,19 +23,17 @@ import java.io.File;
* 子线程下载信息类 * 子线程下载信息类
*/ */
public class SubThreadConfig<TASK_WRAPPER extends AbsTaskWrapper> { public class SubThreadConfig<TASK_WRAPPER extends AbsTaskWrapper> {
//线程Id
public int THREAD_ID; public TASK_WRAPPER taskWrapper;
//文件总长度 public boolean isBlock = false;
public long TOTAL_FILE_SIZE; // 启动的线程
//子线程启动下载位置 public int startThreadNum;
public long START_LOCATION; public String url;
//子线程结束下载位置 public File tempFile;
public long END_LOCATION; // 线程记录
//下载文件或上传的文件路径 public ThreadRecord record;
public File TEMP_FILE; // 状态处理器
//服务器地址 public Handler stateHandler;
public String URL; // 动态文件
public TASK_WRAPPER TASK_WRAPPER; public boolean isOpenDynamicFile;
public boolean SUPPORT_BP = true;
public ThreadRecord THREAD_RECORD;
} }

@ -0,0 +1,226 @@
/*
* 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 Handler.Callback {
private final String TAG = "ThreadTaskStateManager";
public static final int STATE_STOP = 0x01;
public static final int STATE_FAIL = 0x02;
public static final int STATE_CANCEL = 0x03;
public static final int STATE_COMPLETE = 0x04;
public static final int STATE_RUNNING = 0x05;
public static final int STATE_UPDATE_PROGRESS = 0x06;
public static final String KEY_RETRY = "KEY_RETRY";
public static final String KEY_ERROR_INFO = "KEY_ERROR_INFO";
/**
* 任务状态回调
*/
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 当前任务下载进度
*/
public long getCurrentProgress() {
return mProgress;
}
/**
* 所有子线程是否都已经停止
*/
public boolean isStop() {
//ALog.d(TAG,
// String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mStopNum == mThreadNum || mStopNum + mCompleteNum == mThreadNum;
}
/**
* 所有子线程是否都已经失败
*/
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);
}
/**
* 所有子线程是否都已经完成
*/
public boolean isComplete() {
//ALog.d(TAG,
// String.format("isComplete; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCompleteNum == mThreadNum;
}
/**
* 所有子线程是否都已经取消
*/
public boolean isCancel() {
//ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s",
// mStopNum,
// mCancelNum, mFailNum, mCompleteNum));
return mCancelNum == mThreadNum;
}
/**
* 更新分块任务s的真实进度
*/
private long updateBlockProgress() {
long size = 0;
for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) {
File temp = new File(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i));
if (temp.exists()) {
size += temp.length();
}
}
return size;
}
/**
* 合并文件
*
* @return {@code true} 合并成功{@code false}合并失败
*/
private boolean mergeFile() {
List<String> partPath = new ArrayList<>();
for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) {
partPath.add(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i));
}
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
if (isSuccess) {
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s",
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength));
return false;
}
return true;
} else {
ALog.e(TAG, "合并失败");
return false;
}
}
}

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

@ -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));
}
}
}
}

@ -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 android.os.Handler;
import com.arialyy.aria.core.common.BaseListener; 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.IDownloadListener;
import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType; 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> public class BaseDListener extends BaseListener<DownloadEntity, DTaskWrapper, DownloadTask>
implements IDownloadListener { implements IDownloadListener {
private static final String TAG = "BaseDListener";
BaseDListener(DownloadTask task, Handler outHandler) { BaseDListener(DownloadTask task, Handler outHandler) {
super(task, outHandler); super(task, outHandler);
@ -50,30 +50,16 @@ public class BaseDListener extends BaseListener<DownloadEntity, DTaskWrapper, Do
} }
} }
@Override @Override protected void handleCancel() {
protected void saveData(int state, long location) { int sType = getTask().getSchedulerType();
mTaskWrapper.setState(state); if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setState(state); mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
if (state == IEntity.STATE_CANCEL) { CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
int sType = getTask().getSchedulerType(); mTaskWrapper.isRemoveFile(), false);
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { } else {
mEntity.setComplete(false); CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
mEntity.setState(IEntity.STATE_WAIT); mTaskWrapper.isRemoveFile(), true);
CommonUtil.delTaskRecord(mEntity.getFilePath(), 1, 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);
} }
mEntity.update();
} }
} }

@ -16,6 +16,7 @@
package com.arialyy.aria.core.download; package com.arialyy.aria.core.download;
import android.text.TextUtils; import android.text.TextUtils;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.inf.ITargetHandler; import com.arialyy.aria.core.inf.ITargetHandler;
import com.arialyy.aria.core.inf.IConfigHandler; import com.arialyy.aria.core.inf.IConfigHandler;
import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.manager.TaskWrapperManager;
@ -136,7 +137,7 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
return false; return false;
} else { } else {
ALog.w(TAG, "保存路径【" + filePath + "】已经被其它任务占用,当前任务将覆盖该路径的文件"); ALog.w(TAG, "保存路径【" + filePath + "】已经被其它任务占用,当前任务将覆盖该路径的文件");
CommonUtil.delTaskRecord(filePath, 1); CommonUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD);
mTarget.setTaskWrapper( mTarget.setTaskWrapper(
TaskWrapperManager.getInstance() TaskWrapperManager.getInstance()
.getHttpTaskWrapper(DTaskWrapper.class, mUrl)); .getHttpTaskWrapper(DTaskWrapper.class, mUrl));
@ -144,7 +145,7 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
} }
File oldFile = new File(mEntity.getDownloadPath()); File oldFile = new File(mEntity.getDownloadPath());
File newFile = new File(filePath); File newFile = new File(filePath);
mEntity.setDownloadPath(filePath); mEntity.setFilePath(filePath);
mEntity.setFileName(newFile.getName()); mEntity.setFileName(newFile.getName());
// 如过使用Content-Disposition中的文件名,将不会执行重命名工作 // 如过使用Content-Disposition中的文件名,将不会执行重命名工作
if (mTarget.getTaskWrapper().asHttp().isUseServerFileName()) { 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;
}
}

@ -34,7 +34,6 @@ import com.arialyy.aria.util.ErrorHelp;
class DownloadGroupListener class DownloadGroupListener
extends BaseListener<DownloadGroupEntity, DGTaskWrapper, DownloadGroupTask> extends BaseListener<DownloadGroupEntity, DGTaskWrapper, DownloadGroupTask>
implements IDownloadGroupListener { implements IDownloadGroupListener {
private final String TAG = "DownloadGroupListener";
private GroupSendParams<DownloadGroupTask, DownloadEntity> mSeedEntity; private GroupSendParams<DownloadGroupTask, DownloadEntity> mSeedEntity;
DownloadGroupListener(DownloadGroupTask task, Handler outHandler) { DownloadGroupListener(DownloadGroupTask task, Handler outHandler) {
@ -130,28 +129,14 @@ class DownloadGroupListener
} }
@Override @Override protected void handleCancel() {
protected void saveData(int state, long location) { int sType = getTask().getSchedulerType();
mTaskWrapper.setState(state); if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setState(state); mEntity.setComplete(false);
if (state == IEntity.STATE_CANCEL) { mEntity.setState(IEntity.STATE_WAIT);
int sType = getTask().getSchedulerType(); CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), false);
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { } else {
mEntity.setComplete(false); CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), true);
mEntity.setState(IEntity.STATE_WAIT);
CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), false);
} 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.GetDelegate;
import com.arialyy.aria.core.common.http.HttpDelegate; import com.arialyy.aria.core.common.http.HttpDelegate;
import com.arialyy.aria.core.common.http.PostDelegate; import com.arialyy.aria.core.common.http.PostDelegate;
import com.arialyy.aria.core.download.m3u8.M3U8Delegate;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
import java.net.Proxy; import java.net.Proxy;
import java.util.Map; import java.util.Map;
@ -59,6 +60,11 @@ public class DownloadTarget extends AbsDTarget<DownloadTarget> {
return (GetDelegate) mHttpDelegate; return (GetDelegate) mHttpDelegate;
} }
@CheckResult
public M3U8Delegate<DownloadTarget> asM3U8() {
return new M3U8Delegate<>(this);
}
/** /**
* 是否使用服务器通过content-disposition传递的文件名内容格式{@code attachment;filename=***} * 是否使用服务器通过content-disposition传递的文件名内容格式{@code attachment;filename=***}
* 如果获取不到服务器文件名则使用用户设置的文件名 * 如果获取不到服务器文件名则使用用户设置的文件名

@ -44,14 +44,18 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity, DTaskWrapper> {
/** /**
* 获取文件保存路径 * 获取文件保存路径
* *
* @return 如果路径不存在返回null * @deprecated 后续版本将删除该方法请使用{@link #getFilePath()}
*/ */
@Deprecated
public String getDownloadPath() { public String getDownloadPath() {
File file = new File(mEntity.getDownloadPath()); return getFilePath();
if (!file.exists()) { }
return null;
} /**
return mEntity.getDownloadPath(); * 获取文件保存路径
*/
public String getFilePath() {
return mEntity.getFilePath();
} }
public DownloadEntity getEntity() { public DownloadEntity getEntity() {

@ -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;
}
}

@ -17,6 +17,7 @@ package com.arialyy.aria.core.download.downloader;
import com.arialyy.aria.core.common.AbsFileer; import com.arialyy.aria.core.common.AbsFileer;
import com.arialyy.aria.core.common.AbsThreadTask; import com.arialyy.aria.core.common.AbsThreadTask;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.common.SubThreadConfig;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
@ -26,7 +27,6 @@ import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.BufferedRandomAccessFile;
import com.arialyy.aria.util.CommonUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -38,22 +38,10 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
public Downloader(IDownloadListener listener, DTaskWrapper taskWrapper) { public Downloader(IDownloadListener listener, DTaskWrapper taskWrapper) {
super(listener, taskWrapper); super(listener, taskWrapper);
mTempFile = new File(mEntity.getDownloadPath()); mTempFile = new File(mEntity.getFilePath());
setUpdateInterval(taskWrapper.getConfig().getUpdateInterval()); 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() { @Override protected boolean handleNewTask() {
if (!mRecord.isBlock) { if (!mRecord.isBlock) {
if (mTempFile.exists()) { if (mTempFile.exists()) {
@ -62,7 +50,7 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
//CommonUtil.createFile(mTempFile.getPath()); //CommonUtil.createFile(mTempFile.getPath());
} else { } else {
for (int i = 0; i < mTotalThreadNum; i++) { 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()) { if (blockFile.exists()) {
ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i)); ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i));
blockFile.delete(); blockFile.delete();
@ -97,9 +85,9 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
* 如果使用"Content-Disposition"中的文件名需要更新{@link #mTempFile}的路径 * 如果使用"Content-Disposition"中的文件名需要更新{@link #mTempFile}的路径
*/ */
void updateTempFile() { void updateTempFile() {
if (!mTempFile.getPath().equals(mEntity.getDownloadPath())) { if (!mTempFile.getPath().equals(mEntity.getFilePath())) {
if (!mTempFile.exists()) { if (!mTempFile.exists()) {
mTempFile = new File(mEntity.getDownloadPath()); mTempFile = new File(mEntity.getFilePath());
} else { } else {
boolean b = mTempFile.renameTo(new File(mEntity.getDownloadPath())); boolean b = mTempFile.renameTo(new File(mEntity.getDownloadPath()));
ALog.d(TAG, String.format("更新tempFile文件名%s", b ? "成功" : "失败")); 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() { @Override protected void onPostPre() {
super.onPostPre(); super.onPostPre();
((IDownloadListener) mListener).onPostPre(mEntity.getFileSize()); ((IDownloadListener) mListener).onPostPre(mEntity.getFileSize());
@ -124,9 +108,9 @@ public class Downloader extends AbsFileer<DownloadEntity, DTaskWrapper> {
switch (mTaskWrapper.getRequestType()) { switch (mTaskWrapper.getRequestType()) {
case AbsTaskWrapper.D_FTP: case AbsTaskWrapper.D_FTP:
case AbsTaskWrapper.D_FTP_DIR: case AbsTaskWrapper.D_FTP_DIR:
return new FtpThreadTask(mConstance, (IDownloadListener) mListener, config); return new FtpThreadTask(config);
case AbsTaskWrapper.D_HTTP: case AbsTaskWrapper.D_HTTP:
return new HttpThreadTask(mConstance, (IDownloadListener) mListener, config); return new HttpThreadTask(config);
} }
return null; 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.FTPClient;
import aria.apache.commons.net.ftp.FTPReply; 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.SubThreadConfig;
import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask; import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask;
import com.arialyy.aria.core.config.BaseTaskConfig;
import com.arialyy.aria.core.config.DownloadConfig; import com.arialyy.aria.core.config.DownloadConfig;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.inf.IDownloadListener;
import com.arialyy.aria.exception.AriaIOException; import com.arialyy.aria.exception.AriaIOException;
import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
@ -43,37 +40,31 @@ import java.nio.channels.ReadableByteChannel;
*/ */
class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> { class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
private final String TAG = "FtpThreadTask"; private final String TAG = "FtpThreadTask";
private boolean isOpenDynamicFile;
private boolean isBlock;
FtpThreadTask(StateConstance constance, IDownloadListener listener, FtpThreadTask(SubThreadConfig<DTaskWrapper> config) {
SubThreadConfig<DTaskWrapper> downloadInfo) { super(config);
super(constance, listener, downloadInfo);
isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile;
isBlock = getState().TASK_RECORD.isBlock;
} }
@Override public FtpThreadTask call() throws Exception { @Override public FtpThreadTask call() throws Exception {
super.call(); super.call();
if (getConfig().THREAD_RECORD.isComplete) { if (mRecord.isComplete) {
handleComplete(); handleComplete();
return this; return this;
} }
mChildCurrentLocation = getConfig().START_LOCATION;
FTPClient client = null; FTPClient client = null;
InputStream is = null; InputStream is = null;
try { try {
ALog.d(TAG, ALog.d(TAG,
String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(),
getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); mRecord.threadId, mRecord.startLocation, mRecord.endLocation));
client = createClient(); client = createClient();
if (client == null) { if (client == null) {
fail(mChildCurrentLocation, new TaskException(TAG, "ftp client 创建失败")); fail(mChildCurrentLocation, new TaskException(TAG, "ftp client 创建失败"));
return this; return this;
} }
if (getConfig().START_LOCATION > 0) { if (mRecord.startLocation > 0) {
client.setRestartOffset(getConfig().START_LOCATION); client.setRestartOffset(mRecord.startLocation);
} }
//发送第二次指令时,还需要再做一次判断 //发送第二次指令时,还需要再做一次判断
int reply = client.getReplyCode(); int reply = client.getReplyCode();
@ -97,7 +88,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
return this; return this;
} }
if (isOpenDynamicFile) { if (getConfig().isOpenDynamicFile) {
readDynamicFile(is); readDynamicFile(is);
} else { } else {
readNormal(is); readNormal(is);
@ -105,10 +96,10 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
} }
} catch (IOException e) { } catch (IOException e) {
fail(mChildCurrentLocation, fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} catch (Exception e) { } catch (Exception e) {
fail(mChildCurrentLocation, fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} finally { } finally {
try { try {
if (is != null) { if (is != null) {
@ -134,27 +125,9 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
if (!checkBlock()) { if (!checkBlock()) {
return; return;
} }
ALog.i(TAG, ALog.i(TAG, String.format("任务【%s】线程__%s__下载完毕", getFileName(), mRecord.threadId));
String.format("任务【%s】线程__%s__下载完毕", getConfig().TEMP_FILE.getName(), writeConfig(true, mRecord.endLocation);
getConfig().THREAD_ID)); sendCompleteMsg();
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;
}
}
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; ReadableByteChannel fic = null;
try { try {
int len; int len;
fos = new FileOutputStream(getConfig().TEMP_FILE, true); fos = new FileOutputStream(getConfig().tempFile, true);
foc = fos.getChannel(); foc = fos.getChannel();
fic = Channels.newChannel(is); fic = Channels.newChannel(is);
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize());
@ -177,8 +150,8 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
if (mSpeedBandUtil != null) { if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len); mSpeedBandUtil.limitNextBytes(len);
} }
if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { if (mChildCurrentLocation + len >= mRecord.endLocation) {
len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); len = (int) (mRecord.endLocation - mChildCurrentLocation);
bf.flip(); bf.flip();
fos.write(bf.array(), 0, len); fos.write(bf.array(), 0, len);
bf.compact(); bf.compact();
@ -194,7 +167,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
handleComplete(); handleComplete();
} catch (IOException e) { } catch (IOException e) {
fail(mChildCurrentLocation, fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} finally { } finally {
try { try {
if (fos != null) { if (fos != null) {
@ -219,8 +192,8 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
BufferedRandomAccessFile file = null; BufferedRandomAccessFile file = null;
try { try {
file = file =
new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize());
file.seek(getConfig().START_LOCATION); file.seek(mRecord.startLocation);
byte[] buffer = new byte[getTaskConfig().getBuffSize()]; byte[] buffer = new byte[getTaskConfig().getBuffSize()];
int len; int len;
while (isLive() && (len = is.read(buffer)) != -1) { while (isLive() && (len = is.read(buffer)) != -1) {
@ -230,8 +203,8 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
if (mSpeedBandUtil != null) { if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len); mSpeedBandUtil.limitNextBytes(len);
} }
if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { if (mChildCurrentLocation + len >= mRecord.endLocation) {
len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); len = (int) (mRecord.endLocation - mChildCurrentLocation);
file.write(buffer, 0, len); file.write(buffer, 0, len);
progress(len); progress(len);
break; break;
@ -242,7 +215,7 @@ class FtpThreadTask extends AbsFtpThreadTask<DownloadEntity, DTaskWrapper> {
} }
} catch (IOException e) { } catch (IOException e) {
fail(mChildCurrentLocation, fail(mChildCurrentLocation,
new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e));
} finally { } finally {
try { try {
if (file != null) { if (file != null) {

@ -141,9 +141,14 @@ public class HttpFileInfoThread implements Runnable {
} }
Map<String, List<String>> headers = conn.getHeaderFields(); Map<String, List<String>> headers = conn.getHeaderFields();
String disposition = conn.getHeaderField("Content-Disposition"); String disposition = conn.getHeaderField("Content-Disposition");
if (mTaskDelegate.isUseServerFileName() && !TextUtils.isEmpty(disposition)) {
mEntity.setDisposition(CommonUtil.encryptBASE64(disposition)); if (mTaskDelegate.isUseServerFileName()) {
handleContentDisposition(disposition); if (!TextUtils.isEmpty(disposition)) {
mEntity.setDisposition(CommonUtil.encryptBASE64(disposition));
handleContentDisposition(disposition);
} else {
ALog.w(TAG, "Content-Disposition对于端字段为空,使用服务器端文件名失败");
}
} }
CookieManager msCookieManager = new CookieManager(); CookieManager msCookieManager = new CookieManager();
List<String> cookiesHeader = headers.get("Set-Cookie"); List<String> cookiesHeader = headers.get("Set-Cookie");
@ -264,14 +269,15 @@ public class HttpFileInfoThread implements Runnable {
return; return;
} }
ALog.d(TAG, String.format("文件重命名为:%s", newName)); ALog.d(TAG, String.format("文件重命名为:%s", newName));
File oldFile = new File(mEntity.getDownloadPath()); File oldFile = new File(mEntity.getFilePath());
String newPath = oldFile.getParent() + "/" + newName; String newPath = oldFile.getParent() + "/" + newName;
if (oldFile.exists()) { if (oldFile.exists()) {
boolean b = oldFile.renameTo(new File(newPath)); boolean b = oldFile.renameTo(new File(newPath));
ALog.d(TAG, String.format("文件重命名%s", b ? "成功" : "失败")); ALog.d(TAG, String.format("文件重命名%s", b ? "成功" : "失败"));
} }
mEntity.setFileName(newName); 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.AbsThreadTask;
import com.arialyy.aria.core.common.RequestEnum; 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.common.SubThreadConfig;
import com.arialyy.aria.core.config.DownloadConfig; import com.arialyy.aria.core.config.DownloadConfig;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
import com.arialyy.aria.core.common.http.HttpTaskConfig; 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.AriaIOException;
import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.exception.TaskException;
import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.ALog;
@ -49,37 +47,30 @@ import java.util.Set;
*/ */
final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> { final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
private final String TAG = "HttpThreadTask"; private final String TAG = "HttpThreadTask";
private boolean isOpenDynamicFile;
private boolean isBlock;
HttpThreadTask(StateConstance constance, IDownloadListener listener, HttpThreadTask(SubThreadConfig<DTaskWrapper> config) {
SubThreadConfig<DTaskWrapper> downloadInfo) { super(config);
super(constance, listener, downloadInfo);
isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile;
isBlock = getState().TASK_RECORD.isBlock;
} }
@Override public HttpThreadTask call() throws Exception { @Override public HttpThreadTask call() throws Exception {
super.call(); super.call();
if (getThreadRecord().isComplete) { if (mRecord.isComplete) {
handleComplete(); handleComplete();
return this; return this;
} }
HttpURLConnection conn = null; HttpURLConnection conn = null;
BufferedInputStream is = null; BufferedInputStream is = null;
BufferedRandomAccessFile file = null; BufferedRandomAccessFile file = null;
//当前子线程的下载位置
mChildCurrentLocation = getConfig().START_LOCATION;
try { try {
HttpTaskConfig taskDelegate = getTaskWrapper().asHttp(); HttpTaskConfig taskDelegate = getTaskWrapper().asHttp();
URL url = ConnectionHelp.handleUrl(getConfig().URL, taskDelegate); URL url = ConnectionHelp.handleUrl(getConfig().url, taskDelegate);
conn = ConnectionHelp.handleConnection(url, taskDelegate); conn = ConnectionHelp.handleConnection(url, taskDelegate);
if (getConfig().SUPPORT_BP) { if (mTaskWrapper.isSupportBP()) {
ALog.d(TAG, ALog.d(TAG,
String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(),
getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); mRecord.threadId, mRecord.startLocation, mRecord.endLocation));
conn.setRequestProperty("Range", String.format("bytes=%s-%s", getConfig().START_LOCATION, conn.setRequestProperty("Range", String.format("bytes=%s-%s", mRecord.startLocation,
(getConfig().END_LOCATION - 1))); (mRecord.endLocation - 1)));
} else { } else {
ALog.w(TAG, "该下载不支持断点"); ALog.w(TAG, "该下载不支持断点");
} }
@ -112,29 +103,29 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn));
if (taskDelegate.isChunked()) { if (taskDelegate.isChunked()) {
readChunked(is); readChunked(is);
} else if (isOpenDynamicFile) { } else if (getConfig().isOpenDynamicFile) {
readDynamicFile(is); readDynamicFile(is);
} else { } else {
//创建可设置位置的文件 //创建可设置位置的文件
file = file =
new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", new BufferedRandomAccessFile(getConfig().tempFile, "rwd",
getTaskConfig().getBuffSize()); getTaskConfig().getBuffSize());
//设置每条线程写入文件的位置 //设置每条线程写入文件的位置
file.seek(getConfig().START_LOCATION); file.seek(mRecord.startLocation);
readNormal(is, file); readNormal(is, file);
handleComplete(); handleComplete();
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
fail(mChildCurrentLocation, new TaskException(TAG, 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)); getEntity().getDownloadPath(), getEntity().getUrl()), e));
} catch (IOException e) { } catch (IOException e) {
fail(mChildCurrentLocation, new TaskException(TAG, 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)); getEntity().getDownloadPath(), getEntity().getUrl()), e));
} catch (Exception e) { } catch (Exception e) {
fail(mChildCurrentLocation, new TaskException(TAG, 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)); getEntity().getDownloadPath(), getEntity().getUrl()), e));
} finally { } finally {
try { try {
@ -160,7 +151,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
private void readChunked(InputStream is) { private void readChunked(InputStream is) {
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
fos = new FileOutputStream(getConfig().TEMP_FILE, true); fos = new FileOutputStream(getConfig().tempFile, true);
byte[] buffer = new byte[getTaskConfig().getBuffSize()]; byte[] buffer = new byte[getTaskConfig().getBuffSize()];
int len; int len;
while (isLive() && (len = is.read(buffer)) != -1) { while (isLive() && (len = is.read(buffer)) != -1) {
@ -177,7 +168,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
} catch (IOException e) { } catch (IOException e) {
fail(mChildCurrentLocation, new AriaIOException(TAG, fail(mChildCurrentLocation, new AriaIOException(TAG,
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(),
getConfig().URL), getConfig().url),
e)); e));
} finally { } finally {
if (fos != null) { if (fos != null) {
@ -199,7 +190,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
ReadableByteChannel fic = null; ReadableByteChannel fic = null;
try { try {
int len; int len;
fos = new FileOutputStream(getConfig().TEMP_FILE, true); fos = new FileOutputStream(getConfig().tempFile, true);
foc = fos.getChannel(); foc = fos.getChannel();
fic = Channels.newChannel(is); fic = Channels.newChannel(is);
ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize());
@ -212,8 +203,8 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
if (mSpeedBandUtil != null) { if (mSpeedBandUtil != null) {
mSpeedBandUtil.limitNextBytes(len); mSpeedBandUtil.limitNextBytes(len);
} }
if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { if (mChildCurrentLocation + len >= mRecord.endLocation) {
len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); len = (int) (mRecord.endLocation - mChildCurrentLocation);
bf.flip(); bf.flip();
fos.write(bf.array(), 0, len); fos.write(bf.array(), 0, len);
bf.compact(); bf.compact();
@ -230,7 +221,7 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
} catch (IOException e) { } catch (IOException e) {
fail(mChildCurrentLocation, new AriaIOException(TAG, fail(mChildCurrentLocation, new AriaIOException(TAG,
String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(),
getConfig().URL), getConfig().url),
e)); e));
} finally { } finally {
try { try {
@ -284,36 +275,12 @@ final class HttpThreadTask extends AbsThreadTask<DownloadEntity, DTaskWrapper> {
return; return;
} }
if (mChildCurrentLocation == getConfig().END_LOCATION) { //支持断点的处理
//支持断点的处理 if (mTaskWrapper.isSupportBP()) {
if (getConfig().SUPPORT_BP) { writeConfig(true, mRecord.endLocation);
ALog.i(TAG, sendCompleteMsg();
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;
}
}
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 { } else {
getState().FAIL_NUM++; sendCompleteMsg();
} }
} }

@ -13,12 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.arialyy.aria.core.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;
} }

@ -29,7 +29,26 @@ public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET>
mTarget.getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_FILE); mTarget.getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_FILE);
} }
//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,48 @@
/*
* 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.AriaManager;
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;
/**
* 解析url中获取到到m3u信息
* https://www.cnblogs.com/renhui/p/10351870.html
*/
public class M3U8FileInfothread implements Runnable {
private final String TAG = "HttpFileInfoThread";
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() {
}
}

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

@ -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; 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.common.TaskRecord;
import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DTaskWrapper;
import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DownloadEntity;
@ -83,7 +83,7 @@ class DTaskWrapperFactory implements INormalTEFactory<DownloadEntity, DTaskWrapp
if (record.isBlock) { if (record.isBlock) {
int count = 0; int count = 0;
for (int i = 0, len = record.threadNum; i < len; i++) { 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()) { if (!temp.exists()) {
count++; count++;
} }

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

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

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

@ -42,20 +42,12 @@ class Uploader extends AbsFileer<UploadEntity, UTaskWrapper> {
return true; return true;
} }
@Override protected int getType() {
return UPLOAD;
}
@Override protected int setNewTaskThreadNum() {
return 1;
}
@Override protected AbsThreadTask selectThreadTask(SubThreadConfig<UTaskWrapper> config) { @Override protected AbsThreadTask selectThreadTask(SubThreadConfig<UTaskWrapper> config) {
switch (mTaskWrapper.getRequestType()) { switch (mTaskWrapper.getRequestType()) {
case AbsTaskWrapper.U_FTP: case AbsTaskWrapper.U_FTP:
return new FtpThreadTask(mConstance, mListener, config); return new FtpThreadTask(config);
case AbsTaskWrapper.U_HTTP: case AbsTaskWrapper.U_HTTP:
return new HttpThreadTask(mConstance, (IUploadListener) mListener, config); return new HttpThreadTask(config);
} }
return null; return null;
} }

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

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

@ -47,6 +47,7 @@ import com.arialyy.simple.common.ModifyUrlDialog;
import com.arialyy.simple.databinding.ActivitySingleBinding; import com.arialyy.simple.databinding.ActivitySingleBinding;
import com.arialyy.simple.util.AppUtil; import com.arialyy.simple.util.AppUtil;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -201,6 +202,7 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
@Download.onTaskRunning @Download.onTaskRunning
protected void running(DownloadTask task) { protected void running(DownloadTask task) {
if (task.getKey().equals(mUrl)) { if (task.getKey().equals(mUrl)) {
ALog.d(TAG, "isRunning");
//Log.d(TAG, task.getKey()); //Log.d(TAG, task.getKey());
long len = task.getFileSize(); long len = task.getFileSize();
if (len == 0) { if (len == 0) {
@ -248,13 +250,13 @@ public class SingleTaskActivity extends BaseActivity<ActivitySingleBinding> {
@Download.onTaskComplete @Download.onTaskComplete
void taskComplete(DownloadTask task) { void taskComplete(DownloadTask task) {
if (task.getKey().equals(mUrl)) { if (task.getKey().equals(mUrl)) {
getBinding().setProgress(100); getBinding().setProgress(100);
Toast.makeText(SingleTaskActivity.this, getString(R.string.download_success), Toast.makeText(SingleTaskActivity.this, getString(R.string.download_success),
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
getBinding().setStateStr(getString(R.string.re_start)); getBinding().setStateStr(getString(R.string.re_start));
getBinding().setSpeed(""); getBinding().setSpeed("");
ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getFilePath())));
} }
} }

Loading…
Cancel
Save