/******************************************************************************* * This file is part of RedReader. * * RedReader 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. * * RedReader 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 RedReader. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.quantumbadger.redreader.fragments; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import org.apache.commons.lang3.StringEscapeUtils; import org.quantumbadger.redreader.R; import org.quantumbadger.redreader.account.RedditAccount; import org.quantumbadger.redreader.account.RedditAccountManager; import org.quantumbadger.redreader.activities.BugReportActivity; import org.quantumbadger.redreader.activities.OptionsMenuUtility; import org.quantumbadger.redreader.activities.SessionChangeListener; import org.quantumbadger.redreader.adapters.PostListingManager; import org.quantumbadger.redreader.cache.CacheManager; import org.quantumbadger.redreader.cache.CacheRequest; import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategy; import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyAlways; import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyIfNotCached; import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyIfTimestampOutsideBounds; import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyNever; import org.quantumbadger.redreader.common.AndroidApi; import org.quantumbadger.redreader.common.Constants; import org.quantumbadger.redreader.common.General; import org.quantumbadger.redreader.common.LinkHandler; import org.quantumbadger.redreader.common.PrefsUtility; import org.quantumbadger.redreader.common.RRError; import org.quantumbadger.redreader.common.RRTime; import org.quantumbadger.redreader.common.TimestampBound; import org.quantumbadger.redreader.image.GetImageInfoListener; import org.quantumbadger.redreader.image.ImageInfo; import org.quantumbadger.redreader.io.RequestResponseHandler; import org.quantumbadger.redreader.jsonwrap.JsonBufferedArray; import org.quantumbadger.redreader.jsonwrap.JsonBufferedObject; import org.quantumbadger.redreader.jsonwrap.JsonValue; import org.quantumbadger.redreader.listingcontrollers.CommentListingController; import org.quantumbadger.redreader.reddit.PostSort; import org.quantumbadger.redreader.reddit.RedditPostListItem; import org.quantumbadger.redreader.reddit.RedditSubredditManager; import org.quantumbadger.redreader.reddit.api.RedditSubredditSubscriptionManager; import org.quantumbadger.redreader.reddit.api.SubredditRequestFailure; import org.quantumbadger.redreader.reddit.prepared.RedditParsedPost; import org.quantumbadger.redreader.reddit.prepared.RedditPreparedPost; import org.quantumbadger.redreader.reddit.things.RedditPost; import org.quantumbadger.redreader.reddit.things.RedditSubreddit; import org.quantumbadger.redreader.reddit.things.RedditThing; import org.quantumbadger.redreader.reddit.url.PostCommentListingURL; import org.quantumbadger.redreader.reddit.url.PostListingURL; import org.quantumbadger.redreader.reddit.url.RedditURLParser; import org.quantumbadger.redreader.reddit.url.SubredditPostListURL; import org.quantumbadger.redreader.views.PostListingHeader; import org.quantumbadger.redreader.views.RedditPostView; import org.quantumbadger.redreader.views.ScrollbarRecyclerViewManager; import org.quantumbadger.redreader.views.liststatus.ErrorView; import java.net.URI; import java.text.NumberFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; public class PostListingFragment extends RRFragment implements RedditPostView.PostSelectionListener { private static final String TAG = "PostListingFragment"; private static final String SAVEDSTATE_FIRST_VISIBLE_POS = "firstVisiblePosition"; private final PostListingURL mPostListingURL; private RedditSubreddit mSubreddit; private UUID mSession; private final int mPostCountLimit; private TextView mLoadMoreView; private final SharedPreferences mSharedPreferences; private final PostListingManager mPostListingManager; private final RecyclerView mRecyclerView; private final View mOuter; private String mAfter = null, mLastAfter = null; private CacheRequest mRequest; private boolean mReadyToDownloadMore = false; private long mTimestamp; private int mPostCount = 0; private final AtomicInteger mPostRefreshCount = new AtomicInteger(0); private final HashSet<String> mPostIds = new HashSet<>(200); private Integer mPreviousFirstVisibleItemPosition; // Session may be null public PostListingFragment( final AppCompatActivity parent, final Bundle savedInstanceState, final Uri url, final UUID session, final boolean forceDownload) { super(parent, savedInstanceState); mPostListingManager = new PostListingManager(parent); if(savedInstanceState != null) { mPreviousFirstVisibleItemPosition = savedInstanceState.getInt(SAVEDSTATE_FIRST_VISIBLE_POS); } try { mPostListingURL = (PostListingURL) RedditURLParser.parseProbablePostListing(url); } catch(ClassCastException e) { Toast.makeText(getActivity(), "Invalid post listing URL.", Toast.LENGTH_LONG).show(); // TODO proper error handling -- show error view throw new RuntimeException("Invalid post listing URL"); } mSession = session; final Context context = getContext(); mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); // TODO output failed URL if(mPostListingURL == null) { mPostListingManager.addFooterError( new ErrorView(getActivity(), new RRError("Invalid post listing URL", "Could not navigate to that URL."))); // TODO proper error handling throw new RuntimeException("Invalid post listing URL"); } switch(PrefsUtility.pref_behaviour_post_count(context, mSharedPreferences)) { case ALL: mPostCountLimit = -1; break; case R25: mPostCountLimit = 25; break; case R50: mPostCountLimit = 50; break; case R100: mPostCountLimit = 100; break; default: mPostCountLimit = 0; break; } if(mPostCountLimit > 0) { restackRefreshCount(); } final ScrollbarRecyclerViewManager recyclerViewManager = new ScrollbarRecyclerViewManager(context, null, false); if(parent instanceof OptionsMenuUtility.OptionsMenuPostsListener && PrefsUtility.pref_behaviour_enable_swipe_refresh(context, mSharedPreferences)) { recyclerViewManager.enablePullToRefresh(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { ((OptionsMenuUtility.OptionsMenuPostsListener)parent).onRefreshPosts(); } }); } mRecyclerView = recyclerViewManager.getRecyclerView(); mPostListingManager.setLayoutManager((LinearLayoutManager) mRecyclerView.getLayoutManager()); mRecyclerView.setAdapter(mPostListingManager.getAdapter()); mOuter = recyclerViewManager.getOuterView(); mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) { onLoadMoreItemsCheck(); } }); mRecyclerView.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT; int limit = 50; if(mPostCountLimit > 0 && limit > mPostCountLimit) { limit = mPostCountLimit; } final DownloadStrategy downloadStrategy; if(forceDownload) { downloadStrategy = DownloadStrategyAlways.INSTANCE; } else if(session == null && savedInstanceState == null && General.isNetworkConnected(context)) { final long maxAgeMs = PrefsUtility.pref_cache_rerequest_postlist_age_ms(context, mSharedPreferences); downloadStrategy = new DownloadStrategyIfTimestampOutsideBounds(TimestampBound.notOlderThan(maxAgeMs)); } else { downloadStrategy = DownloadStrategyIfNotCached.INSTANCE; } mRequest = new PostListingRequest( mPostListingURL.generateJsonUri(), RedditAccountManager.getInstance(context).getDefaultAccount(), session, downloadStrategy, true); // The request doesn't go ahead until the header is in place. switch(mPostListingURL.pathType()) { case RedditURLParser.USER_POST_LISTING_URL: case RedditURLParser.SEARCH_POST_LISTING_URL: case RedditURLParser.MULTIREDDIT_POST_LISTING_URL: setHeader(mPostListingURL.humanReadableName(getActivity(), true), mPostListingURL.humanReadableUrl()); CacheManager.getInstance(context).makeRequest(mRequest); break; case RedditURLParser.SUBREDDIT_POST_LISTING_URL: SubredditPostListURL subredditPostListURL = (SubredditPostListURL)mPostListingURL; switch(subredditPostListURL.type) { case FRONTPAGE: case ALL: case SUBREDDIT_COMBINATION: case ALL_SUBTRACTION: case POPULAR: setHeader(mPostListingURL.humanReadableName(getActivity(), true), mPostListingURL.humanReadableUrl()); CacheManager.getInstance(context).makeRequest(mRequest); break; case SUBREDDIT: { // Request the subreddit data final RequestResponseHandler<RedditSubreddit, SubredditRequestFailure> subredditHandler = new RequestResponseHandler<RedditSubreddit, SubredditRequestFailure>() { @Override public void onRequestFailed(SubredditRequestFailure failureReason) { // Ignore AndroidApi.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { CacheManager.getInstance(context).makeRequest(mRequest); } }); } @Override public void onRequestSuccess(final RedditSubreddit result, final long timeCached) { AndroidApi.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { mSubreddit = result; onSubredditReceived(); CacheManager.getInstance(context).makeRequest(mRequest); } }); } }; try { RedditSubredditManager .getInstance(getActivity(), RedditAccountManager.getInstance(getActivity()).getDefaultAccount()) .getSubreddit(RedditSubreddit.getCanonicalName(subredditPostListURL.subreddit), TimestampBound.NONE, subredditHandler, null); } catch(RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } break; } } break; } } private LinearLayout createVerticalLinearLayout(Context context) { final LinearLayout result = new LinearLayout(context); result.setOrientation(LinearLayout.VERTICAL); return result; } @Override public View getView() { return mOuter; } @Override public Bundle onSaveInstanceState() { final Bundle bundle = new Bundle(); final LinearLayoutManager layoutManager = (LinearLayoutManager)mRecyclerView.getLayoutManager(); bundle.putInt(SAVEDSTATE_FIRST_VISIBLE_POS, layoutManager.findFirstVisibleItemPosition()); return bundle; } public void cancel() { if(mRequest != null) mRequest.cancel(); } public synchronized void restackRefreshCount() { while(mPostRefreshCount.get() <= 0) { mPostRefreshCount.addAndGet(mPostCountLimit); } } private void onSubredditReceived() { final String subtitle; if(mPostListingURL.getOrder() == null || mPostListingURL.getOrder() == PostSort.HOT) { if(mSubreddit.subscribers == null) { subtitle = getString(R.string.header_subscriber_count_unknown); } else { subtitle = getContext().getString(R.string.header_subscriber_count, NumberFormat.getNumberInstance(Locale.getDefault()).format(mSubreddit.subscribers)); } } else { subtitle = mPostListingURL.humanReadableUrl(); } getActivity().runOnUiThread(new Runnable() { @Override public void run() { setHeader(StringEscapeUtils.unescapeHtml4(mSubreddit.title), subtitle); getActivity().invalidateOptionsMenu(); } }); } private void setHeader(final String title, final String subtitle) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { final PostListingHeader postListingHeader = new PostListingHeader(getActivity(), title, subtitle); mPostListingManager.addPostListingHeader(postListingHeader); } }); } public void onPostSelected(final RedditPreparedPost post) { ((RedditPostView.PostSelectionListener)getActivity()).onPostSelected(post); new Thread() { @Override public void run() { post.markAsRead(getActivity()); } }.start(); } public void onPostCommentsSelected(final RedditPreparedPost post) { ((RedditPostView.PostSelectionListener)getActivity()).onPostCommentsSelected(post); new Thread() { @Override public void run() { post.markAsRead(getActivity()); } }.start(); } private void onLoadMoreItemsCheck() { General.checkThisIsUIThread(); if(mReadyToDownloadMore && mAfter != null && !mAfter.equals(mLastAfter)) { final LinearLayoutManager layoutManager = (LinearLayoutManager)mRecyclerView.getLayoutManager(); if(mPostListingManager.getPostCount() > 0 && (layoutManager.getItemCount() - layoutManager.findLastVisibleItemPosition() < 20 && (mPostCountLimit <= 0 || mPostRefreshCount.get() > 0) || (mPreviousFirstVisibleItemPosition != null && layoutManager.getItemCount() <= mPreviousFirstVisibleItemPosition))) { mLastAfter = mAfter; mReadyToDownloadMore = false; final Uri newUri = mPostListingURL.after(mAfter).generateJsonUri(); // TODO customise (currently 3 hrs) final DownloadStrategy strategy = (RRTime.since(mTimestamp) < 3 * 60 * 60 * 1000) ? DownloadStrategyIfNotCached.INSTANCE : DownloadStrategyNever.INSTANCE; int limit = 50; if(mPostCountLimit > 0 && limit > mPostRefreshCount.get()) { limit = mPostRefreshCount.get(); } mRequest = new PostListingRequest(newUri, RedditAccountManager.getInstance(getActivity()).getDefaultAccount(), mSession, strategy, false); mPostListingManager.setLoadingVisible(true); CacheManager.getInstance(getActivity()).makeRequest(mRequest); } else if(mPostCountLimit > 0 && mPostRefreshCount.get() <= 0) { if(mLoadMoreView == null) { mLoadMoreView = (TextView)LayoutInflater.from(getContext()).inflate(R.layout.load_more_posts, null); mLoadMoreView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mPostListingManager.removeLoadMoreButton(); mLoadMoreView = null; restackRefreshCount(); onLoadMoreItemsCheck(); } }); mPostListingManager.addLoadMoreButton(mLoadMoreView); } } } } public void onSubscribe() { if(mPostListingURL.pathType() != RedditURLParser.SUBREDDIT_POST_LISTING_URL) return; try { RedditSubredditSubscriptionManager .getSingleton(getActivity(), RedditAccountManager.getInstance(getActivity()).getDefaultAccount()) .subscribe(RedditSubreddit.getCanonicalName(mPostListingURL.asSubredditPostListURL().subreddit), getActivity()); } catch(RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } } public void onUnsubscribe() { if(mSubreddit == null) return; try { RedditSubredditSubscriptionManager .getSingleton(getActivity(), RedditAccountManager.getInstance(getActivity()).getDefaultAccount()) .unsubscribe(mSubreddit.getCanonicalName(), getActivity()); } catch(RedditSubreddit.InvalidSubredditNameException e) { throw new RuntimeException(e); } } public RedditSubreddit getSubreddit() { return mSubreddit; } private static Uri setUriDownloadCount(final Uri input, final int count) { return input.buildUpon().appendQueryParameter("limit", String.valueOf(count)).build(); } public void onPostsAdded() { if(mPreviousFirstVisibleItemPosition == null) { return; } final LinearLayoutManager layoutManager = (LinearLayoutManager)mRecyclerView.getLayoutManager(); if(layoutManager.getItemCount() > mPreviousFirstVisibleItemPosition) { layoutManager.scrollToPositionWithOffset(mPreviousFirstVisibleItemPosition, 0); mPreviousFirstVisibleItemPosition = null; } else { layoutManager.scrollToPosition(layoutManager.getItemCount() - 1); } } private class PostListingRequest extends CacheRequest { private final boolean firstDownload; protected PostListingRequest(Uri url, RedditAccount user, UUID requestSession, DownloadStrategy downloadStrategy, boolean firstDownload) { super(General.uriFromString(url.toString()), user, requestSession, Constants.Priority.API_POST_LIST, 0, downloadStrategy, Constants.FileType.POST_LIST, DOWNLOAD_QUEUE_REDDIT_API, true, false, getActivity()); this.firstDownload = firstDownload; } @Override protected void onDownloadNecessary() {} @Override protected void onDownloadStarted() {} @Override protected void onCallbackException(final Throwable t) { BugReportActivity.handleGlobalError(context, t); } @Override protected void onFailure(final @CacheRequest.RequestFailureType int type, final Throwable t, final Integer status, final String readableMessage) { AndroidApi.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { mPostListingManager.setLoadingVisible(false); final RRError error; if(type == CacheRequest.REQUEST_FAILURE_CACHE_MISS) { error = new RRError( context.getString(R.string.error_postlist_cache_title), context.getString(R.string.error_postlist_cache_message), t, status, url.toString()); } else { error = General.getGeneralErrorForFailure(context, type, t, status, url.toString()); } mPostListingManager.addFooterError(new ErrorView(getActivity(), error)); } }); } @Override protected void onProgress(final boolean authorizationInProgress, final long bytesRead, final long totalBytes) {} @Override protected void onSuccess(final CacheManager.ReadableCacheFile cacheFile, final long timestamp, final UUID session, final boolean fromCache, final String mimetype) {} @Override public void onJsonParseStarted(final JsonValue value, final long timestamp, final UUID session, final boolean fromCache) { final AppCompatActivity activity = getActivity(); // TODO pref (currently 10 mins) if(firstDownload && fromCache && RRTime.since(timestamp) > 10 * 60 * 1000) { AndroidApi.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { final TextView cacheNotif = (TextView)LayoutInflater.from(getActivity()) .inflate(R.layout.cached_header, null, false); cacheNotif.setText(getActivity().getString( R.string.listing_cached, RRTime.formatDateTime(timestamp, getActivity()))); mPostListingManager.addNotification(cacheNotif); } }); } // TODO resuming a copy if(firstDownload) { ((SessionChangeListener)activity).onSessionChanged(session, SessionChangeListener.SessionChangeType.POSTS, timestamp); PostListingFragment.this.mSession = session; PostListingFragment.this.mTimestamp = timestamp; } // TODO {"error": 403} is received for unauthorized subreddits try { final JsonBufferedObject thing = value.asObject(); final JsonBufferedObject listing = thing.getObject("data"); final JsonBufferedArray posts = listing.getArray("children"); final boolean isNsfwAllowed = PrefsUtility.pref_behaviour_nsfw(activity, mSharedPreferences); final boolean isConnectionWifi = General.isConnectionWifi(activity); final PrefsUtility.AppearanceThumbnailsShow thumbnailsPref = PrefsUtility.appearance_thumbnails_show( activity, mSharedPreferences); final boolean downloadThumbnails = thumbnailsPref == PrefsUtility.AppearanceThumbnailsShow.ALWAYS || (thumbnailsPref == PrefsUtility.AppearanceThumbnailsShow.WIFIONLY && isConnectionWifi); final boolean showNsfwThumbnails = PrefsUtility.appearance_thumbnails_nsfw_show(activity, mSharedPreferences); final PrefsUtility.CachePrecacheImages imagePrecachePref = PrefsUtility.cache_precache_images(activity, mSharedPreferences); final PrefsUtility.CachePrecacheComments commentPrecachePref = PrefsUtility.cache_precache_comments(activity, mSharedPreferences); final boolean precacheImages = (imagePrecachePref == PrefsUtility.CachePrecacheImages.ALWAYS || (imagePrecachePref == PrefsUtility.CachePrecacheImages.WIFIONLY && isConnectionWifi)) && !General.isCacheDiskFull(activity); final boolean precacheComments = (commentPrecachePref == PrefsUtility.CachePrecacheComments.ALWAYS || (commentPrecachePref == PrefsUtility.CachePrecacheComments.WIFIONLY && isConnectionWifi)); final PrefsUtility.ImageViewMode imageViewMode = PrefsUtility.pref_behaviour_imageview_mode(activity, mSharedPreferences); final PrefsUtility.GifViewMode gifViewMode = PrefsUtility.pref_behaviour_gifview_mode(activity, mSharedPreferences); final PrefsUtility.VideoViewMode videoViewMode = PrefsUtility.pref_behaviour_videoview_mode(activity, mSharedPreferences); final boolean subredditFilteringEnabled = mPostListingURL.pathType() == RedditURLParser.SUBREDDIT_POST_LISTING_URL && (mPostListingURL.asSubredditPostListURL().type == SubredditPostListURL.Type.ALL || mPostListingURL.asSubredditPostListURL().type == SubredditPostListURL.Type.ALL_SUBTRACTION || mPostListingURL.asSubredditPostListURL().type == SubredditPostListURL.Type.POPULAR); final List<String> blockedSubreddits = PrefsUtility.pref_blocked_subreddits(activity, mSharedPreferences); // Grab this so we don't have to pull from the prefs every post Log.i(TAG, "Precaching images: " + (precacheImages ? "ON" : "OFF")); Log.i(TAG, "Precaching comments: " + (precacheComments ? "ON" : "OFF")); final CacheManager cm = CacheManager.getInstance(activity); final boolean showSubredditName = !(mPostListingURL != null && mPostListingURL.pathType() == RedditURLParser.SUBREDDIT_POST_LISTING_URL && mPostListingURL.asSubredditPostListURL().type == SubredditPostListURL.Type.SUBREDDIT); final ArrayList<RedditPostListItem> downloadedPosts = new ArrayList<>(25); for(final JsonValue postThingValue : posts) { final RedditThing postThing = postThingValue.asObject(RedditThing.class); if(!postThing.getKind().equals(RedditThing.Kind.POST)) continue; final RedditPost post = postThing.asPost(); mAfter = post.name; final boolean isPostBlocked = subredditFilteringEnabled && getIsPostBlocked(blockedSubreddits, post); if(!isPostBlocked && (!post.over_18 || isNsfwAllowed) && mPostIds.add(post.getIdAlone())) { final boolean downloadThisThumbnail = downloadThumbnails && (!post.over_18 || showNsfwThumbnails); final int positionInList = mPostCount; final RedditParsedPost parsedPost = new RedditParsedPost(post, false); final RedditPreparedPost preparedPost = new RedditPreparedPost( activity, cm, positionInList, parsedPost, timestamp, showSubredditName, downloadThisThumbnail); if(precacheComments) { final CommentListingController controller = new CommentListingController( PostCommentListingURL.forPostId(preparedPost.src.getIdAlone()), activity); CacheManager.getInstance(activity).makeRequest(new CacheRequest( General.uriFromString(controller.getUri().toString()), RedditAccountManager.getInstance(activity).getDefaultAccount(), null, Constants.Priority.COMMENT_PRECACHE, positionInList, DownloadStrategyIfNotCached.INSTANCE, Constants.FileType.COMMENT_LIST, DOWNLOAD_QUEUE_REDDIT_API, false, // Don't parse the JSON false, activity) { @Override protected void onCallbackException(final Throwable t) {} @Override protected void onDownloadNecessary() {} @Override protected void onDownloadStarted() {} @Override protected void onFailure(final @CacheRequest.RequestFailureType int type, final Throwable t, final Integer status, final String readableMessage) { Log.e(TAG, "Failed to precache " + url.toString() + "(RequestFailureType code: " + type + ")"); } @Override protected void onProgress(final boolean authorizationInProgress, final long bytesRead, final long totalBytes) {} @Override protected void onSuccess(final CacheManager.ReadableCacheFile cacheFile, final long timestamp, final UUID session, final boolean fromCache, final String mimetype) { Log.i(TAG, "Successfully precached " + url.toString()); } }); } LinkHandler.getImageInfo(activity, parsedPost.getUrl(), Constants.Priority.IMAGE_PRECACHE, positionInList, new GetImageInfoListener() { @Override public void onFailure(final @CacheRequest.RequestFailureType int type, final Throwable t, final Integer status, final String readableMessage) {} @Override public void onNotAnImage() {} @Override public void onSuccess(final ImageInfo info) { if(!precacheImages) return; // Don't precache huge images if(info.size != null && info.size > 15 * 1024 * 1024) { Log.i(TAG, String.format( "Not precaching '%s': too big (%d kB)", post.url, info.size / 1024)); return; } // Don't precache gifs if they're opened externally if(ImageInfo.MediaType.GIF.equals(info.mediaType) && !gifViewMode.downloadInApp) { Log.i(TAG, String.format( "Not precaching '%s': GIFs are opened externally", post.url)); return; } // Don't precache images if they're opened externally if(ImageInfo.MediaType.IMAGE.equals(info.mediaType) && !imageViewMode.downloadInApp) { Log.i(TAG, String.format( "Not precaching '%s': images are opened externally", post.url)); return; } // Don't precache videos if they're opened externally if(ImageInfo.MediaType.VIDEO.equals(info.mediaType) && !videoViewMode.downloadInApp) { Log.i(TAG, String.format( "Not precaching '%s': videos are opened externally", post.url)); return; } final URI uri = General.uriFromString(info.urlOriginal); if(uri == null) { Log.i(TAG, String.format( "Not precaching '%s': failed to parse URL", post.url)); return; } CacheManager.getInstance(activity).makeRequest(new CacheRequest( uri, RedditAccountManager.getAnon(), null, Constants.Priority.IMAGE_PRECACHE, positionInList, DownloadStrategyIfNotCached.INSTANCE, Constants.FileType.IMAGE, DOWNLOAD_QUEUE_IMAGE_PRECACHE, false, false, activity ) { @Override protected void onCallbackException(final Throwable t) {} @Override protected void onDownloadNecessary() {} @Override protected void onDownloadStarted() {} @Override protected void onFailure(final @CacheRequest.RequestFailureType int type, final Throwable t, final Integer status, final String readableMessage) { Log.e(TAG, String.format( Locale.US, "Failed to precache %s (RequestFailureType %d, status %s, readable '%s')", info.urlOriginal, type, status == null ? "NULL" : status.toString(), readableMessage == null ? "NULL" : readableMessage)); } @Override protected void onProgress(final boolean authorizationInProgress, final long bytesRead, final long totalBytes) {} @Override protected void onSuccess(final CacheManager.ReadableCacheFile cacheFile, final long timestamp, final UUID session, final boolean fromCache, final String mimetype) { Log.i(TAG, "Successfully precached " + info.urlOriginal); } }); } }); downloadedPosts.add(new RedditPostListItem( preparedPost, PostListingFragment.this, activity, mPostListingURL)); mPostCount++; mPostRefreshCount.decrementAndGet(); } } AndroidApi.UI_THREAD_HANDLER.post(new Runnable() { @Override public void run() { mPostListingManager.addPosts(downloadedPosts); mPostListingManager.setLoadingVisible(false); onPostsAdded(); mRequest = null; mReadyToDownloadMore = true; onLoadMoreItemsCheck(); } }); } catch (Throwable t) { notifyFailure(CacheRequest.REQUEST_FAILURE_PARSE, t, null, "Parse failure"); } } } private boolean getIsPostBlocked( @NonNull final List<String> blockedSubreddits, @NonNull final RedditPost post) throws RedditSubreddit.InvalidSubredditNameException { for (String blockedSubredditName : blockedSubreddits) { if (blockedSubredditName.equalsIgnoreCase(RedditSubreddit.getCanonicalName(post.subreddit))) { return true; } } return false; } }