From aa4f96cacf27a99c60e529058d430a2160bcd07c Mon Sep 17 00:00:00 2001 From: Floens Date: Tue, 3 Jan 2017 20:22:03 +0100 Subject: [PATCH] Make Post fields final, use a builder object. Refactoring around Site and ChanLoaders. Inject FilterEngine. --- .../java/org/floens/chan/chan/ChanLoader.java | 135 +++---- .../floens/chan/chan/ChanLoaderRequest.java | 33 ++ .../chan/chan/ChanLoaderRequestParams.java | 62 ++++ .../floens/chan/chan/ChanLoaderResponse.java | 34 ++ .../java/org/floens/chan/chan/ChanParser.java | 145 ++++---- .../database/DatabaseSavedReplyManager.java | 3 + .../org/floens/chan/core/di/ChanGraph.java | 7 +- .../chan/core/manager/FilterEngine.java | 44 +-- .../chan/core/manager/WatchManager.java | 19 +- .../org/floens/chan/core/model/Board.java | 7 +- .../org/floens/chan/core/model/Loadable.java | 18 +- .../java/org/floens/chan/core/model/Post.java | 331 ++++++++++++------ .../org/floens/chan/core/model/PostImage.java | 114 ++++-- .../floens/chan/core/model/PostLinkable.java | 4 +- .../floens/chan/core/model/SiteReference.java | 30 ++ ...LoaderPool.java => ChanLoaderFactory.java} | 31 +- .../chan/core/presenter/ThreadPresenter.java | 48 +-- .../java/org/floens/chan/core/site/Site.java | 9 +- .../floens/chan/core/site/SiteEndpoints.java | 8 +- .../loaders/Chan4ReaderRequest.java} | 256 ++++++++------ .../loaders}/PostParseCallable.java | 36 +- .../chan/core/site/sites/chan4/Chan4.java | 27 +- .../org/floens/chan/test/TestActivity.java | 4 +- .../floens/chan/ui/adapter/PostsFilter.java | 3 +- .../org/floens/chan/ui/cell/CardPostCell.java | 2 +- .../org/floens/chan/ui/cell/PostCell.java | 8 +- .../chan/ui/cell/PostCellInterface.java | 2 +- .../chan/ui/controller/BrowseController.java | 1 - .../chan/ui/controller/FiltersController.java | 6 +- .../ui/controller/PostRepliesController.java | 2 +- .../controller/ThemeSettingsController.java | 32 +- .../floens/chan/ui/layout/FilterLayout.java | 12 +- .../floens/chan/ui/layout/ThreadLayout.java | 5 +- .../chan/ui/layout/ThreadListLayout.java | 5 +- .../floens/chan/ui/service/WatchNotifier.java | 2 +- 35 files changed, 975 insertions(+), 510 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequest.java create mode 100644 Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequestParams.java create mode 100644 Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderResponse.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/model/SiteReference.java rename Clover/app/src/main/java/org/floens/chan/core/pool/{LoaderPool.java => ChanLoaderFactory.java} (79%) rename Clover/app/src/main/java/org/floens/chan/core/{net/ChanReaderRequest.java => site/loaders/Chan4ReaderRequest.java} (71%) rename Clover/app/src/main/java/org/floens/chan/core/{net => site/loaders}/PostParseCallable.java (72%) diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java index f9da753a..13e98882 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoader.java @@ -27,7 +27,6 @@ import org.floens.chan.core.exception.ChanLoaderException; import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Post; -import org.floens.chan.core.net.ChanReaderRequest; import org.floens.chan.ui.helper.PostHelper; import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.Logger; @@ -44,11 +43,18 @@ import javax.inject.Inject; import static org.floens.chan.Chan.getGraph; -public class ChanLoader implements Response.ErrorListener, Response.Listener { +/** + * A ChanLoader is the loader for Loadables. + *

Obtain ChanLoaders with {@link org.floens.chan.core.pool.ChanLoaderFactory}. + *

ChanLoaders can load boards and threads, and return {@link ChanThread} objects on success, through + * {@link ChanLoaderCallback}. + *

For threads timers can be started with {@link #setTimer()} to do a request later. + */ +public class ChanLoader implements Response.ErrorListener, Response.Listener { private static final String TAG = "ChanLoader"; private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - private static final int[] watchTimeouts = {10, 15, 20, 30, 60, 90, 120, 180, 240, 300, 600, 1800, 3600}; + private static final int[] WATCH_TIMEOUTS = {10, 15, 20, 30, 60, 90, 120, 180, 240, 300, 600, 1800, 3600}; @Inject RequestQueue volleyRequestQueue; @@ -57,13 +63,16 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener pendingFuture; + /** + * Do not call this constructor yourself, obtain ChanLoaders through {@link org.floens.chan.core.pool.ChanLoaderFactory} + */ public ChanLoader(Loadable loadable) { this.loadable = loadable; @@ -94,7 +103,7 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener + * This clears any pending pending timers, created with {@link #setTimer()}. * - * @return true if a request was started, false otherwise + * @return {@code true} if a new request was started, {@code false} otherwise. */ public boolean requestMoreData() { clearPendingRunnable(); @@ -179,6 +193,31 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener cached = thread == null ? new ArrayList() : thread.posts; + + request = loadable.getSite().loaderRequest(new ChanLoaderRequestParams(loadable, cached, this, this)); + + volleyRequestQueue.add(request.getVolleyRequest()); + + return request; } @Override - public void onResponse(ChanReaderRequest.ChanReaderResponse response) { + public void onResponse(ChanLoaderResponse response) { request = null; - if (response.posts.size() == 0) { + if (response.posts.isEmpty()) { onErrorResponse(new VolleyError("Post size is 0")); return; } @@ -228,7 +275,7 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener 0) { // Replace some op parameters to the real op (index 0). // This is done on the main thread to avoid race conditions. Post realOp = thread.posts.get(0); thread.op = realOp; - Post fakeOp = response.op; + Post.Builder fakeOp = response.op; if (fakeOp != null) { thread.closed = realOp.closed = fakeOp.closed; thread.archived = realOp.archived = fakeOp.archived; @@ -276,29 +308,19 @@ public class ChanLoader implements Response.ErrorListener, Response.Listener cached = thread == null ? new ArrayList() : thread.posts; - ChanReaderRequest request = ChanReaderRequest.newInstance(loadable, cached, this, this); - - volleyRequestQueue.add(request); - - return request; - } - public interface ChanLoaderCallback { void onChanLoaderData(ChanThread result); diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequest.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequest.java new file mode 100644 index 00000000..8df98937 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequest.java @@ -0,0 +1,33 @@ +/* + * 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.chan; + + +import com.android.volley.Request; + +public class ChanLoaderRequest { + private Request volleyRequest; + + public ChanLoaderRequest(Request volleyRequest) { + this.volleyRequest = volleyRequest; + } + + public Request getVolleyRequest() { + return volleyRequest; + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequestParams.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequestParams.java new file mode 100644 index 00000000..e8d75b6d --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderRequestParams.java @@ -0,0 +1,62 @@ +/* + * 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.chan; + + +import com.android.volley.Response; + +import org.floens.chan.core.model.Loadable; +import org.floens.chan.core.model.Post; + +import java.util.List; + +/** + * A request from ChanLoader to load something. + */ +public class ChanLoaderRequestParams { + /** + * Related loadable for the request. + */ + public final Loadable loadable; + + /** + * Cached Post objects from previous loads, or an empty list. + */ + public final List cached; + + /** + * Success listener. + */ + public final Response.Listener listener; + + /** + * Error listener. + */ + public final Response.ErrorListener errorListener; + + public ChanLoaderRequestParams(Loadable loadable, + List cached, + Response.Listener listener, + Response.ErrorListener errorListener) { + + this.loadable = loadable; + this.cached = cached; + this.listener = listener; + this.errorListener = errorListener; + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderResponse.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderResponse.java new file mode 100644 index 00000000..d13a7f62 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanLoaderResponse.java @@ -0,0 +1,34 @@ +/* + * 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.chan; + +import org.floens.chan.core.model.Post; + +import java.util.List; + +public class ChanLoaderResponse { + // Op Post that is created new each time. + // Used to later copy members like image count to the real op on the main thread. + public final Post.Builder op; + public final List posts; + + public ChanLoaderResponse(Post.Builder op, List posts) { + this.op = op; + this.posts = posts; + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java index 6f30bc77..e7a502a9 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java @@ -61,7 +61,9 @@ import static org.floens.chan.utils.AndroidUtils.sp; @Singleton public class ChanParser { private static final String TAG = "ChanParser"; - private static final Pattern colorPattern = Pattern.compile("color:#([0-9a-fA-F]*)"); + private static final Pattern COLOR_PATTERN = Pattern.compile("color:#([0-9a-fA-F]*)"); + private static final String SAVED_REPLY_SUFFIX = " (You)"; + private static final String OP_REPLY_SUFFIX = " (OP)"; @Inject DatabaseManager databaseManager; @@ -70,34 +72,36 @@ public class ChanParser { public ChanParser() { } - public void parse(Post post) { - parse(null, post); + public Post parse(Post.Builder post) { + return parse(null, post); } - public void parse(Theme theme, Post post) { + public Post parse(Theme theme, Post.Builder builder) { if (theme == null) { theme = ThemeHelper.getInstance().getTheme(); } try { - if (!TextUtils.isEmpty(post.name)) { - post.name = Parser.unescapeEntities(post.name, false); + if (!TextUtils.isEmpty(builder.name)) { + builder.name = Parser.unescapeEntities(builder.name, false); } - if (!TextUtils.isEmpty(post.subject)) { - post.subject = Parser.unescapeEntities(post.subject, false); + if (!TextUtils.isEmpty(builder.subject)) { + builder.subject = Parser.unescapeEntities(builder.subject, false); } } catch (Exception e) { e.printStackTrace(); } - parseSpans(theme, post); + parseSpans(theme, builder); - if (post.rawComment != null) { - post.comment = parseComment(theme, post, post.rawComment); + if (builder.comment != null) { + builder.comment = parseComment(theme, builder, builder.comment); } else { - post.comment = ""; + builder.comment = ""; } + + return builder.build(); } /** @@ -105,48 +109,55 @@ public class ChanParser { * This is done on a background thread for performance, even when it is UI code.
* The results will be placed on the Post.*Span members. * - * @param theme Theme to use for parsing - * @param post Post to get data from + * @param theme Theme to use for parsing + * @param builder Post builder to get data from */ - private void parseSpans(Theme theme, Post post) { + private void parseSpans(Theme theme, Post.Builder builder) { boolean anonymize = ChanSettings.anonymize.get(); boolean anonymizeIds = ChanSettings.anonymizeIds.get(); + final String defaultName = "Anonymous"; if (anonymize) { - post.name = "Anonymous"; - post.tripcode = ""; + builder.name(defaultName); + builder.tripcode(""); } if (anonymizeIds) { - post.id = ""; + builder.posterId(""); } + SpannableString subjectSpan = null; + SpannableString nameSpan = null; + SpannableString tripcodeSpan = null; + SpannableString idSpan = null; + SpannableString capcodeSpan = null; + int detailsSizePx = sp(Integer.parseInt(ChanSettings.fontSize.get()) - 4); - if (!TextUtils.isEmpty(post.subject)) { - post.subjectSpan = new SpannableString(post.subject); + if (!TextUtils.isEmpty(builder.subject)) { + subjectSpan = new SpannableString(builder.subject); // Do not set another color when the post is in stub mode, it sets text_color_secondary - if (!post.filterStub) { - post.subjectSpan.setSpan(new ForegroundColorSpanHashed(theme.subjectColor), 0, post.subjectSpan.length(), 0); + if (!builder.filterStub) { + subjectSpan.setSpan(new ForegroundColorSpanHashed(theme.subjectColor), 0, subjectSpan.length(), 0); } } - if (!TextUtils.isEmpty(post.name) && (!post.name.equals("Anonymous") || ChanSettings.showAnonymousName.get())) { - post.nameSpan = new SpannableString(post.name); - post.nameSpan.setSpan(new ForegroundColorSpanHashed(theme.nameColor), 0, post.nameSpan.length(), 0); + if (!TextUtils.isEmpty(builder.name) && (!builder.name.equals(defaultName) || ChanSettings.showAnonymousName.get())) { + nameSpan = new SpannableString(builder.name); + nameSpan.setSpan(new ForegroundColorSpanHashed(theme.nameColor), 0, nameSpan.length(), 0); } - if (!TextUtils.isEmpty(post.tripcode)) { - post.tripcodeSpan = new SpannableString(post.tripcode); - post.tripcodeSpan.setSpan(new ForegroundColorSpanHashed(theme.nameColor), 0, post.tripcodeSpan.length(), 0); - post.tripcodeSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, post.tripcodeSpan.length(), 0); + if (!TextUtils.isEmpty(builder.tripcode)) { + tripcodeSpan = new SpannableString(builder.tripcode); + tripcodeSpan.setSpan(new ForegroundColorSpanHashed(theme.nameColor), 0, tripcodeSpan.length(), 0); + tripcodeSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, tripcodeSpan.length(), 0); } - if (!TextUtils.isEmpty(post.id)) { - post.idSpan = new SpannableString(" ID: " + post.id + " "); + if (!TextUtils.isEmpty(builder.posterId)) { + idSpan = new SpannableString(" ID: " + builder.posterId + " "); // Stolen from the 4chan extension - int hash = post.id.hashCode(); + int hash = builder.posterId.hashCode(); int r = (hash >> 24) & 0xff; int g = (hash >> 16) & 0xff; @@ -156,40 +167,42 @@ public class ChanParser { boolean lightColor = (r * 0.299f) + (g * 0.587f) + (b * 0.114f) > 125f; int idBgColor = lightColor ? theme.idBackgroundLight : theme.idBackgroundDark; - post.idSpan.setSpan(new ForegroundColorSpanHashed(idColor), 0, post.idSpan.length(), 0); - post.idSpan.setSpan(new BackgroundColorSpan(idBgColor), 0, post.idSpan.length(), 0); - post.idSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, post.idSpan.length(), 0); + idSpan.setSpan(new ForegroundColorSpanHashed(idColor), 0, idSpan.length(), 0); + idSpan.setSpan(new BackgroundColorSpan(idBgColor), 0, idSpan.length(), 0); + idSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, idSpan.length(), 0); } - if (!TextUtils.isEmpty(post.capcode)) { - post.capcodeSpan = new SpannableString("Capcode: " + post.capcode); - post.capcodeSpan.setSpan(new ForegroundColorSpanHashed(theme.capcodeColor), 0, post.capcodeSpan.length(), 0); - post.capcodeSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, post.capcodeSpan.length(), 0); + if (!TextUtils.isEmpty(builder.moderatorCapcode)) { + capcodeSpan = new SpannableString("Capcode: " + builder.moderatorCapcode); + capcodeSpan.setSpan(new ForegroundColorSpanHashed(theme.capcodeColor), 0, capcodeSpan.length(), 0); + capcodeSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, capcodeSpan.length(), 0); } - post.nameTripcodeIdCapcodeSpan = new SpannableString(""); - if (post.nameSpan != null) { - post.nameTripcodeIdCapcodeSpan = TextUtils.concat(post.nameTripcodeIdCapcodeSpan, post.nameSpan, " "); + CharSequence nameTripcodeIdCapcodeSpan = new SpannableString(""); + if (nameSpan != null) { + nameTripcodeIdCapcodeSpan = TextUtils.concat(nameTripcodeIdCapcodeSpan, nameSpan, " "); } - if (post.tripcodeSpan != null) { - post.nameTripcodeIdCapcodeSpan = TextUtils.concat(post.nameTripcodeIdCapcodeSpan, post.tripcodeSpan, " "); + if (tripcodeSpan != null) { + nameTripcodeIdCapcodeSpan = TextUtils.concat(nameTripcodeIdCapcodeSpan, tripcodeSpan, " "); } - if (post.idSpan != null) { - post.nameTripcodeIdCapcodeSpan = TextUtils.concat(post.nameTripcodeIdCapcodeSpan, post.idSpan, " "); + if (idSpan != null) { + nameTripcodeIdCapcodeSpan = TextUtils.concat(nameTripcodeIdCapcodeSpan, idSpan, " "); } - if (post.capcodeSpan != null) { - post.nameTripcodeIdCapcodeSpan = TextUtils.concat(post.nameTripcodeIdCapcodeSpan, post.capcodeSpan, " "); + if (capcodeSpan != null) { + nameTripcodeIdCapcodeSpan = TextUtils.concat(nameTripcodeIdCapcodeSpan, capcodeSpan, " "); } + + builder.spans(subjectSpan, nameTripcodeIdCapcodeSpan); } - private CharSequence parseComment(Theme theme, Post post, String commentRaw) { + private CharSequence parseComment(Theme theme, Post.Builder post, CharSequence commentRaw) { CharSequence total = new SpannableString(""); try { - String comment = commentRaw.replace("", ""); + String comment = commentRaw.toString().replace("", ""); Document document = Jsoup.parseBodyFragment(comment); @@ -211,7 +224,7 @@ public class ChanParser { return total; } - private CharSequence parseNode(Theme theme, Post post, Node node) { + private CharSequence parseNode(Theme theme, Post.Builder post, Node node) { if (node instanceof TextNode) { String text = ((TextNode) node).text(); SpannableString spannable = new SpannableString(text); @@ -243,8 +256,8 @@ public class ChanParser { if (!TextUtils.isEmpty(style)) { style = style.replace(" ", ""); - // private static final Pattern colorPattern = Pattern.compile("color:#([0-9a-fA-F]*)"); - Matcher matcher = colorPattern.matcher(style); + // private static final Pattern COLOR_PATTERN = Pattern.compile("color:#([0-9a-fA-F]*)"); + Matcher matcher = COLOR_PATTERN.matcher(style); int hexColor = 0xff0000; if (matcher.find()) { @@ -331,9 +344,9 @@ public class ChanParser { SpannableString link = new SpannableString(spoiler.text()); - PostLinkable pl = new PostLinkable(theme, post, spoiler.text(), spoiler.text(), PostLinkable.Type.SPOILER); + PostLinkable pl = new PostLinkable(theme, spoiler.text(), spoiler.text(), PostLinkable.Type.SPOILER); link.setSpan(pl, 0, link.length(), 0); - post.linkables.add(pl); + post.addLinkable(pl); return link; } @@ -363,7 +376,7 @@ public class ChanParser { } } - private CharSequence parseAnchor(Theme theme, Post post, Element anchor) { + private CharSequence parseAnchor(Theme theme, Post.Builder post, Element anchor) { String href = anchor.attr("href"); Set classes = anchor.classNames(); @@ -411,16 +424,16 @@ public class ChanParser { t = PostLinkable.Type.QUOTE; key = anchor.text(); value = id; - post.repliesTo.add(id); + post.addReplyTo(id); // Append OP when its a reply to OP - if (id == post.resto) { - key += " (OP)"; + if (id == post.opId) { + key += OP_REPLY_SUFFIX; } // Append You when it's a reply to an saved reply - if (databaseManager.getDatabaseSavedReplyManager().isSaved(post.boardId, id)) { - key += " (You)"; + if (databaseManager.getDatabaseSavedReplyManager().isSaved(post.board.code, id)) { + key += SAVED_REPLY_SUFFIX; } } } @@ -433,9 +446,9 @@ public class ChanParser { if (t != null && key != null && value != null) { SpannableString link = new SpannableString(key); - PostLinkable pl = new PostLinkable(theme, post, key, value, t); + PostLinkable pl = new PostLinkable(theme, key, value, t); link.setSpan(pl, 0, link.length(), 0); - post.linkables.add(pl); + post.addLinkable(pl); return link; } else { @@ -443,7 +456,7 @@ public class ChanParser { } } - private void detectLinks(Theme theme, Post post, String text, SpannableString spannable) { + private void detectLinks(Theme theme, Post.Builder post, String text, SpannableString spannable) { int startPos = 0; int endPos; while (true) { @@ -466,9 +479,9 @@ public class ChanParser { String linkString = text.substring(startPos, endPos); - PostLinkable pl = new PostLinkable(theme, post, linkString, linkString, PostLinkable.Type.LINK); + PostLinkable pl = new PostLinkable(theme, linkString, linkString, PostLinkable.Type.LINK); spannable.setSpan(pl, startPos, endPos, 0); - post.linkables.add(pl); + post.addLinkable(pl); startPos = endPos; } diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSavedReplyManager.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSavedReplyManager.java index 2cda0964..678efea8 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSavedReplyManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSavedReplyManager.java @@ -17,6 +17,8 @@ */ package org.floens.chan.core.database; +import android.support.annotation.AnyThread; + import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.table.TableUtils; @@ -57,6 +59,7 @@ public class DatabaseSavedReplyManager { * @param no post number * @return {@code true} if the post is in the saved reply database, {@code false} otherwise. */ + @AnyThread public boolean isSaved(String board, int no) { // TODO(multi-site) synchronized (savedRepliesByNo) { diff --git a/Clover/app/src/main/java/org/floens/chan/core/di/ChanGraph.java b/Clover/app/src/main/java/org/floens/chan/core/di/ChanGraph.java index 592d33a5..c9e5f77b 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/di/ChanGraph.java +++ b/Clover/app/src/main/java/org/floens/chan/core/di/ChanGraph.java @@ -11,7 +11,8 @@ import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.http.ReplyManager; import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.FilterEngine; -import org.floens.chan.core.net.ChanReaderRequest; +import org.floens.chan.core.manager.WatchManager; +import org.floens.chan.core.site.loaders.Chan4ReaderRequest; import org.floens.chan.core.presenter.ImageViewerPresenter; import org.floens.chan.core.presenter.ReplyPresenter; import org.floens.chan.core.presenter.ThreadPresenter; @@ -66,7 +67,7 @@ public interface ChanGraph { void inject(ReplyPresenter replyPresenter); - void inject(ChanReaderRequest chanReaderRequest); + void inject(Chan4ReaderRequest chanReaderRequest); void inject(ThreadLayout threadLayout); @@ -113,4 +114,6 @@ public interface ChanGraph { void inject(ImageSaveTask imageSaveTask); void inject(ViewThreadController viewThreadController); + + void inject(WatchManager.PinWatcher pinWatcher); } 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 e16eb39d..d3bc4880 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 @@ -17,9 +17,9 @@ */ package org.floens.chan.core.manager; +import android.support.annotation.AnyThread; 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; @@ -37,18 +37,12 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.inject.Inject; +import javax.inject.Singleton; -import static org.floens.chan.Chan.getGraph; - +@Singleton public class FilterEngine { private static final String TAG = "FilterEngine"; - private static final FilterEngine instance = new FilterEngine(); - - public static FilterEngine getInstance() { - return instance; - } - public enum FilterAction { HIDE(0), COLOR(1), @@ -73,21 +67,18 @@ public class FilterEngine { } } - private final Map patternCache = new HashMap<>(); - - @Inject - DatabaseManager databaseManager; - - @Inject - BoardManager boardManager; + private final DatabaseManager databaseManager; + private final BoardManager boardManager; private final DatabaseFilterManager databaseFilterManager; - private List filters; + private final Map patternCache = new HashMap<>(); private final List enabledFilters = new ArrayList<>(); - private FilterEngine() { - getGraph().inject(this); + @Inject + public FilterEngine(DatabaseManager databaseManager, BoardManager boardManager) { + this.databaseManager = databaseManager; + this.boardManager = boardManager; databaseFilterManager = databaseManager.getDatabaseFilterManager(); update(); } @@ -111,7 +102,7 @@ public class FilterEngine { } // threadsafe - public boolean matches(Filter filter, Post post) { + public boolean matches(Filter filter, Post.Builder post) { if ((filter.type & FilterType.TRIPCODE.flag) != 0 && matches(filter, FilterType.TRIPCODE.isRegex, post.tripcode, false)) { return true; } @@ -120,11 +111,11 @@ public class FilterEngine { return true; } - if ((filter.type & FilterType.COMMENT.flag) != 0 && matches(filter, FilterType.COMMENT.isRegex, post.rawComment, false)) { + if ((filter.type & FilterType.COMMENT.flag) != 0 && matches(filter, FilterType.COMMENT.isRegex, post.comment.toString(), false)) { return true; } - if ((filter.type & FilterType.ID.flag) != 0 && matches(filter, FilterType.ID.isRegex, post.id, false)) { + if ((filter.type & FilterType.ID.flag) != 0 && matches(filter, FilterType.ID.isRegex, post.posterId, false)) { return true; } @@ -132,14 +123,15 @@ public class FilterEngine { return true; } - if ((filter.type & FilterType.FILENAME.flag) != 0 && matches(filter, FilterType.FILENAME.isRegex, post.filename, false)) { + String filename = post.image != null ? post.image.filename : null; + if (filename != null && (filter.type & FilterType.FILENAME.flag) != 0 && matches(filter, FilterType.FILENAME.isRegex, filename, false)) { return true; } return false; } - // threadsafe + @AnyThread public boolean matches(Filter filter, boolean matchRegex, String text, boolean forceCompile) { if (TextUtils.isEmpty(text)) { return false; @@ -184,7 +176,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 + @AnyThread public Pattern compile(String rawPattern) { if (TextUtils.isEmpty(rawPattern)) { return null; @@ -262,7 +254,7 @@ public class FilterEngine { } private void update() { - filters = databaseManager.runTaskSync(databaseFilterManager.getFilters()); + List filters = databaseManager.runTaskSync(databaseFilterManager.getFilters()); List enabled = new ArrayList<>(); for (Filter filter : filters) { if (filter.enabled) { diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java index bbc58705..6a5f100b 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java @@ -35,7 +35,8 @@ import org.floens.chan.core.model.ChanThread; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Post; -import org.floens.chan.core.pool.LoaderPool; +import org.floens.chan.core.model.PostImage; +import org.floens.chan.core.pool.ChanLoaderFactory; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.helper.PostHelper; import org.floens.chan.ui.service.WatchNotifier; @@ -56,6 +57,7 @@ import javax.inject.Singleton; import de.greenrobot.event.EventBus; +import static org.floens.chan.Chan.getGraph; import static org.floens.chan.utils.AndroidUtils.getAppContext; /** @@ -159,7 +161,8 @@ public class WatchManager { Pin pin = new Pin(); pin.loadable = loadable; pin.loadable.title = PostHelper.getTitle(opPost, loadable); - pin.thumbnailUrl = opPost.thumbnailUrl; + PostImage image = opPost.image; + pin.thumbnailUrl = image == null ? "" : image.thumbnailUrl; return createPin(pin); } @@ -648,6 +651,9 @@ public class WatchManager { public class PinWatcher implements ChanLoader.ChanLoaderCallback { private static final String TAG = "PinWatcher"; + @Inject + ChanLoaderFactory chanLoaderFactory; + private final Pin pin; private ChanLoader chanLoader; @@ -658,9 +664,10 @@ public class WatchManager { public PinWatcher(Pin pin) { this.pin = pin; + getGraph().inject(this); Logger.d(TAG, "PinWatcher: created for " + pin); - chanLoader = LoaderPool.getInstance().obtain(pin.loadable, this); + chanLoader = chanLoaderFactory.obtain(pin.loadable, this); } public List getUnviewedPosts() { @@ -696,7 +703,7 @@ public class WatchManager { private void destroy() { if (chanLoader != null) { Logger.d(TAG, "PinWatcher: destroyed for " + pin); - LoaderPool.getInstance().release(chanLoader, this); + chanLoaderFactory.release(chanLoader, this); chanLoader = null; } } @@ -738,8 +745,8 @@ public class WatchManager { public void onChanLoaderData(ChanThread thread) { pin.isError = false; - if (pin.thumbnailUrl == null && thread.op != null && thread.op.hasImage) { - pin.thumbnailUrl = thread.op.thumbnailUrl; + if (pin.thumbnailUrl == null && thread.op != null && thread.op.image != null) { + pin.thumbnailUrl = thread.op.image.thumbnailUrl; } // Populate posts list diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Board.java b/Clover/app/src/main/java/org/floens/chan/core/model/Board.java index 236cb693..9446e733 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Board.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Board.java @@ -25,7 +25,7 @@ import com.j256.ormlite.table.DatabaseTable; import org.floens.chan.core.site.Site; @DatabaseTable -public class Board { +public class Board implements SiteReference { public Board() { } @@ -148,6 +148,11 @@ public class Board { return true; } + @Override + public Site getSite() { + return site; + } + public String getName() { return "/" + code + "/ \u2013 " + name; } diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Loadable.java b/Clover/app/src/main/java/org/floens/chan/core/model/Loadable.java index c7fb633f..1e87b78a 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Loadable.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Loadable.java @@ -31,9 +31,11 @@ import org.floens.chan.core.site.Site; * - It keeps track of the list index where the user last viewed.
* - It keeps track of what post was last seen and loaded.
* - It keeps track of the title the toolbar should show, generated from the first post (so after loading).
+ *

Obtain Loadables through {@link org.floens.chan.core.database.DatabaseLoadableManager} to make sure everyone + * references the same loadable and that the loadable is properly saved in the database. */ @DatabaseTable -public class Loadable { +public class Loadable implements SiteReference { @DatabaseField(generatedId = true) public int id; @@ -116,6 +118,11 @@ public class Loadable { return loadable; } + @Override + public Site getSite() { + return site; + } + public void setTitle(String title) { if (!TextUtils.equals(this.title, title)) { this.title = title; @@ -161,6 +168,10 @@ public class Loadable { Loadable other = (Loadable) object; + if ((site.id() == other.site.id() && (site != other.site))) { + throw new IllegalStateException(); // TODO(multi-site) remove + } + if (site != other.site) { return false; } @@ -220,6 +231,11 @@ public class Loadable { return mode == Mode.CATALOG; } + // TODO(multi-site) remove + public boolean isFromDatabase() { + return id > 0; + } + public static Loadable readFromParcel(Parcel parcel) { Loadable loadable = new Loadable(); /*loadable.id = */ diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java index 1ed023d9..69e49a62 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Post.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Post.java @@ -17,170 +17,305 @@ */ package org.floens.chan.core.model; -import android.text.SpannableString; -import android.text.TextUtils; - -import org.floens.chan.chan.ChanParser; -import org.floens.chan.core.settings.ChanSettings; -import org.jsoup.parser.Parser; - import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; -import static org.floens.chan.Chan.getGraph; - /** * Contains all data needed to represent a single post.
- * Call {@link #finish()} to parse the comment etc. The post data is invalid if finish returns false.
- * This class has members that are threadsafe and some that are not, see the source for more info. + * All {@code final} fields are thread-safe. */ public class Post { - // *** These next members don't get changed after finish() is called. Effectively final. *** - public String boardId; + public final String boardId; - public Board board; + public final Board board; - public int no = -1; + public final int no; - public int resto = -1; + public final boolean isOP; - public boolean isOP = false; +// public final String date; - public String date; + public final String name; - public String name = ""; + public final CharSequence comment; - public CharSequence comment = ""; + public final String subject; - public String subject = ""; + public final PostImage image; - public long tim = -1; + public final String tripcode; - public String ext; + public final String id; - public String filename; + public final String capcode; - public int imageWidth; + public final String country; - public int imageHeight; + public final String countryName; - public boolean hasImage = false; + /** + * Unix timestamp, in seconds. + */ + public final long time; - public PostImage image; + public final String countryUrl; - public String thumbnailUrl; + public final boolean isSavedReply; - public String imageUrl; + public final int filterHighlightedColor; - public String tripcode = ""; + public final boolean filterStub; - public String id = ""; + public final boolean filterRemove; - public String capcode = ""; + /** + * This post replies to the these ids. + */ + public final Set repliesTo; - public String country = ""; + public final List linkables; - public String countryName = ""; + public final CharSequence subjectSpan; - public long time = -1; + public final CharSequence nameTripcodeIdCapcodeSpan; - public long fileSize; + // These members may only mutate on the main thread. + public boolean sticky = false; + public boolean closed = false; + public boolean archived = false; + public int replies = -1; + public int images = -1; + public int uniqueIps = 1; + public String title = ""; - public String rawComment; + // Atomic, any thread. + public final AtomicBoolean deleted = new AtomicBoolean(false); - public String countryUrl; + /** + * These ids replied to this post.
+ * synchronize on this when accessing. + */ + public final List repliesFrom = new ArrayList<>(); - public boolean spoiler = false; + private Post(Builder builder) { + board = builder.board; + boardId = builder.board.code; + no = builder.id; - public boolean isSavedReply = false; + isOP = builder.op; + replies = builder.replies; + images = builder.images; + uniqueIps = builder.uniqueIps; + sticky = builder.sticky; + closed = builder.closed; + archived = builder.archived; - public int filterHighlightedColor = 0; + subject = builder.subject; + name = builder.name; + comment = builder.comment; + tripcode = builder.tripcode; - public boolean filterStub = false; + time = builder.unixTimestampSeconds; + image = builder.image; - public boolean filterRemove = false; + country = builder.countryCode; + countryName = builder.countryName; + countryUrl = builder.countryUrl; + id = builder.posterId; + capcode = builder.moderatorCapcode; - /** - * This post replies to the these ids. Is an unmodifiable set after finish(). - */ - public Set repliesTo = new TreeSet<>(); + filterHighlightedColor = builder.filterHighlightedColor; + filterStub = builder.filterStub; + filterRemove = builder.filterRemove; - public final ArrayList linkables = new ArrayList<>(); + isSavedReply = builder.isSavedReply; - public SpannableString subjectSpan; + subjectSpan = builder.subjectSpan; + nameTripcodeIdCapcodeSpan = builder.nameTripcodeIdCapcodeSpan; - public SpannableString nameSpan; + linkables = Collections.unmodifiableList(builder.linkables); + repliesTo = Collections.unmodifiableSet(builder.repliesToIds); + } - public SpannableString tripcodeSpan; + public static final class Builder { + public Board board; + public int id = -1; + public int opId = -1; - public SpannableString idSpan; + public boolean op; + public int replies; + public int images; + public int uniqueIps; + public boolean sticky; + public boolean closed; + public boolean archived; - public SpannableString capcodeSpan; + public String subject = ""; + public String name = ""; + public CharSequence comment = ""; + public String tripcode = ""; - public CharSequence nameTripcodeIdCapcodeSpan; + public long unixTimestampSeconds = -1; + public PostImage image; - // *** These next members may only change on the main thread after finish(). *** - public boolean sticky = false; - public boolean closed = false; - public boolean archived = false; - public int replies = -1; - public int images = -1; - public int uniqueIps = 1; - public String title = ""; + public String countryCode; + public String countryName; + public String countryUrl; - // *** Threadsafe members, may be read and modified on any thread. *** - public AtomicBoolean deleted = new AtomicBoolean(false); + public String posterId = ""; + public String moderatorCapcode = ""; - // *** Manual synchronization needed. *** - /** - * These ids replied to this post.
- * synchronize on this when accessing. - */ - public final List repliesFrom = new ArrayList<>(); + public int filterHighlightedColor; + public boolean filterStub; + public boolean filterRemove; - /** - * Finish up the data: parse the comment, check if the data is valid etc. - * - * @return false if this data is invalid - */ - public boolean finish() { - if (boardId == null || no < 0 || resto < 0 || date == null || time < 0) { - return false; + public boolean isSavedReply; + + public CharSequence subjectSpan; + public CharSequence nameTripcodeIdCapcodeSpan; + + private List linkables = new ArrayList<>(); + private Set repliesToIds = new HashSet<>(); + + public Builder() { } - isOP = resto == 0; + public Builder board(Board board) { + this.board = board; + return this; + } + + public Builder id(int id) { + this.id = id; + return this; + } + + public Builder opId(int opId) { + this.opId = opId; + return this; + } + + public Builder op(boolean op) { + this.op = op; + return this; + } + + public Builder replies(int replies) { + this.replies = replies; + return this; + } + + public Builder images(int images) { + this.images = images; + return this; + } + + public Builder uniqueIps(int uniqueIps) { + this.uniqueIps = uniqueIps; + return this; + } + + public Builder sticky(boolean sticky) { + this.sticky = sticky; + return this; + } - if (isOP && (replies < 0 || images < 0)) { - return false; + public Builder archived(boolean archived) { + this.archived = archived; + return this; } - if (filename != null && ext != null && imageWidth > 0 && imageHeight > 0 && tim >= 0) { - hasImage = true; - // TODO: only use #image - imageUrl = board.site.endpoints().imageUrl(this); - filename = Parser.unescapeEntities(filename, false); - thumbnailUrl = board.site.endpoints().thumbnailUrl(this); - image = new PostImage(String.valueOf(tim), thumbnailUrl, imageUrl, filename, ext, imageWidth, imageHeight, spoiler, fileSize); + public Builder closed(boolean closed) { + this.closed = closed; + return this; } - if (!TextUtils.isEmpty(country)) { - countryUrl = board.site.endpoints().flag(this); + public Builder subject(String subject) { + this.subject = subject; + return this; } - if (ChanSettings.revealImageSpoilers.get()) { - spoiler = false; + public Builder name(String name) { + this.name = name; + return this; } - ChanParser chanParser = getGraph().getChanParser(); - chanParser.parse(this); + public Builder comment(String comment) { + this.comment = comment; + return this; + } - repliesTo = Collections.unmodifiableSet(repliesTo); + public Builder tripcode(String tripcode) { + this.tripcode = tripcode; + return this; + } - return true; + public Builder setUnixTimestampSeconds(long unixTimestampSeconds) { + this.unixTimestampSeconds = unixTimestampSeconds; + return this; + } + + public Builder image(PostImage image) { + this.image = image; + return this; + } + + public Builder posterId(String posterId) { + this.posterId = posterId; + return this; + } + + public Builder moderatorCapcode(String moderatorCapcode) { + this.moderatorCapcode = moderatorCapcode; + return this; + } + + public Builder country(String countryCode, String countryName, String countryUrl) { + this.countryCode = countryCode; + this.countryName = countryName; + this.countryUrl = countryUrl; + return this; + } + + public Builder filter(int highlightedColor, boolean stub, boolean remove) { + filterHighlightedColor = highlightedColor; + filterStub = stub; + filterRemove = remove; + return this; + } + + public Builder isSavedReply(boolean isSavedReply) { + this.isSavedReply = isSavedReply; + return this; + } + + public Builder spans(CharSequence subjectSpan, CharSequence nameTripcodeIdCapcodeSpan) { + this.subjectSpan = subjectSpan; + this.nameTripcodeIdCapcodeSpan = nameTripcodeIdCapcodeSpan; + return this; + } + + public Builder addLinkable(PostLinkable linkable) { + linkables.add(linkable); + return this; + } + + public Builder addReplyTo(int postId) { + repliesToIds.add(postId); + return this; + } + + public Post build() { + if (board == null || id < 0 || opId < 0 || unixTimestampSeconds < 0 || comment == null) { + throw new IllegalArgumentException("Post data not complete"); + } + + return new Post(this); + } } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java b/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java index 7d21918b..c4ed6524 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/PostImage.java @@ -17,33 +17,35 @@ */ package org.floens.chan.core.model; +import org.floens.chan.core.settings.ChanSettings; + public class PostImage { public enum Type { STATIC, GIF, MOVIE } - public String originalName; - public String thumbnailUrl; - public String imageUrl; - public String filename; - public String extension; - public int imageWidth; - public int imageHeight; - public boolean spoiler; - public long size; - - public Type type; - - public PostImage(String originalName, String thumbnailUrl, String imageUrl, String filename, String extension, int imageWidth, int imageHeight, boolean spoiler, long size) { - this.originalName = originalName; - this.thumbnailUrl = thumbnailUrl; - this.imageUrl = imageUrl; - this.filename = filename; - this.extension = extension; - this.imageWidth = imageWidth; - this.imageHeight = imageHeight; - this.spoiler = spoiler; - this.size = size; + public final String originalName; + public final String thumbnailUrl; + public final String imageUrl; + public final String filename; + public final String extension; + public final int imageWidth; + public final int imageHeight; + public final boolean spoiler; + public final long size; + + public final Type type; + + private PostImage(Builder builder) { + this.originalName = builder.originalName; + this.thumbnailUrl = builder.thumbnailUrl; + this.imageUrl = builder.imageUrl; + this.filename = builder.filename; + this.extension = builder.extension; + this.imageWidth = builder.imageWidth; + this.imageHeight = builder.imageHeight; + this.spoiler = builder.spoiler; + this.size = builder.size; switch (extension) { case "gif": @@ -57,4 +59,72 @@ public class PostImage { break; } } + + public static final class Builder { + private String originalName; + private String thumbnailUrl; + private String imageUrl; + private String filename; + private String extension; + private int imageWidth; + private int imageHeight; + private boolean spoiler; + private long size; + + public Builder() { + } + + public Builder originalName(String originalName) { + this.originalName = originalName; + return this; + } + + public Builder thumbnailUrl(String thumbnailUrl) { + this.thumbnailUrl = thumbnailUrl; + return this; + } + + public Builder imageUrl(String imageUrl) { + this.imageUrl = imageUrl; + return this; + } + + public Builder filename(String filename) { + this.filename = filename; + return this; + } + + public Builder extension(String extension) { + this.extension = extension; + return this; + } + + public Builder imageWidth(int imageWidth) { + this.imageWidth = imageWidth; + return this; + } + + public Builder imageHeight(int imageHeight) { + this.imageHeight = imageHeight; + return this; + } + + public Builder spoiler(boolean spoiler) { + this.spoiler = spoiler; + return this; + } + + public Builder size(long size) { + this.size = size; + return this; + } + + public PostImage build() { + if (ChanSettings.revealImageSpoilers.get()) { + spoiler = false; + } + + return new PostImage(this); + } + } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java b/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java index 5a7f6a05..3931aee9 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/PostLinkable.java @@ -37,7 +37,6 @@ public class PostLinkable extends ClickableSpan { } public final Theme theme; - public final Post post; public final String key; public final Object value; public final Type type; @@ -45,9 +44,8 @@ public class PostLinkable extends ClickableSpan { private boolean spoilerVisible = ChanSettings.revealTextSpoilers.get(); private int markedNo = -1; - public PostLinkable(Theme theme, Post post, String key, Object value, Type type) { + public PostLinkable(Theme theme, String key, Object value, Type type) { this.theme = theme; - this.post = post; this.key = key; this.value = value; this.type = type; diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/SiteReference.java b/Clover/app/src/main/java/org/floens/chan/core/model/SiteReference.java new file mode 100644 index 00000000..fde84620 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/model/SiteReference.java @@ -0,0 +1,30 @@ +/* + * 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.model; + + +import org.floens.chan.core.site.Site; + +public interface SiteReference { + /** + * Get the Site object that this model references. + * + * @return a {@link Site} + */ + Site getSite(); +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/pool/LoaderPool.java b/Clover/app/src/main/java/org/floens/chan/core/pool/ChanLoaderFactory.java similarity index 79% rename from Clover/app/src/main/java/org/floens/chan/core/pool/LoaderPool.java rename to Clover/app/src/main/java/org/floens/chan/core/pool/ChanLoaderFactory.java index 3eb31d90..0a1de06a 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/pool/LoaderPool.java +++ b/Clover/app/src/main/java/org/floens/chan/core/pool/ChanLoaderFactory.java @@ -25,29 +25,34 @@ import org.floens.chan.core.model.Loadable; import java.util.HashMap; import java.util.Map; -public class LoaderPool { - // private static final String TAG = "LoaderPool"; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * ChanLoaderFactory is a factory for ChanLoaders. ChanLoaders for threads are cached. + *

Each reference to a loader is a {@link org.floens.chan.chan.ChanLoader.ChanLoaderCallback}, these + * references can be obtained with {@link #obtain(Loadable, ChanLoader.ChanLoaderCallback)}} and released + * with {@link #release(ChanLoader, ChanLoader.ChanLoaderCallback)}. + */ +@Singleton +public class ChanLoaderFactory { + // private static final String TAG = "ChanLoaderFactory"; public static final int THREAD_LOADERS_CACHE_SIZE = 25; - private static LoaderPool instance = new LoaderPool(); - - public static LoaderPool getInstance() { - return instance; - } - private Map threadLoaders = new HashMap<>(); private LruCache threadLoadersCache = new LruCache<>(THREAD_LOADERS_CACHE_SIZE); - private LoaderPool() { + @Inject + public ChanLoaderFactory() { } -// public Loadable obtainLoadable() { -// -// } - public ChanLoader obtain(Loadable loadable, ChanLoader.ChanLoaderCallback listener) { ChanLoader chanLoader; if (loadable.isThreadMode()) { + if (!loadable.isFromDatabase()) { + throw new IllegalArgumentException(); + } + chanLoader = threadLoaders.get(loadable); if (chanLoader == null) { chanLoader = threadLoadersCache.get(loadable); diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java index 6f8e9467..0965fd5a 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java @@ -27,7 +27,6 @@ import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.exception.ChanLoaderException; import org.floens.chan.core.http.DeleteHttpCall; import org.floens.chan.core.http.ReplyManager; -import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.model.Board; import org.floens.chan.core.model.ChanThread; @@ -38,7 +37,7 @@ import org.floens.chan.core.model.Post; import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostLinkable; import org.floens.chan.core.model.SavedReply; -import org.floens.chan.core.pool.LoaderPool; +import org.floens.chan.core.pool.ChanLoaderFactory; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.site.Site; import org.floens.chan.ui.adapter.PostAdapter; @@ -52,6 +51,7 @@ import org.floens.chan.ui.view.ThumbnailView; import org.floens.chan.utils.AndroidUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.inject.Inject; @@ -85,6 +85,9 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt @Inject ReplyManager replyManager; + @Inject + ChanLoaderFactory chanLoaderFactory; + private ThreadPresenterCallback threadPresenterCallback; private Loadable loadable; @@ -115,14 +118,14 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt } this.loadable = loadable; - chanLoader = LoaderPool.getInstance().obtain(loadable, this); + chanLoader = chanLoaderFactory.obtain(loadable, this); } } public void unbindLoadable() { if (chanLoader != null) { chanLoader.clearTimer(); - LoaderPool.getInstance().release(chanLoader, this); + chanLoaderFactory.release(chanLoader, this); chanLoader = null; loadable = null; historyAdded = false; @@ -228,7 +231,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt int index = 0; for (int i = 0; i < posts.size(); i++) { Post item = posts.get(i); - if (item.hasImage) { + if (item.image != null) { images.add(item.image); } if (i == displayPosition) { @@ -323,7 +326,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt List posts = threadPresenterCallback.getDisplayingPosts(); for (int i = 0; i < posts.size(); i++) { Post post = posts.get(i); - if (post.hasImage && post.image == postImage) { + if (post.image == postImage) { position = i; break; } @@ -398,7 +401,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt List posts = threadPresenterCallback.getDisplayingPosts(); for (int i = 0; i < posts.size(); i++) { Post item = posts.get(i); - if (item.hasImage) { + if (item.image != null) { images.add(item.image); if (item.no == post.no) { index = images.size() - 1; @@ -465,7 +468,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt break; case POST_OPTION_LINKS: if (post.linkables.size() > 0) { - threadPresenterCallback.showPostLinkables(post.linkables); + threadPresenterCallback.showPostLinkables(post); } break; case POST_OPTION_COPY_TEXT: @@ -514,13 +517,12 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt } @Override - public void onPostLinkableClicked(PostLinkable linkable) { + public void onPostLinkableClicked(Post post, PostLinkable linkable) { if (linkable.type == PostLinkable.Type.QUOTE) { - Post post = findPostById((Integer) linkable.value); - - List list = new ArrayList<>(1); - list.add(post); - threadPresenterCallback.showPostsPopup(linkable.post, list); + Post linked = findPostById((int) linkable.value); + if (linked != null) { + threadPresenterCallback.showPostsPopup(post, Collections.singletonList(linked)); + } } else if (linkable.type == PostLinkable.Type.LINK) { threadPresenterCallback.openLink((String) linkable.value); } else if (linkable.type == PostLinkable.Type.THREAD) { @@ -635,18 +637,19 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt private void showPostInfo(Post post) { String text = ""; - if (post.hasImage) { - text += "Filename: " + post.filename + "." + post.ext + " \nDimensions: " + post.imageWidth + "x" - + post.imageHeight + "\nSize: " + AndroidUtils.getReadableFileSize(post.fileSize, false); + if (post.image != null) { + text += "Filename: " + post.image.filename + "." + post.image.extension + " \nDimensions: " + post.image.imageWidth + "x" + + post.image.imageHeight + "\nSize: " + AndroidUtils.getReadableFileSize(post.image.size, false); - if (post.spoiler) { + if (post.image.spoiler) { text += "\nSpoilered"; } - text += "\n\n"; + text += "\n"; } - text += "Date: " + post.date; + // TODO(multi-site) get this from the timestamp +// text += "Date: " + post.date; if (!TextUtils.isEmpty(post.id)) { text += "\nId: " + post.id; @@ -685,7 +688,8 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt historyAdded = true; History history = new History(); history.loadable = loadable; - history.thumbnailUrl = chanLoader.getThread().op.thumbnailUrl; + PostImage image = chanLoader.getThread().op.image; + history.thumbnailUrl = image == null ? "" : image.thumbnailUrl; databaseManager.getDatabaseHistoryManager().add(history); } } @@ -701,7 +705,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt void showPostInfo(String info); - void showPostLinkables(List linkables); + void showPostLinkables(Post post); void clipboardPost(Post post); diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/Site.java b/Clover/app/src/main/java/org/floens/chan/core/site/Site.java index 61228b2f..109a1b90 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/Site.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/Site.java @@ -1,5 +1,7 @@ package org.floens.chan.core.site; +import org.floens.chan.chan.ChanLoaderRequest; +import org.floens.chan.chan.ChanLoaderRequestParams; import org.floens.chan.core.model.Board; public interface Site { @@ -9,11 +11,6 @@ public interface Site { */ POSTING, - /** - * This site supports a 4chan like boards.json endpoint. - */ - DYNAMIC_BOARDS, - /** * This site supports deleting posts. */ @@ -80,6 +77,8 @@ public interface Site { Board board(String name); + ChanLoaderRequest loaderRequest(ChanLoaderRequestParams request); + interface BoardsListener { void onBoardsReceived(Boards boards); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java index 6d92a375..088cbc67 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java @@ -4,6 +4,8 @@ import org.floens.chan.core.model.Board; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Post; +import java.util.Map; + /** * Endpoints for {@link Site}. */ @@ -12,11 +14,11 @@ public interface SiteEndpoints { String thread(Board board, Loadable loadable); - String imageUrl(Post post); + String imageUrl(Post.Builder post, Map arg); - String thumbnailUrl(Post post); + String thumbnailUrl(Post.Builder post, boolean spoiler, Map arg); - String flag(Post post); + String flag(Post.Builder post, String countryCode, Map arg); String boards(); 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/site/loaders/Chan4ReaderRequest.java similarity index 71% rename from Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java rename to Clover/app/src/main/java/org/floens/chan/core/site/loaders/Chan4ReaderRequest.java index f43a41aa..ac3a6db9 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/net/ChanReaderRequest.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/loaders/Chan4ReaderRequest.java @@ -15,22 +15,27 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.floens.chan.core.net; +package org.floens.chan.core.site.loaders; import android.util.JsonReader; -import com.android.volley.Response.ErrorListener; -import com.android.volley.Response.Listener; - +import org.floens.chan.chan.ChanLoaderRequestParams; +import org.floens.chan.chan.ChanLoaderResponse; +import org.floens.chan.chan.ChanParser; import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.database.DatabaseSavedReplyManager; import org.floens.chan.core.manager.FilterEngine; import org.floens.chan.core.model.Filter; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Post; +import org.floens.chan.core.model.PostImage; +import org.floens.chan.core.net.JsonReaderRequest; +import org.floens.chan.core.site.SiteEndpoints; import org.floens.chan.utils.Time; +import org.jsoup.parser.Parser; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -49,8 +54,8 @@ import static org.floens.chan.Chan.getGraph; * This class is highly multithreaded, take good care to not access models that are to be only * changed on the main thread. */ -public class ChanReaderRequest extends JsonReaderRequest { - private static final String TAG = "ChanReaderRequest"; +public class Chan4ReaderRequest extends JsonReaderRequest { + private static final String TAG = "Chan4ReaderRequest"; private static final boolean LOG_TIMING = false; private static final int THREAD_COUNT; @@ -64,26 +69,54 @@ public class ChanReaderRequest extends JsonReaderRequest cached; - private Post op; - private FilterEngine filterEngine; + private Post.Builder op; private DatabaseSavedReplyManager databaseSavedReplyManager; private List filters; private long startLoad; - private ChanReaderRequest(String url, Listener listener, ErrorListener errorListener) { - super(url, listener, errorListener); - + public Chan4ReaderRequest(ChanLoaderRequestParams request) { + super(getChanUrl(request.loadable), request.listener, request.errorListener); getGraph().inject(this); - filterEngine = FilterEngine.getInstance(); + // Copy the loadable and cached list. The cached array may changed/cleared by other threads. + loadable = request.loadable.copy(); + cached = new ArrayList<>(request.cached); + + filters = new ArrayList<>(); + List enabledFilters = 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 + filters.add(filter.copy()); + } else { + String[] boardCodes = filter.boardCodes(); + for (String code : boardCodes) { + if (code.equals(loadable.boardCode)) { + // copy the filter because it will get used on other threads + filters.add(filter.copy()); + break; + } + } + } + } + + startLoad = Time.startTiming(); + databaseSavedReplyManager = databaseManager.getDatabaseSavedReplyManager(); } - public static ChanReaderRequest newInstance( - Loadable loadable, List cached, Listener listener, ErrorListener errorListener) { + private static String getChanUrl(Loadable loadable) { String url; if (loadable.site == null) { @@ -101,36 +134,7 @@ public class ChanReaderRequest extends JsonReaderRequest(cached); - - request.filters = new ArrayList<>(); - 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.boardCode)) { - // copy the filter because it will get used on other threads - request.filters.add(filter.copy()); - break; - } - } - } - } - - request.startLoad = Time.startTiming(); - - return request; + return url; } @Override @@ -139,7 +143,7 @@ public class ChanReaderRequest extends JsonReaderRequest> tasks = new ArrayList<>(queue.toParse.size()); for (int i = 0; i < queue.toParse.size(); i++) { - Post post = queue.toParse.get(i); - tasks.add(new PostParseCallable(filterEngine, filters, databaseSavedReplyManager, post)); + Post.Builder post = queue.toParse.get(i); + tasks.add(new PostParseCallable(filterEngine, filters, databaseSavedReplyManager, post, chanParser)); } if (!tasks.isEmpty()) { @@ -202,10 +206,8 @@ public class ChanReaderRequest extends JsonReaderRequest serverPosts) throws Exception { - ChanReaderResponse response = new ChanReaderResponse(); - response.posts = new ArrayList<>(serverPosts.size()); - response.op = op; + private ChanLoaderResponse processPosts(List serverPosts) throws Exception { + ChanLoaderResponse response = new ChanLoaderResponse(op, new ArrayList(serverPosts.size())); List cachedPosts = new ArrayList<>(); List newPosts = new ArrayList<>(); @@ -352,9 +354,21 @@ public class ChanReaderRequest extends JsonReaderRequest cachedByNo) throws Exception { - Post post = new Post(); - post.board = loadable.board; - post.boardId = loadable.boardCode; + Post.Builder builder = new Post.Builder(); + builder.board(loadable.board); + + // File + long fileId = 0; + String fileExt = null; + int fileWidth = 0; + int fileHeight = 0; + long fileSize = 0; + boolean fileSpoiler = false; + String fileName = null; + + // Country flag + String countryCode = null; + String countryName = null; reader.beginObject(); while (reader.hasNext()) { @@ -362,79 +376,81 @@ public class ChanReaderRequest extends JsonReaderRequest hack = new HashMap<>(2); + hack.put("tim", String.valueOf(fileId)); + hack.put("ext", fileExt); + builder.image(new PostImage.Builder() + .originalName(String.valueOf(fileId)) + .thumbnailUrl(endpoints.thumbnailUrl(builder, fileSpoiler, hack)) + .imageUrl(endpoints.imageUrl(builder, hack)) + .filename(Parser.unescapeEntities(fileName, false)) + .extension(fileExt) + .imageWidth(fileWidth) + .imageHeight(fileHeight) + .spoiler(fileSpoiler) + .size(fileSize) + .build()); + } + + if (countryCode != null && countryName != null) { + String countryUrl = endpoints.flag(builder, countryCode, Collections.emptyMap()); + builder.country(countryCode, countryName, countryUrl); } - } - public static class ChanReaderResponse { - // Op Post that is created new each time. - // Used to later copy members like image count to the real op on the main thread. - public Post op; - public List posts; + queue.toParse.add(builder); } private static class ProcessingQueue { public List cached = new ArrayList<>(); - public List toParse = new ArrayList<>(); + public List toParse = new ArrayList<>(); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/net/PostParseCallable.java b/Clover/app/src/main/java/org/floens/chan/core/site/loaders/PostParseCallable.java similarity index 72% rename from Clover/app/src/main/java/org/floens/chan/core/net/PostParseCallable.java rename to Clover/app/src/main/java/org/floens/chan/core/site/loaders/PostParseCallable.java index 748317a2..d969b2f7 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/net/PostParseCallable.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/loaders/PostParseCallable.java @@ -15,13 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.floens.chan.core.net; +package org.floens.chan.core.site.loaders; +import org.floens.chan.chan.ChanParser; import org.floens.chan.core.database.DatabaseSavedReplyManager; import org.floens.chan.core.manager.FilterEngine; import org.floens.chan.core.model.Filter; import org.floens.chan.core.model.Post; -import org.floens.chan.utils.Logger; import java.util.List; import java.util.concurrent.Callable; @@ -33,14 +33,19 @@ class PostParseCallable implements Callable { private FilterEngine filterEngine; private List filters; private DatabaseSavedReplyManager savedReplyManager; - private Post post; + private Post.Builder post; + private ChanParser parser; - public PostParseCallable(FilterEngine filterEngine, List filters, - DatabaseSavedReplyManager savedReplyManager, Post post) { + public PostParseCallable(FilterEngine filterEngine, + List filters, + DatabaseSavedReplyManager savedReplyManager, + Post.Builder post, + ChanParser parser) { this.filterEngine = filterEngine; this.filters = filters; this.savedReplyManager = savedReplyManager; this.post = post; + this.parser = parser; } @Override @@ -48,17 +53,16 @@ class PostParseCallable implements Callable { // Process the filters before finish, because parsing the html is dependent on filter matches processPostFilter(post); - if (!post.finish()) { - Logger.e(TAG, "Incorrect data about post received for post " + post.no); - return null; - } - - post.isSavedReply = savedReplyManager.isSaved(post.boardId, post.no); + post.isSavedReply(savedReplyManager.isSaved(post.board.code, post.id)); - return post; +// if (!post.parse(parser)) { +// Logger.e(TAG, "Incorrect data about post received for post " + post.no); +// return null; +// } + return parser.parse(post); } - private void processPostFilter(Post post) { + private void processPostFilter(Post.Builder post) { int filterSize = filters.size(); for (int i = 0; i < filterSize; i++) { Filter filter = filters.get(i); @@ -66,13 +70,13 @@ class PostParseCallable implements Callable { FilterEngine.FilterAction action = FilterEngine.FilterAction.forId(filter.action); switch (action) { case COLOR: - post.filterHighlightedColor = filter.color; + post.filter(filter.color, false, false); break; case HIDE: - post.filterStub = true; + post.filter(0, true, false); break; case REMOVE: - post.filterRemove = true; + post.filter(0, false, true); break; } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java index 0bdb6740..f4a9596e 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java @@ -3,18 +3,22 @@ package org.floens.chan.core.site.sites.chan4; import com.android.volley.Response; import com.android.volley.VolleyError; +import org.floens.chan.chan.ChanLoaderRequest; +import org.floens.chan.chan.ChanLoaderRequestParams; import org.floens.chan.core.model.Board; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Post; import org.floens.chan.core.site.Boards; import org.floens.chan.core.site.Site; import org.floens.chan.core.site.SiteEndpoints; +import org.floens.chan.core.site.loaders.Chan4ReaderRequest; import org.floens.chan.utils.Logger; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Random; import static org.floens.chan.Chan.getGraph; @@ -36,13 +40,13 @@ public class Chan4 implements Site { } @Override - public String imageUrl(Post post) { - return "https://i.4cdn.org/" + post.boardId + "/" + Long.toString(post.tim) + "." + post.ext; + public String imageUrl(Post.Builder post, Map arg) { + return "https://i.4cdn.org/" + post.board.code + "/" + arg.get("tim") + "." + arg.get("ext"); } @Override - public String thumbnailUrl(Post post) { - if (post.spoiler) { + public String thumbnailUrl(Post.Builder post, boolean spoiler, Map arg) { + if (spoiler) { if (post.board.customSpoilers >= 0) { int i = random.nextInt(post.board.customSpoilers) + 1; return "https://s.4cdn.org/image/spoiler-" + post.board.code + i + ".png"; @@ -50,13 +54,13 @@ public class Chan4 implements Site { return "https://s.4cdn.org/image/spoiler.png"; } } else { - return "https://t.4cdn.org/" + post.board.code + "/" + post.tim + "s.jpg"; + return "https://t.4cdn.org/" + post.board.code + "/" + arg.get("tim") + "s.jpg"; } } @Override - public String flag(Post post) { - return "https://s.4cdn.org/image/country/" + post.country.toLowerCase(Locale.ENGLISH) + ".gif"; + public String flag(Post.Builder post, String countryCode, Map arg) { + return "https://s.4cdn.org/image/country/" + countryCode.toLowerCase(Locale.ENGLISH) + ".gif"; } @Override @@ -90,9 +94,6 @@ public class Chan4 implements Site { case POSTING: // yes, we support posting. return true; - case DYNAMIC_BOARDS: - // yes, boards.json - return true; case LOGIN: // 4chan pass. return true; @@ -106,6 +107,7 @@ public class Chan4 implements Site { @Override public BoardsType boardsType() { + // yes, boards.json return BoardsType.DYNAMIC; } @@ -163,4 +165,9 @@ public class Chan4 implements Site { } })); } + + @Override + public ChanLoaderRequest loaderRequest(ChanLoaderRequestParams request) { + return new ChanLoaderRequest(new Chan4ReaderRequest(request)); + } } diff --git a/Clover/app/src/main/java/org/floens/chan/test/TestActivity.java b/Clover/app/src/main/java/org/floens/chan/test/TestActivity.java index bdcd4b0b..a978b8e2 100644 --- a/Clover/app/src/main/java/org/floens/chan/test/TestActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/test/TestActivity.java @@ -207,8 +207,8 @@ public class TestActivity extends Activity implements View.OnClickListener { @Override public void onChanLoaderData(ChanThread result) { for (Post post : result.posts) { - if (post.hasImage) { - final String imageUrl = post.imageUrl; + if (post.image != null) { + final String imageUrl = post.image.imageUrl; fileCache.downloadFile(imageUrl, new FileCache.DownloadedCallback() { @Override public void onProgress(long downloaded, long total, boolean done) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java index c12f8bdc..d0294755 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PostsFilter.java @@ -19,7 +19,6 @@ package org.floens.chan.ui.adapter; import android.text.TextUtils; -import org.floens.chan.Chan; import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.model.Post; @@ -117,7 +116,7 @@ public class PostsFilter { add = true; } else if (item.name.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) { add = true; - } else if (item.filename != null && item.filename.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) { + } else if (item.image != null && item.image.filename != null && item.image.filename.toLowerCase(Locale.ENGLISH).contains(lowerQuery)) { add = true; } if (!add) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java index 1127867a..a948e3d0 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/CardPostCell.java @@ -184,7 +184,7 @@ public class CardPostCell extends CardView implements PostCellInterface, View.On private void bindPost(Theme theme, Post post) { bound = true; - if (post.hasImage && !ChanSettings.textOnly.get()) { + if (post.image != null && !ChanSettings.textOnly.get()) { thumbnailView.setVisibility(View.VISIBLE); thumbnailView.setPostImage(post.image, thumbnailView.getWidth(), thumbnailView.getHeight()); } else { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java index 81f61643..3a5b8615 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java @@ -324,7 +324,7 @@ public class PostCell extends LinearLayout implements PostCellInterface { filterMatchColor.setVisibility(View.GONE); } - if (post.hasImage && !ChanSettings.textOnly.get()) { + if (post.image != null && !ChanSettings.textOnly.get()) { thumbnailView.setVisibility(View.VISIBLE); thumbnailView.setPostImage(post.image, thumbnailView.getLayoutParams().width, thumbnailView.getLayoutParams().height); } else { @@ -369,7 +369,7 @@ public class PostCell extends LinearLayout implements PostCellInterface { titleParts.add(date); - if (post.hasImage) { + if (post.image != null) { PostImage image = post.image; boolean postFileName = ChanSettings.postFilename.get(); @@ -421,7 +421,7 @@ public class PostCell extends LinearLayout implements PostCellInterface { } comment.setText(commentText); - comment.setVisibility(isEmpty(commentText) && !post.hasImage ? GONE : VISIBLE); + comment.setVisibility(isEmpty(commentText) && post.image == null ? GONE : VISIBLE); if (commentClickable != threadMode) { commentClickable = threadMode; @@ -525,7 +525,7 @@ public class PostCell extends LinearLayout implements PostCellInterface { ignoreNextOnClick = true; clickableSpan.onClick(widget); if (clickableSpan instanceof PostLinkable) { - callback.onPostLinkableClicked((PostLinkable) clickableSpan); + callback.onPostLinkableClicked(post, (PostLinkable) clickableSpan); } buffer.removeSpan(BACKGROUND_SPAN); } else if (action == MotionEvent.ACTION_DOWN && clickableSpan instanceof PostLinkable) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java index 3a17d26e..b195da01 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/PostCellInterface.java @@ -47,7 +47,7 @@ public interface PostCellInterface { void onPostOptionClicked(Post post, Object id); - void onPostLinkableClicked(PostLinkable linkable); + void onPostLinkableClicked(Post post, PostLinkable linkable); void onPostNoClicked(Post post); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java index a60b5004..7793854f 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java @@ -28,7 +28,6 @@ import android.view.animation.DecelerateInterpolator; import android.widget.BaseAdapter; import android.widget.TextView; -import org.floens.chan.Chan; import org.floens.chan.R; import org.floens.chan.chan.ChanUrls; import org.floens.chan.core.database.DatabaseManager; 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 0e9a2f12..2c913e4e 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 @@ -30,7 +30,6 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import org.floens.chan.Chan; import org.floens.chan.R; import org.floens.chan.controller.Controller; import org.floens.chan.core.database.DatabaseManager; @@ -63,7 +62,8 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too @Inject DatabaseManager databaseManager; - private FilterEngine filterEngine; + @Inject + FilterEngine filterEngine; private RecyclerView recyclerView; private FloatingActionButton add; @@ -108,8 +108,6 @@ public class FiltersController extends Controller implements ToolbarMenuItem.Too super.onCreate(); getGraph().inject(this); - filterEngine = FilterEngine.getInstance(); - navigationItem.setTitle(R.string.filters_screen); navigationItem.menu = new ToolbarMenu(context); navigationItem.menu.addItem(new ToolbarMenuItem(context, this, SEARCH_ID, R.drawable.ic_search_white_24dp)); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java index f6ce9a3d..d0e39c9a 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java @@ -111,7 +111,7 @@ public class PostRepliesController extends Controller { if (view instanceof PostCellInterface) { PostCellInterface postView = (PostCellInterface) view; Post post = postView.getPost(); - if (post.hasImage && post.imageUrl.equals(postImage.imageUrl)) { + if (post.image != null && post.image.imageUrl.equals(postImage.imageUrl)) { thumbnail = postView.getThumbnailView(); break; } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java index 081e59a5..1d64f380 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java @@ -96,7 +96,7 @@ public class ThemeSettingsController extends Controller implements View.OnClickL } @Override - public void onPostLinkableClicked(PostLinkable linkable) { + public void onPostLinkableClicked(Post post, PostLinkable linkable) { } @Override @@ -236,22 +236,20 @@ public class ThemeSettingsController extends Controller implements View.OnClickL Context themeContext = new ContextThemeWrapper(context, theme.resValue); - Post post = new Post(); - post.no = 123456789; - post.time = (Time.get() - (30 * 60 * 1000)) / 1000; - // No synchronization needed, this is a dummy - post.repliesFrom.add(1); - post.repliesFrom.add(2); - post.repliesFrom.add(3); - post.subject = "Lorem ipsum"; - post.rawComment = ">>123456789
" + - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.
" + - "
" + - ">>123456789
" + - "http://example.com/" + - "
" + - "Phasellus consequat semper sodales. Donec dolor lectus, aliquet nec mollis vel, rutrum vel enim."; - getGraph().getChanParser().parse(theme, post); + Post.Builder builder = new Post.Builder() + .board(new Board(Sites.defaultSite(), "a", "a", false, false)) + .id(123456789) + .opId(1) + .setUnixTimestampSeconds((Time.get() - (30 * 60 * 1000)) / 1000) + .subject("Lorem ipsum") + .comment(">>123456789
" + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.
" + + "
" + + ">>123456789
" + + "http://example.com/" + + "
" + + "Phasellus consequat semper sodales. Donec dolor lectus, aliquet nec mollis vel, rutrum vel enim."); + Post post = getGraph().getChanParser().parse(theme, builder); LinearLayout linearLayout = new LinearLayout(themeContext); linearLayout.setOrientation(LinearLayout.VERTICAL); 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 28da0bd3..565148b3 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 @@ -38,7 +38,6 @@ import android.widget.ImageView; 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.manager.FilterEngine; @@ -79,6 +78,9 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { @Inject BoardManager boardManager; + @Inject + FilterEngine filterEngine; + private FilterLayoutCallback callback; private Filter filter; @@ -161,7 +163,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { public void setFilter(Filter filter) { this.filter = filter; appliedBoards.clear(); - appliedBoards.addAll(FilterEngine.getInstance().getBoardsForFilter(filter)); + appliedBoards.addAll(filterEngine.getBoardsForFilter(filter)); pattern.setText(filter.pattern); @@ -180,7 +182,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { public Filter getFilter() { filter.enabled = enabled.isChecked(); - FilterEngine.getInstance().saveBoardsToFilter(appliedBoards, filter); + filterEngine.saveBoardsToFilter(appliedBoards, filter); return filter; } @@ -341,7 +343,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { } private void updateFilterValidity() { - boolean valid = !TextUtils.isEmpty(filter.pattern) && FilterEngine.getInstance().compile(filter.pattern) != null; + boolean valid = !TextUtils.isEmpty(filter.pattern) && filterEngine.compile(filter.pattern) != null; if (valid != patternContainerErrorShowing) { patternContainerErrorShowing = valid; @@ -386,7 +388,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener { private void updatePatternPreview() { String text = patternPreview.getText().toString(); - boolean matches = text.length() > 0 && FilterEngine.getInstance().matches(filter, true, text, true); + boolean matches = text.length() > 0 && filterEngine.matches(filter, true, text, true); patternPreviewStatus.setText(matches ? R.string.filter_matches : R.string.filter_no_matches); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java index 9365b1d2..5dca4300 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java @@ -240,7 +240,8 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T .show(); } - public void showPostLinkables(final List linkables) { + public void showPostLinkables(final Post post) { + final List linkables = post.linkables; String[] keys = new String[linkables.size()]; for (int i = 0; i < linkables.size(); i++) { keys[i] = linkables.get(i).key; @@ -250,7 +251,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T .setItems(keys, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - presenter.onPostLinkableClicked(linkables.get(which)); + presenter.onPostLinkableClicked(post, linkables.get(which)); } }) .show(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java index b26e0a19..6dfb8ea7 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java @@ -346,7 +346,7 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa if (view instanceof PostCellInterface) { PostCellInterface postView = (PostCellInterface) view; Post post = postView.getPost(); - if (post.hasImage && post.imageUrl.equals(postImage.imageUrl)) { + if (post.image != null && post.image.imageUrl.equals(postImage.imageUrl)) { thumbnail = postView.getThumbnailView(); break; } @@ -503,7 +503,8 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa View child = parent.getChildAt(i); if (child instanceof PostCellInterface) { PostCellInterface postView = (PostCellInterface) child; - if (postView.getPost().hasImage) { + Post post = postView.getPost(); + if (post.isOP && post.image != null) { RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int top = child.getTop() + params.topMargin; int left = child.getLeft() + params.leftMargin; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java index b95b424a..bbca5067 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java @@ -196,7 +196,7 @@ public class WatchNotifier extends Service { prefix = postForExpandedLine.title.subSequence(0, SUBJECT_LENGTH); } - String comment = postForExpandedLine.hasImage ? IMAGE_TEXT : ""; + String comment = postForExpandedLine.image != null ? IMAGE_TEXT : ""; if (postForExpandedLine.comment.length() > 0) { comment += postForExpandedLine.comment; }