-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathFileDownloadHelper.java
269 lines (246 loc) · 12.7 KB
/
FileDownloadHelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/*
* 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}.
* </p>
* {@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.
* <p>
* 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.
* <p>
* 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}.
* </p>
* {@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:
* <p>
* 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 ).
* <p/>
* 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}.
* <p/>
* The data stored in the database is only used for task resumes from the breakpoint.
* <p>
* 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.
* <p>
* <strong>Note:</strong> 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;
}
}