/******************************************************************************* * 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.reddit.api; import android.content.Context; import android.net.Uri; import android.util.Log; import org.quantumbadger.redreader.account.RedditAccount; import org.quantumbadger.redreader.cache.CacheManager; import org.quantumbadger.redreader.cache.CacheRequest; import org.quantumbadger.redreader.cache.downloadstrategy.DownloadStrategyAlways; import org.quantumbadger.redreader.common.Constants; import org.quantumbadger.redreader.common.General; import org.quantumbadger.redreader.common.TimestampBound; import org.quantumbadger.redreader.common.UnexpectedInternalStateException; import org.quantumbadger.redreader.io.CacheDataSource; import org.quantumbadger.redreader.io.RequestResponseHandler; import org.quantumbadger.redreader.io.WritableHashSet; import org.quantumbadger.redreader.jsonwrap.JsonBuffered; import org.quantumbadger.redreader.jsonwrap.JsonBufferedArray; import org.quantumbadger.redreader.jsonwrap.JsonBufferedObject; import org.quantumbadger.redreader.jsonwrap.JsonValue; import org.quantumbadger.redreader.reddit.RedditSubredditManager; import org.quantumbadger.redreader.reddit.things.RedditSubreddit; import org.quantumbadger.redreader.reddit.things.RedditThing; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.UUID; public class RedditAPIIndividualSubredditListRequester implements CacheDataSource<RedditSubredditManager.SubredditListType, WritableHashSet, SubredditRequestFailure> { private final Context context; private final RedditAccount user; public RedditAPIIndividualSubredditListRequester(Context context, RedditAccount user) { this.context = context; this.user = user; } public void performRequest(final RedditSubredditManager.SubredditListType type, final TimestampBound timestampBound, final RequestResponseHandler<WritableHashSet, SubredditRequestFailure> handler) { if(type == RedditSubredditManager.SubredditListType.DEFAULTS) { final long now = System.currentTimeMillis(); final HashSet<String> data = new HashSet<>(Constants.Reddit.DEFAULT_SUBREDDITS.length + 1); for(String name : Constants.Reddit.DEFAULT_SUBREDDITS) { data.add(General.asciiLowercase(name)); } data.add("/r/redreader"); final WritableHashSet result = new WritableHashSet(data, now, "DEFAULTS"); handler.onRequestSuccess(result, now); return; } if(type == RedditSubredditManager.SubredditListType.MOST_POPULAR) { doSubredditListRequest(RedditSubredditManager.SubredditListType.MOST_POPULAR, handler, null); } else if(user.isAnonymous()) { switch(type) { case SUBSCRIBED: performRequest(RedditSubredditManager.SubredditListType.DEFAULTS, timestampBound, handler); return; case MODERATED: { final long curTime = System.currentTimeMillis(); handler.onRequestSuccess(new WritableHashSet( new HashSet<String>(), curTime, RedditSubredditManager.SubredditListType.MODERATED.name()), curTime); return; } case MULTIREDDITS: { final long curTime = System.currentTimeMillis(); handler.onRequestSuccess(new WritableHashSet( new HashSet<String>(), curTime, RedditSubredditManager.SubredditListType.MULTIREDDITS.name()), curTime); return; } default: throw new RuntimeException("Internal error: unknown subreddit list type '" + type.name() + "'"); } } else { doSubredditListRequest(type, handler, null); } } private void doSubredditListRequest(final RedditSubredditManager.SubredditListType type, final RequestResponseHandler<WritableHashSet, SubredditRequestFailure> handler, final String after) { URI uri; switch(type) { case SUBSCRIBED: uri = Constants.Reddit.getUri(Constants.Reddit.PATH_SUBREDDITS_MINE_SUBSCRIBER); break; case MODERATED: uri = Constants.Reddit.getUri(Constants.Reddit.PATH_SUBREDDITS_MINE_MODERATOR); break; case MOST_POPULAR: uri = Constants.Reddit.getUri(Constants.Reddit.PATH_SUBREDDITS_POPULAR); break; default: throw new UnexpectedInternalStateException(type.name()); } if(after != null) { // TODO move this logic to General? final Uri.Builder builder = Uri.parse(uri.toString()).buildUpon(); builder.appendQueryParameter("after", after); uri = General.uriFromString(builder.toString()); } final CacheRequest aboutSubredditCacheRequest = new CacheRequest( uri, user, null, Constants.Priority.API_SUBREDDIT_INVIDIVUAL, 0, DownloadStrategyAlways.INSTANCE, Constants.FileType.SUBREDDIT_LIST, CacheRequest.DOWNLOAD_QUEUE_REDDIT_API, true, false, context ) { @Override protected void onCallbackException(Throwable t) { handler.onRequestFailed(new SubredditRequestFailure(CacheRequest.REQUEST_FAILURE_PARSE, t, null, "Internal error", url)); } @Override protected void onDownloadNecessary() {} @Override protected void onDownloadStarted() {} @Override protected void onProgress(final boolean authorizationInProgress, long bytesRead, long totalBytes) {} @Override protected void onFailure(@CacheRequest.RequestFailureType int type, Throwable t, Integer status, String readableMessage) { handler.onRequestFailed(new SubredditRequestFailure(type, t, status, readableMessage, url.toString())); } @Override protected void onSuccess(CacheManager.ReadableCacheFile cacheFile, long timestamp, UUID session, boolean fromCache, String mimetype) {} @Override public void onJsonParseStarted(JsonValue result, long timestamp, UUID session, boolean fromCache) { try { final HashSet<String> output = new HashSet<>(); final ArrayList<RedditSubreddit> toWrite = new ArrayList<>(); final JsonBufferedObject redditListing = result.asObject().getObject("data"); final JsonBufferedArray subreddits = redditListing.getArray("children"); final @JsonBuffered.Status int joinStatus = subreddits.join(); if(joinStatus == JsonBuffered.STATUS_FAILED) { handler.onRequestFailed(new SubredditRequestFailure(CacheRequest.REQUEST_FAILURE_PARSE, null, null, "Unknown parse error", url.toString())); return; } if(type == RedditSubredditManager.SubredditListType.SUBSCRIBED && subreddits.getCurrentItemCount() == 0 && after == null) { performRequest(RedditSubredditManager.SubredditListType.DEFAULTS, TimestampBound.ANY, handler); return; } for(final JsonValue v : subreddits) { final RedditThing thing = v.asObject(RedditThing.class); final RedditSubreddit subreddit = thing.asSubreddit(); subreddit.downloadTime = timestamp; toWrite.add(subreddit); output.add(subreddit.getCanonicalName()); } RedditSubredditManager.getInstance(context, user).offerRawSubredditData(toWrite, timestamp); final String receivedAfter = redditListing.getString("after"); if(receivedAfter != null && type != RedditSubredditManager.SubredditListType.MOST_POPULAR) { doSubredditListRequest(type, new RequestResponseHandler<WritableHashSet, SubredditRequestFailure>() { public void onRequestFailed(SubredditRequestFailure failureReason) { handler.onRequestFailed(failureReason); } public void onRequestSuccess(WritableHashSet result, long timeCached) { output.addAll(result.toHashset()); handler.onRequestSuccess(new WritableHashSet(output, timeCached, type.name()), timeCached); if(after == null) { Log.i("SubredditListRequester", "Got " + output.size() + " subreddits in multiple requests"); } } }, receivedAfter); } else { handler.onRequestSuccess(new WritableHashSet(output, timestamp, type.name()), timestamp); if(after == null) { Log.i("SubredditListRequester", "Got " + output.size() + " subreddits in 1 request"); } } } catch(Exception e) { handler.onRequestFailed(new SubredditRequestFailure(CacheRequest.REQUEST_FAILURE_PARSE, e, null, "Parse error", url.toString())); } } }; CacheManager.getInstance(context).makeRequest(aboutSubredditCacheRequest); } public void performRequest(Collection<RedditSubredditManager.SubredditListType> keys, TimestampBound timestampBound, RequestResponseHandler<HashMap<RedditSubredditManager.SubredditListType, WritableHashSet>, SubredditRequestFailure> handler) { // TODO batch API? or just make lots of requests and build up a hash map? throw new UnsupportedOperationException(); } public void performWrite(WritableHashSet value) { throw new UnsupportedOperationException(); } public void performWrite(Collection<WritableHashSet> values) { throw new UnsupportedOperationException(); } }