| Index: adblockplussbrowser/src/main/java/org/adblockplus/sbrowser/contentblocker/engine/Downloader.java |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/adblockplussbrowser/src/main/java/org/adblockplus/sbrowser/contentblocker/engine/Downloader.java |
| @@ -0,0 +1,261 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-present eyeo GmbH |
| + * |
| + * Adblock Plus is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 3 as |
| + * published by the Free Software Foundation. |
| + * |
| + * Adblock Plus is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| + */ |
| + |
| +package org.adblockplus.sbrowser.contentblocker.engine; |
| + |
| +import java.io.BufferedReader; |
| +import java.io.IOException; |
| +import java.io.InputStreamReader; |
| +import java.net.HttpURLConnection; |
| +import java.net.URL; |
| +import java.nio.charset.StandardCharsets; |
| +import java.util.HashMap; |
| +import java.util.HashSet; |
| +import java.util.Map; |
| +import java.util.Map.Entry; |
| +import java.util.concurrent.LinkedBlockingQueue; |
| +import java.util.concurrent.TimeUnit; |
| +import java.util.concurrent.locks.ReentrantLock; |
| +import android.annotation.SuppressLint; |
| +import android.util.Log; |
| + |
| +@SuppressLint("DefaultLocale") |
| +final class Downloader |
| +{ |
| + private static final int MAX_RETRIES = 5; |
| + |
| + private static final String TAG = Downloader.class.getSimpleName(); |
| + private final Engine engine; |
| + private final ReentrantLock accessLock = new ReentrantLock(); |
| + private Thread downloaderThread; |
| + private final LinkedBlockingQueue<DownloadJob> downloadJobs = new LinkedBlockingQueue<>(); |
| + private final HashSet<String> enqueuedIds = new HashSet<>(); |
| + private boolean downloaderEnabled = true; |
| + |
| + private Downloader(final Engine engine) |
| + { |
| + this.engine = engine; |
| + } |
| + |
| + void lock() |
| + { |
| + this.accessLock.lock(); |
| + } |
| + |
| + void unlock() |
| + { |
| + this.accessLock.unlock(); |
| + } |
| + |
| + void connectivityChanged() |
| + { |
| + this.lock(); |
| + if (!this.downloaderEnabled) |
| + { |
| + Log.d(TAG, "Re-checking download permission"); |
| + } |
| + this.downloaderEnabled = true; |
| + this.unlock(); |
| + } |
| + |
| + static void download(final DownloadJob job) throws IOException |
| + { |
| + final HttpURLConnection connection = (HttpURLConnection) job.url.openConnection(); |
| + connection.setRequestMethod("GET"); |
| + for (final Entry<String, String> e : job.headers.entrySet()) |
| + { |
| + connection.addRequestProperty(e.getKey(), e.getValue()); |
| + } |
| + connection.connect(); |
| + |
| + job.responseCode = connection.getResponseCode(); |
| + job.responseHeaders.clear(); |
| + job.responseText = null; |
| + |
| + for (int i = 1;; i++) |
| + { |
| + final String key = connection.getHeaderFieldKey(i); |
| + if (key == null) |
| + { |
| + break; |
| + } |
| + final String value = connection.getHeaderField(i); |
| + job.responseHeaders.put(key.toLowerCase(), value); |
| + } |
| + |
| + final StringBuilder sb = new StringBuilder(); |
| + try (final BufferedReader r = new BufferedReader(new InputStreamReader( |
| + connection.getInputStream(), StandardCharsets.UTF_8))) |
| + { |
| + for (int ch = r.read(); ch != -1; ch = r.read()) |
| + { |
| + sb.append((char) ch); |
| + } |
| + job.responseText = sb.toString(); |
| + } |
| + } |
| + |
| + public void enqueueDownload(final URL url, final String id, final Map<String, String> headers) |
| + { |
| + this.lock(); |
| + try |
| + { |
| + if (!this.enqueuedIds.contains(id)) |
| + { |
| + this.enqueuedIds.add(id); |
| + this.downloadJobs.add(new DownloadJob(url, id, headers)); |
| + } |
| + } |
| + finally |
| + { |
| + this.unlock(); |
| + } |
| + } |
| + |
| + public static Downloader create(final Engine engine) |
| + { |
| + final Downloader downloader = new Downloader(engine); |
| + |
| + downloader.downloaderThread = new Thread(new DownloaderHandler(downloader)); |
| + downloader.downloaderThread.setDaemon(true); |
| + downloader.downloaderThread.start(); |
| + |
| + return downloader; |
| + } |
| + |
| + private static class DownloaderHandler implements Runnable |
| + { |
| + private static final String TAG = DownloaderHandler.class.getSimpleName(); |
| + |
| + private final Downloader downloader; |
| + |
| + public DownloaderHandler(final Downloader downloader) |
| + { |
| + this.downloader = downloader; |
| + } |
| + |
| + @Override |
| + public void run() |
| + { |
| + Log.d(TAG, "Handler thread started"); |
| + final LinkedBlockingQueue<DownloadJob> reQueue = new LinkedBlockingQueue<>(); |
| + boolean interrupted = false; |
| + while (!interrupted) |
| + { |
| + DownloadJob job = null; |
| + try |
| + { |
| + if (!this.downloader.downloaderEnabled) |
| + { |
| + Thread.sleep(30000); |
| + continue; |
| + } |
| + job = this.downloader.downloadJobs.poll(5 * 60, TimeUnit.SECONDS); |
| + if (job != null) |
| + { |
| + if (this.downloader.engine.canUseInternet()) |
| + { |
| + Log.d(TAG, "Downloading '" + job.id + "' using " + job.url); |
| + download(job); |
| + Log.d(TAG, "Downloading '" + job.id + "' finished with response code " |
| + + job.responseCode); |
| + this.downloader.lock(); |
| + try |
| + { |
| + this.downloader.enqueuedIds.remove(job.id); |
| + } |
| + finally |
| + { |
| + this.downloader.unlock(); |
| + } |
| + |
| + this.downloader.engine.downloadFinished(job.id, job.responseCode, job.responseText, |
| + job.responseHeaders); |
| + |
| + // Check for retries |
| + if (!reQueue.isEmpty()) |
| + { |
| + this.downloader.downloadJobs.add(reQueue.poll()); |
| + } |
| + } |
| + else |
| + { |
| + // we just keep jobs in queue |
| + Log.d(TAG, "Updates disabled, re-queuing and disabling downloader"); |
| + this.downloader.downloadJobs.add(job); |
| + this.downloader.lock(); |
| + this.downloader.downloaderEnabled = false; |
| + this.downloader.unlock(); |
| + } |
| + } |
| + } |
| + catch (final InterruptedException e) |
| + { |
| + Log.d(TAG, "Handler interrupted", e); |
| + interrupted = true; |
| + } |
| + catch (final Throwable t) |
| + { |
| + Log.e(TAG, "Downloading failed: " + t.getMessage(), t); |
| + if (job != null) |
| + { |
| + if (job.retryCount++ < MAX_RETRIES) |
| + { |
| + reQueue.add(job); |
| + } |
| + else |
| + { |
| + this.downloader.lock(); |
| + try |
| + { |
| + this.downloader.enqueuedIds.remove(job.id); |
| + } |
| + finally |
| + { |
| + this.downloader.unlock(); |
| + } |
| + this.downloader.engine.downloadFinished(job.id, -1, null, null); |
| + } |
| + } |
| + } |
| + } |
| + Log.d(TAG, "Handler thread finished"); |
| + } |
| + } |
| + |
| + private static class DownloadJob |
| + { |
| + private final URL url; |
| + private final String id; |
| + private final HashMap<String, String> headers = new HashMap<>(); |
| + private int retryCount = 0; |
| + |
| + private int responseCode = 0; |
| + private final HashMap<String, String> responseHeaders = new HashMap<>(); |
| + private String responseText = null; |
| + |
| + public DownloadJob(final URL url, final String id, final Map<String, String> headers) |
| + { |
| + this.url = url; |
| + this.id = id; |
| + if (headers != null) |
| + { |
| + this.headers.putAll(headers); |
| + } |
| + } |
| + } |
| +} |