Changeset View
Changeset View
Standalone View
Standalone View
src/org/kde/kdeconnect/Plugins/MprisPlugin/AlbumArtCache.java
Show All 27 Lines | |||||
28 | import android.net.ConnectivityManager; | 28 | import android.net.ConnectivityManager; | ||
29 | import android.os.AsyncTask; | 29 | import android.os.AsyncTask; | ||
30 | import android.os.Build; | 30 | import android.os.Build; | ||
31 | import android.support.v4.util.LruCache; | 31 | import android.support.v4.util.LruCache; | ||
32 | import android.util.Log; | 32 | import android.util.Log; | ||
33 | 33 | | |||
34 | import com.jakewharton.disklrucache.DiskLruCache; | 34 | import com.jakewharton.disklrucache.DiskLruCache; | ||
35 | 35 | | |||
36 | import org.kde.kdeconnect.NetworkPacket; | ||||
37 | | ||||
36 | import java.io.File; | 38 | import java.io.File; | ||
37 | import java.io.IOException; | 39 | import java.io.IOException; | ||
38 | import java.io.InputStream; | 40 | import java.io.InputStream; | ||
39 | import java.io.OutputStream; | 41 | import java.io.OutputStream; | ||
40 | import java.net.HttpURLConnection; | 42 | import java.net.HttpURLConnection; | ||
41 | import java.net.MalformedURLException; | 43 | import java.net.MalformedURLException; | ||
42 | import java.net.URL; | 44 | import java.net.URL; | ||
43 | import java.net.URLDecoder; | 45 | import java.net.URLDecoder; | ||
▲ Show 20 Lines • Show All 194 Lines • ▼ Show 20 Line(s) | 224 | private static void fetchUrl(URL url) { | |||
238 | } | 240 | } | ||
239 | 241 | | |||
240 | fetchUrlList.add(url); | 242 | fetchUrlList.add(url); | ||
241 | initiateFetch(); | 243 | initiateFetch(); | ||
242 | } | 244 | } | ||
243 | 245 | | |||
244 | private static final class FetchURLTask extends AsyncTask<Void, Void, Boolean> { | 246 | private static final class FetchURLTask extends AsyncTask<Void, Void, Boolean> { | ||
245 | private final URL url; | 247 | private final URL url; | ||
246 | private InputStream input; | 248 | private NetworkPacket.Payload payload; | ||
247 | private final DiskLruCache.Editor cacheItem; | 249 | private final DiskLruCache.Editor cacheItem; | ||
248 | private OutputStream output; | 250 | private OutputStream output; | ||
249 | 251 | | |||
250 | /** | 252 | /** | ||
251 | * Initialize an url fetch | 253 | * Initialize an url fetch | ||
252 | * | 254 | * | ||
253 | * @param url The url being fetched | 255 | * @param url The url being fetched | ||
254 | * @param payloadInput A payload input stream (if from the connected device). null if fetched from http(s) | 256 | * @param payload A NetworkPacket Payload (if from the connected device). null if fetched from http(s) | ||
255 | * @param cacheItem The disk cache item to edit | 257 | * @param cacheItem The disk cache item to edit | ||
256 | */ | 258 | */ | ||
257 | FetchURLTask(URL url, InputStream payloadInput, DiskLruCache.Editor cacheItem) throws IOException { | 259 | FetchURLTask(URL url, NetworkPacket.Payload payload, DiskLruCache.Editor cacheItem) throws IOException { | ||
258 | this.url = url; | 260 | this.url = url; | ||
259 | this.input = payloadInput; | 261 | this.payload = payload; | ||
260 | this.cacheItem = cacheItem; | 262 | this.cacheItem = cacheItem; | ||
261 | output = cacheItem.newOutputStream(0); | 263 | output = cacheItem.newOutputStream(0); | ||
262 | } | 264 | } | ||
263 | 265 | | |||
264 | /** | 266 | /** | ||
265 | * Opens the http(s) connection | 267 | * Opens the http(s) connection | ||
266 | * | 268 | * | ||
267 | * @return True if succeeded | 269 | * @return True if succeeded | ||
268 | */ | 270 | */ | ||
269 | private boolean openHttp() throws IOException { | 271 | private InputStream openHttp() throws IOException { | ||
270 | //Default android behaviour does not follow https -> http urls, so do this manually | 272 | //Default android behaviour does not follow https -> http urls, so do this manually | ||
271 | if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { | 273 | if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { | ||
272 | throw new AssertionError("Invalid url: not http(s) in background album art fetch"); | 274 | throw new AssertionError("Invalid url: not http(s) in background album art fetch"); | ||
273 | } | 275 | } | ||
274 | URL currentUrl = url; | 276 | URL currentUrl = url; | ||
275 | HttpURLConnection connection; | 277 | HttpURLConnection connection; | ||
276 | for (int i = 0; i < 5; ++i) { | 278 | for (int i = 0; i < 5; ++i) { | ||
277 | connection = (HttpURLConnection) currentUrl.openConnection(); | 279 | connection = (HttpURLConnection) currentUrl.openConnection(); | ||
278 | connection.setConnectTimeout(10000); | 280 | connection.setConnectTimeout(10000); | ||
279 | connection.setReadTimeout(10000); | 281 | connection.setReadTimeout(10000); | ||
280 | connection.setInstanceFollowRedirects(false); | 282 | connection.setInstanceFollowRedirects(false); | ||
281 | 283 | | |||
282 | switch (connection.getResponseCode()) { | 284 | switch (connection.getResponseCode()) { | ||
283 | case HttpURLConnection.HTTP_MOVED_PERM: | 285 | case HttpURLConnection.HTTP_MOVED_PERM: | ||
284 | case HttpURLConnection.HTTP_MOVED_TEMP: | 286 | case HttpURLConnection.HTTP_MOVED_TEMP: | ||
285 | String location = connection.getHeaderField("Location"); | 287 | String location = connection.getHeaderField("Location"); | ||
286 | location = URLDecoder.decode(location, "UTF-8"); | 288 | location = URLDecoder.decode(location, "UTF-8"); | ||
287 | currentUrl = new URL(currentUrl, location); // Deal with relative URLs | 289 | currentUrl = new URL(currentUrl, location); // Deal with relative URLs | ||
288 | //Again, only support http(s) | 290 | //Again, only support http(s) | ||
289 | if (!currentUrl.getProtocol().equals("http") && !currentUrl.getProtocol().equals("https")) { | 291 | if (!currentUrl.getProtocol().equals("http") && !currentUrl.getProtocol().equals("https")) { | ||
290 | return false; | 292 | return null; | ||
291 | } | 293 | } | ||
292 | connection.disconnect(); | 294 | connection.disconnect(); | ||
293 | continue; | 295 | continue; | ||
294 | } | 296 | } | ||
295 | 297 | | |||
296 | //Found a non-redirecting connection, so do something with it | 298 | //Found a non-redirecting connection, so do something with it | ||
297 | input = connection.getInputStream(); | 299 | return connection.getInputStream(); | ||
298 | return true; | | |||
299 | } | 300 | } | ||
300 | 301 | | |||
301 | return false; | 302 | return null; | ||
302 | } | 303 | } | ||
303 | 304 | | |||
304 | @Override | 305 | @Override | ||
305 | protected Boolean doInBackground(Void... params) { | 306 | protected Boolean doInBackground(Void... params) { | ||
306 | try { | | |||
307 | //See if we need to open a http(s) connection here, or if we use a payload input stream | 307 | //See if we need to open a http(s) connection here, or if we use a payload input stream | ||
308 | try (InputStream input = payload == null ? openHttp() : payload.getInputStream()) { | ||||
308 | if (input == null) { | 309 | if (input == null) { | ||
309 | if (!openHttp()) { | | |||
310 | return false; | 310 | return false; | ||
311 | } | 311 | } | ||
312 | } | | |||
313 | 312 | | |||
314 | byte[] buffer = new byte[4096]; | 313 | byte[] buffer = new byte[4096]; | ||
315 | int bytesRead; | 314 | int bytesRead; | ||
316 | while ((bytesRead = input.read(buffer)) != -1) { | 315 | while ((bytesRead = input.read(buffer)) != -1) { | ||
317 | output.write(buffer, 0, bytesRead); | 316 | output.write(buffer, 0, bytesRead); | ||
318 | } | 317 | } | ||
319 | output.flush(); | 318 | output.flush(); | ||
320 | output.close(); | 319 | output.close(); | ||
321 | return true; | 320 | return true; | ||
322 | } catch (IOException e) { | 321 | } catch (IOException e) { | ||
323 | return false; | 322 | return false; | ||
323 | } finally { | ||||
324 | if (payload != null) { | ||||
325 | payload.close(); | ||||
326 | } | ||||
324 | } | 327 | } | ||
325 | } | 328 | } | ||
326 | 329 | | |||
327 | @Override | 330 | @Override | ||
328 | protected void onPostExecute(Boolean success) { | 331 | protected void onPostExecute(Boolean success) { | ||
329 | try { | 332 | try { | ||
330 | if (success) { | 333 | if (success) { | ||
331 | cacheItem.commit(); | 334 | cacheItem.commit(); | ||
▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Line(s) | |||||
420 | } | 423 | } | ||
421 | 424 | | |||
422 | /** | 425 | /** | ||
423 | * Transfer an asked-for album art payload to the disk cache. | 426 | * Transfer an asked-for album art payload to the disk cache. | ||
424 | * | 427 | * | ||
425 | * @param albumUrl The url of the album art (should be a file:// url) | 428 | * @param albumUrl The url of the album art (should be a file:// url) | ||
426 | * @param payload The payload input stream | 429 | * @param payload The payload input stream | ||
427 | */ | 430 | */ | ||
428 | static void payloadToDiskCache(String albumUrl, InputStream payload) { | 431 | static void payloadToDiskCache(String albumUrl, NetworkPacket.Payload payload) { | ||
429 | //We need the disk cache for this | 432 | //We need the disk cache for this | ||
433 | if (payload == null) { | ||||
434 | return; | ||||
435 | } | ||||
436 | | ||||
430 | if (diskCache == null) { | 437 | if (diskCache == null) { | ||
431 | Log.e("KDE/Mpris/AlbumArtCache", "The disk cache is not intialized!"); | 438 | Log.e("KDE/Mpris/AlbumArtCache", "The disk cache is not intialized!"); | ||
432 | try { | | |||
433 | payload.close(); | 439 | payload.close(); | ||
434 | } catch (IOException ignored) {} | | |||
435 | return; | | |||
436 | } | | |||
437 | if (payload == null) { | | |||
438 | return; | 440 | return; | ||
439 | } | 441 | } | ||
440 | 442 | | |||
441 | URL url; | 443 | URL url; | ||
442 | try { | 444 | try { | ||
443 | url = new URL(albumUrl); | 445 | url = new URL(albumUrl); | ||
444 | } catch (MalformedURLException e) { | 446 | } catch (MalformedURLException e) { | ||
445 | //Shouldn't happen (checked on receival of the url), but just to be sure | 447 | //Shouldn't happen (checked on receival of the url), but just to be sure | ||
446 | try { | | |||
447 | payload.close(); | 448 | payload.close(); | ||
448 | } catch (IOException ignored) {} | | |||
449 | return; | 449 | return; | ||
450 | } | 450 | } | ||
451 | 451 | | |||
452 | if (!"file".equals(url.getProtocol())) { | 452 | if (!"file".equals(url.getProtocol())) { | ||
453 | //Shouldn't happen (otherwise we wouldn't have asked for the payload), but just to be sure | 453 | //Shouldn't happen (otherwise we wouldn't have asked for the payload), but just to be sure | ||
454 | try { | | |||
455 | payload.close(); | 454 | payload.close(); | ||
456 | } catch (IOException ignored) {} | | |||
457 | return; | 455 | return; | ||
458 | } | 456 | } | ||
459 | 457 | | |||
460 | //Only fetch the URL if we're not fetching it already | 458 | //Only fetch the URL if we're not fetching it already | ||
461 | if (isFetchingList.contains(url)) { | 459 | if (isFetchingList.contains(url)) { | ||
462 | try { | | |||
463 | payload.close(); | 460 | payload.close(); | ||
464 | } catch (IOException ignored) {} | | |||
465 | return; | 461 | return; | ||
466 | } | 462 | } | ||
467 | 463 | | |||
468 | //Check if we already have this art | 464 | //Check if we already have this art | ||
469 | try { | 465 | try { | ||
470 | if (memoryCache.get(albumUrl) != null || diskCache.get(urlToDiskCacheKey(albumUrl)) != null) { | 466 | if (memoryCache.get(albumUrl) != null || diskCache.get(urlToDiskCacheKey(albumUrl)) != null) { | ||
471 | try { | | |||
472 | payload.close(); | 467 | payload.close(); | ||
473 | } catch (IOException ignored) {} | | |||
474 | return; | 468 | return; | ||
475 | } | 469 | } | ||
476 | } catch (IOException e) { | 470 | } catch (IOException e) { | ||
477 | Log.e("KDE/Mpris/AlbumArtCache", "Disk cache problem!", e); | 471 | Log.e("KDE/Mpris/AlbumArtCache", "Disk cache problem!", e); | ||
478 | try { | | |||
479 | payload.close(); | 472 | payload.close(); | ||
480 | } catch (IOException ignored) {} | | |||
481 | return; | 473 | return; | ||
482 | } | 474 | } | ||
483 | 475 | | |||
484 | //Add it to the currently-fetching list | 476 | //Add it to the currently-fetching list | ||
485 | isFetchingList.add(url); | 477 | isFetchingList.add(url); | ||
486 | ++numFetching; | 478 | ++numFetching; | ||
487 | 479 | | |||
488 | try { | 480 | try { | ||
489 | DiskLruCache.Editor cacheItem = diskCache.edit(urlToDiskCacheKey(url.toString())); | 481 | DiskLruCache.Editor cacheItem = diskCache.edit(urlToDiskCacheKey(url.toString())); | ||
490 | if (cacheItem == null) { | 482 | if (cacheItem == null) { | ||
491 | Log.e("KDE/Mpris/AlbumArtCache", | 483 | Log.e("KDE/Mpris/AlbumArtCache", | ||
492 | "Two disk cache edits happened at the same time, should be impossible!"); | 484 | "Two disk cache edits happened at the same time, should be impossible!"); | ||
493 | --numFetching; | 485 | --numFetching; | ||
494 | try { | | |||
495 | payload.close(); | 486 | payload.close(); | ||
496 | } catch (IOException ignored) {} | | |||
497 | return; | 487 | return; | ||
498 | } | 488 | } | ||
499 | 489 | | |||
500 | //Do the actual fetch in the background | 490 | //Do the actual fetch in the background | ||
501 | new FetchURLTask(url, payload, cacheItem).execute(); | 491 | new FetchURLTask(url, payload, cacheItem).execute(); | ||
502 | } catch (IOException e) { | 492 | } catch (IOException e) { | ||
503 | Log.e("KDE/Mpris/AlbumArtCache", "Problems with the disk cache", e); | 493 | Log.e("KDE/Mpris/AlbumArtCache", "Problems with the disk cache", e); | ||
504 | --numFetching; | 494 | --numFetching; | ||
505 | } | 495 | } | ||
506 | } | 496 | } | ||
507 | } | 497 | } |