12
12
import com .parse .http .ParseHttpRequest ;
13
13
import java .io .File ;
14
14
import java .io .IOException ;
15
+ import java .util .ArrayList ;
16
+ import java .util .List ;
15
17
import java .util .concurrent .CancellationException ;
16
18
import org .json .JSONObject ;
17
19
@@ -21,6 +23,7 @@ class ParseFileController {
21
23
private final Object lock = new Object ();
22
24
private final ParseHttpClient restClient ;
23
25
private final File cachePath ;
26
+ private final List <String > currentlyDownloadedFilesNames = new ArrayList <>();
24
27
25
28
private ParseHttpClient fileClient ;
26
29
@@ -168,64 +171,80 @@ public Task<File> fetchAsync(
168
171
if (cancellationToken != null && cancellationToken .isCancelled ()) {
169
172
return Task .cancelled ();
170
173
}
171
- final File cacheFile = getCacheFile (state );
172
- return Task .call (cacheFile ::exists , ParseExecutors .io ())
173
- .continueWithTask (
174
- task -> {
175
- boolean result = task .getResult ();
176
- if (result ) {
177
- return Task .forResult (cacheFile );
178
- }
179
- if (cancellationToken != null && cancellationToken .isCancelled ()) {
180
- return Task .cancelled ();
174
+ return Task .call (
175
+ () -> {
176
+ final File cacheFile = getCacheFile (state );
177
+
178
+ synchronized (lock ) {
179
+ if (currentlyDownloadedFilesNames .contains (state .name ())) {
180
+ while (currentlyDownloadedFilesNames .contains (state .name ())) {
181
+ lock .wait ();
181
182
}
182
-
183
- // Generate the temp file path for caching ParseFile content based on
184
- // ParseFile's url
185
- // The reason we do not write to the cacheFile directly is because there
186
- // is no way we can
187
- // verify if a cacheFile is complete or not. If download is interrupted
188
- // in the middle, next
189
- // time when we download the ParseFile, since cacheFile has already
190
- // existed, we will return
191
- // this incomplete cacheFile
192
- final File tempFile = getTempFile (state );
193
-
194
- // network
195
- final ParseFileRequest request =
196
- new ParseFileRequest (
197
- ParseHttpRequest .Method .GET , state .url (), tempFile );
198
-
199
- // We do not need to delete the temp file since we always try to
200
- // overwrite it
201
- return request .executeAsync (
202
- fileClient (),
203
- null ,
204
- downloadProgressCallback ,
205
- cancellationToken )
206
- .continueWithTask (
207
- task1 -> {
208
- // If the top-level task was cancelled, don't
209
- // actually set the data -- just move on.
210
- if (cancellationToken != null
211
- && cancellationToken .isCancelled ()) {
212
- throw new CancellationException ();
213
- }
214
- if (task1 .isFaulted ()) {
215
- ParseFileUtils .deleteQuietly (tempFile );
216
- return task1 .cast ();
217
- }
218
-
219
- // Since we give the cacheFile pointer to
220
- // developers, it is not safe to guarantee
221
- // cacheFile always does not exist here, so it is
222
- // better to delete it manually,
223
- // otherwise moveFile may throw an exception.
224
- ParseFileUtils .deleteQuietly (cacheFile );
225
- ParseFileUtils .moveFile (tempFile , cacheFile );
226
- return Task .forResult (cacheFile );
227
- },
228
- ParseExecutors .io ());
229
- });
183
+ }
184
+
185
+ if (cacheFile .exists ()) {
186
+ return cacheFile ;
187
+ } else {
188
+ currentlyDownloadedFilesNames .add (state .name ());
189
+ }
190
+ }
191
+
192
+ try {
193
+ if (cancellationToken != null && cancellationToken .isCancelled ()) {
194
+ throw new CancellationException ();
195
+ }
196
+
197
+ // Generate the temp file path for caching ParseFile content based on
198
+ // ParseFile's url
199
+ // The reason we do not write to the cacheFile directly is because there
200
+ // is no way we can
201
+ // verify if a cacheFile is complete or not. If download is interrupted
202
+ // in the middle, next
203
+ // time when we download the ParseFile, since cacheFile has already
204
+ // existed, we will return
205
+ // this incomplete cacheFile
206
+ final File tempFile = getTempFile (state );
207
+
208
+ // network
209
+ final ParseFileRequest request =
210
+ new ParseFileRequest (
211
+ ParseHttpRequest .Method .GET , state .url (), tempFile );
212
+
213
+ // We do not need to delete the temp file since we always try to
214
+ // overwrite it
215
+ Task <Void > downloadTask =
216
+ request .executeAsync (
217
+ fileClient (),
218
+ null ,
219
+ downloadProgressCallback ,
220
+ cancellationToken );
221
+ ParseTaskUtils .wait (downloadTask );
222
+
223
+ // If the top-level task was cancelled, don't
224
+ // actually set the data -- just move on.
225
+ if (cancellationToken != null && cancellationToken .isCancelled ()) {
226
+ throw new CancellationException ();
227
+ }
228
+ if (downloadTask .isFaulted ()) {
229
+ ParseFileUtils .deleteQuietly (tempFile );
230
+ throw new RuntimeException (downloadTask .getError ());
231
+ }
232
+
233
+ // Since we give the cacheFile pointer to
234
+ // developers, it is not safe to guarantee
235
+ // cacheFile always does not exist here, so it is
236
+ // better to delete it manually,
237
+ // otherwise moveFile may throw an exception.
238
+ ParseFileUtils .deleteQuietly (cacheFile );
239
+ ParseFileUtils .moveFile (tempFile , cacheFile );
240
+ return cacheFile ;
241
+ } finally {
242
+ synchronized (lock ) {
243
+ currentlyDownloadedFilesNames .remove (state .name ());
244
+ lock .notifyAll ();
245
+ }
246
+ }
247
+ },
248
+ ParseExecutors .io ());
230
249
}
231
250
}
0 commit comments