/* * Copyright 2017 Mauricio Colli * Extractors.java is part of NewPipe * * License: GPL-3.0+ * 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.schabi.newpipe.util; import android.util.Log; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchResult; import org.schabi.newpipe.extractor.stream.StreamInfo; import java.io.InterruptedIOException; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import io.reactivex.Maybe; import io.reactivex.MaybeSource; import io.reactivex.Single; import io.reactivex.annotations.NonNull; import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; public final class ExtractorHelper { private static final String TAG = ExtractorHelper.class.getSimpleName(); private static final InfoCache cache = InfoCache.getInstance(); private ExtractorHelper() { //no instance } public static Single searchFor(final int serviceId, final String query, final int pageNumber, final String searchLanguage, final SearchEngine.Filter filter) { return Single.fromCallable(new Callable() { @Override public SearchResult call() throws Exception { return SearchResult.getSearchResult(NewPipe.getService(serviceId).getSearchEngine(), query, pageNumber, searchLanguage, filter); } }); } public static Single getMoreSearchItems(final int serviceId, final String query, final int nextPageNumber, final String searchLanguage, final SearchEngine.Filter filter) { return searchFor(serviceId, query, nextPageNumber, searchLanguage, filter) .map(new Function() { @Override public NextItemsResult apply(@NonNull SearchResult searchResult) throws Exception { return new NextItemsResult(searchResult.resultList, nextPageNumber + "", searchResult.errors); } }); } public static Single> suggestionsFor(final int serviceId, final String query, final String searchLanguage) { return Single.fromCallable(new Callable>() { @Override public List call() throws Exception { return NewPipe.getService(serviceId).getSuggestionExtractor().suggestionList(query, searchLanguage); } }); } public static Single getStreamInfo(final int serviceId, final String url, boolean forceLoad) { return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable() { @Override public StreamInfo call() throws Exception { return StreamInfo.getInfo(NewPipe.getService(serviceId), url); } })); } public static Single getChannelInfo(final int serviceId, final String url, boolean forceLoad) { return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable() { @Override public ChannelInfo call() throws Exception { return ChannelInfo.getInfo(NewPipe.getService(serviceId), url); } })); } public static Single getMoreChannelItems(final int serviceId, final String nextStreamsUrl) { return Single.fromCallable(new Callable() { @Override public NextItemsResult call() throws Exception { return ChannelInfo.getMoreItems(NewPipe.getService(serviceId), nextStreamsUrl); } }); } public static Single getPlaylistInfo(final int serviceId, final String url, boolean forceLoad) { return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable() { @Override public PlaylistInfo call() throws Exception { return PlaylistInfo.getInfo(NewPipe.getService(serviceId), url); } })); } public static Single getMorePlaylistItems(final int serviceId, final String nextStreamsUrl) { return Single.fromCallable(new Callable() { @Override public NextItemsResult call() throws Exception { return PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), nextStreamsUrl); } }); } /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ /** * Check if we can load it from the cache (forceLoad parameter), if we can't, load from the network (Single loadFromNetwork) * and put the results in the cache. */ private static Single checkCache(boolean forceLoad, int serviceId, String url, Single loadFromNetwork) { loadFromNetwork = loadFromNetwork.doOnSuccess(new Consumer() { @Override public void accept(@NonNull I i) throws Exception { cache.putInfo(i); } }); Single load; if (forceLoad) { cache.removeInfo(serviceId, url); load = loadFromNetwork; } else { load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url), loadFromNetwork.toMaybe()) .firstElement() //Take the first valid .toSingle(); } return load; } /** * Default implementation uses the {@link InfoCache} to get cached results */ public static Maybe loadFromCache(final int serviceId, final String url) { return Maybe.defer(new Callable>() { @Override public MaybeSource call() throws Exception { //noinspection unchecked I info = (I) cache.getFromKey(serviceId, url); if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info); // Only return info if it's not null (it is cached) if (info != null) { return Maybe.just(info); } return Maybe.empty(); } }); } /** * Check if throwable have the cause that can be assignable from the causes to check. * * @see Class#isAssignableFrom(Class) */ public static boolean hasAssignableCauseThrowable(Throwable throwable, Class... causesToCheck) { // Check if getCause is not the same as cause (the getCause is already the root), // as it will cause a infinite loop if it is Throwable cause, getCause = throwable; for (Class causesEl : causesToCheck) { if (throwable.getClass().isAssignableFrom(causesEl)) { return true; } } while ((cause = throwable.getCause()) != null && getCause != cause) { getCause = cause; for (Class causesEl : causesToCheck) { if (cause.getClass().isAssignableFrom(causesEl)) { return true; } } } return false; } /** * Check if throwable have the exact cause from one of the causes to check. */ public static boolean hasExactCauseThrowable(Throwable throwable, Class... causesToCheck) { // Check if getCause is not the same as cause (the getCause is already the root), // as it will cause a infinite loop if it is Throwable cause, getCause = throwable; for (Class causesEl : causesToCheck) { if (throwable.getClass().equals(causesEl)) { return true; } } while ((cause = throwable.getCause()) != null && getCause != cause) { getCause = cause; for (Class causesEl : causesToCheck) { if (cause.getClass().equals(causesEl)) { return true; } } } return false; } /** * Check if throwable have Interrupted* exception as one of its causes. */ public static boolean isInterruptedCaused(Throwable throwable) { return ExtractorHelper.hasExactCauseThrowable(throwable, InterruptedIOException.class, InterruptedException.class); } }