/*
* Copyright (c) 2015 Ha Duy Trung
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.hidroh.materialistic.data;
import android.content.ContentResolver;
import android.content.Context;
import android.support.annotation.NonNull;
import java.io.IOException;
import javax.inject.Inject;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Path;
import rx.Observable;
import rx.Scheduler;
import rx.android.schedulers.AndroidSchedulers;
/**
* Client to retrieve Hacker News content asynchronously
*/
public class HackerNewsClient implements ItemManager, UserManager {
public static final String HOST = "hacker-news.firebaseio.com";
public static final String BASE_WEB_URL = "https://news.ycombinator.com";
public static final String WEB_ITEM_PATH = BASE_WEB_URL + "/item?id=%s";
static final String BASE_API_URL = "https://" + HOST + "/v0/";
private final RestService mRestService;
private final SessionManager mSessionManager;
private final FavoriteManager mFavoriteManager;
private final ContentResolver mContentResolver;
private Scheduler mIoScheduler;
@Inject
public HackerNewsClient(Context context, RestServiceFactory factory,
SessionManager sessionManager,
FavoriteManager favoriteManager,
Scheduler ioScheduler) {
mRestService = factory.rxEnabled(true).create(BASE_API_URL, RestService.class);
mSessionManager = sessionManager;
mFavoriteManager = favoriteManager;
mContentResolver = context.getApplicationContext().getContentResolver();
mIoScheduler = ioScheduler;
}
@Override
public void getStories(@FetchMode String filter, @CacheMode int cacheMode,
final ResponseListener<Item[]> listener) {
if (listener == null) {
return;
}
Observable.defer(() -> getStoriesObservable(filter, cacheMode))
.subscribeOn(mIoScheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listener::onResponse,
t -> listener.onError(t != null ? t.getMessage() : ""));
}
@Override
public void getItem(final String itemId, @CacheMode int cacheMode, ResponseListener<Item> listener) {
if (listener == null) {
return;
}
Observable<HackerNewsItem> itemObservable;
switch (cacheMode) {
case MODE_DEFAULT:
default:
itemObservable = mRestService.itemRx(itemId);
break;
case MODE_NETWORK:
itemObservable = mRestService.networkItemRx(itemId);
break;
case MODE_CACHE:
itemObservable = mRestService.cachedItemRx(itemId)
.onErrorResumeNext(mRestService.itemRx(itemId));
break;
}
Observable.defer(() -> Observable.zip(
mSessionManager.isViewed(mContentResolver, itemId),
mFavoriteManager.check(mContentResolver, itemId),
itemObservable,
(isViewed, favorite, hackerNewsItem) -> {
if (hackerNewsItem != null) {
hackerNewsItem.preload();
hackerNewsItem.setIsViewed(isViewed);
hackerNewsItem.setFavorite(favorite);
}
return hackerNewsItem;
}))
.subscribeOn(mIoScheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listener::onResponse,
t -> listener.onError(t != null ? t.getMessage() : ""));
}
@Override
public Item[] getStories(String filter, @CacheMode int cacheMode) {
try {
return toItems(getStoriesCall(filter, cacheMode).execute().body());
} catch (IOException e) {
return new Item[0];
}
}
@Override
public Item getItem(String itemId, @CacheMode int cacheMode) {
Call<HackerNewsItem> call;
switch (cacheMode) {
case MODE_DEFAULT:
case MODE_CACHE:
default:
call = mRestService.item(itemId);
break;
case MODE_NETWORK:
call = mRestService.networkItem(itemId);
break;
}
try {
return call.execute().body();
} catch (IOException e) {
return null;
}
}
@Override
public void getUser(String username, final ResponseListener<User> listener) {
if (listener == null) {
return;
}
mRestService.userRx(username)
.map(userItem -> {
if (userItem != null) {
userItem.setSubmittedItems(toItems(userItem.getSubmitted()));
}
return userItem;
})
.subscribeOn(mIoScheduler)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(listener::onResponse,
t -> listener.onError(t != null ? t.getMessage() : ""));
}
@NonNull
private Observable<Item[]> getStoriesObservable(@FetchMode String filter, @CacheMode int cacheMode) {
Observable<int[]> observable;
switch (filter) {
case NEW_FETCH_MODE:
observable = cacheMode == MODE_NETWORK ?
mRestService.networkNewStoriesRx() : mRestService.newStoriesRx();
break;
case SHOW_FETCH_MODE:
observable = cacheMode == MODE_NETWORK ?
mRestService.networkShowStoriesRx() : mRestService.showStoriesRx();
break;
case ASK_FETCH_MODE:
observable = cacheMode == MODE_NETWORK ?
mRestService.networkAskStoriesRx() : mRestService.askStoriesRx();
break;
case JOBS_FETCH_MODE:
observable = cacheMode == MODE_NETWORK ?
mRestService.networkJobStoriesRx() : mRestService.jobStoriesRx();
break;
case BEST_FETCH_MODE:
observable = cacheMode == MODE_NETWORK ?
mRestService.networkBestStoriesRx() : mRestService.bestStoriesRx();
break;
default:
observable = cacheMode == MODE_NETWORK ?
mRestService.networkTopStoriesRx() : mRestService.topStoriesRx();
break;
}
return observable.map(this::toItems);
}
@NonNull
private Call<int[]> getStoriesCall(@FetchMode String filter, @CacheMode int cacheMode) {
Call<int[]> call;
if (filter == null) {
// for legacy 'new stories' widgets
return cacheMode == MODE_NETWORK ?
mRestService.networkNewStories() : mRestService.newStories();
}
switch (filter) {
case NEW_FETCH_MODE:
call = cacheMode == MODE_NETWORK ?
mRestService.networkNewStories() : mRestService.newStories();
break;
case SHOW_FETCH_MODE:
call = cacheMode == MODE_NETWORK ?
mRestService.networkShowStories() : mRestService.showStories();
break;
case ASK_FETCH_MODE:
call = cacheMode == MODE_NETWORK ?
mRestService.networkAskStories() : mRestService.askStories();
break;
case JOBS_FETCH_MODE:
call = cacheMode == MODE_NETWORK ?
mRestService.networkJobStories() : mRestService.jobStories();
break;
case BEST_FETCH_MODE:
call = cacheMode == MODE_NETWORK ?
mRestService.networkBestStories() : mRestService.bestStories();
break;
default:
call = cacheMode == MODE_NETWORK ?
mRestService.networkTopStories() : mRestService.topStories();
break;
}
return call;
}
private HackerNewsItem[] toItems(int[] ids) {
if (ids == null) {
return null;
}
HackerNewsItem[] items = new HackerNewsItem[ids.length];
for (int i = 0; i < items.length; i++) {
HackerNewsItem item = new HackerNewsItem(ids[i]);
item.rank = i + 1;
items[i] = item;
}
return items;
}
interface RestService {
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("topstories.json")
Observable<int[]> topStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("newstories.json")
Observable<int[]> newStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("showstories.json")
Observable<int[]> showStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("askstories.json")
Observable<int[]> askStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("jobstories.json")
Observable<int[]> jobStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("beststories.json")
Observable<int[]> bestStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("topstories.json")
Observable<int[]> networkTopStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("newstories.json")
Observable<int[]> networkNewStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("showstories.json")
Observable<int[]> networkShowStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("askstories.json")
Observable<int[]> networkAskStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("jobstories.json")
Observable<int[]> networkJobStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("beststories.json")
Observable<int[]> networkBestStoriesRx();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("item/{itemId}.json")
Observable<HackerNewsItem> itemRx(@Path("itemId") String itemId);
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("item/{itemId}.json")
Observable<HackerNewsItem> networkItemRx(@Path("itemId") String itemId);
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_CACHE)
@GET("item/{itemId}.json")
Observable<HackerNewsItem> cachedItemRx(@Path("itemId") String itemId);
@GET("user/{userId}.json")
Observable<UserItem> userRx(@Path("userId") String userId);
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("topstories.json")
Call<int[]> topStories();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("newstories.json")
Call<int[]> newStories();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("showstories.json")
Call<int[]> showStories();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("askstories.json")
Call<int[]> askStories();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("jobstories.json")
Call<int[]> jobStories();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("beststories.json")
Call<int[]> bestStories();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("topstories.json")
Call<int[]> networkTopStories();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("newstories.json")
Call<int[]> networkNewStories();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("showstories.json")
Call<int[]> networkShowStories();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("askstories.json")
Call<int[]> networkAskStories();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("jobstories.json")
Call<int[]> networkJobStories();
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("beststories.json")
Call<int[]> networkBestStories();
@Headers(RestServiceFactory.CACHE_CONTROL_MAX_AGE_30M)
@GET("item/{itemId}.json")
Call<HackerNewsItem> item(@Path("itemId") String itemId);
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_NETWORK)
@GET("item/{itemId}.json")
Call<HackerNewsItem> networkItem(@Path("itemId") String itemId);
@Headers(RestServiceFactory.CACHE_CONTROL_FORCE_CACHE)
@GET("item/{itemId}.json")
Call<HackerNewsItem> cachedItem(@Path("itemId") String itemId);
@GET("user/{userId}.json")
Call<UserItem> user(@Path("userId") String userId);
}
}