From d3fb30d8b9620fa89392f0f3d9213d579b0c0088 Mon Sep 17 00:00:00 2001 From: laoyuyu <511455842@qq.com> Date: Fri, 31 May 2019 22:33:12 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=BA=BF=E7=A8=8B=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/arialyy/frame/cache/CacheHelp.java | 81 -- .../com/arialyy/frame/cache/CacheObj.java | 56 -- .../frame/cache/diskcache/DiskLruCache.java | 951 ------------------ .../cache/diskcache/DiskLruCacheUtil.java | 77 -- .../cache/diskcache/StrictLineReader.java | 194 ---- .../com/arialyy/aria/core/AriaManager.java | 7 +- .../arialyy/aria/core/common/AbsFileer.java | 551 +--------- .../aria/core/common/AbsThreadTask.java | 366 +++---- .../aria/core/common/BaseListener.java | 48 +- .../aria/core/common/CompleteInfo.java | 6 + .../aria/core/common/IThreadState.java | 67 ++ .../aria/core/common/NormalFileer.java | 190 ++++ .../aria/core/common/OnFileInfoCallback.java | 2 +- .../aria/core/common/RecordHandler.java | 419 ++++++++ .../aria/core/common/StateConstance.java | 82 -- .../aria/core/common/SubThreadConfig.java | 29 +- .../arialyy/aria/core/common/TaskRecord.java | 8 + .../aria/core/common/ThreadRecord.java | 11 + .../aria/core/common/ThreadStateManager.java | 223 ++++ .../core/common/ftp/AbsFtpInfoThread.java | 4 - .../core/common/ftp/AbsFtpThreadTask.java | 11 +- .../aria/core/common/ftp/FtpTaskDelegate.java | 88 -- .../core/common/http/HttpHeaderDelegate.java | 144 --- .../aria/core/common/http/HttpTaskConfig.java | 3 + .../core/common/http/HttpTaskDelegate.java | 199 ---- .../aria/core/download/AbsGroupDelegate.java | 159 --- .../aria/core/download/BaseDListener.java | 36 +- .../core/download/DNormalConfigHandler.java | 19 +- .../aria/core/download/DNormalDelegate.java | 201 ---- .../aria/core/download/DTaskWrapper.java | 10 + .../core/download/DownloadGroupListener.java | 57 +- .../aria/core/download/DownloadTarget.java | 6 + .../aria/core/download/DownloadTask.java | 25 +- .../aria/core/download/FtpDirDelegate.java | 91 -- .../aria/core/download/HttpGroupDelegate.java | 247 ----- .../download/downloader/ConnectionHelp.java | 11 +- .../core/download/downloader/Downloader.java | 34 +- .../download/downloader/FtpThreadTask.java | 71 +- .../downloader/HttpFileInfoThread.java | 21 +- .../download/downloader/HttpThreadTask.java | 85 +- .../downloader/SimpleDownloadUtil.java | 31 +- .../download/group/DownloadGroupUtil.java | 7 +- .../download/group/FtpDirDownloadUtil.java | 6 +- .../download/m3u8/IM3U8UrlExtInfHandler.java | 32 + .../m3u8/LiveProtocol.java} | 55 +- .../aria/core/download/m3u8/M3U8Delegate.java | 62 +- .../download/m3u8/M3U8FileInfoThread.java | 158 +++ .../core/download/m3u8/M3U8FileLoader.java | 325 ++++++ .../aria/core/download/m3u8/M3U8FileUtil.java | 136 +++ .../core/download/m3u8/M3U8LiveDelegate.java | 22 +- .../core/download/m3u8/M3U8TaskConfig.java | 63 ++ .../download/m3u8/M3U8ThreadStateManager.java | 140 +++ .../core/download/m3u8/M3U8ThreadTask.java | 247 +++++ .../arialyy/aria/core/inf/IGroupTarget.java | 58 -- .../arialyy/aria/core/inf/INormalTarget.java | 66 -- .../core/manager/DTaskWrapperFactory.java | 4 +- .../aria/core/upload/BaseUListener.java | 31 +- .../aria/core/upload/UNormalDelegate.java | 155 --- .../upload/uploader/FtpFileInfoThread.java | 5 +- .../core/upload/uploader/FtpThreadTask.java | 40 +- .../core/upload/uploader/HttpThreadTask.java | 14 +- .../upload/uploader/SimpleUploadUtil.java | 26 +- .../aria/core/upload/uploader/Uploader.java | 16 +- .../arialyy/aria/exception/M3U8Exception.java | 28 + .../java/com/arialyy/aria/orm/DBConfig.java | 2 +- .../com/arialyy/aria/util/CommonUtil.java | 63 +- .../com/arialyy/aria/util/IdGenerator.java | 79 ++ DEV_LOG.md | 1 + app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 3 +- .../java/com/arialyy/simple/MainActivity.java | 47 +- .../core/download/DownloadActivity.java | 75 +- .../core/download/SingleTaskActivity.java | 4 +- .../download/m3u8/M3U8DownloadActivity.java | 268 +++++ .../simple/core/download/m3u8/M3U8Module.java | 95 ++ .../arialyy/simple/modlue/CommonModule.java | 52 +- app/src/main/res/drawable/ic_ts.xml | 33 + app/src/main/res/layout/activity_m3u8.xml | 80 ++ app/src/main/res/values/strings.xml | 2 + 79 files changed, 3337 insertions(+), 4088 deletions(-) delete mode 100644 AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java delete mode 100644 AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java delete mode 100644 AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java delete mode 100644 AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java delete mode 100644 AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskDelegate.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/AbsGroupDelegate.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/DNormalDelegate.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDelegate.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/HttpGroupDelegate.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IM3U8UrlExtInfHandler.java rename Aria/src/main/java/com/arialyy/aria/core/{inf/ITargetHeadDelegate.java => download/m3u8/LiveProtocol.java} (67%) create mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileInfoThread.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileLoader.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileUtil.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadStateManager.java create mode 100644 Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/inf/IGroupTarget.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/inf/INormalTarget.java delete mode 100644 Aria/src/main/java/com/arialyy/aria/core/upload/UNormalDelegate.java create mode 100644 Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java create mode 100644 Aria/src/main/java/com/arialyy/aria/util/IdGenerator.java create mode 100644 app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8DownloadActivity.java create mode 100644 app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8Module.java create mode 100644 app/src/main/res/drawable/ic_ts.xml create mode 100644 app/src/main/res/layout/activity_m3u8.xml diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java deleted file mode 100644 index 8ba889fc..00000000 --- a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java +++ /dev/null @@ -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 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 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 param) { - if (param == null || param.size() == 0) { - return url; - } - String p = ""; - for (Map.Entry entry : param.entrySet()) { - p += "[" + entry.getKey() + "," + entry.getValue() + "];"; - } - url += ";" + p; - return url; - } -} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java deleted file mode 100644 index 68fd469b..00000000 --- a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java +++ /dev/null @@ -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; - } -} diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java deleted file mode 100644 index 1ef48fd1..00000000 --- a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java +++ /dev/null @@ -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 [a-z0-9_-]{1,120}. Values are byte sequences, - * accessible as streams or files. Each value must be between {@code 0} and - * {@code Integer.MAX_VALUE} bytes in length. - * - *

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

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

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

    - *
  • When an entry is being created it is necessary to - * supply a full set of values; the empty value should be used as a - * placeholder if necessary. - *
  • When an entry is being edited, it is not necessary - * to supply data for every value; values default to their previous - * value. - *
