diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseFilterManager.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseFilterManager.java new file mode 100644 index 00000000..cbff6e5a --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseFilterManager.java @@ -0,0 +1,74 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ +package org.floens.chan.core.database; + +import org.floens.chan.core.model.Filter; + +import java.util.List; +import java.util.concurrent.Callable; + +public class DatabaseFilterManager { + private static final String TAG = "DatabaseFilterManager"; + + private DatabaseManager databaseManager; + private DatabaseHelper helper; + + public DatabaseFilterManager(DatabaseManager databaseManager, DatabaseHelper helper) { + this.databaseManager = databaseManager; + this.helper = helper; + } + + public Callable createFilter(final Filter filter) { + return new Callable() { + @Override + public Filter call() throws Exception { + helper.filterDao.create(filter); + return filter; + } + }; + } + + public Callable deleteFilter(final Filter filter) { + return new Callable() { + @Override + public Void call() throws Exception { + helper.filterDao.delete(filter); + return null; + } + }; + } + + public Callable updateFilter(final Filter filter) { + return new Callable() { + @Override + public Filter call() throws Exception { + helper.filterDao.update(filter); + return filter; + } + }; + } + + public Callable> getFilters() { + return new Callable>() { + @Override + public List call() throws Exception { + return helper.filterDao.queryForAll(); + } + }; + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java index e9476f79..92d8e7d9 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java @@ -27,7 +27,6 @@ import com.j256.ormlite.table.TableUtils; import org.floens.chan.Chan; import org.floens.chan.core.model.Board; -import org.floens.chan.core.model.Filter; import org.floens.chan.core.model.Post; import org.floens.chan.core.model.ThreadHide; import org.floens.chan.utils.Logger; @@ -63,6 +62,7 @@ public class DatabaseManager { private final DatabaseLoadableManager databaseLoadableManager; private final DatabaseHistoryManager databaseHistoryManager; private final DatabaseSavedReplyManager databaseSavedReplyManager; + private final DatabaseFilterManager databaseFilterManager; public DatabaseManager(Context context) { backgroundExecutor = Executors.newSingleThreadExecutor(); @@ -72,6 +72,7 @@ public class DatabaseManager { databasePinManager = new DatabasePinManager(this, helper, databaseLoadableManager); databaseHistoryManager = new DatabaseHistoryManager(this, helper, databaseLoadableManager); databaseSavedReplyManager = new DatabaseSavedReplyManager(this, helper); + databaseFilterManager = new DatabaseFilterManager(this, helper); initialize(); EventBus.getDefault().register(this); } @@ -92,6 +93,10 @@ public class DatabaseManager { return databaseSavedReplyManager; } + public DatabaseFilterManager getDatabaseFilterManager() { + return databaseFilterManager; + } + // Called when the app changes foreground state public void onEvent(Chan.ForegroundChangedMessage message) { if (!message.inForeground) { @@ -113,33 +118,6 @@ public class DatabaseManager { initialize(); } - public void addOrUpdateFilter(Filter filter) { - try { - helper.filterDao.createOrUpdate(filter); - } catch (SQLException e) { - Logger.e(TAG, "Error adding filter to db", e); - } - } - - public void removeFilter(Filter filter) { - try { - helper.filterDao.delete(filter); - } catch (SQLException e) { - Logger.e(TAG, "Error removing filter from db", e); - } - } - - public List getFilters() { - List list = null; - try { - list = helper.filterDao.queryForAll(); - } catch (SQLException e) { - Logger.e(TAG, "Error getting filters from db", e); - } - - return list; - } - /** * Create or updates these boards in the boards table. * diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java b/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java index 6fd43f42..1172faa5 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/FilterEngine.java @@ -20,6 +20,7 @@ package org.floens.chan.core.manager; import android.text.TextUtils; import org.floens.chan.Chan; +import org.floens.chan.core.database.DatabaseFilterManager; import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.model.Board; import org.floens.chan.core.model.Filter; @@ -96,48 +97,36 @@ public class FilterEngine { } private final DatabaseManager databaseManager; + private final DatabaseFilterManager databaseFilterManager; private List filters; private final List enabledFilters = new ArrayList<>(); private FilterEngine() { databaseManager = Chan.getDatabaseManager(); - filters = databaseManager.getFilters(); - updateEnabledFilters(); + databaseFilterManager = databaseManager.getDatabaseFilterManager(); + update(); } - /** - * Add or update a filter, thread-safe. - * The filter will be updated in the db if the {@link Filter#id} was non-null. - * - * @param filter filter too add or update. - */ - public void addOrUpdate(Filter filter) { - databaseManager.addOrUpdateFilter(filter); - filters = databaseManager.getFilters(); - updateEnabledFilters(); + public void deleteFilter(Filter filter) { + databaseManager.runTaskSync(databaseFilterManager.deleteFilter(filter)); + update(); } - /** - * Remove a filter, thread-safe. - * - * @param filter filter to remove - */ - public void remove(Filter filter) { - databaseManager.removeFilter(filter); - filters = databaseManager.getFilters(); - updateEnabledFilters(); + public void createOrUpdateFilter(Filter filter) { + if (filter.id == 0) { + databaseManager.runTaskSync(databaseFilterManager.createFilter(filter)); + } else { + databaseManager.runTaskSync(databaseFilterManager.updateFilter(filter)); + } + update(); } - /** - * Get all enabled filters. - * - * @return List of enabled filters - */ public List getEnabledFilters() { return enabledFilters; } + // threadsafe public boolean matches(Filter filter, Post post) { // Post has not been finish()ed yet, account for invalid values String text = null; @@ -166,6 +155,7 @@ public class FilterEngine { return !TextUtils.isEmpty(text) && matches(filter, text, false); } + // threadsafe public boolean matches(Filter filter, String text, boolean forceCompile) { FilterType type = FilterType.forId(filter.type); if (type.isRegex) { @@ -203,6 +193,7 @@ public class FilterEngine { private static final Pattern filterFilthyPattern = Pattern.compile("(\\.|\\^|\\$|\\*|\\+|\\?|\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\||\\-)"); private static final Pattern wildcardPattern = Pattern.compile("\\\\\\*"); // an escaped \ and an escaped *, to replace an escaped * from escapeRegex + // threadsafe public Pattern compile(String rawPattern) { Pattern pattern; @@ -279,7 +270,8 @@ public class FilterEngine { return filterFilthyPattern.matcher(filthy).replaceAll("\\\\$1"); // Escape regex special characters with a \ } - private void updateEnabledFilters() { + private void update() { + filters = databaseManager.runTaskSync(databaseFilterManager.getFilters()); List enabled = new ArrayList<>(); for (Filter filter : filters) { if (filter.enabled) { @@ -287,9 +279,7 @@ public class FilterEngine { } } - synchronized (enabledFilters) { - enabledFilters.clear(); - enabledFilters.addAll(enabled); - } + enabledFilters.clear(); + enabledFilters.addAll(enabled); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java b/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java index df2de087..377f32ef 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Filter.java @@ -55,6 +55,10 @@ public class Filter { */ public Matcher compiledMatcher; + public String[] boardCodes() { + return boards.split(","); + } + public void apply(Filter filter) { enabled = filter.enabled; type = filter.type; @@ -67,7 +71,6 @@ public class Filter { public Filter copy() { Filter copy = new Filter(); - copy.id = id; copy.enabled = enabled; copy.type = type; copy.pattern = pattern; diff --git a/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java b/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java index 772f8b84..9027d9b9 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java +++ b/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java @@ -26,7 +26,6 @@ import org.floens.chan.Chan; import org.floens.chan.chan.ChanUrls; import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.manager.FilterEngine; -import org.floens.chan.core.model.Board; import org.floens.chan.core.model.Filter; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Post; @@ -69,12 +68,21 @@ public class ChanReaderRequest extends JsonReaderRequest(cached); request.filters = new ArrayList<>(); - for (Filter filter : request.filterEngine.getEnabledFilters()) { - List boards = request.filterEngine.getBoardsForFilter(filter); - for (Board board : boards) { - if (board.code.equals(loadable.board)) { - request.filters.add(filter.copy()); - break; + List enabledFilters = request.filterEngine.getEnabledFilters(); + for (int i = 0; i < enabledFilters.size(); i++) { + Filter filter = enabledFilters.get(i); + + if (filter.allBoards) { + // copy the filter because it will get used on other threads + request.filters.add(filter.copy()); + } else { + String[] boardCodes = filter.boardCodes(); + for (String code : boardCodes) { + if (code.equals(loadable.board)) { + // copy the filter because it will get used on other threads + request.filters.add(filter.copy()); + break; + } } } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java index 7f170d52..c4e49b83 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/FiltersController.java @@ -148,9 +148,7 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Filter newFilter = filterLayout.getFilter(); - newFilter.id = filter.id; - filterEngine.addOrUpdate(newFilter); + filterEngine.createOrUpdateFilter(filterLayout.getFilter()); EventBus.getDefault().post(new RefreshUIMessage("filters")); adapter.load(); } @@ -163,11 +161,12 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled); } }); + filterLayout.setFilter(filter); } private void deleteFilter(Filter filter) { - filterEngine.remove(filter); + filterEngine.deleteFilter(filter); EventBus.getDefault().post(new RefreshUIMessage("filters")); adapter.load(); //TODO: undo @@ -237,7 +236,7 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too private void load() { sourceList.clear(); - sourceList.addAll(databaseManager.getFilters()); + sourceList.addAll(databaseManager.runTaskSync(databaseManager.getDatabaseFilterManager().getFilters())); filter(); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java index 278c74ba..22e2854d 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java @@ -47,6 +47,7 @@ import org.floens.chan.core.model.Filter; import org.floens.chan.ui.controller.FiltersController; import org.floens.chan.ui.dialog.ColorPickerView; import org.floens.chan.ui.drawable.DropdownArrowDrawable; +import org.floens.chan.ui.helper.BoardHelper; import org.floens.chan.ui.view.FloatingMenu; import org.floens.chan.ui.view.FloatingMenuItem; @@ -74,7 +75,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { private BoardManager boardManager; private FilterLayoutCallback callback; - private Filter filter = new Filter(); + private Filter filter; private List appliedBoards = new ArrayList<>(); @@ -154,7 +155,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { } public void setFilter(Filter filter) { - this.filter.apply(filter); + this.filter = filter; appliedBoards.clear(); appliedBoards.addAll(FilterEngine.getInstance().getBoardsForFilter(filter)); @@ -208,18 +209,49 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { menu.setItems(menuItems); menu.show(); } else if (v == boardsSelector) { - final BoardSelectLayout boardSelectLayout = (BoardSelectLayout) LayoutInflater.from(getContext()).inflate(R.layout.layout_board_select, null); + @SuppressWarnings("unchecked") + final SelectLayout selectLayout = (SelectLayout) LayoutInflater.from(getContext()).inflate(R.layout.layout_select, null); + + List> items = new ArrayList<>(); + List savedList = boardManager.getSavedBoards(); + for (int i = 0; i < savedList.size(); i++) { + Board saved = savedList.get(i); + String name = BoardHelper.getName(saved); + String description = BoardHelper.getDescription(saved); + String search = name + " " + saved.code; + + boolean checked = false; + for (int j = 0; j < appliedBoards.size(); j++) { + Board appliedBoard = appliedBoards.get(j); + if (appliedBoard.code.equals(saved.code)) { + checked = true; + break; + } + } + + items.add(new SelectLayout.SelectItem<>( + saved, saved.id, name, description, search, checked + )); + } - boardSelectLayout.setCheckedBoards(appliedBoards); + selectLayout.setItems(items); new AlertDialog.Builder(getContext()) - .setView(boardSelectLayout) + .setView(selectLayout) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { appliedBoards.clear(); - appliedBoards.addAll(boardSelectLayout.getCheckedBoards()); - filter.allBoards = boardSelectLayout.getAllChecked(); + + List> items = selectLayout.getItems(); + for (int i = 0; i < items.size(); i++) { + SelectLayout.SelectItem selectItem = items.get(i); + if (selectItem.checked) { + appliedBoards.add(selectItem.item); + } + } + + filter.allBoards = selectLayout.areAllChecked(); updateBoardsSummary(); } }) diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/BoardSelectLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/SelectLayout.java similarity index 64% rename from Clover/app/src/main/java/org/floens/chan/ui/layout/BoardSelectLayout.java rename to Clover/app/src/main/java/org/floens/chan/ui/layout/SelectLayout.java index c5f4927b..6c0877d6 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/BoardSelectLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/SelectLayout.java @@ -31,11 +31,7 @@ import android.widget.CompoundButton; import android.widget.LinearLayout; import android.widget.TextView; -import org.floens.chan.Chan; import org.floens.chan.R; -import org.floens.chan.core.manager.BoardManager; -import org.floens.chan.core.model.Board; -import org.floens.chan.ui.helper.BoardHelper; import java.util.ArrayList; import java.util.List; @@ -44,25 +40,24 @@ import java.util.Locale; import static org.floens.chan.utils.AndroidUtils.getAttrColor; import static org.floens.chan.utils.AndroidUtils.getString; -public class BoardSelectLayout extends LinearLayout implements SearchLayout.SearchLayoutCallback, View.OnClickListener { +public class SelectLayout extends LinearLayout implements SearchLayout.SearchLayoutCallback, View.OnClickListener { private SearchLayout searchLayout; private RecyclerView recyclerView; private Button checkAllButton; - private List boards = new ArrayList<>(); - private BoardManager boardManager; - private BoardSelectAdapter adapter; + private List> items = new ArrayList<>(); + private SelectAdapter adapter; private boolean allChecked = false; - public BoardSelectLayout(Context context) { + public SelectLayout(Context context) { super(context); } - public BoardSelectLayout(Context context, AttributeSet attrs) { + public SelectLayout(Context context, AttributeSet attrs) { super(context, attrs); } - public BoardSelectLayout(Context context, AttributeSet attrs, int defStyle) { + public SelectLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @@ -75,13 +70,6 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear protected void onFinishInflate() { super.onFinishInflate(); - boardManager = Chan.getBoardManager(); - - List savedList = boardManager.getSavedBoards(); - for (Board saved : savedList) { - boards.add(new BoardChecked(saved, false)); - } - searchLayout = (SearchLayout) findViewById(R.id.search_layout); searchLayout.setCallback(this); searchLayout.setHint(getString(R.string.search_hint)); @@ -95,18 +83,38 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear recyclerView = (RecyclerView) findViewById(R.id.recycler_view); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + } + + public void setItems(List> items) { + this.items.clear(); + this.items.addAll(items); - adapter = new BoardSelectAdapter(); + adapter = new SelectAdapter(); recyclerView.setAdapter(adapter); adapter.load(); updateAllSelected(); } + public List> getItems() { + return items; + } + + public List> getSelectedItems() { + List> result = new ArrayList<>(items.size()); + for (int i = 0; i < items.size(); i++) { + SelectItem item = items.get(i); + if (item.checked) { + result.add(item); + } + } + return result; + } + @Override public void onClick(View v) { if (v == checkAllButton) { - for (BoardChecked item : boards) { + for (SelectItem item : items) { item.checked = !allChecked; } @@ -115,53 +123,28 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear } } - public void setCheckedBoards(List checked) { - for (BoardChecked board : boards) { - for (Board check : checked) { - if (check.code.equals(board.board.code)) { - board.checked = true; - break; - } - } - } - - recyclerView.getAdapter().notifyDataSetChanged(); - } - - public List getCheckedBoards() { - List list = new ArrayList<>(); - for (int i = 0; i < boards.size(); i++) { - BoardSelectLayout.BoardChecked board = boards.get(i); - if (board.checked) { - list.add(board.board); - } - } - - return list; - } - - public boolean getAllChecked() { + public boolean areAllChecked() { return allChecked; } private void updateAllSelected() { int checkedCount = 0; - for (BoardChecked item : boards) { + for (SelectItem item : items) { if (item.checked) { checkedCount++; } } - allChecked = checkedCount == boards.size(); + allChecked = checkedCount == items.size(); checkAllButton.setText(allChecked ? R.string.board_select_none : R.string.board_select_all); } - private class BoardSelectAdapter extends RecyclerView.Adapter { - private List sourceList = new ArrayList<>(); - private List displayList = new ArrayList<>(); + private class SelectAdapter extends RecyclerView.Adapter { + private List sourceList = new ArrayList<>(); + private List displayList = new ArrayList<>(); private String searchQuery; - public BoardSelectAdapter() { + public SelectAdapter() { setHasStableIds(true); } @@ -172,10 +155,10 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear @Override public void onBindViewHolder(BoardSelectViewHolder holder, int position) { - BoardChecked board = displayList.get(position); - holder.checkBox.setChecked(board.checked); - holder.text.setText(BoardHelper.getName(board.board)); - holder.description.setText(BoardHelper.getDescription(board.board)); + SelectItem item = displayList.get(position); + holder.checkBox.setChecked(item.checked); + holder.text.setText(item.name); + holder.description.setText(item.description); } @Override @@ -185,7 +168,7 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear @Override public long getItemId(int position) { - return displayList.get(position).board.id; + return displayList.get(position).id; } public void search(String query) { @@ -195,7 +178,7 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear private void load() { sourceList.clear(); - sourceList.addAll(boards); + sourceList.addAll(items); filter(); } @@ -204,12 +187,10 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear displayList.clear(); if (!TextUtils.isEmpty(searchQuery)) { String query = searchQuery.toLowerCase(Locale.ENGLISH); - for (BoardChecked boardChecked : sourceList) { - String description = boardChecked.board.description == null ? "" : boardChecked.board.description; - if (boardChecked.board.name.toLowerCase(Locale.ENGLISH).contains(query) || - boardChecked.board.code.toLowerCase(Locale.ENGLISH).contains(query) || - description.toLowerCase(Locale.ENGLISH).contains(query)) { - displayList.add(boardChecked); + for (int i = 0; i < sourceList.size(); i++) { + SelectItem item = sourceList.get(i); + if (item.searchTerm.toLowerCase(Locale.ENGLISH).contains(query)) { + displayList.add(item); } } } else { @@ -239,7 +220,7 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView == checkBox) { - BoardChecked board = adapter.displayList.get(getAdapterPosition()); + SelectItem board = adapter.displayList.get(getAdapterPosition()); board.checked = isChecked; updateAllSelected(); } @@ -251,13 +232,22 @@ public class BoardSelectLayout extends LinearLayout implements SearchLayout.Sear } } - public static class BoardChecked { - public Board board; + public static class SelectItem { + public final T item; + public final long id; + public final String name; + public final String description; + public final String searchTerm; public boolean checked; - public BoardChecked(Board board, boolean checked) { - this.board = board; + public SelectItem(T item, long id, String name, String description, String searchTerm, boolean checked) { + this.item = item; + this.id = id; + this.name = name; + this.description = description; + this.searchTerm = searchTerm; this.checked = checked; } } } + diff --git a/Clover/app/src/main/res/layout/layout_board_select.xml b/Clover/app/src/main/res/layout/layout_select.xml similarity index 92% rename from Clover/app/src/main/res/layout/layout_board_select.xml rename to Clover/app/src/main/res/layout/layout_select.xml index 3eaa698c..bad18803 100644 --- a/Clover/app/src/main/res/layout/layout_board_select.xml +++ b/Clover/app/src/main/res/layout/layout_select.xml @@ -15,7 +15,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . --> -. android:scrollbarStyle="outsideOverlay" android:scrollbars="vertical" /> - +