/* * Copyright (c) 2015 LingoChamp Inc. * * 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.liulishuo.filedownloader.util; import android.annotation.SuppressLint; import android.content.Context; import com.liulishuo.filedownloader.IThreadPoolMonitor; import com.liulishuo.filedownloader.connection.FileDownloadConnection; import com.liulishuo.filedownloader.database.SqliteDatabaseImpl; import com.liulishuo.filedownloader.exception.PathConflictException; import com.liulishuo.filedownloader.message.MessageSnapshotFlow; import com.liulishuo.filedownloader.message.MessageSnapshotTaker; import com.liulishuo.filedownloader.model.FileDownloadModel; import com.liulishuo.filedownloader.database.FileDownloadDatabase; import com.liulishuo.filedownloader.stream.FileDownloadOutputStream; import com.liulishuo.filedownloader.stream.FileDownloadRandomAccessFile; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; /** * The helper for cache the {@code APP_CONTEXT} and {@code OK_HTTP_CLIENT} for the main process and * the filedownloader process. */ public class FileDownloadHelper { @SuppressLint("StaticFieldLeak") private static Context APP_CONTEXT; public static void holdContext(final Context context) { APP_CONTEXT = context; } public static Context getAppContext() { return APP_CONTEXT; } @SuppressWarnings("UnusedParameters") public interface IdGenerator { /** * Invoke this method when the data is restoring from the database. * * @param oldId the old download id which is generated through the different. * @param url the url path. * @param path the download path. * @param pathAsDirectory {@code true}: if the {@code path} is absolute directory to store * the downloading file, and the {@code filename} will be found in * contentDisposition from the response as default, if can't find * contentDisposition,the {@code filename} will be generated by * {@link FileDownloadUtils#generateFileName(String)} with * {@code url}. *

* {@code false}: if the {@code path} = (absolute directory/filename) * @return the new download id. */ int transOldId(final int oldId, final String url, final String path, final boolean pathAsDirectory); /** * Invoke this method when there is a new task from very beginning. *

* Important Ting: this method would be used on the FileDownloadService and the upper-layer, * so as default FileDownloadService is running on the `:filedownloader` process, and * upper-layer * is on the user process, in this case, if you want to cache something on this instance, it * would be two different caches on two processes. *

* Tips: if you want the FileDownloadService runs on the upper-layer process too, just * config {@code process.non-separate=true} on the filedownloader.properties, more detail * please move to {@link FileDownloadProperties} * * @param url the download url. * @param path the download path. * @param pathAsDirectory {@code true}: if the {@code path} is absolute directory to store * the downloading file, and the {@code filename} will be found in * contentDisposition from the response as default, if can't find * contentDisposition,the {@code filename} will be generated by * {@link FileDownloadUtils#generateFileName(String)} with * {@code url}. *

* {@code false}: if the {@code path} = (absolute directory/filename) * @return the download task identify. */ int generateId(final String url, final String path, final boolean pathAsDirectory); } @SuppressWarnings("UnusedParameters") public interface ConnectionCountAdapter { /** * Before invoke this method to determine how many connection will be used to downloading * this task, * there are several conditions must be confirmed: *

* 1. the connection is support multiple connection(SUPPORT"Partial Content(206)" AND NOT * Chunked) * 2. the current {@link FileDownloadOutputStream} support seek(The default * one({@link FileDownloadRandomAccessFile} is support) * 3. this is a new task NOT resume from breakpoint( If the task resume from breakpoint * the connection count would be using * the one you determined when the task * first created ). *

* The best strategy is refer to how much speed of each connection for the ip:port not file * size. * * @param downloadId the download id. * @param url the task url. * @param path the task path. * @param totalLength the total length of the file. * @return the count of connection you want for the task. the value must be large than 0. */ int determineConnectionCount(int downloadId, String url, String path, long totalLength); } public interface DatabaseCustomMaker { /** * The database is used for storing the {@link FileDownloadModel}. *

* The data stored in the database is only used for task resumes from the breakpoint. *

* The task of the data stored in the database must be a task that has not finished * downloading yet, and if the task has finished downloading, its data will be * {@link FileDownloadDatabase#remove(int)} from the database, since that data is no longer * available for resumption of its task pass. * * @return Nullable, Customize {@link FileDownloadDatabase} which will be used for storing * downloading model. * @see SqliteDatabaseImpl */ FileDownloadDatabase customMake(); } public interface OutputStreamCreator { /** * The output stream creator is used for creating {@link FileDownloadOutputStream} which is * used to write the input stream to the file for downloading. *

* Note: please create a output stream which append the content to the * exist file, which means that bytes would be written to the end of the file rather than * the beginning. * * @param file the file will used for storing the downloading content. * @return The output stream used to write downloading byte array to the {@code file}. * @throws FileNotFoundException if the file exists but is a directory * rather than a regular file, does not exist but cannot * be created, or cannot be opened for any other reason * @see FileDownloadRandomAccessFile.Creator */ FileDownloadOutputStream create(File file) throws IOException; /** * @return {@code true} if the {@link FileDownloadOutputStream} is created through * {@link #create(File)} support {@link FileDownloadOutputStream#seek(long)} function. * If the {@link FileDownloadOutputStream} is created through {@link #create(File)} doesn't * support {@link FileDownloadOutputStream#seek(long)}, please return {@code false}, in * order to let the internal mechanism can predict this situation, and handle it smoothly. */ boolean supportSeek(); } public interface ConnectionCreator { /** * The connection creator is used for creating {@link FileDownloadConnection} component * which is used to use some protocol to connect to the remote server. * * @param url the uniform resource locator, which direct the aim resource we need to connect * @return The connection creator. * @throws IOException if an I/O exception occurs. */ FileDownloadConnection create(String url) throws IOException; } /** * @param id the {@code id} used for filter out which task would be notified the * 'completed' message if need. * @param path if the file with {@code path} is exist it means the relate task would * be completed. * @param forceReDownload whether the task is force to re-download ignore whether the file has * been exist or not. * @param flowDirectly {@code true} if flow the message if need directly without throw to the * message-queue. * @return whether the task with {@code id} has been downloaded. */ public static boolean inspectAndInflowDownloaded(int id, String path, boolean forceReDownload, boolean flowDirectly) { if (forceReDownload) { return false; } if (path != null) { final File file = new File(path); if (file.exists()) { MessageSnapshotFlow.getImpl().inflow(MessageSnapshotTaker. catchCanReusedOldFile(id, file, flowDirectly)); return true; } } return false; } /** * @param id the {@code id} used for filter out which task would be notified the * 'warn' message if need. * @param model the target model will be checked for judging whether it is downloading. * @param monitor the monitor for download-thread. * @param flowDirectly {@code true} if flow the message if need directly without throw to the * message-queue. * @return whether the {@code model} is downloading. */ public static boolean inspectAndInflowDownloading(int id, FileDownloadModel model, IThreadPoolMonitor monitor, boolean flowDirectly) { if (monitor.isDownloading(model)) { MessageSnapshotFlow.getImpl(). inflow(MessageSnapshotTaker.catchWarn(id, model.getSoFar(), model.getTotal(), flowDirectly)); return true; } return false; } /** * @param id the {@code id} used for filter out which task would be notified the * 'error' message if need. * @param sofar the so far bytes of the current checking-task. * @param tempFilePath the temp file path(file path used for downloading) for the current * checking-task. * @param targetFilePath the target file path for the current checking-task. * @param monitor the monitor for download-thread. * @return whether the task with {@code id} is refused to start, because of there is an another * running task with the same {@code tempFilePath}. */ public static boolean inspectAndInflowConflictPath(int id, long sofar, String tempFilePath, String targetFilePath, IThreadPoolMonitor monitor) { if (targetFilePath != null && tempFilePath != null) { final int anotherSameTempPathTaskId = monitor.findRunningTaskIdBySameTempPath(tempFilePath, id); if (anotherSameTempPathTaskId != 0) { MessageSnapshotFlow.getImpl(). inflow(MessageSnapshotTaker.catchException(id, sofar, new PathConflictException(anotherSameTempPathTaskId, tempFilePath, targetFilePath))); return true; } } return false; } }