| Index: adblockplussbrowser/src/main/java/org/adblockplus/sbrowser/contentblocker/engine/Subscriptions.java |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/adblockplussbrowser/src/main/java/org/adblockplus/sbrowser/contentblocker/engine/Subscriptions.java |
| @@ -0,0 +1,298 @@ |
| +/* |
| + * 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.BufferedWriter; |
| +import java.io.File; |
| +import java.io.FileOutputStream; |
| +import java.io.IOException; |
| +import java.io.OutputStreamWriter; |
| +import java.nio.charset.StandardCharsets; |
| +import java.util.ArrayList; |
| +import java.util.HashMap; |
| +import java.util.HashSet; |
| +import java.util.List; |
| +import java.util.Locale; |
| +import java.util.Map; |
| + |
| +import org.adblockplus.sbrowser.contentblocker.engine.Subscription.Type; |
| + |
| +import android.util.Log; |
| + |
| +/** |
| + * This class holds all listed subscriptions and manages the subscription |
| + * aggregation cache folder. |
| + */ |
| +final class Subscriptions |
| +{ |
| + private static final String TAG = Subscriptions.class.getSimpleName(); |
| + private static final String[] USER_SUBSCRIPTIONS = |
| + { Engine.USER_FILTERS_TITLE, Engine.USER_EXCEPTIONS_TITLE }; |
| + // Filters that begin with '|$' , '||$' , '@@|$' or '@@||$' |
| + // See https://issues.adblockplus.org/ticket/4772 |
| + private static final String UNSUPPORTED_FILTERS_REGEX = "^(\\|\\$|\\|\\|\\$|@@\\|\\$|@@\\|\\|\\$).*"; |
| + private final HashMap<String, Subscription> subscriptions = new HashMap<>(); |
| + |
| + private final Engine engine; |
| + private final File subscriptionFolder; |
| + private final File cacheFolder; |
| + private final boolean wasUninitialized; |
| + |
| + private Subscriptions(final Engine engine, final File appFolder, final File cacheFolder) |
| + { |
| + this.engine = engine; |
| + this.subscriptionFolder = appFolder; |
| + this.wasUninitialized = !this.subscriptionFolder.exists(); |
| + this.cacheFolder = cacheFolder; |
| + } |
| + |
| + public boolean wasUnitialized() |
| + { |
| + return this.wasUninitialized; |
| + } |
| + |
| + public File createAndWriteFile() throws IOException |
| + { |
| + for (;;) |
| + { |
| + final File file = new File(this.cacheFolder, String.format(Locale.ENGLISH, "tmp-%d.txt", |
| + (int) (Math.random() * 1e8))); |
| + if (!file.exists()) |
| + { |
| + Log.d(TAG, "Writing filters to " + file); |
| + this.writeFile(file); |
| + return file; |
| + } |
| + } |
| + } |
| + |
| + List<SubscriptionInfo> getSubscriptions(final Engine engine) |
| + { |
| + final ArrayList<SubscriptionInfo> subs = new ArrayList<>(); |
| + for (final Subscription sub : this.subscriptions.values()) |
| + { |
| + subs.add(SubscriptionInfo.create(engine, sub)); |
| + } |
| + return subs; |
| + } |
| + |
| + void loadSubscriptions(final List<Subscription> list) |
| + { |
| + list.addAll(this.subscriptions.values()); |
| + } |
| + |
| + public boolean hasSubscription(final String id) |
| + { |
| + return this.subscriptions.containsKey(id); |
| + } |
| + |
| + public boolean isSubscriptionEnabled(final String id) |
| + { |
| + final Subscription sub = this.subscriptions.get(id); |
| + return sub != null && sub.isEnabled(); |
| + } |
| + |
| + public boolean changeSubscriptionState(final String id, final boolean enabled) throws IOException |
| + { |
| + final Subscription sub = this.subscriptions.get(id); |
| + if (sub != null) |
| + { |
| + if (enabled != sub.isEnabled()) |
| + { |
| + sub.setEnabled(enabled); |
| + sub.serializeMetaData(this.getMetaFile(sub)); |
| + if (enabled) |
| + { |
| + this.engine.enqueueDownload(sub, true); |
| + } |
| + |
| + this.engine.subscriptionStateChanged(); |
| + this.engine.requestUpdateBroadcast(); |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + File getFiltersFile(final Subscription sub) |
| + { |
| + final File filtersFile; |
| + if (sub.getType() == Type.USER) |
| + { |
| + filtersFile = new File(this.subscriptionFolder, "user_" + sub.getTitle() + ".sub"); |
| + } |
| + else |
| + { |
| + filtersFile = new File(this.subscriptionFolder, "url_" |
| + + new File(sub.getURL().getPath()).getName() + ".sub"); |
| + } |
| + return filtersFile; |
| + } |
| + |
| + File getMetaFile(final Subscription sub) |
| + { |
| + return new File(getFiltersFile(sub).getAbsolutePath() + ".meta"); |
| + } |
| + |
| + void persistSubscription(final Subscription sub) throws IOException |
| + { |
| + sub.serializeSubscription(this.getMetaFile(sub), this.getFiltersFile(sub)); |
| + } |
| + |
| + void persistSubscriptions() throws IOException |
| + { |
| + for (final Subscription sub : this.subscriptions.values()) |
| + { |
| + this.persistSubscription(sub); |
| + } |
| + } |
| + |
| + /** |
| + * This method combines all currently listed and enabled subscriptions into |
| + * one text file. |
| + * |
| + * @param output |
| + * @throws IOException |
| + */ |
| + private void writeFile(final File output) throws IOException |
| + { |
| + final HashSet<String> filters = new HashSet<>(); |
| + for (final Subscription s : this.subscriptions.values()) |
| + { |
| + if (s.isEnabled()) |
| + { |
| + Log.d(TAG, "Adding filters for '" + s.getId() + "'"); |
| + s.deserializeFilters(this.getFiltersFile(s)); |
| + s.copyFilters(filters); |
| + s.clearFilters(); |
| + } |
| + if ((!s.isMetaDataValid() || !s.isFiltersValid()) && s.getURL() != null) |
| + { |
| + this.engine.enqueueDownload(s, true); |
| + } |
| + } |
| + |
| + try (final BufferedWriter w = new BufferedWriter( |
| + new OutputStreamWriter(new FileOutputStream(output), StandardCharsets.UTF_8))) |
| + { |
| + Log.d(TAG, "Writing " + filters.size() + " filters"); |
| + Engine.writeFilterHeaders(w); |
| + for (final String filter : filters) |
| + { |
| + // This is a temporary fix to not write filters that might crash Samsung Internet |
| + // See https://issues.adblockplus.org/ticket/4772 |
| + if (!filter.matches(UNSUPPORTED_FILTERS_REGEX)) |
| + { |
| + w.write(filter); |
| + w.write('\n'); |
| + } |
| + else |
| + { |
| + Log.d(TAG, "Ignoring unsupported filter: " + filter); |
| + } |
| + } |
| + } |
| + } |
| + |
| + public Subscription add(final Subscription sub) |
| + { |
| + final String id = sub.getId(); |
| + if (!this.subscriptions.containsKey(id)) |
| + { |
| + this.subscriptions.put(id, sub); |
| + return sub; |
| + } |
| + return this.subscriptions.get(id); |
| + } |
| + |
| + public boolean remove(final String id) |
| + { |
| + return this.subscriptions.remove(id) != null; |
| + } |
| + |
| + public static Subscriptions initialize(final Engine engine, final File appFolder, |
| + final File cacheFolder) |
| + { |
| + final Subscriptions subs = new Subscriptions(engine, appFolder, cacheFolder); |
| + |
| + subs.subscriptionFolder.mkdirs(); |
| + subs.cacheFolder.mkdirs(); |
| + |
| + final File[] files = subs.subscriptionFolder.listFiles(); |
| + for (File f : files) |
| + { |
| + if (f.getName().endsWith(".sub")) |
| + { |
| + final File metaFile = new File(f.getAbsolutePath() + ".meta"); |
| + if (metaFile.exists()) |
| + { |
| + final Subscription sub = Subscription.deserializeSubscription(metaFile); |
| + if (sub != null) |
| + { |
| + subs.subscriptions.put(sub.getId(), sub); |
| + } |
| + } |
| + } |
| + } |
| + |
| + subs.createUserSubscriptions(); |
| + |
| + return subs; |
| + } |
| + |
| + /** |
| + * Adds default user subscriptions if not exist. |
| + */ |
| + private void createUserSubscriptions() |
| + { |
| + for (String title : USER_SUBSCRIPTIONS) |
| + { |
| + final Subscription userSub = Subscription.createUserSubscription(title); |
| + if (!this.subscriptions.containsKey(userSub.getId())) |
| + { |
| + this.subscriptions.put(userSub.getId(), userSub); |
| + } |
| + } |
| + } |
| + |
| + public void checkForUpdates() throws IOException |
| + { |
| + for (Subscription sub : this.subscriptions.values()) |
| + { |
| + if (sub.isEnabled()) |
| + { |
| + this.engine.enqueueDownload(sub, false); |
| + } |
| + } |
| + } |
| + |
| + public void updateSubscription(final String id, final int responseCode, final String text, |
| + final Map<String, String> httpHeaders) |
| + throws IOException |
| + { |
| + final Subscription sub = this.subscriptions.get(id); |
| + if (sub != null) |
| + { |
| + if (sub.updateSubscription(responseCode, text, httpHeaders, this.getMetaFile(sub), |
| + this.getFiltersFile(sub))) |
| + { |
| + this.engine.requestUpdateBroadcast(); |
| + } |
| + } |
| + } |
| +} |