1+ /*
2+ * Copyright (c) 2015 LingoChamp Inc.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+ package com .liulishuo .filedownloader .database ;
18+
19+ import com .liulishuo .filedownloader .model .FileDownloadModel ;
20+ import com .liulishuo .filedownloader .model .FileDownloadStatus ;
21+ import com .liulishuo .filedownloader .util .FileDownloadExecutors ;
22+ import com .liulishuo .filedownloader .util .FileDownloadHelper ;
23+ import com .liulishuo .filedownloader .util .FileDownloadLog ;
24+ import com .liulishuo .filedownloader .util .FileDownloadUtils ;
25+
26+ import java .io .File ;
27+ import java .util .ArrayList ;
28+ import java .util .Iterator ;
29+ import java .util .List ;
30+ import java .util .concurrent .Future ;
31+ import java .util .concurrent .ThreadPoolExecutor ;
32+ import java .util .concurrent .atomic .AtomicInteger ;
33+
34+ public class DatabaseMaintainer {
35+
36+ private final ThreadPoolExecutor maintainThreadPool ;
37+ private final FileDownloadDatabase .Maintainer maintainer ;
38+ private final FileDownloadHelper .IdGenerator idGenerator ;
39+
40+ public DatabaseMaintainer (FileDownloadDatabase .Maintainer maintainer , FileDownloadHelper .IdGenerator idGenerator ) {
41+ this .maintainer = maintainer ;
42+ this .idGenerator = idGenerator ;
43+ this .maintainThreadPool = FileDownloadExecutors .newDefaultThreadPool (3 ,
44+ FileDownloadUtils .getThreadPoolName ("MaintainDatabase" ));
45+ }
46+
47+ public void doMaintainAction () {
48+ final Iterator <FileDownloadModel > iterator = maintainer .iterator ();
49+
50+ final AtomicInteger removedDataCount = new AtomicInteger (0 );
51+ final AtomicInteger resetIdCount = new AtomicInteger (0 );
52+ final AtomicInteger refreshDataCount = new AtomicInteger (0 );
53+
54+ final long startTimestamp = System .currentTimeMillis ();
55+ final List <Future > futures = new ArrayList <>();
56+ int total = 0 ;
57+ try {
58+ while (iterator .hasNext ()) {
59+ total ++;
60+ final FileDownloadModel model = iterator .next ();
61+ final Future modelFuture = maintainThreadPool .submit (new Runnable () {
62+ @ Override
63+ public void run () {
64+ boolean isInvalid = false ;
65+ do {
66+ if (model .getStatus () == FileDownloadStatus .progress
67+ || model .getStatus () == FileDownloadStatus .connected
68+ || model .getStatus () == FileDownloadStatus .error
69+ || (model .getStatus () == FileDownloadStatus .pending && model
70+ .getSoFar () > 0 )
71+ ) {
72+ // Ensure can be covered by RESUME FROM BREAKPOINT.
73+ model .setStatus (FileDownloadStatus .paused );
74+ }
75+ final String targetFilePath = model .getTargetFilePath ();
76+ if (targetFilePath == null ) {
77+ // no target file path, can't used to resume from breakpoint.
78+ isInvalid = true ;
79+ break ;
80+ }
81+
82+ final File targetFile = new File (targetFilePath );
83+ // consider check in new thread, but SQLite lock | file lock aways effect, so
84+ // sync
85+ if (model .getStatus () == FileDownloadStatus .paused
86+ && FileDownloadUtils .isBreakpointAvailable (model .getId (), model ,
87+ model .getPath (), null )) {
88+ // can be reused in the old mechanism(no-temp-file).
89+
90+ final File tempFile = new File (model .getTempFilePath ());
91+
92+ if (!tempFile .exists () && targetFile .exists ()) {
93+ final boolean successRename = targetFile .renameTo (tempFile );
94+ if (FileDownloadLog .NEED_LOG ) {
95+ FileDownloadLog .d (DatabaseMaintainer .class ,
96+ "resume from the old no-temp-file architecture "
97+ + "[%B], [%s]->[%s]" ,
98+ successRename , targetFile .getPath (), tempFile .getPath ());
99+
100+ }
101+ }
102+ }
103+
104+ /**
105+ * Remove {@code model} from DB if it can't used for judging whether the
106+ * old-downloaded file is valid for reused & it can't used for resuming from
107+ * BREAKPOINT, In other words, {@code model} is no use anymore for
108+ * FileDownloader.
109+ */
110+ if (model .getStatus () == FileDownloadStatus .pending && model .getSoFar () <= 0 ) {
111+ // This model is redundant.
112+ isInvalid = true ;
113+ break ;
114+ }
115+
116+ if (!FileDownloadUtils .isBreakpointAvailable (model .getId (), model )) {
117+ // It can't used to resuming from breakpoint.
118+ isInvalid = true ;
119+ break ;
120+ }
121+
122+ if (targetFile .exists ()) {
123+ // It has already completed downloading.
124+ isInvalid = true ;
125+ break ;
126+ }
127+
128+ } while (false );
129+
130+ if (isInvalid ) {
131+ maintainer .onRemovedInvalidData (model );
132+ removedDataCount .addAndGet (1 );
133+ } else {
134+ final int oldId = model .getId ();
135+ final int newId = idGenerator .transOldId (oldId ,
136+ model .getUrl (), model .getPath (),
137+ model .isPathAsDirectory ());
138+ if (newId != oldId ) {
139+ if (FileDownloadLog .NEED_LOG ) {
140+ FileDownloadLog .d (DatabaseMaintainer .class ,
141+ "the id is changed on restoring from db:"
142+ + " old[%d] -> new[%d]" ,
143+ oldId , newId );
144+ }
145+ model .setId (newId );
146+ maintainer .changeFileDownloadModelId (oldId , model );
147+ resetIdCount .addAndGet (1 );
148+ }
149+
150+ maintainer .onRefreshedValidData (model );
151+ refreshDataCount .addAndGet (1 );
152+ }
153+ }
154+ });
155+ futures .add (modelFuture );
156+ }
157+ } finally {
158+ final Future markConvertedFuture = maintainThreadPool .submit (new Runnable () {
159+ @ Override
160+ public void run () {
161+ FileDownloadUtils .markConverted (FileDownloadHelper .getAppContext ());
162+ }
163+ });
164+ futures .add (markConvertedFuture );
165+ final Future finishMaintainFuture = maintainThreadPool .submit (new Runnable () {
166+ @ Override
167+ public void run () {
168+ maintainer .onFinishMaintain ();
169+ }
170+ });
171+ futures .add (finishMaintainFuture );
172+ for (Future future : futures ) {
173+ try {
174+ if (!future .isDone ()) future .get ();
175+ } catch (Exception e ) {
176+ if (FileDownloadLog .NEED_LOG ) FileDownloadLog .e (this , e , e .getMessage ());
177+ }
178+ }
179+ futures .clear ();
180+ if (FileDownloadLog .NEED_LOG ) {
181+ FileDownloadLog .d (this ,
182+ "refreshed data count: %d , delete data count: %d, reset id count:"
183+ + " %d. consume %d, total: %d" ,
184+ refreshDataCount .get (), removedDataCount .get (), resetIdCount .get (),
185+ System .currentTimeMillis () - startTimestamp , total );
186+ }
187+ }
188+ }
189+ }
0 commit comments