2323import com .liulishuo .filedownloader .services .DownloadMgrInitialParams ;
2424import com .liulishuo .filedownloader .services .ForegroundServiceConfig ;
2525import com .liulishuo .filedownloader .stream .FileDownloadOutputStream ;
26+ import com .liulishuo .filedownloader .util .FileDownloadExecutors ;
2627import com .liulishuo .filedownloader .util .FileDownloadHelper ;
2728import com .liulishuo .filedownloader .util .FileDownloadLog ;
2829import com .liulishuo .filedownloader .util .FileDownloadUtils ;
2930
3031import java .io .File ;
3132import java .io .IOException ;
33+ import java .util .ArrayList ;
3234import java .util .Iterator ;
35+ import java .util .List ;
36+ import java .util .concurrent .Future ;
37+ import java .util .concurrent .ThreadPoolExecutor ;
38+ import java .util .concurrent .atomic .AtomicInteger ;
3339
3440/**
3541 * The holder for supported custom components.
@@ -88,17 +94,7 @@ public FileDownloadDatabase getDatabaseInstance() {
8894 synchronized (this ) {
8995 if (database == null ) {
9096 database = getDownloadMgrInitialParams ().createDatabase ();
91- // There is no reusable thread for this action and this action has
92- // a very low frequency, so, a new thread is simplest way to make this action
93- // run on background thread.
94- final String maintainThreadName =
95- FileDownloadUtils .getThreadPoolName ("MaintainDatabase" );
96- new Thread (new Runnable () {
97- @ Override
98- public void run () {
99- maintainDatabase (database .maintainer ());
100- }
101- }, maintainThreadName ).start ();
97+ maintainDatabase (database .maintainer ());
10298 }
10399 }
104100
@@ -178,123 +174,149 @@ private DownloadMgrInitialParams getDownloadMgrInitialParams() {
178174 return initialParams ;
179175 }
180176
181- private static void maintainDatabase (FileDownloadDatabase .Maintainer maintainer ) {
177+ private static void maintainDatabase (final FileDownloadDatabase .Maintainer maintainer ) {
182178 final Iterator <FileDownloadModel > iterator = maintainer .iterator ();
183- long refreshDataCount = 0 ;
184- long removedDataCount = 0 ;
185- long resetIdCount = 0 ;
179+ final AtomicInteger removedDataCount = new AtomicInteger ( 0 ) ;
180+ final AtomicInteger resetIdCount = new AtomicInteger ( 0 ) ;
181+ final AtomicInteger refreshDataCount = new AtomicInteger ( 0 ) ;
186182 final FileDownloadHelper .IdGenerator idGenerator = getImpl ().getIdGeneratorInstance ();
187183
188184 final long startTimestamp = System .currentTimeMillis ();
185+ final List <Future > futures = new ArrayList <>();
186+ final ThreadPoolExecutor maintainThreadPool = FileDownloadExecutors .newDefaultThreadPool (3 ,
187+ FileDownloadUtils .getThreadPoolName ("MaintainDatabase" ));
189188 try {
190189 while (iterator .hasNext ()) {
191- boolean isInvalid = false ;
192190 final FileDownloadModel model = iterator .next ();
193- do {
194- if (model .getStatus () == FileDownloadStatus .progress
195- || model .getStatus () == FileDownloadStatus .connected
196- || model .getStatus () == FileDownloadStatus .error
197- || (model .getStatus () == FileDownloadStatus .pending && model
198- .getSoFar () > 0 )
199- ) {
200- // Ensure can be covered by RESUME FROM BREAKPOINT.
201- model .setStatus (FileDownloadStatus .paused );
202- }
203- final String targetFilePath = model .getTargetFilePath ();
204- if (targetFilePath == null ) {
205- // no target file path, can't used to resume from breakpoint.
206- isInvalid = true ;
207- break ;
208- }
209-
210- final File targetFile = new File (targetFilePath );
211- // consider check in new thread, but SQLite lock | file lock aways effect, so
212- // sync
213- if (model .getStatus () == FileDownloadStatus .paused
214- && FileDownloadUtils .isBreakpointAvailable (model .getId (), model ,
215- model .getPath (), null )) {
216- // can be reused in the old mechanism(no-temp-file).
217-
218- final File tempFile = new File (model .getTempFilePath ());
191+ final Future modelFuture = maintainThreadPool .submit (new Runnable () {
192+ @ Override
193+ public void run () {
194+ boolean isInvalid = false ;
195+ do {
196+ if (model .getStatus () == FileDownloadStatus .progress
197+ || model .getStatus () == FileDownloadStatus .connected
198+ || model .getStatus () == FileDownloadStatus .error
199+ || (model .getStatus () == FileDownloadStatus .pending && model
200+ .getSoFar () > 0 )
201+ ) {
202+ // Ensure can be covered by RESUME FROM BREAKPOINT.
203+ model .setStatus (FileDownloadStatus .paused );
204+ }
205+ final String targetFilePath = model .getTargetFilePath ();
206+ if (targetFilePath == null ) {
207+ // no target file path, can't used to resume from breakpoint.
208+ isInvalid = true ;
209+ break ;
210+ }
219211
220- if (!tempFile .exists () && targetFile .exists ()) {
221- final boolean successRename = targetFile .renameTo (tempFile );
222- if (FileDownloadLog .NEED_LOG ) {
223- FileDownloadLog .d (FileDownloadDatabase .class ,
224- "resume from the old no-temp-file architecture "
225- + "[%B], [%s]->[%s]" ,
226- successRename , targetFile .getPath (), tempFile .getPath ());
212+ final File targetFile = new File (targetFilePath );
213+ // consider check in new thread, but SQLite lock | file lock aways
214+ // effect, so sync
215+ if (model .getStatus () == FileDownloadStatus .paused
216+ && FileDownloadUtils .isBreakpointAvailable (model .getId (), model ,
217+ model .getPath (), null )) {
218+ // can be reused in the old mechanism(no-temp-file).
219+
220+ final File tempFile = new File (model .getTempFilePath ());
221+
222+ if (!tempFile .exists () && targetFile .exists ()) {
223+ final boolean successRename = targetFile .renameTo (tempFile );
224+ if (FileDownloadLog .NEED_LOG ) {
225+ FileDownloadLog .d (FileDownloadDatabase .class ,
226+ "resume from the old no-temp-file architecture "
227+ + "[%B], [%s]->[%s]" ,
228+ successRename , targetFile .getPath (),
229+ tempFile .getPath ());
230+ }
231+ }
232+ }
227233
234+ /**
235+ * Remove {@code model} from DB if it can't used for judging whether the
236+ * old-downloaded file is valid for reused & it can't used for resuming
237+ * from BREAKPOINT, In other words, {@code model} is no use anymore for
238+ * FileDownloader.
239+ */
240+ if (model .getStatus () == FileDownloadStatus .pending
241+ && model .getSoFar () <= 0 ) {
242+ // This model is redundant.
243+ isInvalid = true ;
244+ break ;
228245 }
229- }
230- }
231246
232- /**
233- * Remove {@code model} from DB if it can't used for judging whether the
234- * old-downloaded file is valid for reused & it can't used for resuming from
235- * BREAKPOINT, In other words, {@code model} is no use anymore for
236- * FileDownloader.
237- */
238- if (model .getStatus () == FileDownloadStatus .pending && model .getSoFar () <= 0 ) {
239- // This model is redundant.
240- isInvalid = true ;
241- break ;
242- }
247+ if (!FileDownloadUtils .isBreakpointAvailable (model .getId (), model )) {
248+ // It can't used to resuming from breakpoint.
249+ isInvalid = true ;
250+ break ;
251+ }
243252
244- if (! FileDownloadUtils . isBreakpointAvailable ( model . getId (), model )) {
245- // It can't used to resuming from breakpoint .
246- isInvalid = true ;
247- break ;
248- }
253+ if (targetFile . exists ( )) {
254+ // It has already completed downloading .
255+ isInvalid = true ;
256+ break ;
257+ }
249258
250- if (targetFile .exists ()) {
251- // It has already completed downloading.
252- isInvalid = true ;
253- break ;
254- }
259+ } while (false );
260+
261+
262+ if (isInvalid ) {
263+ maintainer .onRemovedInvalidData (model );
264+ removedDataCount .addAndGet (1 );
265+ } else {
266+ final int oldId = model .getId ();
267+ final int newId = idGenerator .transOldId (oldId , model .getUrl (),
268+ model .getPath (), model .isPathAsDirectory ());
269+ if (newId != oldId ) {
270+ if (FileDownloadLog .NEED_LOG ) {
271+ FileDownloadLog .d (FileDownloadDatabase .class ,
272+ "the id is changed on restoring from db:"
273+ + " old[%d] -> new[%d]" ,
274+ oldId , newId );
275+ }
276+ model .setId (newId );
277+ maintainer .changeFileDownloadModelId (oldId , model );
278+ resetIdCount .addAndGet (1 );
279+ }
255280
256- } while (false );
257-
258-
259- if (isInvalid ) {
260- iterator .remove ();
261- maintainer .onRemovedInvalidData (model );
262- removedDataCount ++;
263- } else {
264- final int oldId = model .getId ();
265- final int newId = idGenerator .transOldId (oldId , model .getUrl (), model .getPath (),
266- model .isPathAsDirectory ());
267- if (newId != oldId ) {
268- if (FileDownloadLog .NEED_LOG ) {
269- FileDownloadLog .d (FileDownloadDatabase .class ,
270- "the id is changed on restoring from db:"
271- + " old[%d] -> new[%d]" ,
272- oldId , newId );
281+ maintainer .onRefreshedValidData (model );
282+ refreshDataCount .addAndGet (1 );
273283 }
274- model .setId (newId );
275- maintainer .changeFileDownloadModelId (oldId , model );
276- resetIdCount ++;
277284 }
278-
279- maintainer .onRefreshedValidData (model );
280- refreshDataCount ++;
281- }
285+ });
286+ futures .add (modelFuture );
282287 }
283288
284289 } finally {
285- FileDownloadUtils .markConverted (FileDownloadHelper .getAppContext ());
286- maintainer .onFinishMaintain ();
287- // 566 data consumes about 140ms
288- // update by rth: different devices have very large disparity, such as,
289- // in my HuaWei Android 8.0, 67 data consumes about 355ms.
290- // so, it's better do this action in background thread.
290+ final Future markConvertedFuture = maintainThreadPool .submit (new Runnable () {
291+ @ Override
292+ public void run () {
293+ FileDownloadUtils .markConverted (FileDownloadHelper .getAppContext ());
294+ }
295+ });
296+ futures .add (markConvertedFuture );
297+ final Future finishMaintainFuture = maintainThreadPool .submit (new Runnable () {
298+ @ Override
299+ public void run () {
300+ maintainer .onFinishMaintain ();
301+ }
302+ });
303+ futures .add (finishMaintainFuture );
304+ for (Future future : futures ) {
305+ try {
306+ if (!future .isDone ()) future .get ();
307+ } catch (Exception e ) {
308+ if (FileDownloadLog .NEED_LOG ) FileDownloadLog
309+ .e (FileDownloadDatabase .class , e , e .getMessage ());
310+ }
311+ }
312+ futures .clear ();
291313 if (FileDownloadLog .NEED_LOG ) {
292314 FileDownloadLog .d (FileDownloadDatabase .class ,
293315 "refreshed data count: %d , delete data count: %d, reset id count:"
294316 + " %d. consume %d" ,
295- refreshDataCount , removedDataCount , resetIdCount ,
317+ refreshDataCount . get () , removedDataCount . get () , resetIdCount . get () ,
296318 System .currentTimeMillis () - startTimestamp );
297319 }
298320 }
299321 }
300- }
322+ }
0 commit comments