- * 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. - * - *

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

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 lruEntries = - new LinkedHashMap(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()); - private final Callable cleanupCallable = new Callable() { - 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 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(lruEntries.values())) { - if (entry.currentEditor != null) { - entry.currentEditor.abort(); - } - } - trimToSize(); - journalWriter.close(); - journalWriter = null; - } - - private void trimToSize() throws IOException { - while (size > maxSize) { - Map.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"); - } - } -} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java deleted file mode 100644 index e44f77cc..00000000 --- a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java +++ /dev/null @@ -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) { - } - } - } -} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java deleted file mode 100644 index 8dbd351a..00000000 --- a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java +++ /dev/null @@ -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. - * - *

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

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

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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java index f1f4ffaf..7614de93 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java +++ b/Aria/src/main/java/com/arialyy/aria/core/AriaManager.java @@ -30,6 +30,7 @@ import android.support.v4.app.Fragment; import android.widget.PopupWindow; import com.arialyy.aria.core.command.ICmd; import com.arialyy.aria.core.common.QueueMod; +import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.config.AppConfig; import com.arialyy.aria.core.config.DGroupConfig; import com.arialyy.aria.core.config.DownloadConfig; @@ -145,7 +146,7 @@ import org.xml.sax.SAXException; } public synchronized Handler getAriaHandler() { - if (mAriaHandler == null){ + if (mAriaHandler == null) { mAriaHandler = new Handler(Looper.getMainLooper()); } return mAriaHandler; @@ -293,13 +294,13 @@ import org.xml.sax.SAXException; public void delRecord(int type, String key, boolean removeFile) { switch (type) { case 1: // 删除普通任务记录 - CommonUtil.delTaskRecord(key, 1, removeFile, true); + CommonUtil.delTaskRecord(key, RecordHandler.TYPE_DOWNLOAD, removeFile, true); break; case 2: CommonUtil.delGroupTaskRecord(key, removeFile); break; case 3: - CommonUtil.delTaskRecord(key, 2); + CommonUtil.delTaskRecord(key, RecordHandler.TYPE_UPLOAD); break; } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java index 6a59d127..71383ce9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java @@ -16,81 +16,57 @@ package com.arialyy.aria.core.common; import android.content.Context; +import android.os.Looper; import android.util.SparseArray; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.download.BaseDListener; -import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.inf.AbsNormalEntity; import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.manager.ThreadTaskManager; -import com.arialyy.aria.core.upload.UploadEntity; -import com.arialyy.aria.orm.DbEntity; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; -import com.arialyy.aria.util.DbDataHelper; import java.io.File; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Properties; -import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** - * Created by AriaL on 2017/7/1. 任务处理器 + * Created by AriaL on 2017/7/1. + * 控制线程任务状态,如:开始,停止,取消,重试 */ public abstract class AbsFileer> implements Runnable { - private static final String STATE = "_state_"; - private static final String RECORD = "_record_"; - /** - * 分块文件路径 - */ - public static final String SUB_PATH = "%s.%s.part"; - /** - * 小于1m的文件不启用多线程 - */ - protected static final long SUB_LEN = 1024 * 1024; - protected static final int DOWNLOAD = 0x1; - protected static final int UPLOAD = 0x2; - - private final String TAG = "AbsFileer"; + protected final String TAG; protected IEventListener mListener; protected TASK_WRAPPER mTaskWrapper; protected ENTITY mEntity; protected Context mContext; protected File mTempFile; //文件 - protected StateConstance mConstance; - //总线程数 - protected int mTotalThreadNum; - //已完成的线程数 - private int mCompleteThreadNum; - private SparseArray mTask = new SparseArray<>(); + private SparseArray mTask = new SparseArray<>(); private ScheduledThreadPoolExecutor mTimer; - @Deprecated private File mConfigFile; + /** * 进度刷新间隔 */ private long mUpdateInterval = 1000; protected TaskRecord mRecord; + private IThreadState mStateManager; + private boolean isCancel = false, isStop = false; - protected AbsFileer(IEventListener listener, TASK_WRAPPER taskEntity) { + protected AbsFileer(IEventListener listener, TASK_WRAPPER wrapper) { mListener = listener; - mTaskWrapper = taskEntity; + mTaskWrapper = wrapper; mEntity = mTaskWrapper.getEntity(); mContext = AriaManager.APP; - mConstance = new StateConstance(); + TAG = CommonUtil.getClassName(getClass()); } - protected abstract int getType(); + protected abstract IThreadState getStateManager(Looper looper); - public void setNewTask(boolean newTask) { - mTaskWrapper.setNewTask(newTask); - } + /** + * 处理任务 + */ + protected abstract void handleTask(); public String getKey() { return mTaskWrapper.getKey(); @@ -100,18 +76,8 @@ public abstract class AbsFileer getTaskList() { + return mTask; } /** @@ -119,8 +85,6 @@ public abstract class AbsFileer 1 && manager.getDownloadConfig().isUseBlock(); - // 线程数不等1并且没有使用块下载,则认为没有使用动态文件 - mRecord.isOpenDynamicFile = mTotalThreadNum == 1 || mRecord.isBlock; - } - - /* - * mTaskWrapper.getEntity().getFileSize() != mTempFile.length()为兼容以前老版本代码 - * 动态长度条件: - * 1、总线程数为1,并且是新任务 - * 2、总线程数为1,不是新任务,但是长度不是文件全长度 - */ - if (mTotalThreadNum == 1 && (mTaskWrapper.getEntity().getFileSize() != mTempFile.length())) { - mRecord.isOpenDynamicFile = true; - } - + mRecord = new RecordHandler(mTaskWrapper).getRecord(); + Looper.prepare(); + Looper looper = Looper.myLooper(); + mStateManager = getStateManager(looper); onPostPre(); - - if (!mTaskWrapper.isSupportBP()) { - handleNoSupportBP(); - } else { - handleBreakpoint(); - } startTimer(); + handleTask(); + Looper.loop(); } @Override public void run() { @@ -195,9 +129,11 @@ public abstract class AbsFileer= 0) { - mListener.onProgress(mConstance.CURRENT_LOCATION); + } else if (mStateManager.getCurrentProgress() >= 0) { + mListener.onProgress(mStateManager.getCurrentProgress()); } } - }, 0, mUpdateInterval, TimeUnit.MILLISECONDS); + }, delayTimer(), mUpdateInterval, TimeUnit.MILLISECONDS); } public synchronized void closeTimer() { + ALog.d(TAG, "关闭定时器"); if (mTimer != null && !mTimer.isShutdown()) { mTimer.shutdown(); } @@ -250,21 +187,22 @@ public abstract class AbsFileer 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 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 keys = pro.keySet(); - // 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录... - // 第一步应该是record 和 state去重取正确的线程数 - Set set = new HashSet<>(); - for (Object key : keys) { - String str = String.valueOf(key); - int i = Integer.parseInt(str.substring(str.length() - 1, str.length())); - set.add(i); - } - int threadNum = set.size(); - if (threadNum == 0) { - ALog.d(TAG, "线程数为空,任务为新任务"); - mTaskWrapper.setNewTask(true); - return; - } - mRecord.threadNum = threadNum; - mTotalThreadNum = threadNum; - - for (int i = 0; i < threadNum; i++) { - ThreadRecord tRecord = new ThreadRecord(); - tRecord.key = mRecord.filePath; - Object state = pro.getProperty(mTempFile.getName() + STATE + i); - Object record = pro.getProperty(mTempFile.getName() + RECORD + i); - if (state != null && Integer.parseInt(String.valueOf(state)) == 1) { - mCompleteThreadNum++; - tRecord.isComplete = true; - continue; - } - if (record != null) { - long temp = Long.parseLong(String.valueOf(record)); - tRecord.startLocation = temp > 0 ? temp : 0; - } else { - tRecord.startLocation = 0; - } - mRecord.threadRecords.add(tRecord); - } - mConfigFile.delete(); - } - } - - /** - * 初始化记录 - */ - private void initRecord(boolean isNewTask) { - mRecord = new TaskRecord(); - mRecord.fileName = mEntity.getFileName(); - mRecord.filePath = getFilePath(); - mRecord.threadRecords = new ArrayList<>(); - mRecord.isGroupRecord = mTaskWrapper.getEntity().isGroupChild(); - if (mRecord.isGroupRecord) { - if (mTaskWrapper.getEntity() instanceof DownloadEntity) { - mRecord.dGroupHash = ((DownloadEntity) mTaskWrapper.getEntity()).getGroupHash(); - } - } - ALog.d(TAG, String.format("初始化记录,任务为%s任务", isNewTask ? "新" : "旧")); - mTaskWrapper.setNewTask(isNewTask); - } - - private String getFilePath() { - if (getType() == DOWNLOAD) { - return ((DownloadEntity) mTaskWrapper.getEntity()).getDownloadPath(); - } else { - return ((UploadEntity) mTaskWrapper.getEntity()).getFilePath(); - } - } - - /** - * 保存任务记录 - */ - private void saveRecord() { - mRecord.threadNum = mRecord.threadRecords.size(); - mRecord.save(); - ALog.d(TAG, String.format("保存记录,线程记录数:%s", mRecord.threadRecords.size())); - DbEntity.saveAll(mRecord.threadRecords); - } - - /** - * 恢复记录地址 - * - * @return {@code true}任务已完成 - */ - private boolean resumeRecordLocation(int i, long startL, long endL) { - mConstance.CURRENT_LOCATION += endL - startL; - ALog.d(TAG, String.format("任务【%s】线程__%s__已完成", mTaskWrapper.getEntity().getFileName(), i)); - mConstance.COMPLETE_THREAD_NUM = mCompleteThreadNum; - mConstance.STOP_NUM++; - mConstance.CANCEL_NUM++; - if (mConstance.isComplete()) { - mRecord.deleteData(); - mListener.onComplete(); - return true; - } - return false; - } - - /** - * 启动断点任务时,创建单线程任务 - * - * @param i 线程id - * @param startL 该任务起始位置 - * @param endL 该任务结束位置 - * @param fileLength 该任务需要处理的文件长度 - */ - private AbsThreadTask createSingThreadTask(int i, long startL, long endL, long fileLength, - ThreadRecord record) { - SubThreadConfig config = new SubThreadConfig<>(); - config.TOTAL_FILE_SIZE = fileLength; - config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); - config.TEMP_FILE = - mRecord.isBlock ? new File(String.format(SUB_PATH, mTempFile.getPath(), i)) : mTempFile; - config.THREAD_ID = i; - config.START_LOCATION = startL; - config.END_LOCATION = endL; - config.SUPPORT_BP = mTaskWrapper.isSupportBP(); - config.TASK_WRAPPER = mTaskWrapper; - config.THREAD_RECORD = record; - return selectThreadTask(config); - } - - private void handleBreakpoint() { - long fileLength = mEntity.getFileSize(); - long blockSize = fileLength / mTotalThreadNum; - Set threads = new HashSet<>(); - //// 如果是新任务,检查下历史记录,如果有遗留的记录,删除并删除分块文件 - //if (mTaskWrapper.isNewTask()) { - // CommonUtil.delTaskRecord(getFilePath(), 1, true); - //} - - mRecord.fileLength = fileLength; - if (mTaskWrapper.isNewTask() && !handleNewTask()) { - return; - } - for (int i = 0; i < mTotalThreadNum; i++) { - long startL = i * blockSize, endL = (i + 1) * blockSize; - ThreadRecord tr; - boolean isNewTr = false; // 是否是新的线程记录 - if (mTaskWrapper.isNewTask()) { - tr = new ThreadRecord(); - tr.key = mRecord.filePath; - tr.threadId = i; - isNewTr = true; - } else { - tr = mRecord.threadRecords.get(i); - } - - if (tr.blockLen == 0) { - tr.blockLen = CommonUtil.getBlockLen(fileLength, i, mTotalThreadNum); - } - - if (tr.isComplete) {//该线程已经完成 - if (resumeRecordLocation(i, startL, endL)) return; - continue; - } - - //如果有记录,则恢复任务 - if (tr.startLocation > 0) { - long r = tr.startLocation; - //记录的位置需要在线程区间中 - if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) { - mConstance.CURRENT_LOCATION += r - startL; - startL = r; - } - ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", mEntity.getFileName(), i)); - } else { - tr.startLocation = startL; - } - //最后一个线程的结束位置即为文件的总长度 - if (i == (mTotalThreadNum - 1)) { - endL = fileLength; - } - if (tr.endLocation <= 0) { - tr.endLocation = endL; - } - if (isNewTr) { - mRecord.threadRecords.add(tr); - } - //ALog.d(TAG, String.format("创建任务线程:%s,线程总数:%s", i, mTotalThreadNum)); - AbsThreadTask task = createSingThreadTask(i, startL, endL, fileLength, tr); - if (task == null) return; - mTask.put(i, task); - threads.add(i); - } - if (mConstance.CURRENT_LOCATION != 0 - && mConstance.CURRENT_LOCATION != mEntity.getCurrentProgress()) { - ALog.d(TAG, String.format("进度修正,当前进度:%s", mConstance.CURRENT_LOCATION)); - mEntity.setCurrentProgress(mConstance.CURRENT_LOCATION); - } - saveRecord(); - startThreadTask(threads); - } - - /** - * 启动单线程任务 - */ - private void startThreadTask(Set recordL) { - if (isBreak()) { - return; - } - if (mConstance.CURRENT_LOCATION > 0) { - mListener.onResume(mConstance.CURRENT_LOCATION); - } else { - mListener.onStart(mConstance.CURRENT_LOCATION); - } - for (int l : recordL) { - if (l == -1) continue; - ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), mTask.get(l)); - } - } - /** * 重试任务 */ @@ -662,59 +251,13 @@ public abstract class AbsFileer config = new SubThreadConfig<>(); - config.TOTAL_FILE_SIZE = mEntity.getFileSize(); - config.URL = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); - config.TEMP_FILE = mTempFile; - config.THREAD_ID = 0; - config.START_LOCATION = 0; - config.END_LOCATION = config.TOTAL_FILE_SIZE; - config.SUPPORT_BP = mTaskWrapper.isSupportBP(); - config.TASK_WRAPPER = mTaskWrapper; - ThreadRecord record = DbEntity.findFirst(ThreadRecord.class, "key=?", mRecord.filePath); - if (record != null) { - record.deleteData(); - } - record = new ThreadRecord(); - record.startLocation = 0; - record.endLocation = config.TOTAL_FILE_SIZE; - record.key = mTempFile.getPath(); - mRecord.threadRecords.add(record); - config.THREAD_RECORD = record; - AbsThreadTask task = selectThreadTask(config); - if (task == null) return; - mTask.put(0, task); - saveRecord(); - ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task); - mListener.onStart(0); - } - - /** - * 选择单任务线程的类型 - */ - protected abstract AbsThreadTask selectThreadTask(SubThreadConfig config); - /** * 任务是否已经中断 * * @return {@code true}中断 */ public boolean isBreak() { - if (mConstance.isCancel || mConstance.isStop) { + if (isCancel || isStop) { closeTimer(); ALog.d(TAG, String.format("任务【%s】已停止或取消了", mEntity.getFileName())); return true; diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java index 4f880308..f1b83f6d 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java @@ -16,29 +16,26 @@ package com.arialyy.aria.core.common; import android.net.TrafficStats; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.Process; +import android.support.annotation.Nullable; import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.config.BaseTaskConfig; import com.arialyy.aria.core.config.DGroupConfig; import com.arialyy.aria.core.config.DownloadConfig; import com.arialyy.aria.core.config.UploadConfig; -import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.inf.AbsNormalEntity; import com.arialyy.aria.core.inf.AbsTaskWrapper; -import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.manager.ThreadTaskManager; -import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.exception.BaseException; -import com.arialyy.aria.exception.FileException; -import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.util.ALog; -import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.ErrorHelp; -import com.arialyy.aria.util.FileUtil; import com.arialyy.aria.util.NetUtils; import java.io.File; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -59,20 +56,20 @@ public abstract class AbsThreadTask sConfig; private ENTITY mEntity; - private TASK_WRAPPER mTaskWrapper; + protected TASK_WRAPPER mTaskWrapper; private int mFailTimes = 0; private long mLastSaveTime; private ExecutorService mConfigThreadPool; private boolean isNotNetRetry; //断网情况是否重试 private boolean taskBreak = false; //任务跳出 - private int mThreadNum; protected BandwidthLimiter mSpeedBandUtil; //速度限制工具 protected AriaManager mAridManager; private boolean isInterrupted = false; + protected boolean isCancel = false, isStop = false; + protected ThreadRecord mRecord; + private Handler mStateHandler; + private SubThreadConfig mConfig; private Thread mConfigThread = new Thread(new Runnable() { @Override public void run() { @@ -82,21 +79,31 @@ public abstract class AbsThreadTask config) { - sState = constance; - sConfig = config; - mListener = listener; - mTaskWrapper = getConfig().TASK_WRAPPER; + protected AbsThreadTask(SubThreadConfig config) { + mConfig = config; + mTaskWrapper = config.taskWrapper; + mRecord = config.record; + mStateHandler = config.stateHandler; mEntity = mTaskWrapper.getEntity(); mLastSaveTime = System.currentTimeMillis(); mConfigThreadPool = Executors.newCachedThreadPool(); - mThreadNum = getState().TASK_RECORD.threadRecords.size(); mAridManager = AriaManager.getInstance(AriaManager.APP); if (getMaxSpeed() > 0) { - mSpeedBandUtil = new BandwidthLimiter(getMaxSpeed(), mThreadNum); + mSpeedBandUtil = new BandwidthLimiter(getMaxSpeed(), config.startThreadNum); } isNotNetRetry = mAridManager.getAppConfig().isNotNetRetry(); + mChildCurrentLocation = mRecord.startLocation; + } + + /** + * 当前线程处理的文件名 + */ + protected String getFileName() { + return mConfig.tempFile.getName(); + } + + protected SubThreadConfig getConfig() { + return mConfig; } /** @@ -121,14 +128,7 @@ public abstract class AbsThreadTask getConfig() { - return sConfig; + void sendState(int state, @Nullable Bundle bundle) { + Message msg = mStateHandler.obtainMessage(); + msg.what = state; + if (bundle != null) { + msg.setData(bundle); + } + msg.sendToTarget(); + } + + public boolean isInterrupted() { + return Thread.currentThread().isInterrupted(); } @Override protected void finalize() throws Throwable { @@ -229,37 +220,7 @@ public abstract class AbsThreadTask partPath = new ArrayList<>(); - for (int i = 0, len = getState().TASK_RECORD.threadNum; i < len; i++) { - partPath.add(String.format(AbsFileer.SUB_PATH, getState().TASK_RECORD.filePath, i)); - } - boolean isSuccess = FileUtil.mergeFile(getState().TASK_RECORD.filePath, partPath); - if (isSuccess) { - for (String pp : partPath) { - File f = new File(pp); - if (f.exists()) { - f.delete(); - } - } - File targetFile = new File(getState().TASK_RECORD.filePath); - if (targetFile.exists() && targetFile.length() > getEntity().getFileSize()) { - ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s", - getConfig().TEMP_FILE.getName(), targetFile.length(), getEntity().getFileSize())); - return false; - } - return true; - } else { - return false; - } + return isCancel || isStop || taskBreak; } /** @@ -268,16 +229,15 @@ public abstract class AbsThreadTask getEntity().getFileSize() && !getTaskWrapper().asHttp() - .isChunked()) { - String errorMsg = - String.format("下载失败,下载长度超出文件真实长度;currentLocation=%s, fileSize=%s", - getState().CURRENT_LOCATION, - getEntity().getFileSize()); - taskBreak = true; - fail(mChildCurrentLocation, new FileException(TAG, errorMsg), false); + //if (getState().CURRENT_LOCATION > getEntity().getFileSize() && !getTaskWrapper().asHttp() + // .isChunked()) { + // String errorMsg = + // String.format("下载失败,下载长度超出文件真实长度;currentLocation=%s, fileSize=%s", + // getState().CURRENT_LOCATION, + // getEntity().getFileSize()); + // taskBreak = true; + // fail(mChildCurrentLocation, new FileException(TAG, errorMsg), false); + // return; + //} + mChildCurrentLocation += len; + if (!mStateHandler.getLooper().getThread().isAlive()) { return; } - mChildCurrentLocation += len; - getState().CURRENT_LOCATION += len; + mStateHandler.obtainMessage(IThreadState.STATE_RUNNING, len).sendToTarget(); if (System.currentTimeMillis() - mLastSaveTime > 5000 - && mChildCurrentLocation < getConfig().END_LOCATION) { + && mChildCurrentLocation < mRecord.endLocation) { mLastSaveTime = System.currentTimeMillis(); if (!mConfigThreadPool.isShutdown()) { mConfigThreadPool.execute(mConfigThread); @@ -354,26 +303,10 @@ public abstract class AbsThreadTask threadRect) { ALog.i(TAG, String.format("分块【%s】错误,将重新下载该分块", temp.getName())); temp.delete(); - tr.startLocation = normalRectLen * tr.threadId; - tr.isComplete = false; - getConfig().START_LOCATION = tr.startLocation; - } else if (blockFileLen < tr.blockLen) { - tr.startLocation = normalRectLen * tr.threadId + blockFileLen; - tr.isComplete = false; - getConfig().START_LOCATION = tr.startLocation; - getState().CURRENT_LOCATION = getBlockRealTotalSize(); + mRecord.startLocation = mRecord.endLocation - mRecord.blockLen; + mRecord.isComplete = false; + } else if (blockFileLen < mRecord.blockLen) { + mRecord.startLocation = mRecord.endLocation - mRecord.blockLen + blockFileLen; + mRecord.isComplete = false; + sendState(IThreadState.STATE_UPDATE_PROGRESS, null); ALog.i(TAG, - String.format("修正分块【%s】,开始位置:%s,当前进度:%s", temp.getName(), tr.startLocation, - getState().CURRENT_LOCATION)); + String.format("修正分块【%s】记录,开始位置:%s,结束位置:%s", temp.getName(), mRecord.startLocation, + mRecord.endLocation)); } else { ALog.i(TAG, String.format("分块【%s】已完成,更新记录", temp.getName())); - getState().COMPLETE_THREAD_NUM++; - tr.isComplete = true; + mRecord.isComplete = true; } } - tr.update(); - } else { - getConfig().START_LOCATION = mChildCurrentLocation == 0 ? getConfig().START_LOCATION - : getConfig().THREAD_RECORD.startLocation; + mRecord.update(); } } @@ -480,51 +408,20 @@ public abstract class AbsThreadTask, TASK extends AbsTask> implements IEventListener { - private static final String TAG = "BaseListener"; + protected static String TAG; private static final int RUN_SAVE_INTERVAL = 5 * 1000; //5s保存一次下载中的进度 protected WeakReference outHandler; private long mLastLen; //上一次发送长度 @@ -53,6 +56,7 @@ public abstract class BaseListener 0) { + mEntity.setCurrentProgress(location); + } + mEntity.update(); + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java b/Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java index 2c355057..34a04ed3 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/CompleteInfo.java @@ -29,6 +29,12 @@ public class CompleteInfo { public AbsTaskWrapper wrapper; + public Object obj; + + public CompleteInfo() { + + } + public CompleteInfo(int code, AbsTaskWrapper wrapper) { this.code = code; this.wrapper = wrapper; diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java b/Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java new file mode 100644 index 00000000..5cbbd8df --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/IThreadState.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.common; + +import android.os.Handler; + +/** + * 线程任务状态 + */ +public interface IThreadState extends Handler.Callback { + int STATE_STOP = 0x01; + int STATE_FAIL = 0x02; + int STATE_CANCEL = 0x03; + int STATE_COMPLETE = 0x04; + int STATE_RUNNING = 0x05; + int STATE_UPDATE_PROGRESS = 0x06; + String KEY_RETRY = "KEY_RETRY"; + String KEY_ERROR_INFO = "KEY_ERROR_INFO"; + + /** + * 任务是否已经停止 + * + * @return true 任务已停止 + */ + boolean isStop(); + + /** + * 任务是否已经失败 + * + * @return true 任务已失败 + */ + boolean isFail(); + + /** + * 任务是否已经完成 + * + * @return true 任务已完成 + */ + boolean isComplete(); + + /** + * 任务是否已经取消 + * + * @return true 任务已取消 + */ + boolean isCancel(); + + /** + * 获取当前任务进度 + * + * @return 任务当前进度 + */ + long getCurrentProgress(); +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java b/Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java new file mode 100644 index 00000000..6ce24cc6 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.common; + +import android.os.Handler; +import android.os.Looper; +import com.arialyy.aria.core.download.BaseDListener; +import com.arialyy.aria.core.inf.AbsNormalEntity; +import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.util.ALog; +import java.io.File; + +public abstract class NormalFileer> + extends AbsFileer { + private ThreadStateManager mStateManager; + private Handler mStateHandler; + protected int mTotalThreadNum; //总线程数 + + protected NormalFileer(IEventListener listener, TASK_WRAPPER wrapper) { + super(listener, wrapper); + } + + /** + * 处理新任务 + * + * @return {@code true}创建新任务成功 + */ + protected abstract boolean handleNewTask(); + + /** + * 选择单任务线程的类型 + */ + protected abstract AbsThreadTask selectThreadTask(SubThreadConfig config); + + /** + * 设置最大下载/上传速度 + * + * @param maxSpeed 单位为:kb + */ + public void setMaxSpeed(int maxSpeed) { + for (int i = 0; i < mTotalThreadNum; i++) { + AbsThreadTask task = getTaskList().get(i); + if (task != null) { + task.setMaxSpeed(maxSpeed); + } + } + } + + @Override protected void onPostPre() { + super.onPostPre(); + mTotalThreadNum = mRecord.threadNum; + } + + @Override protected IThreadState getStateManager(Looper looper) { + mStateManager = new ThreadStateManager(looper, mRecord, mListener); + mStateHandler = new Handler(looper, mStateManager); + return mStateManager; + } + + @Override protected void handleTask() { + if (mTaskWrapper.isSupportBP()) { + handleBreakpoint(); + } else { + handleNoSupportBP(); + } + } + + /** + * 启动断点任务时,创建单线程任务 + * + * @param record 线程记录 + * @param startNum 启动的线程数 + */ + private AbsThreadTask createSingThreadTask(ThreadRecord record, int startNum) { + SubThreadConfig config = new SubThreadConfig<>(); + config.url = mEntity.isRedirect() ? mEntity.getRedirectUrl() : mEntity.getUrl(); + config.tempFile = + mRecord.isBlock ? new File( + String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), record.threadId)) + : mTempFile; + config.isBlock = mRecord.isBlock; + config.isOpenDynamicFile = mRecord.isOpenDynamicFile; + config.startThreadNum = startNum; + config.taskWrapper = mTaskWrapper; + config.record = record; + config.stateHandler = mStateHandler; + return selectThreadTask(config); + } + + private void handleBreakpoint() { + long fileLength = mEntity.getFileSize(); + long blockSize = fileLength / mTotalThreadNum; + long currentProgress = 0; + + mRecord.fileLength = fileLength; + if (mTaskWrapper.isNewTask() && !handleNewTask()) { + return; + } + + int startNum = mRecord.threadNum; + for (ThreadRecord tr : mRecord.threadRecords) { + if (!tr.isComplete) { + startNum++; + } + } + + for (int i = 0; i < mTotalThreadNum; i++) { + long startL = i * blockSize, endL = (i + 1) * blockSize; + ThreadRecord tr = mRecord.threadRecords.get(i); + + if (tr.isComplete) {//该线程已经完成 + currentProgress += endL - startL; + ALog.d(TAG, String.format("任务【%s】线程__%s__已完成", mTaskWrapper.getEntity().getFileName(), i)); + mStateHandler.obtainMessage(IThreadState.STATE_COMPLETE).sendToTarget(); + if (mStateManager.isComplete()) { + mRecord.deleteData(); + mListener.onComplete(); + return; + } + continue; + } + + //如果有记录,则恢复任务 + long r = tr.startLocation; + //记录的位置需要在线程区间中 + if (startL < r && r <= (i == (mTotalThreadNum - 1) ? fileLength : endL)) { + currentProgress += r - startL; + } + ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", mEntity.getFileName(), i)); + + AbsThreadTask task = createSingThreadTask(tr, startNum); + if (task == null) return; + getTaskList().put(tr.threadId, task); + } + if (currentProgress != 0 && currentProgress != mEntity.getCurrentProgress()) { + ALog.d(TAG, String.format("进度修正,当前进度:%s", currentProgress)); + mEntity.setCurrentProgress(currentProgress); + } + mStateHandler.obtainMessage(IThreadState.STATE_UPDATE_PROGRESS, currentProgress) + .sendToTarget(); + startThreadTask(); + } + + /** + * 启动单线程任务 + */ + private void startThreadTask() { + if (isBreak()) { + return; + } + if (mStateManager.getCurrentProgress() > 0) { + mListener.onResume(mStateManager.getCurrentProgress()); + } else { + mListener.onStart(mStateManager.getCurrentProgress()); + } + for (int i = 0; i < getTaskList().size(); i++) { + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), getTaskList().get(i)); + } + } + + /** + * 处理不支持断点的任务 + */ + private void handleNoSupportBP() { + if (mListener instanceof BaseDListener) { + ((BaseDListener) mListener).supportBreakpoint(false); + } + + AbsThreadTask task = createSingThreadTask(mRecord.threadRecords.get(0), 1); + if (task == null) return; + getTaskList().put(0, task); + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task); + mListener.onStart(0); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java b/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java index 7b8b34c0..88fa77a9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/OnFileInfoCallback.java @@ -24,7 +24,7 @@ public interface OnFileInfoCallback { * * @param info 一些回调的信息 */ - void onComplete(String url, CompleteInfo info); + void onComplete(String key, CompleteInfo info); /** * 请求失败 diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java b/Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java new file mode 100644 index 00000000..a9bae7c5 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.common; + +import com.arialyy.aria.core.config.Configuration; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.m3u8.M3U8FileLoader; +import com.arialyy.aria.core.inf.AbsNormalEntity; +import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.core.inf.ITaskWrapper; +import com.arialyy.aria.core.upload.UploadEntity; +import com.arialyy.aria.orm.DbEntity; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.DbDataHelper; +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +/** + * 处理任务记录,分配线程区间 + */ +public class RecordHandler { + private final String TAG = "RecordHandler"; + + public static final int TYPE_DOWNLOAD = 1; + public static final int TYPE_UPLOAD = 2; + + private static final String STATE = "_state_"; + private static final String RECORD = "_record_"; + /** + * 小于1m的文件不启用多线程 + */ + private static final long SUB_LEN = 1024 * 1024; + + /** + * 分块文件路径 + */ + public static final String SUB_PATH = "%s.%s.part"; + + @Deprecated private File mConfigFile; + private TaskRecord mRecord; + private AbsTaskWrapper mTaskWrapper; + private AbsNormalEntity mEntity; + + RecordHandler(AbsTaskWrapper wrapper) { + mTaskWrapper = wrapper; + mEntity = (AbsNormalEntity) mTaskWrapper.getEntity(); + } + + /** + * 获取任务记录,如果任务记录存在,检查任务记录 + * 检查记录 对于分块任务: 子分块不存在或被删除,子线程将重新下载 + * 对于普通任务: 预下载文件不存在,则任务任务呗删除 + * 如果任务记录不存在或线程记录不存在,初始化记录 + * + * @return 任务记录 + */ + TaskRecord getRecord() { + mConfigFile = new File(CommonUtil.getFileConfigPath(false, mEntity.getFileName())); + if (mConfigFile.exists()) { + convertDb(); + } else { + mRecord = DbDataHelper.getTaskRecord(getFilePath()); + if (mRecord == null) { + initRecord(true); + } else { + if (mRecord.threadRecords == null || mRecord.threadRecords.isEmpty()) { + initRecord(false); + } + + if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) { + handleM3U8Record(); + } else { + if (mRecord.isBlock) { + handleBlockRecord(); + } else if (!mTaskWrapper.isSupportBP()) { + handleNoSupportBPRecord(); + } else { + handleSingleThreadRecord(); + } + } + } + } + saveRecord(); + return mRecord; + } + + /** + * 处理m3u8记录, + * 1、如果分片文件存在,并且分片文件的记录没有完成,则需要删除该分片文件 + * 2、如果记录显示已完成,但是分片文件不存在,则重新开始该分片 + * 3、如果记录显示已完成,并且文件存在,记录当前任务进度 + */ + private void handleM3U8Record() { + DTaskWrapper wrapper = (DTaskWrapper) mTaskWrapper; + String cacheDir = wrapper.asM3U8().getCacheDir(); + long currentProgress = 0; + for (ThreadRecord record : mRecord.threadRecords) { + File temp = new File(M3U8FileLoader.getTsFilePath(cacheDir, record.threadId)); + if (!record.isComplete) { + if (temp.exists()) { + temp.delete(); + } + record.startLocation = 0; + ALog.d(TAG, String.format("分片【%s】未完成,将重新下载该分片", record.threadId)); + } else { + if (!temp.exists()) { + record.startLocation = 0; + record.isComplete = false; + ALog.w(TAG, String.format("分片【%s】不存在,将重新下载该分片", record.threadId)); + } else { + currentProgress += temp.length(); + } + } + } + wrapper.getEntity().setCurrentProgress(currentProgress); + } + + /** + * 处理不支持断点的记录 + */ + private void handleNoSupportBPRecord() { + ThreadRecord tr = mRecord.threadRecords.get(0); + tr.startLocation = 0; + tr.endLocation = mEntity.getFileSize(); + tr.key = mRecord.filePath; + tr.blockLen = tr.endLocation; + tr.isComplete = false; + } + + /** + * 处理单线程的任务的记录 + */ + private void handleSingleThreadRecord() { + File file = new File(mRecord.filePath); + ThreadRecord tr = mRecord.threadRecords.get(0); + if (!file.exists()) { + ALog.w(TAG, String.format("文件【%s】不存在,任务将重新开始", file.getPath())); + tr.startLocation = 0; + tr.isComplete = false; + tr.endLocation = mEntity.getFileSize(); + } else if (mRecord.isOpenDynamicFile) { + if (file.length() > mEntity.getFileSize()) { + ALog.i(TAG, String.format("文件【%s】错误,任务重新开始", file.getPath())); + file.delete(); + tr.startLocation = 0; + tr.isComplete = false; + tr.endLocation = mEntity.getFileSize(); + } else if (file.length() == mEntity.getFileSize()) { + tr.isComplete = true; + } else { + if (file.length() != tr.startLocation) { + ALog.i(TAG, String.format("修正【%s】的进度记录为:%s", file.getPath(), file.length())); + tr.startLocation = file.length(); + tr.isComplete = false; + } + } + } + mTaskWrapper.setNewTask(false); + } + + /** + * 处理分块任务的记录,分块文件(blockFileLen)长度必须需要小于等于线程区间(threadRectLen)的长度 + */ + private void handleBlockRecord() { + // 默认线程分块长度 + long normalRectLen = mEntity.getFileSize() / mRecord.threadRecords.size(); + for (ThreadRecord tr : mRecord.threadRecords) { + long threadRect = tr.blockLen; + + File temp = new File(String.format(SUB_PATH, mRecord.filePath, tr.threadId)); + if (!temp.exists()) { + ALog.i(TAG, String.format("分块文件【%s】不存在,该分块将重新开始", temp.getPath())); + tr.isComplete = false; + tr.startLocation = tr.threadId * normalRectLen; + } else { + if (!tr.isComplete) { + ALog.i(TAG, String.format( + "startLocation = %s; endLocation = %s; block = %s; tempLen = %s; threadId = %s", + tr.startLocation, tr.endLocation, threadRect, temp.length(), tr.threadId)); + + long blockFileLen = temp.length(); // 磁盘中的分块文件长度 + /* + * 检查磁盘中的分块文件 + */ + if (blockFileLen > threadRect) { + ALog.i(TAG, String.format("分块【%s】错误,分块长度【%s】 > 线程区间长度【%s】,将重新开始该分块", + tr.threadId, blockFileLen, threadRect)); + temp.delete(); + tr.startLocation = tr.threadId * threadRect; + continue; + } + + long realLocation = + tr.threadId * normalRectLen + blockFileLen; //正常情况下,该线程的startLocation的位置 + /* + * 检查记录文件 + */ + if (blockFileLen == threadRect) { + ALog.i(TAG, String.format("分块【%s】已完成,更新记录", temp.getPath())); + tr.startLocation = blockFileLen; + tr.isComplete = true; + } else if (tr.startLocation != realLocation) { // 处理记录小于分块文件长度的情况 + ALog.i(TAG, String.format("修正分块【%s】的进度记录为:%s", temp.getPath(), realLocation)); + tr.startLocation = realLocation; + } + } else { + ALog.i(TAG, String.format("分块【%s】已完成", temp.getPath())); + } + } + } + mTaskWrapper.setNewTask(false); + } + + /** + * convertDb 是兼容性代码 从3.4.1开始,线程配置信息将存储在数据库中。 将配置文件的内容复制到数据库中,并将配置文件删除 + */ + private void convertDb() { + List 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 keys = pro.keySet(); + // 老版本记录是5s存一次,但是5s中内,如果线程执行完成,record记录是没有的,只有state记录... + // 第一步应该是record 和 state去重取正确的线程数 + Set set = new HashSet<>(); + for (Object key : keys) { + String str = String.valueOf(key); + int i = Integer.parseInt(str.substring(str.length() - 1)); + set.add(i); + } + int threadNum = set.size(); + if (threadNum == 0) { + ALog.d(TAG, "线程数为空,任务为新任务"); + initRecord(true); + return; + } + mTaskWrapper.setNewTask(false); + mRecord = createTaskRecord(threadNum); + mRecord.isOpenDynamicFile = false; + mRecord.isBlock = false; + File tempFile = new File(getFilePath()); + for (int i = 0; i < threadNum; i++) { + ThreadRecord tRecord = new ThreadRecord(); + tRecord.key = mRecord.filePath; + Object state = pro.getProperty(tempFile.getName() + STATE + i); + Object record = pro.getProperty(tempFile.getName() + RECORD + i); + if (state != null && Integer.parseInt(String.valueOf(state)) == 1) { + tRecord.isComplete = true; + continue; + } + if (record != null) { + long temp = Long.parseLong(String.valueOf(record)); + tRecord.startLocation = temp > 0 ? temp : 0; + } else { + tRecord.startLocation = 0; + } + mRecord.threadRecords.add(tRecord); + } + mConfigFile.delete(); + } + } + + /** + * 初始化任务记录,分配线程区间 + * + * @param newRecord {@code true} 需要创建新{@link TaskRecord} + */ + private void initRecord(boolean newRecord) { + if (newRecord) { + mRecord = createTaskRecord(getNewTaskThreadNum()); + } + mTaskWrapper.setNewTask(true); + // 处理线程区间记录 + int requestType = mTaskWrapper.getRequestType(); + long blockSize = mEntity.getFileSize() / mRecord.threadNum; + for (int i = 0; i < mRecord.threadNum; i++) { + long startL = i * blockSize, endL = (i + 1) * blockSize; + ThreadRecord tr; + tr = new ThreadRecord(); + tr.key = mRecord.filePath; + tr.threadId = i; + tr.startLocation = startL; + tr.isComplete = false; + if (requestType == ITaskWrapper.M3U8_FILE) { + tr.startLocation = 0; + tr.threadType = TaskRecord.TYPE_M3U8; + tr.m3u8url = ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().get(i); + } else { + tr.threadType = TaskRecord.TYPE_HTTP_FTP; + //最后一个线程的结束位置即为文件的总长度 + if (i == (mRecord.threadNum - 1)) { + endL = mEntity.getFileSize(); + } + tr.endLocation = endL; + tr.blockLen = CommonUtil.getBlockLen(mEntity.getFileSize(), i, mRecord.threadNum); + } + mRecord.threadRecords.add(tr); + } + } + + /** + * 创建任务记录 + * + * @param threadNum 线程总数 + */ + private TaskRecord createTaskRecord(int threadNum) { + TaskRecord record = new TaskRecord(); + record.fileName = mEntity.getFileName(); + record.filePath = getFilePath(); + record.threadRecords = new ArrayList<>(); + record.threadNum = threadNum; + // 处理分块和动态文件参数 + if (getRecordType() == TYPE_DOWNLOAD) { + record.isBlock = threadNum > 1 && Configuration.getInstance().downloadCfg.isUseBlock(); + // 线程数为1,或者使用了分块,则认为是使用动态长度文件 + record.isOpenDynamicFile = threadNum == 1 || record.isBlock; + } else { + record.isBlock = false; + } + if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) { + record.taskType = TaskRecord.TYPE_M3U8; + record.isOpenDynamicFile = true; + } else { + record.taskType = TaskRecord.TYPE_HTTP_FTP; + } + record.isGroupRecord = mEntity.isGroupChild(); + if (record.isGroupRecord) { + if (mEntity instanceof DownloadEntity) { + record.dGroupHash = ((DownloadEntity) mEntity).getGroupHash(); + } + } + return record; + } + + /** + * 保存任务记录 + */ + private void saveRecord() { + mRecord.threadNum = mRecord.threadRecords.size(); + mRecord.save(); + DbEntity.saveAll(mRecord.threadRecords); + ALog.d(TAG, String.format("保存记录,线程记录数:%s", mRecord.threadRecords.size())); + } + + /** + * 获取记录类型 + * + * @return {@link #TYPE_DOWNLOAD}、{@link #TYPE_UPLOAD} + */ + private int getRecordType() { + if (mEntity instanceof DownloadEntity) { + return TYPE_DOWNLOAD; + } else { + return TYPE_UPLOAD; + } + } + + /** + * 获取任务路径 + * + * @return 任务文件路径 + */ + private String getFilePath() { + if (mEntity instanceof DownloadEntity) { + return ((DownloadEntity) mTaskWrapper.getEntity()).getFilePath(); + } else { + return ((UploadEntity) mTaskWrapper.getEntity()).getFilePath(); + } + } + + /** + * 小于1m的文件或是任务组的子任务、线程数强制为1 + * 不支持断点或chunked模式的线程数都为,线程数强制为1 + */ + private int getNewTaskThreadNum() { + if (getRecordType() == TYPE_DOWNLOAD) { + if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) { + return ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().size(); + } + if (!mTaskWrapper.isSupportBP() || mTaskWrapper.asHttp().isChunked()) { + return 1; + } + int threadNum = Configuration.getInstance().downloadCfg.getThreadNum(); + return mEntity.getFileSize() <= SUB_LEN + || mEntity.isGroupChild() + || threadNum == 1 + ? 1 + : threadNum; + } else { + return 1; + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java b/Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java deleted file mode 100644 index a9269aeb..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/StateConstance.java +++ /dev/null @@ -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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java b/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java index 9d348fe2..104bfa69 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/SubThreadConfig.java @@ -15,6 +15,7 @@ */ package com.arialyy.aria.core.common; +import android.os.Handler; import com.arialyy.aria.core.inf.AbsTaskWrapper; import java.io.File; @@ -22,19 +23,17 @@ import java.io.File; * 子线程下载信息类 */ public class SubThreadConfig { - //线程Id - public int THREAD_ID; - //文件总长度 - public long TOTAL_FILE_SIZE; - //子线程启动下载位置 - public long START_LOCATION; - //子线程结束下载位置 - public long END_LOCATION; - //下载文件或上传的文件路径 - public File TEMP_FILE; - //服务器地址 - public String URL; - public TASK_WRAPPER TASK_WRAPPER; - public boolean SUPPORT_BP = true; - public ThreadRecord THREAD_RECORD; + + public TASK_WRAPPER taskWrapper; + public boolean isBlock = false; + // 启动的线程 + public int startThreadNum; + public String url; + public File tempFile; + // 线程记录 + public ThreadRecord record; + // 状态处理器 + public Handler stateHandler; + // 动态文件 + public boolean isOpenDynamicFile; } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java b/Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java index 3e64e8d8..e1956d58 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java @@ -29,6 +29,8 @@ import java.util.List; * 任务上传或下载的任务记录 */ public class TaskRecord extends DbEntity { + public static final int TYPE_HTTP_FTP = 0; + public static final int TYPE_M3U8 = 1; @Ignore public List threadRecords; @@ -84,4 +86,10 @@ public class TaskRecord extends DbEntity { * {@code true}是,{@code false}不是 */ public boolean isOpenDynamicFile = false; + + /** + * 线程类型 + * {@link #TYPE_HTTP_FTP}、{@link #TYPE_M3U8} + */ + public int taskType = 0; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java b/Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java index 34341c53..a6d1f0b2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java @@ -53,4 +53,15 @@ public class ThreadRecord extends DbEntity { * 分块长度 */ public long blockLen = 0; + + /** + * 线程类型 + * {@link TaskRecord#TYPE_HTTP_FTP}、{@link TaskRecord#TYPE_M3U8} + */ + public int threadType = 0; + + /** + * m3u8文件的下载地址 + */ + public String m3u8url; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java b/Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java new file mode 100644 index 00000000..836699bf --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ThreadStateManager.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.common; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.exception.BaseException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.FileUtil; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * 线程任务管理器,用于处理多线程下载时任务的状态回调 + */ +public class ThreadStateManager implements IThreadState { + private final String TAG = "ThreadTaskStateManager"; + + /** + * 任务状态回调 + */ + private IEventListener mListener; + private int mThreadNum; // 启动的线程总数 + private int mCancelNum = 0; // 已经取消的线程的数 + private int mStopNum = 0; // 已经停止的线程数 + private int mFailNum = 0; // 失败的线程数 + private int mCompleteNum = 0; // 完成的线程数 + private long mProgress; //当前总进度 + private TaskRecord mTaskRecord; // 任务记录 + private Looper mLooper; + + /** + * @param taskRecord 任务记录 + * @param listener 任务事件 + */ + ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) { + mLooper = looper; + mTaskRecord = taskRecord; + mThreadNum = mTaskRecord.threadNum; + mListener = listener; + } + + @Override public boolean handleMessage(Message msg) { + switch (msg.what) { + case STATE_STOP: + mStopNum++; + if (isStop()) { + mListener.onStop(mProgress); + quitLooper(); + } + break; + case STATE_CANCEL: + mCancelNum++; + if (isCancel()) { + ALog.d(TAG, "icCancel"); + mListener.onCancel(); + quitLooper(); + } + break; + case STATE_FAIL: + mFailNum++; + if (isFail()) { + Bundle b = msg.getData(); + mListener.onFail(b.getBoolean(KEY_RETRY, true), + (BaseException) b.getSerializable(KEY_ERROR_INFO)); + quitLooper(); + } + break; + case STATE_COMPLETE: + mCompleteNum++; + if (isComplete()) { + ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum); + if (mTaskRecord.isBlock) { + if (mergeFile()) { + mListener.onComplete(); + } else { + mListener.onFail(false, null); + } + } else { + mListener.onComplete(); + } + quitLooper(); + } + break; + case STATE_RUNNING: + mProgress += (long) msg.obj; + break; + case STATE_UPDATE_PROGRESS: + if (msg.obj == null) { + mProgress = updateBlockProgress(); + } else { + mProgress = (long) msg.obj; + } + break; + } + return false; + } + + /** + * 退出looper循环 + */ + private void quitLooper() { + mLooper.quit(); + } + + /** + * 获取当前任务下载进度 + * + * @return 当前任务下载进度 + */ + @Override + public long getCurrentProgress() { + return mProgress; + } + + /** + * 所有子线程是否都已经停止 + */ + @Override + public boolean isStop() { + //ALog.d(TAG, + // String.format("isStop; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mStopNum == mThreadNum || mStopNum + mCompleteNum == mThreadNum; + } + + /** + * 所有子线程是否都已经失败 + */ + @Override + public boolean isFail() { + //ALog.d(TAG, + // String.format("isFail; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mCompleteNum != mThreadNum + && (mFailNum == mThreadNum || mFailNum + mCompleteNum == mThreadNum); + } + + /** + * 所有子线程是否都已经完成 + */ + @Override + public boolean isComplete() { + //ALog.d(TAG, + // String.format("isComplete; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", + // mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mCompleteNum == mThreadNum; + } + + /** + * 所有子线程是否都已经取消 + */ + @Override + public boolean isCancel() { + //ALog.d(TAG, String.format("isCancel; stopNum: %s, cancelNum: %s, failNum: %s, completeNum: %s", + // mStopNum, + // mCancelNum, mFailNum, mCompleteNum)); + return mCancelNum == mThreadNum; + } + + /** + * 更新分块任务s的真实进度 + */ + private long updateBlockProgress() { + long size = 0; + for (int i = 0, len = mTaskRecord.threadRecords.size(); i < len; i++) { + File temp = new File(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + if (temp.exists()) { + size += temp.length(); + } + } + return size; + } + + /** + * 合并文件 + * + * @return {@code true} 合并成功,{@code false}合并失败 + */ + private boolean mergeFile() { + List partPath = new ArrayList<>(); + for (int i = 0, len = mTaskRecord.threadNum; i < len; i++) { + partPath.add(String.format(RecordHandler.SUB_PATH, mTaskRecord.filePath, i)); + } + boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath); + if (isSuccess) { + for (String pp : partPath) { + File f = new File(pp); + if (f.exists()) { + f.delete(); + } + } + File targetFile = new File(mTaskRecord.filePath); + if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) { + ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s", + targetFile.getName(), targetFile.length(), mTaskRecord.fileLength)); + return false; + } + return true; + } else { + ALog.e(TAG, "合并失败"); + return false; + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java index 52639637..3ef7cf29 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/AbsFtpInfoThread.java @@ -192,10 +192,6 @@ public abstract class AbsFtpInfoThread> - extends AbsThreadTask { +public abstract class AbsFtpThreadTask> + extends AbsThreadTask { private final String TAG = "AbsFtpThreadTask"; protected String charSet; - protected AbsFtpThreadTask(StateConstance constance, IEventListener listener, - SubThreadConfig info) { - super(constance, listener, info); + protected AbsFtpThreadTask(SubThreadConfig config) { + super(config); } /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskDelegate.java deleted file mode 100644 index a6917b5b..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/ftp/FtpTaskDelegate.java +++ /dev/null @@ -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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java deleted file mode 100644 index 1666eda5..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpHeaderDelegate.java +++ /dev/null @@ -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 - implements IHttpHeaderDelegate { - 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 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 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 keys = taskDelegate.getHeaders().keySet(); - for (String key : keys) { - if (headers.containsKey(key)) { - i++; - } else { - break; - } - } - if (i == taskDelegate.getHeaders().size()) { - int j = 0; - Collection values = taskDelegate.getHeaders().values(); - for (String value : values) { - if (headers.containsValue(value)) { - j++; - } else { - break; - } - } - if (j == taskDelegate.getHeaders().size()) { - mapEquals = true; - } - } - } - - if (!mapEquals) { - taskDelegate.getHeaders().clear(); - Set keys = headers.keySet(); - for (String key : keys) { - taskDelegate.getHeaders().put(key, headers.get(key)); - } - } - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java index 8748021c..dc45bbb9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java +++ b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskConfig.java @@ -88,6 +88,9 @@ public class HttpTaskConfig implements ITaskConfig { return fileLenAdapter; } + /** + * 如果是匿名内部类,完成后需要将adapter设置为空,否则会出现内存泄漏 + */ public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) { this.fileLenAdapter = fileLenAdapter; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java deleted file mode 100644 index a1f7cf8f..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/common/http/HttpTaskDelegate.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.arialyy.aria.core.common.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 params; - - /** - * http 请求头 - */ - private Map 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 formFields = new HashMap<>(); - - private IHttpFileLenAdapter fileLenAdapter; - - public IHttpFileLenAdapter getFileLenAdapter() { - return fileLenAdapter; - } - - public void setFileLenAdapter(IHttpFileLenAdapter fileLenAdapter) { - this.fileLenAdapter = fileLenAdapter; - } - - public Map getFormFields() { - return formFields; - } - - public void setFormFields(Map 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 getHeaders() { - return headers; - } - - public void setHeaders(Map 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 getParams() { - return params; - } - - public void setParams(Map params) { - this.params = params; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/AbsGroupDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/AbsGroupDelegate.java deleted file mode 100644 index 152aa5a9..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/AbsGroupDelegate.java +++ /dev/null @@ -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 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 subTasks = mWrapper.getSubTaskWrapper(); - if (subTasks != null && !subTasks.isEmpty()) { - List 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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java index d86714e3..7fda65ac 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java @@ -18,6 +18,7 @@ package com.arialyy.aria.core.download; import android.os.Handler; import com.arialyy.aria.core.common.BaseListener; +import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.TaskSchedulerType; @@ -29,7 +30,6 @@ import com.arialyy.aria.util.CommonUtil; */ public class BaseDListener extends BaseListener implements IDownloadListener { - private static final String TAG = "BaseDListener"; BaseDListener(DownloadTask task, Handler outHandler) { super(task, outHandler); @@ -50,30 +50,16 @@ public class BaseDListener extends BaseListener 0) { - mEntity.setCurrentProgress(location); + @Override protected void handleCancel() { + int sType = getTask().getSchedulerType(); + if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { + mEntity.setComplete(false); + mEntity.setState(IEntity.STATE_WAIT); + CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD, + mTaskWrapper.isRemoveFile(), false); + } else { + CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD, + mTaskWrapper.isRemoveFile(), true); } - mEntity.update(); } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java b/Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java index efe87ce8..e8aea597 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java @@ -16,8 +16,10 @@ package com.arialyy.aria.core.download; import android.text.TextUtils; +import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.inf.ITargetHandler; import com.arialyy.aria.core.inf.IConfigHandler; +import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.manager.TaskWrapperManager; import com.arialyy.aria.core.queue.DownloadTaskQueue; import com.arialyy.aria.orm.DbEntity; @@ -100,6 +102,10 @@ class DNormalConfigHandler implements IConfigHandler if (b) { mEntity.save(); } + if (mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_FILE + && mEntity.getFileSize() == 0) { + ALog.e(TAG, "由于m3u8协议的特殊性质,你需要设置文件长度才能获取到正确的下载进度百分比。设置方法:asM3U8().setFileSize(xxx)"); + } return b; } @@ -109,13 +115,14 @@ class DNormalConfigHandler implements IConfigHandler ALog.e(TAG, "下载失败,文件保存路径为null"); return false; } else if (!filePath.startsWith("/")) { - ALog.e(TAG, "下载失败,文件保存路径【" + filePath + "】错误"); + ALog.e(TAG, String.format("下载失败,文件保存路径【%s】错误", filePath)); return false; } File file = new File(filePath); if (file.isDirectory()) { if (mTarget.getTargetType() == ITargetHandler.D_HTTP) { - ALog.e(TAG, "下载失败,保存路径【" + filePath + "】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip"); + ALog.e(TAG, + String.format("下载失败,保存路径【%s】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip", filePath)); return false; } else if (mTarget.getTargetType() == ITargetHandler.D_FTP) { filePath += mEntity.getFileName(); @@ -132,11 +139,11 @@ class DNormalConfigHandler implements IConfigHandler // 检查路径冲突 if (DbEntity.checkDataExist(DownloadEntity.class, "downloadPath=?", filePath)) { if (!forceDownload) { - ALog.e(TAG, "下载失败,保存路径【" + filePath + "】已经被其它任务占用,请设置其它保存路径"); + ALog.e(TAG, String.format("下载失败,保存路径【%s】已经被其它任务占用,请设置其它保存路径", filePath)); return false; } else { - ALog.w(TAG, "保存路径【" + filePath + "】已经被其它任务占用,当前任务将覆盖该路径的文件"); - CommonUtil.delTaskRecord(filePath, 1); + ALog.w(TAG, String.format("保存路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath)); + CommonUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD); mTarget.setTaskWrapper( TaskWrapperManager.getInstance() .getHttpTaskWrapper(DTaskWrapper.class, mUrl)); @@ -144,7 +151,7 @@ class DNormalConfigHandler implements IConfigHandler } File oldFile = new File(mEntity.getDownloadPath()); File newFile = new File(filePath); - mEntity.setDownloadPath(filePath); + mEntity.setFilePath(filePath); mEntity.setFileName(newFile.getName()); // 如过使用Content-Disposition中的文件名,将不会执行重命名工作 if (mTarget.getTaskWrapper().asHttp().isUseServerFileName()) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DNormalDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/DNormalDelegate.java deleted file mode 100644 index cd0435f0..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DNormalDelegate.java +++ /dev/null @@ -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 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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java b/Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java index 1d2949f0..2fea1375 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DTaskWrapper.java @@ -17,6 +17,7 @@ package com.arialyy.aria.core.download; import com.arialyy.aria.core.config.Configuration; import com.arialyy.aria.core.config.DownloadConfig; +import com.arialyy.aria.core.download.m3u8.M3U8TaskConfig; import com.arialyy.aria.core.inf.AbsTaskWrapper; /** @@ -34,10 +35,19 @@ public class DTaskWrapper extends AbsTaskWrapper { */ private boolean isGroupTask = false; + private M3U8TaskConfig m3u8TaskConfig; + public DTaskWrapper(DownloadEntity entity) { super(entity); } + public M3U8TaskConfig asM3U8() { + if (m3u8TaskConfig == null) { + m3u8TaskConfig = new M3U8TaskConfig(); + } + return m3u8TaskConfig; + } + /** * Task实体对应的key,下载url */ diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java index f636f6c5..fa500758 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java @@ -18,11 +18,13 @@ package com.arialyy.aria.core.download; import android.os.Handler; import com.arialyy.aria.core.common.BaseListener; +import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.download.group.IDownloadGroupListener; import com.arialyy.aria.core.inf.GroupSendParams; import com.arialyy.aria.core.inf.IEntity; import com.arialyy.aria.core.inf.TaskSchedulerType; import com.arialyy.aria.core.scheduler.ISchedulers; +import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.exception.BaseException; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.CommonUtil; @@ -34,7 +36,6 @@ import com.arialyy.aria.util.ErrorHelp; class DownloadGroupListener extends BaseListener implements IDownloadGroupListener { - private final String TAG = "DownloadGroupListener"; private GroupSendParams mSeedEntity; DownloadGroupListener(DownloadGroupTask task, Handler outHandler) { @@ -74,8 +75,10 @@ class DownloadGroupListener public void onSubFail(DownloadEntity subEntity, BaseException e) { saveCurrentLocation(); sendInState2Target(ISchedulers.SUB_FAIL, subEntity); - e.printStackTrace(); - ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e)); + if (e != null) { + e.printStackTrace(); + ErrorHelp.saveError(e.getTag(), "", ALog.getExceptionString(e)); + } } @Override @@ -99,6 +102,24 @@ class DownloadGroupListener mSeedEntity.entity = subEntity; outHandler.get().obtainMessage(state, ISchedulers.IS_SUB_TASK, 0, mSeedEntity).sendToTarget(); } + saveSubState(state, subEntity); + } + + private void saveSubState(int state, DownloadEntity subEntity) { + subEntity.setState(state); + if (state == IEntity.STATE_STOP) { + subEntity.setStopTime(System.currentTimeMillis()); + } else if (state == IEntity.STATE_COMPLETE) { + subEntity.setComplete(true); + subEntity.setCompleteTime(System.currentTimeMillis()); + subEntity.setCurrentProgress(subEntity.getFileSize()); + subEntity.setPercent(100); + subEntity.setConvertSpeed("0kb/s"); + subEntity.setSpeed(0); + ALog.i(TAG, String.format("任务【%s】完成,将删除线程任务记录", mEntity.getKey())); + CommonUtil.delTaskRecord(subEntity.getKey(), RecordHandler.TYPE_DOWNLOAD, false, false); + } + subEntity.update(); } private void saveCurrentLocation() { @@ -130,28 +151,14 @@ class DownloadGroupListener } - @Override - protected void saveData(int state, long location) { - mTaskWrapper.setState(state); - mEntity.setState(state); - if (state == IEntity.STATE_CANCEL) { - int sType = getTask().getSchedulerType(); - if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { - mEntity.setComplete(false); - 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(); + @Override protected void handleCancel() { + int sType = getTask().getSchedulerType(); + if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { + mEntity.setComplete(false); + mEntity.setState(IEntity.STATE_WAIT); + CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), false); + } else { + CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), true); } - if (location > 0) { - mEntity.setCurrentProgress(location); - } - mEntity.update(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java index e65dbe1d..fde8ecf1 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTarget.java @@ -20,6 +20,7 @@ import android.support.annotation.NonNull; import com.arialyy.aria.core.common.http.GetDelegate; import com.arialyy.aria.core.common.http.HttpDelegate; import com.arialyy.aria.core.common.http.PostDelegate; +import com.arialyy.aria.core.download.m3u8.M3U8Delegate; import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import java.net.Proxy; import java.util.Map; @@ -59,6 +60,11 @@ public class DownloadTarget extends AbsDTarget { return (GetDelegate) mHttpDelegate; } + @CheckResult + public M3U8Delegate asM3U8() { + return new M3U8Delegate<>(this); + } + /** * 是否使用服务器通过content-disposition传递的文件名,内容格式{@code attachment;filename=***} * 如果获取不到服务器文件名,则使用用户设置的文件名 diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java index 9396e5e3..a6aadfb2 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java @@ -21,10 +21,11 @@ import android.os.Looper; import com.arialyy.aria.core.AriaManager; import com.arialyy.aria.core.common.IUtil; import com.arialyy.aria.core.download.downloader.SimpleDownloadUtil; +import com.arialyy.aria.core.download.m3u8.M3U8FileUtil; import com.arialyy.aria.core.inf.AbsNormalTask; import com.arialyy.aria.core.inf.IDownloadListener; +import com.arialyy.aria.core.inf.ITaskWrapper; import com.arialyy.aria.core.scheduler.ISchedulers; -import java.io.File; /** * Created by lyy on 2016/8/11. @@ -44,14 +45,18 @@ public class DownloadTask extends AbsNormalTask { /** * 获取文件保存路径 * - * @return 如果路径不存在,返回null + * @deprecated 后续版本将删除该方法,请使用{@link #getFilePath()} */ + @Deprecated public String getDownloadPath() { - File file = new File(mEntity.getDownloadPath()); - if (!file.exists()) { - return null; - } - return mEntity.getDownloadPath(); + return getFilePath(); + } + + /** + * 获取文件保存路径 + */ + public String getFilePath() { + return mEntity.getFilePath(); } public DownloadEntity getEntity() { @@ -84,7 +89,11 @@ public class DownloadTask extends AbsNormalTask { } @Override protected synchronized IUtil createUtil() { - return new SimpleDownloadUtil(mTaskWrapper, (IDownloadListener) mListener); + if (mTaskWrapper.getRequestType() == ITaskWrapper.D_HTTP){ + return new SimpleDownloadUtil(mTaskWrapper, (IDownloadListener) mListener); + }else { + return new M3U8FileUtil(mTaskWrapper, (IDownloadListener) mListener); + } } public static class Builder { diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDelegate.java deleted file mode 100644 index 12cc9a10..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/FtpDirDelegate.java +++ /dev/null @@ -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 { - 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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/HttpGroupDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/HttpGroupDelegate.java deleted file mode 100644 index be8853ff..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/download/HttpGroupDelegate.java +++ /dev/null @@ -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 { - - /** - * 子任务下载地址, - */ - private List mUrls = new ArrayList<>(); - - /** - * 子任务文件名 - */ - private List mSubNameTemp = new ArrayList<>(); - - HttpGroupDelegate(DownloadGroupTarget target, DGTaskWrapper wrapper) { - super(target, wrapper); - mUrls.addAll(wrapper.getEntity().getUrls()); - } - - @CheckResult - DownloadGroupTarget setGroupUrl(List urls) { - mUrls.clear(); - mUrls.addAll(urls); - return getTarget(); - } - - /** - * 设置子任务文件名,该方法必须在{@link #setDirPath(String)}之后调用,否则不生效 - */ - @CheckResult - DownloadGroupTarget setSubFileName(List 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 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 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 repeated = new HashSet<>(); - List 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 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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java index a3c5920c..587510bb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/ConnectionHelp.java @@ -44,7 +44,7 @@ import javax.net.ssl.SSLSocketFactory; /** * Created by lyy on 2017/1/18. 链接帮助类 */ -class ConnectionHelp { +public class ConnectionHelp { private static final String TAG = "ConnectionHelp"; /** @@ -52,7 +52,8 @@ class ConnectionHelp { * * @throws MalformedURLException */ - static URL handleUrl(String url, HttpTaskConfig taskDelegate) throws MalformedURLException { + public static URL handleUrl(String url, HttpTaskConfig taskDelegate) + throws MalformedURLException { Map params = taskDelegate.getParams(); if (params != null && taskDelegate.getRequestEnum() == RequestEnum.GET) { if (url.contains("?")) { @@ -79,7 +80,7 @@ class ConnectionHelp { * @return {@link GZIPInputStream}、{@link InflaterInputStream} * @throws IOException */ - static InputStream convertInputStream(HttpURLConnection connection) throws IOException { + public static InputStream convertInputStream(HttpURLConnection connection) throws IOException { String encoding = connection.getHeaderField("Content-Encoding"); if (TextUtils.isEmpty(encoding)) { return connection.getInputStream(); @@ -98,7 +99,7 @@ class ConnectionHelp { * * @throws IOException */ - static HttpURLConnection handleConnection(URL url, HttpTaskConfig taskDelegate) + public static HttpURLConnection handleConnection(URL url, HttpTaskConfig taskDelegate) throws IOException { HttpURLConnection conn; URLConnection urlConn; @@ -130,7 +131,7 @@ class ConnectionHelp { * * @throws ProtocolException */ - static HttpURLConnection setConnectParam(HttpTaskConfig delegate, HttpURLConnection conn) { + public static HttpURLConnection setConnectParam(HttpTaskConfig delegate, HttpURLConnection conn) { if (delegate.getRequestEnum() == RequestEnum.POST) { conn.setDoInput(true); conn.setDoOutput(true); diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java index e26424c5..e8144b2a 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/Downloader.java @@ -15,8 +15,9 @@ */ package com.arialyy.aria.core.download.downloader; -import com.arialyy.aria.core.common.AbsFileer; import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.NormalFileer; +import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.DTaskWrapper; @@ -26,34 +27,21 @@ import com.arialyy.aria.exception.BaseException; import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.BufferedRandomAccessFile; -import com.arialyy.aria.util.CommonUtil; import java.io.File; import java.io.IOException; /** * Created by AriaL on 2017/7/1. 文件下载器 */ -public class Downloader extends AbsFileer { +public class Downloader extends NormalFileer { private String TAG = "Downloader"; public Downloader(IDownloadListener listener, DTaskWrapper taskWrapper) { super(listener, taskWrapper); - mTempFile = new File(mEntity.getDownloadPath()); + mTempFile = new File(mEntity.getFilePath()); setUpdateInterval(taskWrapper.getConfig().getUpdateInterval()); } - /** - * 小于1m的文件或是任务组的子任务、线程数都是1 - */ - @Override protected int setNewTaskThreadNum() { - int threadNum = mTaskWrapper.getConfig().getThreadNum(); - return mEntity.getFileSize() <= SUB_LEN - || mTaskWrapper.isGroupTask() - || threadNum == 1 - ? 1 - : threadNum; - } - @Override protected boolean handleNewTask() { if (!mRecord.isBlock) { if (mTempFile.exists()) { @@ -62,7 +50,7 @@ public class Downloader extends AbsFileer { //CommonUtil.createFile(mTempFile.getPath()); } else { for (int i = 0; i < mTotalThreadNum; i++) { - File blockFile = new File(String.format(AbsFileer.SUB_PATH, mTempFile.getPath(), i)); + File blockFile = new File(String.format(RecordHandler.SUB_PATH, mTempFile.getPath(), i)); if (blockFile.exists()) { ALog.d(TAG, String.format("分块【%s】已经存在,将删除该分块", i)); blockFile.delete(); @@ -97,9 +85,9 @@ public class Downloader extends AbsFileer { * 如果使用"Content-Disposition"中的文件名,需要更新{@link #mTempFile}的路径 */ void updateTempFile() { - if (!mTempFile.getPath().equals(mEntity.getDownloadPath())) { + if (!mTempFile.getPath().equals(mEntity.getFilePath())) { if (!mTempFile.exists()) { - mTempFile = new File(mEntity.getDownloadPath()); + mTempFile = new File(mEntity.getFilePath()); } else { boolean b = mTempFile.renameTo(new File(mEntity.getDownloadPath())); ALog.d(TAG, String.format("更新tempFile文件名%s", b ? "成功" : "失败")); @@ -107,10 +95,6 @@ public class Downloader extends AbsFileer { } } - @Override protected int getType() { - return DOWNLOAD; - } - @Override protected void onPostPre() { super.onPostPre(); ((IDownloadListener) mListener).onPostPre(mEntity.getFileSize()); @@ -124,9 +108,9 @@ public class Downloader extends AbsFileer { switch (mTaskWrapper.getRequestType()) { case AbsTaskWrapper.D_FTP: case AbsTaskWrapper.D_FTP_DIR: - return new FtpThreadTask(mConstance, (IDownloadListener) mListener, config); + return new FtpThreadTask(config); case AbsTaskWrapper.D_HTTP: - return new HttpThreadTask(mConstance, (IDownloadListener) mListener, config); + return new HttpThreadTask(config); } return null; } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java index 8514465a..9f701b07 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/FtpThreadTask.java @@ -17,14 +17,11 @@ package com.arialyy.aria.core.download.downloader; import aria.apache.commons.net.ftp.FTPClient; import aria.apache.commons.net.ftp.FTPReply; -import com.arialyy.aria.core.common.StateConstance; import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask; -import com.arialyy.aria.core.config.BaseTaskConfig; import com.arialyy.aria.core.config.DownloadConfig; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.exception.AriaIOException; import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.util.ALog; @@ -43,37 +40,31 @@ import java.nio.channels.ReadableByteChannel; */ class FtpThreadTask extends AbsFtpThreadTask { private final String TAG = "FtpThreadTask"; - private boolean isOpenDynamicFile; - private boolean isBlock; - FtpThreadTask(StateConstance constance, IDownloadListener listener, - SubThreadConfig downloadInfo) { - super(constance, listener, downloadInfo); - isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile; - isBlock = getState().TASK_RECORD.isBlock; + FtpThreadTask(SubThreadConfig config) { + super(config); } @Override public FtpThreadTask call() throws Exception { super.call(); - if (getConfig().THREAD_RECORD.isComplete) { + if (mRecord.isComplete) { handleComplete(); return this; } - mChildCurrentLocation = getConfig().START_LOCATION; FTPClient client = null; InputStream is = null; try { ALog.d(TAG, - String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), - getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); + String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(), + mRecord.threadId, mRecord.startLocation, mRecord.endLocation)); client = createClient(); if (client == null) { fail(mChildCurrentLocation, new TaskException(TAG, "ftp client 创建失败")); return this; } - if (getConfig().START_LOCATION > 0) { - client.setRestartOffset(getConfig().START_LOCATION); + if (mRecord.startLocation > 0) { + client.setRestartOffset(mRecord.startLocation); } //发送第二次指令时,还需要再做一次判断 int reply = client.getReplyCode(); @@ -97,7 +88,7 @@ class FtpThreadTask extends AbsFtpThreadTask { return this; } - if (isOpenDynamicFile) { + if (getConfig().isOpenDynamicFile) { readDynamicFile(is); } else { readNormal(is); @@ -105,10 +96,10 @@ class FtpThreadTask extends AbsFtpThreadTask { } } catch (IOException e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); } catch (Exception e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); } finally { try { if (is != null) { @@ -134,27 +125,9 @@ class FtpThreadTask extends AbsFtpThreadTask { if (!checkBlock()) { return; } - ALog.i(TAG, - String.format("任务【%s】线程__%s__下载完毕", getConfig().TEMP_FILE.getName(), - getConfig().THREAD_ID)); - writeConfig(true, getConfig().END_LOCATION); - getState().COMPLETE_THREAD_NUM++; - if (getState().isComplete()) { - if (isBlock) { - boolean success = mergeFile(); - if (!success) { - mListener.onFail(false, - new TaskException(TAG, - String.format("任务【%s】分块文件合并失败", getConfig().TEMP_FILE.getName()))); - return; - } - } - sendCompleteMsg(); - } - if (getState().isFail()) { - mListener.onFail(false, - new TaskException(TAG, String.format("任务【%s】下载失败", getConfig().TEMP_FILE.getName()))); - } + ALog.i(TAG, String.format("任务【%s】线程__%s__下载完毕", getFileName(), mRecord.threadId)); + writeConfig(true, mRecord.endLocation); + sendCompleteMsg(); } /** @@ -166,7 +139,7 @@ class FtpThreadTask extends AbsFtpThreadTask { ReadableByteChannel fic = null; try { int len; - fos = new FileOutputStream(getConfig().TEMP_FILE, true); + fos = new FileOutputStream(getConfig().tempFile, true); foc = fos.getChannel(); fic = Channels.newChannel(is); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); @@ -177,8 +150,8 @@ class FtpThreadTask extends AbsFtpThreadTask { if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { - len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); + if (mChildCurrentLocation + len >= mRecord.endLocation) { + len = (int) (mRecord.endLocation - mChildCurrentLocation); bf.flip(); fos.write(bf.array(), 0, len); bf.compact(); @@ -194,7 +167,7 @@ class FtpThreadTask extends AbsFtpThreadTask { handleComplete(); } catch (IOException e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); } finally { try { if (fos != null) { @@ -219,8 +192,8 @@ class FtpThreadTask extends AbsFtpThreadTask { BufferedRandomAccessFile file = null; try { file = - new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); - file.seek(getConfig().START_LOCATION); + new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize()); + file.seek(mRecord.startLocation); byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; while (isLive() && (len = is.read(buffer)) != -1) { @@ -230,8 +203,8 @@ class FtpThreadTask extends AbsFtpThreadTask { if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { - len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); + if (mChildCurrentLocation + len >= mRecord.endLocation) { + len = (int) (mRecord.endLocation - mChildCurrentLocation); file.write(buffer, 0, len); progress(len); break; @@ -242,7 +215,7 @@ class FtpThreadTask extends AbsFtpThreadTask { } } catch (IOException e) { fail(mChildCurrentLocation, - new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().URL), e)); + new AriaIOException(TAG, String.format("下载失败【%s】", getConfig().url), e)); } finally { try { if (file != null) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java index 5cb2338e..d1a12d31 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java @@ -90,7 +90,10 @@ public class HttpFileInfoThread implements Runnable { conn.disconnect(); } // 销毁文件长度适配器 - mTaskWrapper.asHttp().setFileLenAdapter(null); + IHttpFileLenAdapter lenAdapter = mTaskWrapper.asHttp().getFileLenAdapter(); + if (lenAdapter != null && lenAdapter.getClass().isAnonymousClass()) { + mTaskWrapper.asHttp().setFileLenAdapter(null); + } } } @@ -141,9 +144,14 @@ public class HttpFileInfoThread implements Runnable { } Map> headers = conn.getHeaderFields(); String disposition = conn.getHeaderField("Content-Disposition"); - if (mTaskDelegate.isUseServerFileName() && !TextUtils.isEmpty(disposition)) { - mEntity.setDisposition(CommonUtil.encryptBASE64(disposition)); - handleContentDisposition(disposition); + + if (mTaskDelegate.isUseServerFileName()) { + if (!TextUtils.isEmpty(disposition)) { + mEntity.setDisposition(CommonUtil.encryptBASE64(disposition)); + handleContentDisposition(disposition); + } else { + ALog.w(TAG, "Content-Disposition对于端字段为空,使用服务器端文件名失败"); + } } CookieManager msCookieManager = new CookieManager(); List cookiesHeader = headers.get("Set-Cookie"); @@ -264,14 +272,15 @@ public class HttpFileInfoThread implements Runnable { return; } ALog.d(TAG, String.format("文件重命名为:%s", newName)); - File oldFile = new File(mEntity.getDownloadPath()); + File oldFile = new File(mEntity.getFilePath()); String newPath = oldFile.getParent() + "/" + newName; if (oldFile.exists()) { boolean b = oldFile.renameTo(new File(newPath)); ALog.d(TAG, String.format("文件重命名%s", b ? "成功" : "失败")); } mEntity.setFileName(newName); - mEntity.setDownloadPath(newPath); + mEntity.setFilePath(newPath); + CommonUtil.modifyTaskRecord(oldFile.getPath(), newPath); } /** diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java index d0c6aad3..ae1387eb 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpThreadTask.java @@ -17,13 +17,11 @@ package com.arialyy.aria.core.download.downloader; import com.arialyy.aria.core.common.AbsThreadTask; import com.arialyy.aria.core.common.RequestEnum; -import com.arialyy.aria.core.common.StateConstance; import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.config.DownloadConfig; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.common.http.HttpTaskConfig; -import com.arialyy.aria.core.inf.IDownloadListener; import com.arialyy.aria.exception.AriaIOException; import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.util.ALog; @@ -49,37 +47,30 @@ import java.util.Set; */ final class HttpThreadTask extends AbsThreadTask { private final String TAG = "HttpThreadTask"; - private boolean isOpenDynamicFile; - private boolean isBlock; - HttpThreadTask(StateConstance constance, IDownloadListener listener, - SubThreadConfig downloadInfo) { - super(constance, listener, downloadInfo); - isOpenDynamicFile = getState().TASK_RECORD.isOpenDynamicFile; - isBlock = getState().TASK_RECORD.isBlock; + HttpThreadTask(SubThreadConfig config) { + super(config); } @Override public HttpThreadTask call() throws Exception { super.call(); - if (getThreadRecord().isComplete) { + if (mRecord.isComplete) { handleComplete(); return this; } HttpURLConnection conn = null; BufferedInputStream is = null; BufferedRandomAccessFile file = null; - //当前子线程的下载位置 - mChildCurrentLocation = getConfig().START_LOCATION; try { HttpTaskConfig taskDelegate = getTaskWrapper().asHttp(); - URL url = ConnectionHelp.handleUrl(getConfig().URL, taskDelegate); + URL url = ConnectionHelp.handleUrl(getConfig().url, taskDelegate); conn = ConnectionHelp.handleConnection(url, taskDelegate); - if (getConfig().SUPPORT_BP) { + if (mTaskWrapper.isSupportBP()) { ALog.d(TAG, - String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), - getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); - conn.setRequestProperty("Range", String.format("bytes=%s-%s", getConfig().START_LOCATION, - (getConfig().END_LOCATION - 1))); + String.format("任务【%s】线程__%s__开始下载【开始位置 : %s,结束位置:%s】", getFileName(), + mRecord.threadId, mRecord.startLocation, mRecord.endLocation)); + conn.setRequestProperty("Range", String.format("bytes=%s-%s", mRecord.startLocation, + (mRecord.endLocation - 1))); } else { ALog.w(TAG, "该下载不支持断点"); } @@ -112,29 +103,29 @@ final class HttpThreadTask extends AbsThreadTask { is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); if (taskDelegate.isChunked()) { readChunked(is); - } else if (isOpenDynamicFile) { + } else if (getConfig().isOpenDynamicFile) { readDynamicFile(is); } else { //创建可设置位置的文件 file = - new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", + new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize()); //设置每条线程写入文件的位置 - file.seek(getConfig().START_LOCATION); + file.seek(mRecord.startLocation); readNormal(is, file); handleComplete(); } } catch (MalformedURLException e) { fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(), + String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), getEntity().getDownloadPath(), getEntity().getUrl()), e)); } catch (IOException e) { fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(), + String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), getEntity().getDownloadPath(), getEntity().getUrl()), e)); } catch (Exception e) { fail(mChildCurrentLocation, new TaskException(TAG, - String.format("任务【%s】下载失败,filePath: %s, url: %s", getConfig().TEMP_FILE.getName(), + String.format("任务【%s】下载失败,filePath: %s, url: %s", getFileName(), getEntity().getDownloadPath(), getEntity().getUrl()), e)); } finally { try { @@ -160,7 +151,7 @@ final class HttpThreadTask extends AbsThreadTask { private void readChunked(InputStream is) { FileOutputStream fos = null; try { - fos = new FileOutputStream(getConfig().TEMP_FILE, true); + fos = new FileOutputStream(getConfig().tempFile, true); byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; while (isLive() && (len = is.read(buffer)) != -1) { @@ -177,7 +168,7 @@ final class HttpThreadTask extends AbsThreadTask { } catch (IOException e) { fail(mChildCurrentLocation, new AriaIOException(TAG, String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), - getConfig().URL), + getConfig().url), e)); } finally { if (fos != null) { @@ -199,7 +190,7 @@ final class HttpThreadTask extends AbsThreadTask { ReadableByteChannel fic = null; try { int len; - fos = new FileOutputStream(getConfig().TEMP_FILE, true); + fos = new FileOutputStream(getConfig().tempFile, true); foc = fos.getChannel(); fic = Channels.newChannel(is); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); @@ -212,8 +203,8 @@ final class HttpThreadTask extends AbsThreadTask { if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } - if (mChildCurrentLocation + len >= getConfig().END_LOCATION) { - len = (int) (getConfig().END_LOCATION - mChildCurrentLocation); + if (mChildCurrentLocation + len >= mRecord.endLocation) { + len = (int) (mRecord.endLocation - mChildCurrentLocation); bf.flip(); fos.write(bf.array(), 0, len); bf.compact(); @@ -230,7 +221,7 @@ final class HttpThreadTask extends AbsThreadTask { } catch (IOException e) { fail(mChildCurrentLocation, new AriaIOException(TAG, String.format("文件下载失败,savePath: %s, url: %s", getEntity().getDownloadPath(), - getConfig().URL), + getConfig().url), e)); } finally { try { @@ -284,36 +275,12 @@ final class HttpThreadTask extends AbsThreadTask { return; } - if (mChildCurrentLocation == getConfig().END_LOCATION) { - //支持断点的处理 - if (getConfig().SUPPORT_BP) { - ALog.i(TAG, - String.format("任务【%s】线程__%s__下载完毕", getConfig().TEMP_FILE.getName(), - getConfig().THREAD_ID)); - writeConfig(true, getConfig().END_LOCATION); - getState().COMPLETE_THREAD_NUM++; - if (getState().isComplete()) { - if (isBlock) { - boolean success = mergeFile(); - if (!success) { - mListener.onFail(false, new TaskException(TAG, - String.format("任务【%s】分块文件合并失败", getConfig().TEMP_FILE.getName()))); - return; - } - } - 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(); - } + //支持断点的处理 + if (mTaskWrapper.isSupportBP()) { + writeConfig(true, mRecord.endLocation); + sendCompleteMsg(); } else { - getState().FAIL_NUM++; + sendCompleteMsg(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java index eb5e05f2..31b1f2d1 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java @@ -29,7 +29,7 @@ import com.arialyy.aria.exception.BaseException; * Created by lyy on 2015/8/25. * D_HTTP\FTP单任务下载工具 */ -public class SimpleDownloadUtil implements IUtil, Runnable { +public class SimpleDownloadUtil implements IUtil { private String TAG = "SimpleDownloadUtil"; private IDownloadListener mListener; private Downloader mDownloader; @@ -84,7 +84,17 @@ public class SimpleDownloadUtil implements IUtil, Runnable { if (isStop || isCancel) { return; } - new Thread(this).start(); + mListener.onPre(); + // 如果网址没有变,而服务器端端文件改变,以下代码就没有用了 + //if (mTaskWrapper.getEntity().getFileSize() <= 1 + // || mTaskWrapper.isRefreshInfo() + // || mTaskWrapper.getRequestType() == AbsTaskWrapper.D_FTP + // || mTaskWrapper.getState() == IEntity.STATE_FAIL) { + // new Thread(createInfoThread()).start(); + //} else { + // mDownloader.start(); + //} + new Thread(createInfoThread()).start(); } @Override public void resume() { @@ -102,23 +112,6 @@ public class SimpleDownloadUtil implements IUtil, Runnable { mListener.onFail(needRetry, e); } - @Override public void run() { - mListener.onPre(); - if (isStop || isCancel) { - return; - } - // 如果网址没有变,而服务器端端文件改变,以下代码就没有用了 - //if (mTaskWrapper.getEntity().getFileSize() <= 1 - // || mTaskWrapper.isRefreshInfo() - // || mTaskWrapper.getRequestType() == AbsTaskWrapper.D_FTP - // || mTaskWrapper.getState() == IEntity.STATE_FAIL) { - // new Thread(createInfoThread()).start(); - //} else { - // mDownloader.start(); - //} - new Thread(createInfoThread()).start(); - } - /** * 通过链接类型创建不同的获取文件信息的线程 */ diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java index 17b6ea11..cfa518b9 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/group/DownloadGroupUtil.java @@ -25,6 +25,7 @@ import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.download.downloader.HttpFileInfoThread; import com.arialyy.aria.core.inf.AbsEntity; import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; import com.arialyy.aria.exception.AriaIOException; import com.arialyy.aria.exception.BaseException; import com.arialyy.aria.util.ALog; @@ -145,8 +146,10 @@ public class DownloadGroupUtil extends AbsGroupUtil implements IUtil { } else if (failCount == mGTWrapper.getSubTaskWrapper().size()) { mListener.onFail(true, new AriaIOException(TAG, "获取子任务长度失败")); } - - mGTWrapper.asHttp().setFileLenAdapter(null); + IHttpFileLenAdapter lenAdapter = mGTWrapper.asHttp().getFileLenAdapter(); + if (lenAdapter != null && lenAdapter.getClass().isAnonymousClass()) { + mGTWrapper.asHttp().setFileLenAdapter(null); + } synchronized (LOCK) { LOCK.notifyAll(); } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java index 76e057b6..c4500cab 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/group/FtpDirDownloadUtil.java @@ -43,7 +43,8 @@ public class FtpDirDownloadUtil extends AbsGroupUtil { if (mGTWrapper.getEntity().getFileSize() > 1) { startDownload(); } else { - new FtpDirInfoThread(mGTWrapper, new OnFileInfoCallback() { + + FtpDirInfoThread infoThread = new FtpDirInfoThread(mGTWrapper, new OnFileInfoCallback() { @Override public void onComplete(String url, CompleteInfo info) { if (info.code >= 200 && info.code < 300) { startDownload(); @@ -53,7 +54,8 @@ public class FtpDirDownloadUtil extends AbsGroupUtil { @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { mListener.onFail(needRetry, e); } - }).start(); + }); + new Thread(infoThread).start(); } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IM3U8UrlExtInfHandler.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IM3U8UrlExtInfHandler.java new file mode 100644 index 00000000..ee29d810 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IM3U8UrlExtInfHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.download.m3u8; + +import java.util.List; + +/** + * M3U8 #EXTINF 信息处理器 + */ +public interface IM3U8UrlExtInfHandler { + + /** + * 处理#EXTINF信息,对于某些服务器,返回的切片信息有可能是相对地址,因此,你需要自行转换为可下载http连接 + * + * @param extInf #EXTINF 切片信息列表 + * @return 根据切片信息转换后的http连接列表,如果你的切片信息是可以直接下载的http连接,直接返回extInf便可 + */ + List handler(List extInf); +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/ITargetHeadDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/LiveProtocol.java similarity index 67% rename from Aria/src/main/java/com/arialyy/aria/core/inf/ITargetHeadDelegate.java rename to Aria/src/main/java/com/arialyy/aria/core/download/m3u8/LiveProtocol.java index ee49769f..fed871e4 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/ITargetHeadDelegate.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/LiveProtocol.java @@ -1,24 +1,31 @@ -/* - * 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 AriaL on 2017/6/29. - * 处理任务头部信息等设置等接口 - */ -public interface ITargetHeadDelegate { - -} +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.download.m3u8; + +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * 直播协议 + */ +@IntDef({ + LiveProtocol.HLS +}) +@Retention(RetentionPolicy.SOURCE) +public @interface LiveProtocol { + int HLS = 1; +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java index a9c03ded..9c20ee70 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java @@ -16,20 +16,74 @@ package com.arialyy.aria.core.download.m3u8; import com.arialyy.aria.core.common.BaseDelegate; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.inf.AbsTarget; import com.arialyy.aria.core.inf.AbsTaskWrapper; +import com.arialyy.aria.util.ALog; +import java.io.File; /** * m3u8 委托 */ public class M3U8Delegate extends BaseDelegate { + private DTaskWrapper mTaskWrapper; public M3U8Delegate(TARGET target) { super(target); - mTarget.getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_FILE); + mTaskWrapper = (DTaskWrapper) mTarget.getTaskWrapper(); + mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_FILE); + String filePath = mTaskWrapper.getEntity().getFilePath(); + File file = new File(filePath); + if (!file.isDirectory()) { + mTaskWrapper.asM3U8().setCacheDir(file.getParent() + "/." + file.getName()); + } } - //public M3U8LiveDelegate asLive(){ - // return - //} + /** + * 设置文件长度,由于m3u8协议的特殊性,你需要设置文件长度才能获取到正确的下载进度百分比 + * + * @param fileSize 文件长度 + */ + public M3U8Delegate setFileSize(long fileSize) { + if (fileSize <= 0) { + ALog.e(TAG, "文件长度错误"); + return this; + } + mTaskWrapper.getEntity().setFileSize(fileSize); + return this; + } + + /** + * 如果你的#EXTINF信息是相对路径或有其它需求,你需要设置extinf处理器,将#EXTINF中的切片信息转换为可下载的http连接 + * + * @param handler {@link IM3U8UrlExtInfHandler} + */ + public M3U8Delegate setExtInfHandler(IM3U8UrlExtInfHandler handler) { + mTaskWrapper.asM3U8().setExtInfHandler(handler); + return this; + } + + /** + * 选择需要下载的码率,默认下载最大码率 + * + * @param bandWidth 指定的码率 + */ + public M3U8Delegate setBandWidth(int bandWidth) { + return this; + } + + /** + * 处理直播类的下载 + */ + public M3U8LiveDelegate asLive() { + return new M3U8LiveDelegate<>(mTarget); + } + + /** + * 处理需要解码的ts文件 + */ + public M3U8Delegate setDecodeAdapter() { + return this; + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileInfoThread.java new file mode 100644 index 00000000..daa06461 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileInfoThread.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.download.m3u8; + +import android.net.TrafficStats; +import android.os.Process; +import android.text.TextUtils; +import com.arialyy.aria.core.AriaManager; +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.common.OnFileInfoCallback; +import com.arialyy.aria.core.common.http.HttpTaskConfig; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.downloader.ConnectionHelp; +import com.arialyy.aria.exception.M3U8Exception; +import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CheckUtil; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * 解析url中获取到到m3u信息 + * https://www.cnblogs.com/renhui/p/10351870.html + */ +public class M3U8FileInfoThread implements Runnable { + private final String TAG = "M3U8FileInfoThread"; + private DownloadEntity mEntity; + private DTaskWrapper mTaskWrapper; + private int mConnectTimeOut; + private OnFileInfoCallback onFileInfoCallback; + private HttpTaskConfig mTaskDelegate; + + public M3U8FileInfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) { + this.mTaskWrapper = taskWrapper; + mEntity = taskWrapper.getEntity(); + mConnectTimeOut = + AriaManager.getInstance(AriaManager.APP).getDownloadConfig().getConnectTimeOut(); + onFileInfoCallback = callback; + mTaskDelegate = taskWrapper.asHttp(); + } + + @Override public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + TrafficStats.setThreadStatsTag(UUID.randomUUID().toString().hashCode()); + HttpURLConnection conn = null; + try { + URL url = ConnectionHelp.handleUrl(mEntity.getUrl(), mTaskDelegate); + conn = ConnectionHelp.handleConnection(url, mTaskDelegate); + ConnectionHelp.setConnectParam(mTaskDelegate, conn); + conn.setConnectTimeout(mConnectTimeOut); + conn.connect(); + handleConnect(conn); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + private void handleConnect(HttpURLConnection conn) throws IOException { + int code = conn.getResponseCode(); + if (code == HttpURLConnection.HTTP_OK) { + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String line = reader.readLine(); + if (TextUtils.isEmpty(line) || !line.equalsIgnoreCase("#EXTM3U")) { + failDownload("读取M3U8信息失败,读取不到#EXTM3U标签", false); + return; + } + List extInf = new ArrayList<>(); + boolean isDes = false; + while ((line = reader.readLine()) != null) { + if (line.startsWith("#EXT-X-ENDLIST")) { + break; + } + if (line.startsWith("#EXTINF")) { + isDes = true; + } else if (isDes) { + extInf.add(line); + isDes = false; + } + } + if (extInf.isEmpty()) { + failDownload("获取M3U8下载地址列表失败", false); + return; + } + CompleteInfo info = new CompleteInfo(); + info.obj = extInf; + onFileInfoCallback.onComplete(mEntity.getKey(), info); + } else if (code == HttpURLConnection.HTTP_MOVED_TEMP + || code == HttpURLConnection.HTTP_MOVED_PERM + || code == HttpURLConnection.HTTP_SEE_OTHER + || code == HttpURLConnection.HTTP_CREATED // 201 跳转 + || code == 307) { + handleUrlReTurn(conn, conn.getHeaderField("Location")); + } else if (code == HttpURLConnection.HTTP_NOT_FOUND) { + failDownload("404错误", false); + } else { + failDownload(String.format("不支持的响应,code: %s", code), true); + } + } + + /** + * 处理30x跳转 + */ + private void handleUrlReTurn(HttpURLConnection conn, String newUrl) throws IOException { + ALog.d(TAG, "30x跳转,新url为【" + newUrl + "】"); + if (TextUtils.isEmpty(newUrl) || newUrl.equalsIgnoreCase("null") || !newUrl.startsWith( + "http")) { + if (onFileInfoCallback != null) { + onFileInfoCallback.onFail(mEntity, new TaskException(TAG, "获取重定向链接失败"), false); + } + return; + } + if (!CheckUtil.checkUrlNotThrow(newUrl)) { + failDownload("下载失败,重定向url错误", false); + return; + } + mTaskDelegate.setRedirectUrl(newUrl); + mEntity.setRedirect(true); + mEntity.setRedirectUrl(newUrl); + String cookies = conn.getHeaderField("Set-Cookie"); + URL url = ConnectionHelp.handleUrl(newUrl, mTaskDelegate); + conn = ConnectionHelp.handleConnection(url, mTaskDelegate); + ConnectionHelp.setConnectParam(mTaskDelegate, conn); + conn.setRequestProperty("Cookie", cookies); + conn.setConnectTimeout(mConnectTimeOut); + conn.connect(); + handleConnect(conn); + conn.disconnect(); + } + + private void failDownload(String errorInfo, boolean needRetry) { + onFileInfoCallback.onFail(mEntity, new M3U8Exception(TAG, errorInfo), needRetry); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileLoader.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileLoader.java new file mode 100644 index 00000000..05b46f82 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileLoader.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.download.m3u8; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.arialyy.aria.core.common.AbsFileer; +import com.arialyy.aria.core.common.IThreadState; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.common.TaskRecord; +import com.arialyy.aria.core.common.ThreadRecord; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.inf.IEventListener; +import com.arialyy.aria.core.manager.ThreadTaskManager; +import com.arialyy.aria.exception.BaseException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.aria.util.FileUtil; +import com.arialyy.aria.util.IdGenerator; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * M3U8点播文件下载器 + */ +public class M3U8FileLoader extends AbsFileer { + /** + * 最大执行数 + */ + private static final int EXEC_MAX_NUM = 4; + private Handler mStateHandler; + private ArrayBlockingQueue mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM); + private M3U8ThreadStateManager mManager; + private ReentrantLock LOCK = new ReentrantLock(); + private Condition mCondition = LOCK.newCondition(); + + protected M3U8FileLoader(IEventListener listener, DTaskWrapper wrapper) { + super(listener, wrapper); + } + + @Override protected IThreadState getStateManager(Looper looper) { + mManager = new M3U8ThreadStateManager(looper, mRecord, mListener); + mStateHandler = new Handler(looper, mManager); + return mManager; + } + + @Override protected long delayTimer() { + return 2000; + } + + /** + * 获取ts文件保存路径 + * + * @param dirCache 缓存目录 + * @param threadId ts文件名 + */ + public static String getTsFilePath(String dirCache, int threadId) { + return String.format("%s/%s.ts", dirCache, threadId); + } + + private String getCacheDir() { + String cacheDir = mTaskWrapper.asM3U8().getCacheDir(); + if (!new File(cacheDir).exists()) { + CommonUtil.createDir(cacheDir); + } + return cacheDir; + } + + @Override protected void handleTask() { + + new Thread(new Runnable() { + @Override public void run() { + int index = 0; + while (!isBreak()) { + try { + LOCK.lock(); + while (mFlagQueue.size() < EXEC_MAX_NUM) { + if (index == mRecord.threadRecords.size()) { + break; + } + ThreadRecord tr = mRecord.threadRecords.get(index); + index++; + if (tr.isComplete) { + continue; + } + + M3U8ThreadTask task = createThreadTask(getCacheDir(), tr); + getTaskList().put(tr.threadId, task); + mFlagQueue.offer(startThreadTask(task)); + } + if (mFlagQueue.size() > 0) { + mCondition.await(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + LOCK.unlock(); + } + } + } + }).start(); + } + + private void notifyLock() { + try { + LOCK.lock(); + long id = mFlagQueue.take(); + ALog.d(TAG, String.format("线程【%s】完成", id)); + mCondition.signalAll(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + LOCK.unlock(); + } + } + + /** + * 启动线程任务 + * + * @return 线程唯一id标志 + */ + private long startThreadTask(M3U8ThreadTask task) { + ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task); + return IdGenerator.getInstance().nextId(); + } + + /** + * 配置config + */ + private M3U8ThreadTask createThreadTask(String cacheDir, ThreadRecord record) { + SubThreadConfig config = new SubThreadConfig<>(); + config.url = record.m3u8url; + config.tempFile = new File(getTsFilePath(cacheDir, record.threadId)); + config.isBlock = mRecord.isBlock; + config.isOpenDynamicFile = mRecord.isOpenDynamicFile; + config.taskWrapper = mTaskWrapper; + config.record = record; + config.stateHandler = mStateHandler; + if (!config.tempFile.exists()) { + CommonUtil.createFile(config.tempFile.getPath()); + } + return new M3U8ThreadTask(config); + } + + /** + * M3U8线程状态管理 + */ + private class M3U8ThreadStateManager implements IThreadState { + private final String TAG = "M3U8ThreadStateManager"; + + /** + * 任务状态回调 + */ + private IEventListener mListener; + private int mStartThreadNum; // 启动的线程总数 + private int mCancelNum = 0; // 已经取消的线程的数 + private int mStopNum = 0; // 已经停止的线程数 + private int mFailNum = 0; // 失败的线程数 + private int mCompleteNum = 0; // 完成的线程数 + private long mProgress; //当前总进度 + private TaskRecord mTaskRecord; // 任务记录 + private Looper mLooper; + + /** + * @param taskRecord 任务记录 + * @param listener 任务事件 + */ + M3U8ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) { + mLooper = looper; + mTaskRecord = taskRecord; + for (ThreadRecord record : taskRecord.threadRecords) { + if (!record.isComplete) { + mStartThreadNum++; + } + } + mListener = listener; + } + + /** + * 退出looper循环 + */ + private void quitLooper() { + ALog.d(TAG, "quitLooper"); + mLooper.quit(); + } + + @Override public boolean handleMessage(Message msg) { + switch (msg.what) { + case STATE_STOP: + mStopNum++; + if (isStop()) { + mListener.onStop(mProgress); + quitLooper(); + } + break; + case STATE_CANCEL: + mCancelNum++; + if (isCancel()) { + ALog.d(TAG, "icCancel"); + mListener.onCancel(); + quitLooper(); + } + break; + case STATE_FAIL: + mFailNum++; + if (isFail()) { + Bundle b = msg.getData(); + mListener.onFail(b.getBoolean(KEY_RETRY, true), + (BaseException) b.getSerializable(KEY_ERROR_INFO)); + quitLooper(); + } + break; + case STATE_COMPLETE: + mCompleteNum++; + ALog.d(TAG, "完成"); + notifyLock(); + if (isComplete()) { + ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum); + if (mTaskRecord.isBlock) { + if (mergeFile()) { + mListener.onComplete(); + } else { + mListener.onFail(false, null); + } + } else { + mListener.onComplete(); + } + quitLooper(); + } + break; + case STATE_RUNNING: + mProgress += (long) msg.obj; + break; + } + return false; + } + + @Override public boolean isStop() { + printInfo("isStop"); + return mStartThreadNum == mStopNum + mFailNum + mCompleteNum; + } + + @Override public boolean isFail() { + printInfo("isFail"); + return mStartThreadNum == mStopNum + mFailNum + mCompleteNum + mCancelNum; + } + + @Override public boolean isComplete() { + printInfo("isComplete"); + return mStartThreadNum == mCompleteNum; + } + + @Override public boolean isCancel() { + printInfo("isCancel"); + return mStartThreadNum == mCancelNum + mFailNum + mCompleteNum; + } + + @Override public long getCurrentProgress() { + return mProgress; + } + + private void printInfo(String tag) { + if (false) { + ALog.d(tag, String.format( + "startThreadNum = %s, stopNum = %s, cancelNum = %s, failNum = %s, completeNum = %s", + mStartThreadNum, mStopNum, mCancelNum, mFailNum, mCompleteNum)); + } + } + + /** + * 合并文件 + * + * @return {@code true} 合并成功,{@code false}合并失败 + */ + private boolean mergeFile() { + String cacheDir = getCacheDir(); + + List partPath = new ArrayList<>(); + + for (ThreadRecord tr : mTaskRecord.threadRecords) { + partPath.add(getTsFilePath(cacheDir, tr.threadId)); + } + + boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath); + if (isSuccess) { + for (String pp : partPath) { + File f = new File(pp); + if (f.exists()) { + f.delete(); + } + } + File targetFile = new File(mTaskRecord.filePath); + if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) { + ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s", + targetFile.getName(), targetFile.length(), mTaskRecord.fileLength)); + return false; + } + return true; + } else { + ALog.e(TAG, "合并失败"); + return false; + } + } + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileUtil.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileUtil.java new file mode 100644 index 00000000..4ff61193 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileUtil.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.download.m3u8; + +import com.arialyy.aria.core.common.CompleteInfo; +import com.arialyy.aria.core.common.IUtil; +import com.arialyy.aria.core.common.OnFileInfoCallback; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.inf.AbsEntity; +import com.arialyy.aria.core.inf.IDownloadListener; +import com.arialyy.aria.core.inf.ITaskWrapper; +import com.arialyy.aria.exception.BaseException; +import com.arialyy.aria.exception.M3U8Exception; +import com.arialyy.aria.util.ALog; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * M3U8点播文件下载工具 + * 工作流程: + * 1、创建一个和文件同父路径并且同名隐藏文件夹 + * 2、将所有m3u8的ts文件下载到该文件夹中 + * 3、完成所有分片下载后,合并ts文件 + * 4、删除该隐藏文件夹 + */ +public class M3U8FileUtil implements IUtil { + private final String TAG = "M3U8DownloadUtil"; + + private DTaskWrapper mWrapper; + private IDownloadListener mListener; + private boolean isStop = false, isCancel = false; + private List mUrls = new ArrayList<>(); + + public M3U8FileUtil(DTaskWrapper wrapper, IDownloadListener listener) { + mWrapper = wrapper; + mListener = listener; + } + + @Override public String getKey() { + return mWrapper.getKey(); + } + + @Override public long getFileSize() { + return 0; + } + + @Override public long getCurrentLocation() { + return 0; + } + + @Override public boolean isRunning() { + return false; + } + + @Override public void cancel() { + isCancel = true; + } + + @Override public void stop() { + isStop = true; + } + + @Override public void start() { + if (isStop || isCancel) { + return; + } + mListener.onPre(); + + new Thread(createInfoThread()).start(); + } + + @Override public void resume() { + start(); + } + + @Override public void setMaxSpeed(int speed) { + + } + + private Runnable createInfoThread() { + if (mWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) { + return new M3U8FileInfoThread(mWrapper, new OnFileInfoCallback() { + @Override public void onComplete(String key, CompleteInfo info) { + IM3U8UrlExtInfHandler handler = mWrapper.asM3U8().getExtInfHandler(); + if (handler != null) { + mUrls.addAll(handler.handler((List) info.obj)); + if (handler.getClass().isAnonymousClass()) { + mWrapper.asM3U8().setExtInfHandler(null); + } + } else { + mUrls.addAll((Collection) info.obj); + } + if (mUrls.isEmpty()) { + failDownload(new M3U8Exception(TAG, "获取地址失败"), false); + return; + } else if (!mUrls.get(0).startsWith("http")) { + failDownload(new M3U8Exception(TAG, "地址错误,请使用IM3U8UrlExtInfHandler处理你的url信息"), false); + return; + } + mWrapper.asM3U8().setUrls(mUrls); + new M3U8FileLoader(mListener, mWrapper).start(); + } + + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { + failDownload(e, needRetry); + } + }); + } else if (mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) { + return null; + } else { + ALog.e(TAG, "不支持的类型"); + return null; + } + } + + private void failDownload(BaseException e, boolean needRetry) { + if (isStop || isCancel) { + return; + } + mListener.onFail(needRetry, e); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java index 6ddcf18c..c5615006 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java @@ -15,22 +15,34 @@ */ package com.arialyy.aria.core.download.m3u8; +import com.arialyy.aria.core.common.BaseDelegate; +import com.arialyy.aria.core.inf.AbsTarget; +import com.arialyy.aria.core.inf.AbsTaskWrapper; + /** * m3u8直播参数设置 */ -public class M3U8LiveDelegate { - - M3U8LiveDelegate(M3U8Delegate delegate){ +public class M3U8LiveDelegate extends BaseDelegate { + M3U8LiveDelegate(TARGET target) { + super(target); + mTarget.getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE); } + /** * 设置缓存目录 + * * @param cacheDir 缓存目录路径 - * @return */ - public M3U8LiveDelegate setCacheDir(String cacheDir){ + public M3U8LiveDelegate setCacheDir(String cacheDir) { return this; } + /** + * 设置直播传输协议,{@link LiveProtocol} + */ + public M3U8LiveDelegate setProtocol(@LiveProtocol int protocol) { + return this; + } } diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java new file mode 100644 index 00000000..197ccfe4 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.download.m3u8; + +import java.util.List; + +/** + * m3u8任务配信息 + */ +public class M3U8TaskConfig { + + /** + * 所有ts文件的下载地址 + */ + private List urls; + + /** + * #EXTINF 标签信息处理器 + */ + private IM3U8UrlExtInfHandler extInfHandler; + + /** + * 缓存目录 + */ + private String cacheDir; + + public IM3U8UrlExtInfHandler getExtInfHandler() { + return extInfHandler; + } + + public void setExtInfHandler(IM3U8UrlExtInfHandler extInfHandler) { + this.extInfHandler = extInfHandler; + } + + public List getUrls() { + return urls; + } + + public void setUrls(List urls) { + this.urls = urls; + } + + public String getCacheDir() { + return cacheDir; + } + + public void setCacheDir(String cacheDir) { + this.cacheDir = cacheDir; + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadStateManager.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadStateManager.java new file mode 100644 index 00000000..580ed220 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadStateManager.java @@ -0,0 +1,140 @@ +///* +// * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +//package com.arialyy.aria.core.download.m3u8; +// +//import android.os.Bundle; +//import android.os.Looper; +//import android.os.Message; +//import com.arialyy.aria.core.common.IThreadState; +//import com.arialyy.aria.core.common.TaskRecord; +//import com.arialyy.aria.core.download.DTaskWrapper; +//import com.arialyy.aria.core.inf.IEventListener; +//import com.arialyy.aria.exception.BaseException; +//import com.arialyy.aria.util.ALog; +// +///** +// * M3U8线程状态管理 +// */ +//public class M3U8ThreadStateManager implements IThreadState { +// private final String TAG = "M3U8ThreadStateManager"; +// +// /** +// * 任务状态回调 +// */ +// private IEventListener mListener; +// private int mThreadNum; // 启动的线程总数 +// private int mCancelNum = 0; // 已经取消的线程的数 +// private int mStopNum = 0; // 已经停止的线程数 +// private int mFailNum = 0; // 失败的线程数 +// private int mCompleteNum = 0; // 完成的线程数 +// private long mProgress; //当前总进度 +// private TaskRecord mTaskRecord; // 任务记录 +// private Looper mLooper; +// +// /** +// * @param taskRecord 任务记录 +// * @param listener 任务事件 +// */ +// M3U8ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) { +// mLooper = looper; +// mTaskRecord = taskRecord; +// mThreadNum = mTaskRecord.threadNum; +// mListener = listener; +// } +// +// /** +// * 退出looper循环 +// */ +// private void quitLooper() { +// mLooper.quit(); +// } +// +// @Override public boolean handleMessage(Message msg) { +// switch (msg.what) { +// case STATE_STOP: +// mStopNum++; +// if (isStop()) { +// mListener.onStop(mProgress); +// quitLooper(); +// } +// break; +// case STATE_CANCEL: +// mCancelNum++; +// if (isCancel()) { +// ALog.d(TAG, "icCancel"); +// mListener.onCancel(); +// quitLooper(); +// } +// break; +// case STATE_FAIL: +// mFailNum++; +// if (isFail()) { +// Bundle b = msg.getData(); +// mListener.onFail(b.getBoolean(KEY_RETRY, true), +// (BaseException) b.getSerializable(KEY_ERROR_INFO)); +// quitLooper(); +// } +// break; +// case STATE_COMPLETE: +// mCompleteNum++; +// if (isComplete()) { +// ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum); +// if (mTaskRecord.isBlock) { +// if (mergeFile()) { +// mListener.onComplete(); +// } else { +// mListener.onFail(false, null); +// } +// } else { +// mListener.onComplete(); +// } +// quitLooper(); +// } +// break; +// case STATE_RUNNING: +// mProgress += (long) msg.obj; +// break; +// case STATE_UPDATE_PROGRESS: +// if (msg.obj == null) { +// mProgress = updateBlockProgress(); +// } else { +// mProgress = (long) msg.obj; +// } +// break; +// } +// return false; +// } +// +// @Override public boolean isStop() { +// return false; +// } +// +// @Override public boolean isFail() { +// return false; +// } +// +// @Override public boolean isComplete() { +// return false; +// } +// +// @Override public boolean isCancel() { +// return false; +// } +// +// @Override public long getCurrentProgress() { +// return mProgress; +// } +//} diff --git a/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java new file mode 100644 index 00000000..abeb9804 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadTask.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.core.download.m3u8; + +import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.RequestEnum; +import com.arialyy.aria.core.common.SubThreadConfig; +import com.arialyy.aria.core.common.http.HttpTaskConfig; +import com.arialyy.aria.core.config.DownloadConfig; +import com.arialyy.aria.core.download.DTaskWrapper; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.downloader.ConnectionHelp; +import com.arialyy.aria.exception.AriaIOException; +import com.arialyy.aria.exception.TaskException; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.BufferedRandomAccessFile; +import java.io.BufferedInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by lyy on 2017/1/18. 下载线程 + */ +final class M3U8ThreadTask extends AbsThreadTask { + private final String TAG = "HttpThreadTask"; + + M3U8ThreadTask(SubThreadConfig config) { + super(config); + } + + @Override public M3U8ThreadTask call() throws Exception { + super.call(); + if (mRecord.isComplete) { + handleComplete(); + return this; + } + HttpURLConnection conn = null; + BufferedInputStream is = null; + try { + HttpTaskConfig taskDelegate = getTaskWrapper().asHttp(); + URL url = ConnectionHelp.handleUrl(getConfig().url, taskDelegate); + conn = ConnectionHelp.handleConnection(url, taskDelegate); + ALog.d(TAG, String.format("分片【%s】开始下载", mRecord.threadId)); + ConnectionHelp.setConnectParam(taskDelegate, conn); + conn.setConnectTimeout(getTaskConfig().getConnectTimeOut()); + conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数 + if (taskDelegate.isChunked()) { + conn.setDoInput(true); + conn.setChunkedStreamingMode(0); + } + conn.connect(); + //Map> header = conn.getHeaderFields(); + //int code = conn.getResponseCode(); + //String msg = conn.getResponseMessage(); + // 传递参数 + if (taskDelegate.getRequestEnum() == RequestEnum.POST) { + Map params = taskDelegate.getParams(); + if (params != null) { + OutputStreamWriter dos = new OutputStreamWriter(conn.getOutputStream()); + Set keys = params.keySet(); + StringBuilder sb = new StringBuilder(); + for (String key : keys) { + sb.append(key).append("=").append(URLEncoder.encode(params.get(key))).append("&"); + } + String paramStr = sb.toString(); + paramStr = paramStr.substring(0, paramStr.length() - 1); + dos.write(paramStr); + dos.flush(); + dos.close(); + } + } + + is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); + if (taskDelegate.isChunked()) { + readChunked(is); + } else if (getConfig().isOpenDynamicFile) { + readDynamicFile(is); + } + } catch (MalformedURLException e) { + fail(mChildCurrentLocation, new TaskException(TAG, + String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId, + getConfig().tempFile.getPath(), getEntity().getUrl()), e)); + } catch (IOException e) { + fail(mChildCurrentLocation, new TaskException(TAG, + String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId, + getConfig().tempFile.getPath(), getEntity().getUrl()), e)); + } catch (Exception e) { + fail(mChildCurrentLocation, new TaskException(TAG, + String.format("分片【%s】下载失败,filePath: %s, url: %s", mRecord.threadId, + getConfig().tempFile.getPath(), getEntity().getUrl()), e)); + } finally { + try { + if (is != null) { + is.close(); + } + if (conn != null) { + conn.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return this; + } + + /** + * 读取chunked数据 + */ + private void readChunked(InputStream is) { + FileOutputStream fos = null; + try { + fos = new FileOutputStream(getConfig().tempFile, true); + byte[] buffer = new byte[getTaskConfig().getBuffSize()]; + int len; + while (isLive() && (len = is.read(buffer)) != -1) { + if (isBreak()) { + break; + } + if (mSpeedBandUtil != null) { + mSpeedBandUtil.limitNextBytes(len); + } + fos.write(buffer, 0, len); + progress(len); + } + handleComplete(); + } catch (IOException e) { + fail(mChildCurrentLocation, new AriaIOException(TAG, + String.format("文件下载失败,savePath: %s, url: %s", getConfig().tempFile.getPath(), + getConfig().url), + e)); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * 动态长度文件读取方式 + */ + private void readDynamicFile(InputStream is) { + FileOutputStream fos = null; + FileChannel foc = null; + ReadableByteChannel fic = null; + try { + int len; + fos = new FileOutputStream(getConfig().tempFile, true); + foc = fos.getChannel(); + fic = Channels.newChannel(is); + ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); + //如果要通过 Future 的 cancel 方法取消正在运行的任务,那么该任务必定是可以 对线程中断做出响应 的任务。 + + while (isLive() && (len = fic.read(bf)) != -1) { + if (isBreak()) { + break; + } + if (mSpeedBandUtil != null) { + mSpeedBandUtil.limitNextBytes(len); + } + bf.flip(); + foc.write(bf); + bf.compact(); + progress(len); + } + handleComplete(); + } catch (IOException e) { + fail(mChildCurrentLocation, new AriaIOException(TAG, + String.format("文件下载失败,savePath: %s, url: %s", getConfig().tempFile.getPath(), + getConfig().url), + e)); + } finally { + try { + if (fos != null) { + fos.flush(); + fos.close(); + } + if (foc != null) { + foc.close(); + } + if (fic != null) { + fic.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 处理完成配置文件的更新或事件回调 + */ + private void handleComplete() { + if (isBreak()) { + return; + } + if (getTaskWrapper().asHttp().isChunked()) { + sendCompleteMsg(); + return; + } + + //支持断点的处理 + if (mTaskWrapper.isSupportBP()) { + writeConfig(true, getConfig().tempFile.length()); + sendCompleteMsg(); + } else { + sendCompleteMsg(); + } + } + + @Override public int getMaxSpeed() { + return mAridManager.getDownloadConfig().getMaxSpeed(); + } + + @Override protected DownloadConfig getTaskConfig() { + return getTaskWrapper().getConfig(); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/IGroupTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/IGroupTarget.java deleted file mode 100644 index 48cdd758..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/IGroupTarget.java +++ /dev/null @@ -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(); -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/inf/INormalTarget.java b/Aria/src/main/java/com/arialyy/aria/core/inf/INormalTarget.java deleted file mode 100644 index 5fbb62be..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/inf/INormalTarget.java +++ /dev/null @@ -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(); -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/manager/DTaskWrapperFactory.java b/Aria/src/main/java/com/arialyy/aria/core/manager/DTaskWrapperFactory.java index b39ff432..e0625997 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/manager/DTaskWrapperFactory.java +++ b/Aria/src/main/java/com/arialyy/aria/core/manager/DTaskWrapperFactory.java @@ -15,7 +15,7 @@ */ package com.arialyy.aria.core.manager; -import com.arialyy.aria.core.common.AbsFileer; +import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.common.TaskRecord; import com.arialyy.aria.core.download.DTaskWrapper; import com.arialyy.aria.core.download.DownloadEntity; @@ -83,7 +83,7 @@ class DTaskWrapperFactory implements INormalTEFactory super(task, outHandler); } - @Override - protected void saveData(int state, long location) { - mTaskWrapper.setState(state); - mEntity.setState(state); - if (state == IEntity.STATE_CANCEL) { - int sType = getTask().getSchedulerType(); - if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { - mEntity.setComplete(false); - mEntity.setState(IEntity.STATE_WAIT); - CommonUtil.delTaskRecord(mEntity.getFilePath(), 2, mTaskWrapper.isRemoveFile(), false); - } 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); + @Override protected void handleCancel() { + int sType = getTask().getSchedulerType(); + if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) { + mEntity.setComplete(false); + mEntity.setState(IEntity.STATE_WAIT); + CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD, + mTaskWrapper.isRemoveFile(), false); + } else { + CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD, + mTaskWrapper.isRemoveFile(), true); } - mEntity.update(); } } \ No newline at end of file diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/UNormalDelegate.java b/Aria/src/main/java/com/arialyy/aria/core/upload/UNormalDelegate.java deleted file mode 100644 index ead46797..00000000 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/UNormalDelegate.java +++ /dev/null @@ -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 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; - } -} diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java index ac4bd597..06c7f3cc 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpFileInfoThread.java @@ -103,7 +103,10 @@ class FtpFileInfoThread extends AbsFtpInfoThread { e.printStackTrace(); return false; } finally { - mTaskWrapper.asFtp().setUploadInterceptor(null); + IFtpUploadInterceptor interceptor = mTaskWrapper.asFtp().getUploadInterceptor(); + if (interceptor != null && interceptor.getClass().isAnonymousClass()) { + mTaskWrapper.asFtp().setUploadInterceptor(null); + } } return true; diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java index ea56024c..85e91d51 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/FtpThreadTask.java @@ -19,16 +19,13 @@ import android.text.TextUtils; import aria.apache.commons.net.ftp.FTPClient; import aria.apache.commons.net.ftp.FTPReply; import aria.apache.commons.net.ftp.OnFtpInputStreamListener; -import com.arialyy.aria.core.common.StateConstance; import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.common.ftp.AbsFtpThreadTask; import com.arialyy.aria.core.common.ftp.FtpTaskConfig; import com.arialyy.aria.core.config.UploadConfig; -import com.arialyy.aria.core.inf.IEventListener; import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.exception.AriaIOException; -import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.util.ALog; import com.arialyy.aria.util.BufferedRandomAccessFile; import com.arialyy.aria.util.CommonUtil; @@ -42,9 +39,8 @@ class FtpThreadTask extends AbsFtpThreadTask { private final String TAG = "FtpThreadTask"; private String dir, remotePath; - FtpThreadTask(StateConstance constance, IEventListener listener, - SubThreadConfig info) { - super(constance, listener, info); + FtpThreadTask(SubThreadConfig config) { + super(config); } @Override public int getMaxSpeed() { @@ -57,14 +53,12 @@ class FtpThreadTask extends AbsFtpThreadTask { @Override public FtpThreadTask call() throws Exception { super.call(); - //当前子线程的下载位置 - mChildCurrentLocation = getConfig().START_LOCATION; FTPClient client = null; BufferedRandomAccessFile file = null; try { ALog.d(TAG, - String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getConfig().TEMP_FILE.getName(), - getConfig().THREAD_ID, getConfig().START_LOCATION, getConfig().END_LOCATION)); + String.format("任务【%s】线程__%s__开始上传【开始位置 : %s,结束位置:%s】", getFileName(), + mRecord.threadId, mRecord.startLocation, mRecord.endLocation)); client = createClient(); if (client == null) { return this; @@ -72,7 +66,7 @@ class FtpThreadTask extends AbsFtpThreadTask { initPath(); client.makeDirectory(dir); client.changeWorkingDirectory(dir); - client.setRestartOffset(getConfig().START_LOCATION); + client.setRestartOffset(mRecord.startLocation); int reply = client.getReplyCode(); if (!FTPReply.isPositivePreliminary(reply) && reply != FTPReply.FILE_ACTION_OK) { fail(mChildCurrentLocation, @@ -84,32 +78,22 @@ class FtpThreadTask extends AbsFtpThreadTask { } file = - new BufferedRandomAccessFile(getConfig().TEMP_FILE, "rwd", getTaskConfig().getBuffSize()); - if (getConfig().START_LOCATION != 0) { + new BufferedRandomAccessFile(getConfig().tempFile, "rwd", getTaskConfig().getBuffSize()); + if (mRecord.startLocation != 0) { //file.skipBytes((int) getConfig().START_LOCATION); - file.seek(getConfig().START_LOCATION); + file.seek(mRecord.startLocation); } boolean complete = upload(client, file); if (!complete || isBreak()) { return this; } - ALog.i(TAG, - String.format("任务【%s】线程__%s__上传完毕", getConfig().TEMP_FILE.getName(), - getConfig().THREAD_ID)); - writeConfig(true, getConfig().END_LOCATION); - getState().COMPLETE_THREAD_NUM++; - if (getState().isComplete()) { - sendCompleteMsg(); - } - if (getState().isFail()) { - mListener.onFail(false, new TaskException(TAG, - String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(), - getConfig().URL))); - } + ALog.i(TAG, String.format("任务【%s】线程__%s__上传完毕", getFileName(), mRecord.threadId)); + writeConfig(true, mRecord.endLocation); + sendCompleteMsg(); } catch (IOException e) { fail(mChildCurrentLocation, new AriaIOException(TAG, String.format("上传失败,filePath: %s, uploadUrl: %s", getEntity().getFilePath(), - getConfig().URL))); + getConfig().url))); } catch (Exception e) { fail(mChildCurrentLocation, new AriaIOException(TAG, null, e)); } finally { diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java index e4724188..8196d06e 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/HttpThreadTask.java @@ -16,11 +16,9 @@ package com.arialyy.aria.core.upload.uploader; import com.arialyy.aria.core.common.AbsThreadTask; -import com.arialyy.aria.core.common.StateConstance; import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.common.http.HttpTaskConfig; import com.arialyy.aria.core.config.UploadConfig; -import com.arialyy.aria.core.inf.IUploadListener; import com.arialyy.aria.core.upload.UTaskWrapper; import com.arialyy.aria.core.upload.UploadEntity; import com.arialyy.aria.exception.BaseException; @@ -51,9 +49,8 @@ class HttpThreadTask extends AbsThreadTask { private HttpURLConnection mHttpConn; private OutputStream mOutputStream; - HttpThreadTask(StateConstance constance, IUploadListener listener, - SubThreadConfig uploadInfo) { - super(constance, listener, uploadInfo); + HttpThreadTask(SubThreadConfig config) { + super(config); } @Override public HttpThreadTask call() throws Exception { @@ -65,7 +62,6 @@ class HttpThreadTask extends AbsThreadTask { getEntity().getUrl()))); return this; } - mListener.onPre(); URL url; try { url = new URL(getEntity().getUrl()); @@ -112,7 +108,7 @@ class HttpThreadTask extends AbsThreadTask { private void fail(BaseException e1) { try { - mListener.onFail(true, e1); + sendFailMsg(e1); if (mOutputStream != null) { mOutputStream.close(); } @@ -164,9 +160,9 @@ class HttpThreadTask extends AbsThreadTask { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { - getState().CURRENT_LOCATION += bytesRead; + progress(bytesRead); mOutputStream.write(buffer, 0, bytesRead); - if (getState().isCancel) { + if (isCancel) { break; } if (mSpeedBandUtil != null) { diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java index 8006667a..b6263dfe 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java @@ -54,19 +54,21 @@ public class SimpleUploadUtil implements IUtil, Runnable { mListener.onPre(); switch (mTaskWrapper.getRequestType()) { case AbsTaskWrapper.U_FTP: - new FtpFileInfoThread(mTaskWrapper, new OnFileInfoCallback() { - @Override public void onComplete(String url, CompleteInfo info) { - if (info.code == FtpFileInfoThread.CODE_COMPLETE) { - mListener.onComplete(); - } else { - mUploader.start(); - } - } + FtpFileInfoThread infoThread = + new FtpFileInfoThread(mTaskWrapper, new OnFileInfoCallback() { + @Override public void onComplete(String url, CompleteInfo info) { + if (info.code == FtpFileInfoThread.CODE_COMPLETE) { + mListener.onComplete(); + } else { + mUploader.start(); + } + } - @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { - mListener.onFail(needRetry, e); - } - }).start(); + @Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) { + mListener.onFail(needRetry, e); + } + }); + new Thread(infoThread).start(); break; case AbsTaskWrapper.U_HTTP: mUploader.start(); diff --git a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java index 076d5ed8..3770d30e 100644 --- a/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java +++ b/Aria/src/main/java/com/arialyy/aria/core/upload/uploader/Uploader.java @@ -16,8 +16,8 @@ package com.arialyy.aria.core.upload.uploader; import com.arialyy.aria.core.AriaManager; -import com.arialyy.aria.core.common.AbsFileer; import com.arialyy.aria.core.common.AbsThreadTask; +import com.arialyy.aria.core.common.NormalFileer; import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.inf.AbsTaskWrapper; import com.arialyy.aria.core.inf.IUploadListener; @@ -29,7 +29,7 @@ import java.io.File; * Created by Aria.Lao on 2017/7/27. * 文件上传器 */ -class Uploader extends AbsFileer { +class Uploader extends NormalFileer { Uploader(IUploadListener listener, UTaskWrapper taskEntity) { super(listener, taskEntity); @@ -42,20 +42,12 @@ class Uploader extends AbsFileer { return true; } - @Override protected int getType() { - return UPLOAD; - } - - @Override protected int setNewTaskThreadNum() { - return 1; - } - @Override protected AbsThreadTask selectThreadTask(SubThreadConfig config) { switch (mTaskWrapper.getRequestType()) { case AbsTaskWrapper.U_FTP: - return new FtpThreadTask(mConstance, mListener, config); + return new FtpThreadTask(config); case AbsTaskWrapper.U_HTTP: - return new HttpThreadTask(mConstance, (IUploadListener) mListener, config); + return new HttpThreadTask(config); } return null; } diff --git a/Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java b/Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java new file mode 100644 index 00000000..132841e5 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/exception/M3U8Exception.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arialyy.aria.exception; + +public class M3U8Exception extends BaseException{ + private static final String M3U8_EXCEPTION = "M3U8 Exception:"; + + public M3U8Exception(String tag, String message) { + super(tag, String.format("%s%s", M3U8_EXCEPTION, message)); + } + + public M3U8Exception(String tag, String message, Exception e){ + super(tag, message, e); + } +} diff --git a/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java b/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java index b83ca020..4745e668 100644 --- a/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java +++ b/Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java @@ -36,7 +36,7 @@ class DBConfig { static boolean DEBUG = false; static Map mapping = new HashMap<>(); static String DB_NAME; - static int VERSION = 46; + static int VERSION = 47; /** * 是否将数据库保存在Sd卡,{@code true} 是 diff --git a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java index 5b767adf..0dc3801e 100644 --- a/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java +++ b/Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java @@ -31,7 +31,7 @@ import com.arialyy.aria.core.command.group.AbsGroupCmd; import com.arialyy.aria.core.command.group.GroupCmdFactory; import com.arialyy.aria.core.command.normal.AbsNormalCmd; import com.arialyy.aria.core.command.normal.NormalCmdFactory; -import com.arialyy.aria.core.common.AbsFileer; +import com.arialyy.aria.core.common.RecordHandler; import com.arialyy.aria.core.common.RecordWrapper; import com.arialyy.aria.core.common.TaskRecord; import com.arialyy.aria.core.common.ThreadRecord; @@ -99,7 +99,7 @@ public class CommonUtil { * @return {@code true} 分块文件存在 */ public static boolean blockTaskExists(String filePath) { - return new File(String.format(AbsFileer.SUB_PATH, filePath, 0)).exists(); + return new File(String.format(RecordHandler.SUB_PATH, filePath, 0)).exists(); } /** @@ -604,7 +604,8 @@ public class CommonUtil { /** * 删除任务组记录 * - * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 {@code false}如果任务已经完成,只删除任务数据库记录 + * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; + * {@code false} 如果任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 */ public static void delGroupTaskRecord(String groupHash, boolean removeFile) { if (TextUtils.isEmpty(groupHash)) { @@ -619,7 +620,8 @@ public class CommonUtil { /** * 删除任务组记录 * - * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 {@code false}如果任务已经完成,只删除任务数据库记录 + * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; + * {@code false} 如果任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 */ public static void delGroupTaskRecord(DownloadGroupEntity groupEntity, boolean removeFile, boolean removeEntity) { @@ -641,7 +643,7 @@ public class CommonUtil { if (record.taskRecord.isBlock) { for (int i = 0, len = record.taskRecord.threadNum; i < len; i++) { File partFile = - new File(String.format(AbsFileer.SUB_PATH, record.taskRecord.filePath, i)); + new File(String.format(RecordHandler.SUB_PATH, record.taskRecord.filePath, i)); if (partFile.exists()) { partFile.delete(); } @@ -655,13 +657,14 @@ public class CommonUtil { List subs = groupEntity.getSubEntities(); if (subs != null) { for (DownloadEntity sub : subs) { - File file = new File(sub.getDownloadPath()); + File file = new File(sub.getFilePath()); if (file.exists() && (removeFile || !sub.isComplete())) { file.delete(); } } } + // 删除文件夹 if (!TextUtils.isEmpty(groupEntity.getDirPath())) { File dir = new File(groupEntity.getDirPath()); if (dir.exists() && (removeFile || !groupEntity.isComplete())) { @@ -677,17 +680,19 @@ public class CommonUtil { /** * 删除任务记录,默认删除任务实体 * - * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经完成的文件 {@code false}如果任务已经完成,只删除任务数据库记录 + * @param removeFile{@code true} 无论任务是否完成,都会删除记录和文件; + * {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 + * 如果是上传任务,无论任务是否完成,都只删除记录 */ public static void delTaskRecord(AbsNormalEntity dEntity, boolean removeFile) { if (dEntity == null) return; String filePath; int type; if (dEntity instanceof DownloadEntity) { - type = 1; + type = RecordHandler.TYPE_DOWNLOAD; filePath = ((DownloadEntity) dEntity).getDownloadPath(); } else if (dEntity instanceof UploadEntity) { - type = 2; + type = RecordHandler.TYPE_UPLOAD; filePath = ((UploadEntity) dEntity).getFilePath(); } else { ALog.w(TAG, "删除记录失败,未知类型"); @@ -700,8 +705,11 @@ public class CommonUtil { * 删除任务记录,默认删除文件 * * @param filePath 文件路径 - * @param removeFile {@code true} 不仅删除任务数据库记录,还会删除已经删除完成的文件 {@code false}如果任务已经完成,只删除任务数据库记录 - * @param type {@code 1}下载任务的记录,{@code 2} 上传任务的记录 {@code false}如果任务已经完成,只删除任务数据库记录 + * @param removeFile {@code true} 无论任务是否完成,都会删除记录和文件; + * {@code false} 如果是下载任务,并且任务已经完成,则只删除记录,不删除文件;任务未完成,记录和文件都会删除。 + * 如果是上传任务,无论任务是否完成,都只删除记录 + * @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link RecordHandler#TYPE_UPLOAD} + * 上传任务的记录 * @param removeEntity {@code true} 删除任务实体, */ public static void delTaskRecord(String filePath, int type, boolean removeFile, @@ -714,33 +722,45 @@ public class CommonUtil { } List recordWrapper = DbEntity.findRelationData(RecordWrapper.class, "filePath=?", filePath); - DbEntity.deleteData(ThreadRecord.class, "key=?", filePath); // 必须先获取完成数据再删除线程记录 File file = new File(filePath); if (recordWrapper == null || recordWrapper.isEmpty() || recordWrapper.get(0) == null || recordWrapper.get(0).taskRecord == null) { - ALog.w(TAG, "记录为空"); + ALog.w(TAG, String.format("记录为空,filePath: %s", filePath)); } else { TaskRecord record = recordWrapper.get(0).taskRecord; // 删除分块文件 if (record.isBlock) { for (int i = 0, len = record.threadNum; i < len; i++) { - File partFile = new File(String.format(AbsFileer.SUB_PATH, record.filePath, i)); + File partFile = new File(String.format(RecordHandler.SUB_PATH, record.filePath, i)); if (partFile.exists()) { partFile.delete(); } } + } else if (type == RecordHandler.TYPE_DOWNLOAD) { // 处理单线程任务没有完成的情况 + if (record.threadRecords != null && !record.threadRecords.isEmpty()) { + ThreadRecord tr = record.threadRecords.get(0); + if (!tr.isComplete && file.exists()) { + file.delete(); + } + } else { + if (file.exists()) { + file.delete(); + } + } + } else { // 处理上传的情况 + if (file.exists() && removeFile) { + file.delete(); + } } + // 删除任务记录 + DbEntity.deleteData(ThreadRecord.class, "key=?", filePath); // 必须先获取完成数据再删除线程记录 record.deleteData(); } - if (file.exists() && removeFile) { - file.delete(); - } - if (removeEntity) { if (type == 1) { DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath); @@ -754,7 +774,8 @@ public class CommonUtil { * 删除任务记录,默认删除文件,删除任务实体 * * @param filePath 文件路径 - * @param type {@code 1}下载任务的记录,{@code 2} 上传任务的记录 {@code false}如果任务已经完成,只删除任务数据库记录 + * @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录,{@link RecordHandler#TYPE_UPLOAD} + * 上传任务的记录 */ public static void delTaskRecord(String filePath, int type) { delTaskRecord(filePath, type, false, true); @@ -1208,9 +1229,9 @@ public class CommonUtil { if (record.threadRecords != null && !record.threadRecords.isEmpty()) { for (ThreadRecord tr : record.threadRecords) { tr.key = newPath; - File blockFile = new File(String.format(AbsFileer.SUB_PATH, oldPath, tr.threadId)); + File blockFile = new File(String.format(RecordHandler.SUB_PATH, oldPath, tr.threadId)); if (blockFile.exists()) { - blockFile.renameTo(new File(String.format(AbsFileer.SUB_PATH, newPath, tr.threadId))); + blockFile.renameTo(new File(String.format(RecordHandler.SUB_PATH, newPath, tr.threadId))); } } DbEntity.updateManyData(record.threadRecords); diff --git a/Aria/src/main/java/com/arialyy/aria/util/IdGenerator.java b/Aria/src/main/java/com/arialyy/aria/util/IdGenerator.java new file mode 100644 index 00000000..1fca0ac8 --- /dev/null +++ b/Aria/src/main/java/com/arialyy/aria/util/IdGenerator.java @@ -0,0 +1,79 @@ +package com.arialyy.aria.util; + +public class IdGenerator { + /** + * SnowFlake算法 64位Long类型生成唯一ID 第一位0,表明正数 2-42,41位,表示毫秒时间戳差值,起始值自定义 + * 43-52,10位,机器编号,5位数据中心编号,5位进程编号 53-64,12位,毫秒内计数器 本机内存生成,性能高 + *

+ * 主要就是三部分: 时间戳,进程id,序列号 时间戳41,id10位,序列号12位 + * + * @author chiwei + * @param args + * @since JDK 1.6 + */ + + private static volatile IdGenerator INSTANCE = null; + + private final static long beginTs = 1483200000000L; + + private long lastTs = 0L; + + private long processId; + private int processIdBits = 10; + + private long sequence = 0L; + private int sequenceBits = 12; + + private IdGenerator() { + + } + + public static synchronized IdGenerator getInstance() { + if (INSTANCE == null) { + INSTANCE = new IdGenerator(); + } + return INSTANCE; + } + + // 10位进程ID标识 + public IdGenerator(long processId) { + if (processId > ((1 << processIdBits) - 1)) { + throw new RuntimeException("进程ID超出范围,设置位数" + processIdBits + ",最大" + + ((1 << processIdBits) - 1)); + } + this.processId = processId; + } + + private long timeGen() { + return System.currentTimeMillis(); + } + + public synchronized long nextId() { + long ts = timeGen(); + if (ts < lastTs) {// 刚刚生成的时间戳比上次的时间戳还小,出错 + throw new RuntimeException("时间戳顺序错误"); + } + if (ts == lastTs) {// 刚刚生成的时间戳跟上次的时间戳一样,则需要生成一个sequence序列号 + // sequence循环自增 + sequence = (sequence + 1) & ((1 << sequenceBits) - 1); + // 如果sequence=0则需要重新生成时间戳 + if (sequence == 0) { + // 且必须保证时间戳序列往后 + ts = nextTs(lastTs); + } + } else {// 如果ts>lastTs,时间戳序列已经不同了,此时可以不必生成sequence了,直接取0 + sequence = 0L; + } + lastTs = ts;// 更新lastTs时间戳 + return ((ts - beginTs) << (processIdBits + sequenceBits)) | (processId << sequenceBits) + | sequence; + } + + private long nextTs(long lastTs) { + long ts = timeGen(); + while (ts <= lastTs) { + ts = timeGen(); + } + return ts; + } +} diff --git a/DEV_LOG.md b/DEV_LOG.md index e427b920..01263b66 100644 --- a/DEV_LOG.md +++ b/DEV_LOG.md @@ -2,6 +2,7 @@ + v_3.6.5 - fix bug https://github.com/AriaLyy/Aria/issues/403 - 新增ftp上传拦截器 https://github.com/AriaLyy/Aria/issues/402 + - 重构线程任务模块 + v_3.6.4 (2019/5/16) - 优化任务接收器的代码结构 - 修复`DbEntity.saveAll()`失败的问题 diff --git a/app/build.gradle b/app/build.gradle index eaf22fc7..83893668 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,8 +50,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:${rootProject.ext.kotlin_version}" api project(':Aria') kapt project(':AriaCompiler') - // compile 'com.arialyy.aria:aria-core:3.4.0' - // annotationProcessor 'com.arialyy.aria:aria-compiler:3.4' +// api 'com.arialyy.aria:aria-core:3.6.5_dev_4' +// kapt 'com.arialyy.aria:aria-compiler:3.6.5_dev_4' // compile 'com.arialyy.aria:aria-core:3.3.14' // annotationProcessor 'com.arialyy.aria:aria-compiler:3.3.14' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6b7eedf0..e4beab9d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,9 +44,10 @@ + diff --git a/app/src/main/java/com/arialyy/simple/MainActivity.java b/app/src/main/java/com/arialyy/simple/MainActivity.java index 703cf4aa..f9f6c36f 100644 --- a/app/src/main/java/com/arialyy/simple/MainActivity.java +++ b/app/src/main/java/com/arialyy/simple/MainActivity.java @@ -17,15 +17,16 @@ package com.arialyy.simple; import android.Manifest; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; -import android.content.Intent; import android.os.Build; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatImageView; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; import android.view.View; import android.widget.TextView; import com.arialyy.frame.permission.OnPermissionCallback; @@ -35,6 +36,7 @@ import com.arialyy.simple.base.BaseActivity; import com.arialyy.simple.base.adapter.AbsHolder; import com.arialyy.simple.base.adapter.AbsRVAdapter; import com.arialyy.simple.base.adapter.RvItemClickSupport; +import com.arialyy.simple.core.download.m3u8.M3U8DownloadActivity; import com.arialyy.simple.databinding.ActivityMainBinding; import com.arialyy.simple.core.download.DownloadActivity; import com.arialyy.simple.core.download.FtpDownloadActivity; @@ -44,6 +46,7 @@ import com.arialyy.simple.core.upload.FtpUploadActivity; import com.arialyy.simple.core.upload.HttpUploadActivity; import com.arialyy.simple.modlue.CommonModule; import com.arialyy.simple.to.NormalTo; +import java.util.ArrayList; import java.util.List; /** @@ -60,33 +63,53 @@ public class MainActivity extends BaseActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(false); getBinding().list.setLayoutManager(new LinearLayoutManager(this)); - final List data = getModule(CommonModule.class).getMainData(); - getBinding().list.setAdapter( - new Adapter(this, data)); + final List data = new ArrayList<>(); + final Adapter adapter = new Adapter(this, data); + getBinding().list.setAdapter(adapter); getBinding().list.addItemDecoration( new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + + final CommonModule module = ViewModelProviders.of(this).get(CommonModule.class); + module.getMainData(this).observe(this, new Observer>() { + @Override public void onChanged(@Nullable List normalTos) { + if (normalTos != null) { + data.addAll(normalTos); + adapter.notifyDataSetChanged(); + } + } + }); + RvItemClickSupport.addTo(getBinding().list).setOnItemClickListener( new RvItemClickSupport.OnItemClickListener() { @Override public void onItemClicked(RecyclerView recyclerView, int position, View v) { - CommonModule module = getModule(CommonModule.class); switch (position) { case 0: - module.startNextActivity(data.get(position), DownloadActivity.class); + module.startNextActivity(MainActivity.this, data.get(position), + DownloadActivity.class); break; case 1: - module.startNextActivity(data.get(position), HttpUploadActivity.class); + module.startNextActivity(MainActivity.this, data.get(position), + HttpUploadActivity.class); break; case 2: - module.startNextActivity(data.get(position), DownloadGroupActivity.class); + module.startNextActivity(MainActivity.this, data.get(position), + DownloadGroupActivity.class); break; case 3: - module.startNextActivity(data.get(position), FtpDownloadActivity.class); + module.startNextActivity(MainActivity.this, data.get(position), + FtpDownloadActivity.class); break; case 4: - module.startNextActivity(data.get(position), FTPDirDownloadActivity.class); + module.startNextActivity(MainActivity.this, data.get(position), + FTPDirDownloadActivity.class); break; case 5: - module.startNextActivity(data.get(position), FtpUploadActivity.class); + module.startNextActivity(MainActivity.this, data.get(position), + FtpUploadActivity.class); + break; + case 6: + module.startNextActivity(MainActivity.this, data.get(position), + M3U8DownloadActivity.class); break; } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java index 43d32d7b..8b0516c3 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/DownloadActivity.java @@ -16,14 +16,14 @@ package com.arialyy.simple.core.download; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; -import android.content.Intent; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v7.widget.AppCompatImageView; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.Gravity; import android.view.View; import android.widget.TextView; import com.arialyy.simple.MainActivity; @@ -33,11 +33,10 @@ import com.arialyy.simple.base.adapter.AbsHolder; import com.arialyy.simple.base.adapter.AbsRVAdapter; import com.arialyy.simple.base.adapter.RvItemClickSupport; import com.arialyy.simple.databinding.ActivityDownloadMeanBinding; -import com.arialyy.simple.core.download.fragment_download.FragmentActivity; import com.arialyy.simple.core.download.multi_download.MultiTaskActivity; -import com.arialyy.simple.core.download.service_download.DownloadService; import com.arialyy.simple.modlue.CommonModule; import com.arialyy.simple.to.NormalTo; +import java.util.ArrayList; import java.util.List; /** @@ -54,25 +53,42 @@ public class DownloadActivity extends BaseActivity super.init(savedInstanceState); mTo = getIntent().getParcelableExtra(MainActivity.KEY_MAIN_DATA); setTitle(mTo.title); - final List data = getModule(CommonModule.class).getDownloadData(); + + final List data = new ArrayList<>(); getBinding().list.setLayoutManager(new GridLayoutManager(this, 2)); - getBinding().list.setAdapter(new Adapter(this, data)); + final Adapter adapter = new Adapter(this, data); + getBinding().list.setAdapter(adapter); + final CommonModule module = ViewModelProviders.of(this).get(CommonModule.class); + + module.getDownloadData(this).observe(this, + new Observer>() { + @Override public void onChanged(@Nullable List normalTos) { + if (normalTos != null) { + data.addAll(normalTos); + adapter.notifyDataSetChanged(); + } + } + }); + RvItemClickSupport.addTo(getBinding().list).setOnItemClickListener( new RvItemClickSupport.OnItemClickListener() { @Override public void onItemClicked(RecyclerView recyclerView, int position, View v) { - CommonModule module = getModule(CommonModule.class); switch (position) { case 0: - module.startNextActivity(data.get(position), SingleTaskActivity.class); + module.startNextActivity(DownloadActivity.this, data.get(position), + SingleTaskActivity.class); break; case 1: - module.startNextActivity(data.get(position), MultiTaskActivity.class); + module.startNextActivity(DownloadActivity.this, data.get(position), + MultiTaskActivity.class); break; case 2: - module.startNextActivity(data.get(position), HighestPriorityActivity.class); + module.startNextActivity(DownloadActivity.this, data.get(position), + HighestPriorityActivity.class); break; case 3: - module.startNextActivity(data.get(position), KotlinDownloadActivity.class); + module.startNextActivity(DownloadActivity.this, data.get(position), + KotlinDownloadActivity.class); break; case 4: break; @@ -81,41 +97,6 @@ public class DownloadActivity extends BaseActivity }); } - //public void onClick(View view) { - // switch (view.getId()) { - // case R.id.highest_priority: - // startActivity(new Intent(this, HighestPriorityActivity.class)); - // break; - // case R.id.service: - // startService(new Intent(this, DownloadService.class)); - // break; - // case R.id.single_task: - // startActivity(new Intent(this, SingleTaskActivity.class)); - // break; - // case R.id.multi_task: - // startActivity(new Intent(this, MultiTaskActivity.class)); - // break; - // case R.id.dialog_task: - // DownloadDialog dialog = new DownloadDialog(this); - // - // dialog.show(); - // //DownloadDialogFragment dialog = new DownloadDialogFragment(this); - // //dialog.show(getSupportFragmentManager(), "dialog"); - // break; - // case R.id.pop_task: - // DownloadPopupWindow pop = new DownloadPopupWindow(this); - // pop.showAtLocation(mRootView, Gravity.CENTER_VERTICAL, 0, 0); - // break; - // case R.id.fragment_task: - // startActivity(new Intent(this, FragmentActivity.class)); - // break; - // case R.id.notification: - // //SimpleNotification notification = new SimpleNotification(this); - // //notification.start(); - // break; - // } - //} - private static class Adapter extends AbsRVAdapter { Adapter(Context context, List data) { diff --git a/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java b/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java index 01199ded..30ea9537 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java +++ b/app/src/main/java/com/arialyy/simple/core/download/SingleTaskActivity.java @@ -47,6 +47,7 @@ import com.arialyy.simple.common.ModifyUrlDialog; import com.arialyy.simple.databinding.ActivitySingleBinding; import com.arialyy.simple.util.AppUtil; +import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; @@ -201,6 +202,7 @@ public class SingleTaskActivity extends BaseActivity { @Download.onTaskRunning protected void running(DownloadTask task) { if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "isRunning"); //Log.d(TAG, task.getKey()); long len = task.getFileSize(); if (len == 0) { @@ -248,13 +250,13 @@ public class SingleTaskActivity extends BaseActivity { @Download.onTaskComplete void taskComplete(DownloadTask task) { - if (task.getKey().equals(mUrl)) { getBinding().setProgress(100); Toast.makeText(SingleTaskActivity.this, getString(R.string.download_success), Toast.LENGTH_SHORT).show(); getBinding().setStateStr(getString(R.string.re_start)); getBinding().setSpeed(""); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getDownloadPath()))); } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8DownloadActivity.java b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8DownloadActivity.java new file mode 100644 index 00000000..e297c650 --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8DownloadActivity.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.simple.core.download.m3u8; + +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; +import com.arialyy.annotations.Download; +import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.core.download.DownloadTarget; +import com.arialyy.aria.core.download.DownloadTask; +import com.arialyy.aria.core.inf.IEntity; +import com.arialyy.aria.core.inf.IHttpFileLenAdapter; +import com.arialyy.aria.util.ALog; +import com.arialyy.aria.util.CommonUtil; +import com.arialyy.frame.util.show.T; +import com.arialyy.simple.R; +import com.arialyy.simple.base.BaseActivity; +import com.arialyy.simple.common.ModifyPathDialog; +import com.arialyy.simple.common.ModifyUrlDialog; +import com.arialyy.simple.databinding.ActivityM3u8Binding; +import java.io.File; +import java.util.List; +import java.util.Map; + +public class M3U8DownloadActivity extends BaseActivity { + + private String mUrl; + private String mFilePath; + private M3U8Module mModule; + private DownloadTarget mTarget; + + @Override + protected void init(Bundle savedInstanceState) { + super.init(savedInstanceState); + setTitle("单任务下载"); + Aria.download(this).register(); + mModule = ViewModelProviders.of(this).get(M3U8Module.class); + mModule.getHttpDownloadInfo(this).observe(this, new Observer() { + + @Override public void onChanged(@Nullable DownloadEntity entity) { + if (entity == null) { + return; + } + mTarget = Aria.download(M3U8DownloadActivity.this).load(entity.getUrl()); + if (mTarget.getTaskState() == IEntity.STATE_STOP) { + getBinding().setStateStr(getString(R.string.resume)); + } else if (mTarget.isRunning()) { + getBinding().setStateStr(getString(R.string.stop)); + } + + if (entity.getFileSize() != 0) { + getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize())); + getBinding().setProgress(entity.isComplete() ? 100 + : (int) (entity.getCurrentProgress() * 100 / entity.getFileSize())); + } + getBinding().setUrl(entity.getUrl()); + getBinding().setFilePath(entity.getFilePath()); + mUrl = entity.getUrl(); + mFilePath = entity.getFilePath(); + } + }); + getBinding().setViewModel(this); + } + + public void chooseUrl() { + ModifyUrlDialog dialog = + new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl); + dialog.show(getSupportFragmentManager(), "ModifyUrlDialog"); + } + + public void chooseFilePath() { + ModifyPathDialog dialog = + new ModifyPathDialog(this, getString(R.string.modify_file_path), mFilePath); + dialog.show(getSupportFragmentManager(), "ModifyPathDialog"); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_single_task_activity, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + int speed = -1; + String msg = ""; + switch (item.getItemId()) { + case R.id.help: + msg = "一些小知识点:\n" + + "1、你可以在注解中增加链接,用于指定被注解的方法只能被特定的下载任务回调,以防止progress乱跳\n" + + "2、当遇到网络慢的情况时,你可以先使用onPre()更新UI界面,待连接成功时,再在onTaskPre()获取完整的task数据,然后给UI界面设置正确的数据\n" + + "3、你可以在界面初始化时通过Aria.download(this).load(URL).getPercent()等方法快速获取相关任务的一些数据"; + showMsgDialog("tip", msg); + break; + case R.id.speed_0: + speed = 0; + break; + case R.id.speed_128: + speed = 128; + break; + case R.id.speed_256: + speed = 256; + break; + case R.id.speed_512: + speed = 512; + break; + case R.id.speed_1m: + speed = 1024; + break; + } + if (speed > -1) { + msg = item.getTitle().toString(); + Aria.download(this).setMaxSpeed(speed); + T.showShort(this, msg); + } + return true; + } + + @Download.onWait + void onWait(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + Log.d(TAG, "wait ==> " + task.getDownloadEntity().getFileName()); + } + } + + @Download.onPre + protected void onPre(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().setStateStr(getString(R.string.stop)); + } + } + + @Download.onTaskStart + void taskStart(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().setFileSize(task.getConvertFileSize()); + ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState()); + } + } + + @Download.onTaskRunning + protected void running(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + ALog.d(TAG, "isRunning"); + //Log.d(TAG, task.getKey()); + long len = task.getFileSize(); + if (len == 0) { + getBinding().setProgress(0); + } else { + getBinding().setProgress(task.getPercent()); + } + getBinding().setSpeed(task.getConvertSpeed()); + } + } + + @Download.onTaskResume + void taskResume(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().setStateStr(getString(R.string.stop)); + } + } + + @Download.onTaskStop + void taskStop(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().setStateStr(getString(R.string.resume)); + getBinding().setSpeed(""); + } + } + + @Download.onTaskCancel + void taskCancel(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().setProgress(0); + getBinding().setStateStr(getString(R.string.start)); + getBinding().setSpeed(""); + Log.d(TAG, "cancel"); + } + } + + @Download.onTaskFail + void taskFail(DownloadTask task, Exception e) { + if (task.getKey().equals(mUrl)) { + Toast.makeText(M3U8DownloadActivity.this, getString(R.string.download_fail), + Toast.LENGTH_SHORT) + .show(); + getBinding().setStateStr(getString(R.string.start)); + } + } + + @Download.onTaskComplete + void taskComplete(DownloadTask task) { + if (task.getKey().equals(mUrl)) { + getBinding().setProgress(100); + Toast.makeText(M3U8DownloadActivity.this, getString(R.string.download_success), + Toast.LENGTH_SHORT).show(); + getBinding().setStateStr(getString(R.string.re_start)); + getBinding().setSpeed(""); + ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getDownloadPath()))); + } + } + + @Override + protected int setLayoutId() { + return R.layout.activity_m3u8; + } + + public void onClick(View view) { + switch (view.getId()) { + case R.id.start: + if (mTarget.isRunning()) { + Aria.download(this).load(mUrl).stop(); + } else { + startD(); + } + break; + case R.id.cancel: + Aria.download(this).load(mUrl).cancel(true); + break; + } + } + + private void startD() { + Aria.download(M3U8DownloadActivity.this) + .load(mUrl) + .useServerFileName(true) + .setFilePath(mFilePath, true) + .asM3U8() + .start(); + } + + @Override + protected void onStop() { + super.onStop(); + //Aria.download(this).unRegister(); + } + + @Override protected void dataCallback(int result, Object data) { + super.dataCallback(result, data); + if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { + mModule.uploadUrl(this, String.valueOf(data)); + } else if (result == ModifyPathDialog.MODIFY_PATH_RESULT) { + mModule.updateFilePath(this, String.valueOf(data)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8Module.java b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8Module.java new file mode 100644 index 00000000..ff0aaa7f --- /dev/null +++ b/app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8Module.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.simple.core.download.m3u8; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.content.Context; +import android.os.Environment; +import android.text.TextUtils; +import com.arialyy.aria.core.Aria; +import com.arialyy.aria.core.download.DownloadEntity; +import com.arialyy.aria.util.ALog; +import com.arialyy.frame.base.BaseViewModule; +import com.arialyy.simple.util.AppUtil; +import java.io.File; + +public class M3U8Module extends BaseViewModule { + private final String M3U8_URL_KEY = "M3U8_URL_KEY"; + private final String M3U8_PATH_KEY = "M3U8_PATH_KEY"; + + private final String defUrl = "https://www.gaoya123.cn/2019/1557993797897.m3u8"; + private final String filePath = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath() + + "/1557993797897.m3u8"; + + private MutableLiveData liveData = new MutableLiveData<>(); + private DownloadEntity singDownloadInfo; + + /** + * 单任务下载的信息 + */ + LiveData getHttpDownloadInfo(Context context) { + String url = AppUtil.getConfigValue(context, M3U8_URL_KEY, defUrl); + String filePath = AppUtil.getConfigValue(context, M3U8_PATH_KEY, this.filePath); + + singDownloadInfo = Aria.download(context).getDownloadEntity(url); + if (singDownloadInfo == null) { + singDownloadInfo = new DownloadEntity(); + singDownloadInfo.setUrl(url); + File temp = new File(this.filePath); + singDownloadInfo.setDownloadPath(filePath); + singDownloadInfo.setFileName(temp.getName()); + } else { + AppUtil.setConfigValue(context, M3U8_PATH_KEY, singDownloadInfo.getDownloadPath()); + AppUtil.setConfigValue(context, M3U8_URL_KEY, singDownloadInfo.getUrl()); + } + liveData.postValue(singDownloadInfo); + + return liveData; + } + + /** + * 更新文件保存路径 + * + * @param filePath 文件保存路径 + */ + void updateFilePath(Context context, String filePath) { + if (TextUtils.isEmpty(filePath)) { + ALog.e(TAG, "文件保存路径为空"); + return; + } + File temp = new File(filePath); + AppUtil.setConfigValue(context, M3U8_PATH_KEY, filePath); + singDownloadInfo.setFileName(temp.getName()); + singDownloadInfo.setDownloadPath(filePath); + liveData.postValue(singDownloadInfo); + } + + /** + * 更新url + */ + void uploadUrl(Context context, String url) { + if (TextUtils.isEmpty(url)) { + ALog.e(TAG, "下载地址为空"); + return; + } + AppUtil.setConfigValue(context, M3U8_URL_KEY, url); + singDownloadInfo.setUrl(url); + liveData.postValue(singDownloadInfo); + } +} diff --git a/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java b/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java index f5a2c451..95cd320d 100644 --- a/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java +++ b/app/src/main/java/com/arialyy/simple/modlue/CommonModule.java @@ -15,52 +15,37 @@ */ package com.arialyy.simple.modlue; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; import android.content.Context; import android.content.Intent; -import com.arialyy.aria.core.download.DownloadEntity; -import com.arialyy.frame.module.AbsModule; +import com.arialyy.frame.base.BaseViewModule; import com.arialyy.simple.MainActivity; import com.arialyy.simple.R; import com.arialyy.simple.to.NormalTo; -import com.github.mikephil.charting.data.Entry; import java.util.ArrayList; import java.util.List; /** * 通用Modle块 */ -public class CommonModule extends AbsModule { +public class CommonModule extends BaseViewModule { private int mPosition; + private MutableLiveData> mLiveData = new MutableLiveData<>(); - public CommonModule(Context context) { - super(context); + public CommonModule() { } - public void startNextActivity(NormalTo to, Class clazz) { - Intent intent = new Intent(getContext(), clazz); + public void startNextActivity(Context context, NormalTo to, Class clazz) { + Intent intent = new Intent(context, clazz); intent.putExtra(MainActivity.KEY_MAIN_DATA, to); - getContext().startActivity(intent); + context.startActivity(intent); } - /** - * 创建图表实体 - */ - public Entry createNewEntry(DownloadEntity entity) { - long speed = entity.getSpeed() / 1024; - boolean isKb = true; - if (speed > 1024) { - isKb = false; - speed /= 1024; - } - Entry entry = new Entry(mPosition, speed, isKb); - mPosition++; - return entry; - } - - public List getDownloadData() { + public LiveData> getDownloadData(Context context) { List list = new ArrayList<>(); - String[] titles = getContext().getResources().getStringArray(R.array.download_items); + String[] titles = context.getResources().getStringArray(R.array.download_items); int[] icons = new int[] { R.drawable.ic_http, R.drawable.ic_http_group, @@ -77,20 +62,22 @@ public class CommonModule extends AbsModule { i++; list.add(to); } - return list; + mLiveData.postValue(list); + return mLiveData; } - public List getMainData() { + public LiveData> getMainData(Context context) { List list = new ArrayList<>(); - String[] titles = getContext().getResources().getStringArray(R.array.main_items); - String[] descs = getContext().getResources().getStringArray(R.array.main_items_desc); + String[] titles = context.getResources().getStringArray(R.array.main_items); + String[] descs = context.getResources().getStringArray(R.array.main_items_desc); int[] icons = new int[] { R.drawable.ic_http, R.drawable.ic_http, R.drawable.ic_http_group, R.drawable.ic_ftp, R.drawable.ic_ftp_dir, - R.drawable.ic_ftp + R.drawable.ic_ftp, + R.drawable.ic_ts }; int i = 0; for (String title : titles) { @@ -101,6 +88,7 @@ public class CommonModule extends AbsModule { i++; list.add(to); } - return list; + mLiveData.postValue(list); + return mLiveData; } } diff --git a/app/src/main/res/drawable/ic_ts.xml b/app/src/main/res/drawable/ic_ts.xml new file mode 100644 index 00000000..a8b9c1e0 --- /dev/null +++ b/app/src/main/res/drawable/ic_ts.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_m3u8.xml b/app/src/main/res/layout/activity_m3u8.xml new file mode 100644 index 00000000..95ebe465 --- /dev/null +++ b/app/src/main/res/layout/activity_m3u8.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 202d2e1e..7cf6046e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,7 @@ FTP 下载 FTP 文件夹下载 FTP 文件上传 + M3U8 点播文件下载 @@ -44,6 +45,7 @@ 单文件下载、多文件队列下载 下载服务器上的某个FTP文件夹 上传单个文件到服务器 + 下载M3U8点播文件