use of com.liulishuo.filedownloader.model.FileDownloadModel in project FileDownloader by lingochamp.
the class DefaultDatabaseImpl method refreshDataFromDB.
private void refreshDataFromDB() {
long start = System.currentTimeMillis();
Cursor c = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
List<Integer> dirtyList = new ArrayList<>();
//noinspection TryFinallyCanBeTryWithResources
try {
while (c.moveToNext()) {
FileDownloadModel model = new FileDownloadModel();
model.setId(c.getInt(c.getColumnIndex(FileDownloadModel.ID)));
model.setUrl(c.getString(c.getColumnIndex(FileDownloadModel.URL)));
model.setPath(c.getString(c.getColumnIndex(FileDownloadModel.PATH)), c.getShort(c.getColumnIndex(FileDownloadModel.PATH_AS_DIRECTORY)) == 1);
model.setStatus((byte) c.getShort(c.getColumnIndex(FileDownloadModel.STATUS)));
model.setSoFar(c.getLong(c.getColumnIndex(FileDownloadModel.SOFAR)));
model.setTotal(c.getLong(c.getColumnIndex(FileDownloadModel.TOTAL)));
model.setErrMsg(c.getString(c.getColumnIndex(FileDownloadModel.ERR_MSG)));
model.setETag(c.getString(c.getColumnIndex(FileDownloadModel.ETAG)));
model.setFilename(c.getString(c.getColumnIndex(FileDownloadModel.FILENAME)));
if (model.getStatus() == FileDownloadStatus.progress || model.getStatus() == FileDownloadStatus.connected || model.getStatus() == FileDownloadStatus.error || (model.getStatus() == FileDownloadStatus.pending && model.getSoFar() > 0)) {
// Ensure can be covered by RESUME FROM BREAKPOINT.
model.setStatus(FileDownloadStatus.paused);
}
final String targetFilePath = model.getTargetFilePath();
if (targetFilePath == null) {
// no target file path, can't used to resume from breakpoint.
dirtyList.add(model.getId());
continue;
}
final File targetFile = new File(targetFilePath);
// consider check in new thread, but SQLite lock | file lock aways effect, so sync
if (model.getStatus() == FileDownloadStatus.paused && FileDownloadMgr.isBreakpointAvailable(model.getId(), model, model.getPath(), null)) {
// can be reused in the old mechanism(no-temp-file).
final File tempFile = new File(model.getTempFilePath());
if (!tempFile.exists() && targetFile.exists()) {
final boolean successRename = targetFile.renameTo(tempFile);
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "resume from the old no-temp-file architecture [%B], [%s]->[%s]", successRename, targetFile.getPath(), tempFile.getPath());
}
}
}
/**
* Remove {@code model} from DB if it can't used for judging whether the
* old-downloaded file is valid for reused & it can't used for resuming from
* BREAKPOINT, In other words, {@code model} is no use anymore for FileDownloader.
*/
if (model.getStatus() == FileDownloadStatus.pending && model.getSoFar() <= 0) {
// This model is redundant.
dirtyList.add(model.getId());
} else if (!FileDownloadMgr.isBreakpointAvailable(model.getId(), model)) {
// It can't used to resuming from breakpoint.
dirtyList.add(model.getId());
} else if (targetFile.exists()) {
// It has already completed downloading.
dirtyList.add(model.getId());
} else {
downloaderModelMap.put(model.getId(), model);
}
}
} finally {
c.close();
FileDownloadUtils.markConverted(FileDownloadHelper.getAppContext());
// db
if (dirtyList.size() > 0) {
String args = TextUtils.join(", ", dirtyList);
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "delete %s", args);
}
//noinspection ThrowFromFinallyBlock
db.execSQL(FileDownloadUtils.formatString("DELETE FROM %s WHERE %s IN (%s);", TABLE_NAME, FileDownloadModel.ID, args));
}
// 566 data consumes about 140ms
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "refresh data %d , will delete: %d consume %d", downloaderModelMap.size(), dirtyList.size(), System.currentTimeMillis() - start);
}
}
}
use of com.liulishuo.filedownloader.model.FileDownloadModel in project FileDownloader by lingochamp.
the class FileDownloadMgr method pause.
public boolean pause(final int id) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "request pause the task %d", id);
}
final FileDownloadModel model = mDatabase.find(id);
if (model == null) {
return false;
}
mThreadPool.cancel(id);
// mClient.cancel(id);
return true;
}
use of com.liulishuo.filedownloader.model.FileDownloadModel in project FileDownloader by lingochamp.
the class FileDownloadMgr method start.
// synchronize for safe: check downloading, check resume, update data, execute runnable
public synchronized void start(final String url, final String path, final boolean pathAsDirectory, final int callbackProgressTimes, final int callbackProgressMinIntervalMillis, final int autoRetryTimes, final boolean forceReDownload, final FileDownloadHeader header, final boolean isWifiRequired) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "request start the task with url(%s) path(%s) isDirectory(%B)", url, path, pathAsDirectory);
}
final int id = FileDownloadUtils.generateId(url, path, pathAsDirectory);
FileDownloadModel model = mDatabase.find(id);
if (!pathAsDirectory && model == null) {
// try dir data.
final int dirCaseId = FileDownloadUtils.generateId(url, FileDownloadUtils.getParent(path), true);
model = mDatabase.find(dirCaseId);
if (model != null && path.equals(model.getTargetFilePath())) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "task[%d] find model by dirCaseId[%d]", id, dirCaseId);
}
}
}
if (FileDownloadHelper.inspectAndInflowDownloading(id, model, this, true)) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "has already started download %d", id);
}
return;
}
final String targetFilePath = model != null ? model.getTargetFilePath() : FileDownloadUtils.getTargetFilePath(path, pathAsDirectory, null);
if (FileDownloadHelper.inspectAndInflowDownloaded(id, targetFilePath, forceReDownload, true)) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "has already completed downloading %d", id);
}
return;
}
final long sofar = model != null ? model.getSoFar() : 0;
final String tempFilePath = model != null ? model.getTempFilePath() : FileDownloadUtils.getTempPath(targetFilePath);
if (FileDownloadHelper.inspectAndInflowConflictPath(id, sofar, tempFilePath, targetFilePath, this)) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "there is an another task with the same target-file-path %d %s", id, targetFilePath);
// because of the file is dirty for this task.
if (model != null) {
mDatabase.remove(id);
}
}
return;
}
// real start
// - create model
boolean needUpdate2DB;
if (model != null && (model.getStatus() == FileDownloadStatus.paused || // FileDownloadRunnable invoke
model.getStatus() == FileDownloadStatus.error)) // #isBreakpointAvailable to determine whether it is really invalid.
{
if (model.getId() != id) {
// in try dir case.
mDatabase.remove(model.getId());
model.setId(id);
model.setPath(path, pathAsDirectory);
needUpdate2DB = true;
} else {
needUpdate2DB = false;
}
} else {
if (model == null) {
model = new FileDownloadModel();
}
model.setUrl(url);
model.setPath(path, pathAsDirectory);
model.setId(id);
model.setSoFar(0);
model.setTotal(0);
model.setStatus(FileDownloadStatus.pending);
needUpdate2DB = true;
}
// - update model to db
if (needUpdate2DB) {
mDatabase.update(model);
}
// - execute
mThreadPool.execute(new FileDownloadRunnable(this, mOutputStreamCreator, mConnectionCreator, model, mDatabase, autoRetryTimes, header, callbackProgressMinIntervalMillis, callbackProgressTimes, forceReDownload, isWifiRequired));
}
use of com.liulishuo.filedownloader.model.FileDownloadModel in project FileDownloader by lingochamp.
the class FileDownloadRunnable method loop.
@SuppressWarnings("ConstantConditions")
private void loop(FileDownloadModel model) {
int retryingTimes = 0;
boolean revisedInterval = false;
FileDownloadConnection connection = null;
do {
// loop for retry
long soFar = 0;
final int id = mId;
try {
// Step 1, check state
if (checkState()) {
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "already canceled %d %d", id, model.getStatus());
}
onPause();
break;
}
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(FileDownloadRunnable.class, "start download %s %s", id, model.getUrl());
}
// Step 2, handle resume from breakpoint
checkIsResumeAvailable();
connection = mConnectionCreator.create(model.getUrl());
addHeader(connection);
// start download----------------
// Step 3, init request
// get the request header in here, because of there are many connection
// component(such as HttpsURLConnectionImpl, HttpURLConnectionImpl in okhttp3) don't
// allow access to the request header after it connected.
final Map<String, List<String>> requestHeader = connection.getRequestHeaderFields();
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "%s request header %s", id, requestHeader);
}
// Step 4, build connect
connection.execute();
final int code = connection.getResponseCode();
final boolean isSucceedStart = code == HttpURLConnection.HTTP_OK || code == FileDownloadConnection.NO_RESPONSE_CODE;
// if the response status code isn't point to PARTIAL/OFFSET, isSucceedResume will
// be assigned to false, so filedownloader will download the file from very beginning.
final boolean isSucceedResume = ((code == HttpURLConnection.HTTP_PARTIAL) || (code == FileDownloadConnection.RESPONSE_CODE_FROM_OFFSET)) && isResumeDownloadAvailable;
if (isResumeDownloadAvailable && !isSucceedResume) {
FileDownloadLog.d(this, "want to resume from the breakpoint[%d], but the " + "response status code is[%d]", model.getSoFar(), code);
}
if (isSucceedStart || isSucceedResume) {
long total = model.getTotal();
final String transferEncoding = connection.getResponseHeaderField("Transfer-Encoding");
// Step 5, check response's header
if (isSucceedStart || total <= 0) {
if (transferEncoding == null) {
total = FileDownloadUtils.convertContentLengthString(connection.getResponseHeaderField("Content-Length"));
} else {
// if transfer not nil, ignore content-length
total = TOTAL_VALUE_IN_CHUNKED_RESOURCE;
}
}
// TODO consider if not is chunked & http 1.0/(>=http1.1 & connect not be keep live) may not give content-length
if (total < 0) {
// invalid total length
final boolean isEncodingChunked = transferEncoding != null && transferEncoding.equals("chunked");
if (!isEncodingChunked) {
// not chunked transfer encoding data
if (FileDownloadProperties.getImpl().HTTP_LENIENT) {
// do not response content-length either not chunk transfer encoding,
// but HTTP lenient is true, so handle as the case of transfer encoding chunk
total = TOTAL_VALUE_IN_CHUNKED_RESOURCE;
if (FileDownloadLog.NEED_LOG) {
FileDownloadLog.d(this, "%d response header is not legal but " + "HTTP lenient is true, so handle as the case of " + "transfer encoding chunk", id);
}
} else {
throw new FileDownloadGiveUpRetryException("can't know the size of the " + "download file, and its Transfer-Encoding is not Chunked " + "either.\nyou can ignore such exception by add " + "http.lenient=true to the filedownloader.properties");
}
}
}
if (isSucceedResume) {
soFar = model.getSoFar();
}
// Step 6, callback on connected, and update header to db. for save etag.
onConnected(isSucceedResume, total, findEtag(connection), findFilename(connection));
// Step 7, check whether has same task running after got filename from server/local generate.
if (model.isPathAsDirectory()) {
// this scope for caring about the case of there is another task is provided
// the same path to store file and the same url.
final String targetFilePath = model.getTargetFilePath();
// get the ID after got the filename.
final int fileCaseId = FileDownloadUtils.generateId(model.getUrl(), targetFilePath);
// whether the file with the filename has been existed.
if (FileDownloadHelper.inspectAndInflowDownloaded(id, targetFilePath, isForceReDownload, false)) {
helper.remove(id);
break;
}
final FileDownloadModel fileCaseModel = helper.find(fileCaseId);
if (fileCaseModel != null) {
// whether the another task with the same file and url is downloading.
if (FileDownloadHelper.inspectAndInflowDownloading(id, fileCaseModel, threadPoolMonitor, false)) {
//it has been post to upper layer the 'warn' message, so the current
// task no need to continue download.
helper.remove(id);
break;
}
// the another task with the same file name and url is paused
helper.remove(fileCaseId);
deleteTargetFile();
if (FileDownloadMgr.isBreakpointAvailable(fileCaseId, fileCaseModel)) {
model.setSoFar(fileCaseModel.getSoFar());
model.setTotal(fileCaseModel.getTotal());
model.setETag(fileCaseModel.getETag());
helper.update(model);
// re connect to resume from breakpoint.
continue;
}
}
// whether there is an another running task with the same target-file-path.
if (FileDownloadHelper.inspectAndInflowConflictPath(id, model.getSoFar(), getTempFilePath(), targetFilePath, threadPoolMonitor)) {
helper.remove(id);
break;
}
}
// Step 8, start fetch datum from input stream & write to file
if (fetch(connection, isSucceedResume, soFar, total)) {
break;
}
} else {
final FileDownloadHttpException httpException = new FileDownloadHttpException(code, requestHeader, connection.getResponseHeaderFields());
if (revisedInterval) {
throw httpException;
}
revisedInterval = true;
switch(code) {
case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE:
deleteTaskFiles();
FileDownloadLog.w(FileDownloadRunnable.class, "%d response code %d, " + "range[%d] isn't make sense, so delete the dirty file[%s]" + ", and try to redownload it from byte-0.", id, code, model.getSoFar(), model.getTempFilePath());
onRetry(httpException, retryingTimes++);
break;
default:
throw httpException;
}
}
} catch (Throwable ex) {
// TODO 决策是否需要重试,是否是用户决定,或者根据错误码处理
if (autoRetryTimes > retryingTimes++ && !(ex instanceof FileDownloadGiveUpRetryException)) {
// retry
onRetry(ex, retryingTimes);
} else {
// error
onError(ex);
break;
}
} finally {
if (connection != null) {
connection.ending();
}
}
} while (true);
}
use of com.liulishuo.filedownloader.model.FileDownloadModel in project FileDownloader by lingochamp.
the class DefaultDatabaseImpl method update.
@Override
public void update(List<FileDownloadModel> downloadModelList) {
if (downloadModelList == null) {
FileDownloadLog.w(this, "update a download list, but list == null!");
return;
}
db.beginTransaction();
try {
for (FileDownloadModel model : downloadModelList) {
if (find(model.getId()) != null) {
// replace
downloaderModelMap.remove(model.getId());
downloaderModelMap.put(model.getId(), model);
db.update(TABLE_NAME, model.toContentValues(), FileDownloadModel.ID + " = ? ", new String[] { String.valueOf(model.getId()) });
} else {
// insert new one.
downloaderModelMap.put(model.getId(), model);
db.insert(TABLE_NAME, null, model.toContentValues());
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
Aggregations