/* * Copyright (C) 2017 The Android Open Source Project * * 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 com.android.example.github.repository; import com.android.example.github.AppExecutors; import com.android.example.github.api.ApiResponse; import com.android.example.github.api.GithubService; import com.android.example.github.api.RepoSearchResponse; import com.android.example.github.db.GithubDb; import com.android.example.github.db.RepoDao; import com.android.example.github.util.AbsentLiveData; import com.android.example.github.util.RateLimiter; import com.android.example.github.vo.Contributor; import com.android.example.github.vo.Repo; import com.android.example.github.vo.RepoSearchResult; import com.android.example.github.vo.Resource; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Transformations; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.List; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Singleton; import timber.log.Timber; /** * Repository that handles Repo instances. * * unfortunate naming :/ . * Repo - value object name * Repository - type of this class. */ @Singleton public class RepoRepository { private final GithubDb db; private final RepoDao repoDao; private final GithubService githubService; private final AppExecutors appExecutors; private RateLimiter<String> repoListRateLimit = new RateLimiter<>(10, TimeUnit.MINUTES); @Inject public RepoRepository(AppExecutors appExecutors, GithubDb db, RepoDao repoDao, GithubService githubService) { this.db = db; this.repoDao = repoDao; this.githubService = githubService; this.appExecutors = appExecutors; } public LiveData<Resource<List<Repo>>> loadRepos(String owner) { return new NetworkBoundResource<List<Repo>, List<Repo>>(appExecutors) { @Override protected void saveCallResult(@NonNull List<Repo> item) { repoDao.insertRepos(item); } @Override protected boolean shouldFetch(@Nullable List<Repo> data) { return data == null || data.isEmpty() || repoListRateLimit.shouldFetch(owner); } @NonNull @Override protected LiveData<List<Repo>> loadFromDb() { return repoDao.loadRepositories(owner); } @NonNull @Override protected LiveData<ApiResponse<List<Repo>>> createCall() { return githubService.getRepos(owner); } @Override protected void onFetchFailed() { repoListRateLimit.reset(owner); } }.asLiveData(); } public LiveData<Resource<Repo>> loadRepo(String owner, String name) { return new NetworkBoundResource<Repo, Repo>(appExecutors) { @Override protected void saveCallResult(@NonNull Repo item) { repoDao.insert(item); } @Override protected boolean shouldFetch(@Nullable Repo data) { return data == null; } @NonNull @Override protected LiveData<Repo> loadFromDb() { return repoDao.load(owner, name); } @NonNull @Override protected LiveData<ApiResponse<Repo>> createCall() { return githubService.getRepo(owner, name); } }.asLiveData(); } public LiveData<Resource<List<Contributor>>> loadContributors(String owner, String name) { return new NetworkBoundResource<List<Contributor>, List<Contributor>>(appExecutors) { @Override protected void saveCallResult(@NonNull List<Contributor> contributors) { for (Contributor contributor : contributors) { contributor.setRepoName(name); contributor.setRepoOwner(owner); } db.beginTransaction(); try { repoDao.createRepoIfNotExists(new Repo(Repo.UNKNOWN_ID, name, owner + "/" + name, "", new Repo.Owner(owner, null), 0)); repoDao.insertContributors(contributors); db.setTransactionSuccessful(); } finally { db.endTransaction(); } Timber.d("rece saved contributors to db"); } @Override protected boolean shouldFetch(@Nullable List<Contributor> data) { Timber.d("rece contributor list from db: %s", data); return data == null || data.isEmpty(); } @NonNull @Override protected LiveData<List<Contributor>> loadFromDb() { return repoDao.loadContributors(owner, name); } @NonNull @Override protected LiveData<ApiResponse<List<Contributor>>> createCall() { return githubService.getContributors(owner, name); } }.asLiveData(); } public LiveData<Resource<Boolean>> searchNextPage(String query) { FetchNextSearchPageTask fetchNextSearchPageTask = new FetchNextSearchPageTask( query, githubService, db); appExecutors.networkIO().execute(fetchNextSearchPageTask); return fetchNextSearchPageTask.getLiveData(); } public LiveData<Resource<List<Repo>>> search(String query) { return new NetworkBoundResource<List<Repo>, RepoSearchResponse>(appExecutors) { @Override protected void saveCallResult(@NonNull RepoSearchResponse item) { List<Integer> repoIds = item.getRepoIds(); RepoSearchResult repoSearchResult = new RepoSearchResult( query, repoIds, item.getTotal(), item.getNextPage()); db.beginTransaction(); try { repoDao.insertRepos(item.getItems()); repoDao.insert(repoSearchResult); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } @Override protected boolean shouldFetch(@Nullable List<Repo> data) { return data == null; } @NonNull @Override protected LiveData<List<Repo>> loadFromDb() { return Transformations.switchMap(repoDao.search(query), searchData -> { if (searchData == null) { return AbsentLiveData.create(); } else { return repoDao.loadOrdered(searchData.repoIds); } }); } @NonNull @Override protected LiveData<ApiResponse<RepoSearchResponse>> createCall() { return githubService.searchRepos(query); } @Override protected RepoSearchResponse processResponse(ApiResponse<RepoSearchResponse> response) { RepoSearchResponse body = response.body; if (body != null) { body.setNextPage(response.getNextPage()); } return body; } }.asLiveData(); } }