1 |
|
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
|
8 |
|
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
|
|
13 |
|
|
14 |
|
|
15 |
|
|
16 |
|
|
17 |
|
|
18 |
|
|
19 |
|
package net.sourceforge.subsonic.androidapp.service; |
20 |
|
|
21 |
|
import java.io.File; |
22 |
|
import java.io.FileOutputStream; |
23 |
|
import java.io.IOException; |
24 |
|
import java.io.InputStream; |
25 |
|
import java.io.OutputStream; |
26 |
|
|
27 |
|
import android.content.Context; |
28 |
|
import android.os.PowerManager; |
29 |
|
import android.util.DisplayMetrics; |
30 |
|
import android.util.Log; |
31 |
|
import net.sourceforge.subsonic.androidapp.domain.MusicDirectory; |
32 |
|
import net.sourceforge.subsonic.androidapp.util.CancellableTask; |
33 |
|
import net.sourceforge.subsonic.androidapp.util.FileUtil; |
34 |
|
import net.sourceforge.subsonic.androidapp.util.Util; |
35 |
|
import net.sourceforge.subsonic.androidapp.util.CacheCleaner; |
36 |
|
|
37 |
|
import org.apache.http.HttpResponse; |
38 |
|
import org.apache.http.HttpStatus; |
39 |
|
|
40 |
|
|
41 |
|
@author |
42 |
|
@version |
43 |
|
|
|
|
| 87% |
Uncovered Elements: 12 (92) |
Complexity: 31 |
Complexity Density: 0,6 |
|
44 |
|
public class DownloadFile { |
45 |
|
|
46 |
|
private static final String TAG = DownloadFile.class.getSimpleName(); |
47 |
|
private final Context context; |
48 |
|
private final MusicDirectory.Entry song; |
49 |
|
private final File partialFile; |
50 |
|
private final File completeFile; |
51 |
|
private final File saveFile; |
52 |
|
|
53 |
|
private final MediaStoreService mediaStoreService; |
54 |
|
private CancellableTask downloadTask; |
55 |
|
private boolean save; |
56 |
|
private boolean failed; |
57 |
|
private int bitRate; |
58 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (8) |
Complexity: 1 |
Complexity Density: 0,12 |
|
59 |
164
|
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) {... |
60 |
164
|
this.context = context; |
61 |
164
|
this.song = song; |
62 |
164
|
this.save = save; |
63 |
164
|
saveFile = FileUtil.getSongFile(context, song); |
64 |
164
|
bitRate = Util.getMaxBitrate(context); |
65 |
164
|
partialFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) + |
66 |
|
"." + bitRate + ".partial." + FileUtil.getExtension(saveFile.getName())); |
67 |
164
|
completeFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) + |
68 |
|
".complete." + FileUtil.getExtension(saveFile.getName())); |
69 |
164
|
mediaStoreService = new MediaStoreService(context); |
70 |
|
} |
71 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
72 |
9134
|
public MusicDirectory.Entry getSong() {... |
73 |
9134
|
return song; |
74 |
|
} |
75 |
|
|
76 |
|
|
77 |
|
|
78 |
|
|
|
|
| 71,4% |
Uncovered Elements: 2 (7) |
Complexity: 3 |
Complexity Density: 1 |
|
79 |
30
|
public int getBitRate() {... |
80 |
30
|
if (bitRate > 0) { |
81 |
0
|
return bitRate; |
82 |
|
} |
83 |
30
|
return song.getBitRate() == null ? 160 : song.getBitRate(); |
84 |
|
} |
85 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (4) |
Complexity: 1 |
Complexity Density: 0,25 |
|
86 |
21
|
public synchronized void download() {... |
87 |
21
|
FileUtil.createDirectoryForParent(saveFile); |
88 |
21
|
failed = false; |
89 |
21
|
downloadTask = new DownloadTask(); |
90 |
21
|
downloadTask.start(); |
91 |
|
} |
92 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (4) |
Complexity: 2 |
Complexity Density: 1 |
|
93 |
47
|
public synchronized void cancelDownload() {... |
94 |
47
|
if (downloadTask != null) { |
95 |
20
|
downloadTask.cancel(); |
96 |
|
} |
97 |
|
} |
98 |
|
|
|
|
| 77,8% |
Uncovered Elements: 2 (9) |
Complexity: 3 |
Complexity Density: 0,6 |
|
99 |
1300
|
public File getCompleteFile() {... |
100 |
1300
|
if (saveFile.exists()) { |
101 |
0
|
return saveFile; |
102 |
|
} |
103 |
|
|
104 |
1300
|
if (completeFile.exists()) { |
105 |
320
|
return completeFile; |
106 |
|
} |
107 |
|
|
108 |
980
|
return saveFile; |
109 |
|
} |
110 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
111 |
1366
|
public File getPartialFile() {... |
112 |
1366
|
return partialFile; |
113 |
|
} |
114 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
115 |
408
|
public boolean isSaved() {... |
116 |
408
|
return saveFile.exists(); |
117 |
|
} |
118 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
119 |
530
|
public synchronized boolean isCompleteFileAvailable() {... |
120 |
530
|
return saveFile.exists() || completeFile.exists(); |
121 |
|
} |
122 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
123 |
920
|
public synchronized boolean isWorkDone() {... |
124 |
920
|
return saveFile.exists() || (completeFile.exists() && !save); |
125 |
|
} |
126 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
127 |
1032
|
public synchronized boolean isDownloading() {... |
128 |
1032
|
return downloadTask != null && downloadTask.isRunning(); |
129 |
|
} |
130 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
131 |
56
|
public synchronized boolean isDownloadCancelled() {... |
132 |
56
|
return downloadTask != null && downloadTask.isCancelled(); |
133 |
|
} |
134 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
135 |
57
|
public boolean shouldSave() {... |
136 |
57
|
return save; |
137 |
|
} |
138 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
139 |
51
|
public boolean isFailed() {... |
140 |
51
|
return failed; |
141 |
|
} |
142 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (5) |
Complexity: 1 |
Complexity Density: 0,2 |
|
143 |
4
|
public void delete() {... |
144 |
4
|
cancelDownload(); |
145 |
4
|
Util.delete(partialFile); |
146 |
4
|
Util.delete(completeFile); |
147 |
4
|
Util.delete(saveFile); |
148 |
4
|
mediaStoreService.deleteFromMediaStore(this); |
149 |
|
} |
150 |
|
|
|
|
| 0% |
Uncovered Elements: 4 (4) |
Complexity: 2 |
Complexity Density: 1 |
|
151 |
0
|
public void unpin() {... |
152 |
0
|
if (saveFile.exists()) { |
153 |
0
|
saveFile.renameTo(completeFile); |
154 |
|
} |
155 |
|
} |
156 |
|
|
|
|
| 80% |
Uncovered Elements: 2 (10) |
Complexity: 4 |
Complexity Density: 0,67 |
|
157 |
19
|
public boolean cleanup() {... |
158 |
19
|
boolean ok = true; |
159 |
19
|
if (completeFile.exists() || saveFile.exists()) { |
160 |
5
|
ok = Util.delete(partialFile); |
161 |
|
} |
162 |
19
|
if (saveFile.exists()) { |
163 |
0
|
ok &= Util.delete(completeFile); |
164 |
|
} |
165 |
19
|
return ok; |
166 |
|
} |
167 |
|
|
168 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (3) |
Complexity: 1 |
Complexity Density: 0,33 |
|
169 |
24
|
public void updateModificationDate() {... |
170 |
24
|
updateModificationDate(saveFile); |
171 |
24
|
updateModificationDate(partialFile); |
172 |
24
|
updateModificationDate(completeFile); |
173 |
|
} |
174 |
|
|
|
|
| 87,5% |
Uncovered Elements: 1 (8) |
Complexity: 3 |
Complexity Density: 0,75 |
|
175 |
72
|
private void updateModificationDate(File file) {... |
176 |
72
|
if (file.exists()) { |
177 |
25
|
boolean ok = file.setLastModified(System.currentTimeMillis()); |
178 |
25
|
if (!ok) { |
179 |
25
|
Log.w(TAG, "Failed to set last-modified date on " + file); |
180 |
|
} |
181 |
|
} |
182 |
|
} |
183 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
184 |
309
|
@Override... |
185 |
|
public String toString() { |
186 |
309
|
return "DownloadFile (" + song + ")"; |
187 |
|
} |
188 |
|
|
|
|
| 82,2% |
Uncovered Elements: 19 (107) |
Complexity: 22 |
Complexity Density: 0,3 |
|
189 |
|
private class DownloadTask extends CancellableTask { |
190 |
|
|
|
|
| 76,9% |
Uncovered Elements: 15 (65) |
Complexity: 11 |
Complexity Density: 0,23 |
|
191 |
21
|
@Override... |
192 |
|
public void execute() { |
193 |
|
|
194 |
21
|
InputStream in = null; |
195 |
21
|
FileOutputStream out = null; |
196 |
21
|
PowerManager.WakeLock wakeLock = null; |
197 |
21
|
try { |
198 |
|
|
199 |
21
|
if (Util.isScreenLitOnDownload(context)) { |
200 |
21
|
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); |
201 |
21
|
wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, toString()); |
202 |
21
|
wakeLock.acquire(); |
203 |
21
|
Log.i(TAG, "Acquired wake lock " + wakeLock); |
204 |
|
} |
205 |
|
|
206 |
21
|
if (saveFile.exists()) { |
207 |
0
|
Log.i(TAG, saveFile + " already exists. Skipping."); |
208 |
0
|
return; |
209 |
|
} |
210 |
21
|
if (completeFile.exists()) { |
211 |
0
|
if (save) { |
212 |
0
|
Util.atomicCopy(completeFile, saveFile); |
213 |
|
} else { |
214 |
0
|
Log.i(TAG, completeFile + " already exists. Skipping."); |
215 |
|
} |
216 |
0
|
return; |
217 |
|
} |
218 |
|
|
219 |
21
|
MusicService musicService = MusicServiceFactory.getMusicService(context); |
220 |
|
|
221 |
|
|
222 |
21
|
HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this); |
223 |
21
|
in = response.getEntity().getContent(); |
224 |
21
|
boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT; |
225 |
21
|
if (partial) { |
226 |
3
|
Log.i(TAG, "Executed partial HTTP GET, skipping " + partialFile.length() + " bytes"); |
227 |
|
} |
228 |
|
|
229 |
21
|
out = new FileOutputStream(partialFile, partial); |
230 |
20
|
long n = copy(in, out); |
231 |
7
|
Log.i(TAG, "Downloaded " + n + " bytes to " + partialFile); |
232 |
7
|
out.flush(); |
233 |
7
|
out.close(); |
234 |
|
|
235 |
7
|
if (isCancelled()) { |
236 |
1
|
throw new Exception("Download of '" + song + "' was cancelled"); |
237 |
|
} |
238 |
|
|
239 |
6
|
downloadAndSaveCoverArt(musicService); |
240 |
|
|
241 |
6
|
if (save) { |
242 |
0
|
Util.atomicCopy(partialFile, saveFile); |
243 |
0
|
mediaStoreService.saveInMediaStore(DownloadFile.this); |
244 |
|
} else { |
245 |
6
|
Util.atomicCopy(partialFile, completeFile); |
246 |
|
} |
247 |
|
|
248 |
|
} catch (Exception x) { |
249 |
14
|
Util.close(out); |
250 |
14
|
Util.delete(completeFile); |
251 |
14
|
Util.delete(saveFile); |
252 |
14
|
if (!isCancelled()) { |
253 |
1
|
failed = true; |
254 |
1
|
Log.w(TAG, "Failed to download '" + song + "'.", x); |
255 |
|
} |
256 |
|
|
257 |
|
} finally { |
258 |
20
|
Util.close(in); |
259 |
20
|
Util.close(out); |
260 |
20
|
if (wakeLock != null) { |
261 |
20
|
wakeLock.release(); |
262 |
20
|
Log.i(TAG, "Released wake lock " + wakeLock); |
263 |
|
} |
264 |
20
|
new CacheCleaner(context, DownloadServiceImpl.getInstance()).clean(); |
265 |
|
} |
266 |
|
} |
267 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
268 |
82
|
@Override... |
269 |
|
public String toString() { |
270 |
82
|
return "DownloadTask (" + song + ")"; |
271 |
|
} |
272 |
|
|
|
|
| 75% |
Uncovered Elements: 2 (8) |
Complexity: 3 |
Complexity Density: 0,5 |
|
273 |
6
|
private void downloadAndSaveCoverArt(MusicService musicService) throws Exception {... |
274 |
6
|
try { |
275 |
6
|
if (song.getCoverArt() != null) { |
276 |
6
|
DisplayMetrics metrics = context.getResources().getDisplayMetrics(); |
277 |
6
|
int size = Math.min(metrics.widthPixels, metrics.heightPixels); |
278 |
6
|
musicService.getCoverArt(context, song, size, true, null); |
279 |
|
} |
280 |
|
} catch (Exception x) { |
281 |
0
|
Log.e(TAG, "Failed to get cover art.", x); |
282 |
|
} |
283 |
|
} |
284 |
|
|
|
|
| 88,2% |
Uncovered Elements: 2 (17) |
Complexity: 4 |
Complexity Density: 0,31 |
|
285 |
20
|
private long copy(final InputStream in, OutputStream out) throws IOException, InterruptedException {... |
286 |
|
|
287 |
|
|
288 |
|
|
289 |
20
|
new Thread() { |
|
|
| 100% |
Uncovered Elements: 0 (11) |
Complexity: 3 |
Complexity Density: 0,43 |
|
290 |
20
|
@Override... |
291 |
|
public void run() { |
292 |
20
|
while (true) { |
293 |
69
|
Util.sleepQuietly(3000L); |
294 |
68
|
if (isCancelled()) { |
295 |
14
|
Util.close(in); |
296 |
14
|
return; |
297 |
|
} |
298 |
54
|
if (!isRunning()) { |
299 |
5
|
return; |
300 |
|
} |
301 |
|
} |
302 |
|
} |
303 |
|
}.start(); |
304 |
|
|
305 |
20
|
byte[] buffer = new byte[1024 * 16]; |
306 |
20
|
long count = 0; |
307 |
20
|
int n; |
308 |
20
|
long lastLog = System.currentTimeMillis(); |
309 |
|
|
310 |
?
|
while (!isCancelled() && (n = in.read(buffer)) != -1) { |
311 |
17376
|
out.write(buffer, 0, n); |
312 |
17376
|
count += n; |
313 |
|
|
314 |
17376
|
long now = System.currentTimeMillis(); |
315 |
17376
|
if (now - lastLog > 3000L) { |
316 |
48
|
Log.i(TAG, "Downloaded " + Util.formatBytes(count) + " of " + song); |
317 |
48
|
lastLog = now; |
318 |
|
} |
319 |
|
} |
320 |
7
|
return count; |
321 |
|
} |
322 |
|
} |
323 |
|
} |