Java tutorial
/* == This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2013, Enno Gottschalk <mrmaffen@googlemail.com> * * Tomahawk 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. * * Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>. */ package org.runbuddy.libtomahawk.resolver; import android.os.Looper; import android.os.Message; import android.util.Log; import android.widget.ImageView; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.google.gson.reflect.TypeToken; import com.squareup.okhttp.Response; import org.jdeferred.Promise; import org.runbuddy.libtomahawk.authentication.AuthenticatorManager; import org.runbuddy.libtomahawk.authentication.AuthenticatorUtils; import org.runbuddy.libtomahawk.resolver.models.ScriptResolverAccessTokenResult; import org.runbuddy.libtomahawk.resolver.models.ScriptResolverConfigUiField; import org.runbuddy.libtomahawk.resolver.models.ScriptResolverSettings; import org.runbuddy.libtomahawk.resolver.models.ScriptResolverStreamUrlResult; import org.runbuddy.libtomahawk.resolver.models.ScriptResolverUrlResult; import org.runbuddy.libtomahawk.utils.ADeferredObject; import org.runbuddy.libtomahawk.utils.GsonHelper; import org.runbuddy.libtomahawk.utils.NetworkUtils; import org.runbuddy.tomahawk.app.TomahawkApp; import org.runbuddy.tomahawk.utils.WeakReferenceHandler; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import de.greenrobot.event.EventBus; /** * This class represents a javascript resolver. */ public class ScriptResolver implements Resolver, ScriptPlugin { private final static String TAG = ScriptResolver.class.getSimpleName(); public static class EnabledStateChangedEvent { } private String mId; private ScriptObject mScriptObject; private ScriptAccount mScriptAccount; private int mWeight; private int mTimeout; private List<ScriptResolverConfigUiField> mConfigUi; private boolean mEnabled; private boolean mInitialized; private boolean mStopped; private final Set<String> mWaitingUrlLookups = Collections .newSetFromMap(new ConcurrentHashMap<String, Boolean>()); private final Set<Query> mWaitingQueries = Collections.newSetFromMap(new ConcurrentHashMap<Query, Boolean>()); private static final int TIMEOUT_HANDLER_MSG = 1337; // Handler which sets the mStopped bool to true after the timeout has occured. // Meaning this resolver is no longer being shown as resolving. private final TimeOutHandler mTimeOutHandler = new TimeOutHandler(this); private static class TimeOutHandler extends WeakReferenceHandler<ScriptResolver> { public TimeOutHandler(ScriptResolver scriptResolver) { super(Looper.getMainLooper(), scriptResolver); } @Override public void handleMessage(Message msg) { if (getReferencedObject() != null) { removeMessages(msg.what); getReferencedObject().mStopped = true; } } } /** * Construct a new {@link ScriptResolver} */ public ScriptResolver(ScriptObject object, ScriptAccount account) { mScriptObject = object; mScriptAccount = account; mScriptAccount.setScriptResolver(this); mInitialized = false; mStopped = true; mId = mScriptAccount.getName(); if (getConfig().get(ScriptAccount.ENABLED_KEY) != null) { mEnabled = (Boolean) getConfig().get(ScriptAccount.ENABLED_KEY); } else { // Enable soundcloud and jamendo by default mEnabled = TomahawkApp.PLUGINNAME_JAMENDO.equals(mId) || TomahawkApp.PLUGINNAME_SOUNDCLOUD.equals(mId); } settings(); if (mEnabled) { init(); } } /** * @return whether or not this {@link Resolver} is ready */ @Override public boolean isInitialized() { return mInitialized; } /** * @return whether or not this {@link ScriptResolver} is currently resolving */ @Override public boolean isResolving() { return mInitialized && !mStopped; } @Override public void loadIcon(ImageView imageView, boolean grayOut) { mScriptAccount.loadIcon(imageView, grayOut); } @Override public void loadIconWhite(ImageView imageView, int tintColorResId) { mScriptAccount.loadIconWhite(imageView, tintColorResId); } @Override public void loadIconBackground(ImageView imageView, boolean grayOut) { mScriptAccount.loadIconBackground(imageView, grayOut); } @Override public String getPrettyName() { return mScriptAccount.getMetaData().name; } @Override public ScriptAccount getScriptAccount() { return mScriptAccount; } @Override public ScriptObject getScriptObject() { return mScriptObject; } /** * This method calls the js function resolver.init(). */ private void init() { ScriptJob.start(mScriptObject, "init", new ScriptJob.ResultsEmptyCallback() { @Override public void onReportResults() { mInitialized = true; Log.d(TAG, "ScriptResolver " + mId + " initialized successfully."); invokeWaitingJobs(); } }, new ScriptJob.FailureCallback() { @Override public void onReportFailure(String errormessage) { Log.d(TAG, "ScriptResolver " + mId + " failed to initialize."); } }); } private synchronized void invokeWaitingJobs() { Log.d(TAG, "Resolving " + mWaitingQueries.size() + " waiting queries. Looking up " + mWaitingUrlLookups.size() + " waiting URLs."); for (Query query : mWaitingQueries) { resolve(query); } mWaitingQueries.clear(); for (String url : mWaitingUrlLookups) { lookupUrl(url); } mWaitingUrlLookups.clear(); } /** * This method tries to get the {@link Resolver}'s settings. */ private void settings() { ScriptJob.start(mScriptObject, "settings", new ScriptJob.ResultsCallback<ScriptResolverSettings>(ScriptResolverSettings.class) { @Override public void onReportResults(ScriptResolverSettings results) { mWeight = results.weight; mTimeout = results.timeout * 1000; resolverGetConfigUi(); } }); } /** * This method tries to save the {@link Resolver}'s UserConfig. */ public void saveUserConfig() { ScriptJob.start(mScriptObject, "saveUserConfig"); } /** * This method tries to get the {@link Resolver}'s UserConfig. */ private void resolverGetConfigUi() { ScriptJob.start(mScriptObject, "configUi", new ScriptJob.ResultsArrayCallback() { @Override public void onReportResults(JsonArray results) { Type type = new TypeToken<List<ScriptResolverConfigUiField>>() { }.getType(); mConfigUi = GsonHelper.get().fromJson(results, type); } }); } public void lookupUrl(final String url) { if (mInitialized) { HashMap<String, Object> args = new HashMap<>(); args.put("url", url); ScriptJob.start(mScriptObject, "lookupUrl", args, new ScriptJob.ResultsCallback<ScriptResolverUrlResult>(ScriptResolverUrlResult.class) { @Override public void onReportResults(ScriptResolverUrlResult results) { Log.d(TAG, "reportUrlResult - url: " + url); PipeLine.UrlResultsEvent event = new PipeLine.UrlResultsEvent(); event.mResolver = ScriptResolver.this; event.mResult = results; EventBus.getDefault().post(event); mStopped = true; } }); } else { mWaitingUrlLookups.add(url); } } /** * Invoke the javascript to resolve the given {@link Query}. * * @param query the {@link Query} which should be resolved */ @Override public void resolve(final Query query) { if (mInitialized) { mStopped = false; mTimeOutHandler.removeCallbacksAndMessages(null); mTimeOutHandler.sendEmptyMessageDelayed(TIMEOUT_HANDLER_MSG, mTimeout); ScriptJob.ResultsObjectCallback callback = new ScriptJob.ResultsObjectCallback() { @Override public void onReportResults(JsonObject results) { JsonArray tracks = results.getAsJsonArray("tracks"); ArrayList<Result> parsedResults = ScriptUtils.parseResultList(ScriptResolver.this, tracks); PipeLine.get().reportResults(query, parsedResults, mId); mTimeOutHandler.removeCallbacksAndMessages(null); mStopped = true; } }; if (query.isFullTextQuery()) { HashMap<String, Object> args = new HashMap<>(); args.put("query", query.getFullTextQuery()); ScriptJob.start(mScriptObject, "_adapter_search", args, callback); } else { HashMap<String, Object> args = new HashMap<>(); args.put("artist", query.getBasicTrack().getArtist().getName()); args.put("album", query.getBasicTrack().getAlbum().getName()); args.put("track", query.getBasicTrack().getName()); ScriptJob.start(mScriptObject, "_adapter_resolve", args, callback); } } else { mWaitingQueries.add(query); } } public Promise<String, Throwable, Void> getStreamUrl(final Result result) { final ADeferredObject<String, Throwable, Void> deferred = new ADeferredObject<>(); if (result != null) { HashMap<String, Object> args = new HashMap<>(); args.put("url", result.getPath()); ScriptJob.start(mScriptObject, "getStreamUrl", args, new ScriptJob.ResultsCallback<ScriptResolverStreamUrlResult>( ScriptResolverStreamUrlResult.class) { @Override public void onReportResults(ScriptResolverStreamUrlResult results) { Response response = null; try { if (results.headers != null) { // If headers are given we first have to resolve the url that // the call is being redirected to response = NetworkUtils.httpRequest("GET", results.url, results.headers, null, null, null, false, null); deferred.resolve(response.header("Location")); } else { deferred.resolve(results.url); } } catch (IOException e) { Log.e(TAG, "reportStreamUrl: " + e.getClass() + ": " + e.getLocalizedMessage()); deferred.reject(e); } finally { if (response != null) { try { response.body().close(); } catch (IOException e) { Log.e(TAG, "getStreamUrl: " + e.getClass() + ": " + e.getLocalizedMessage()); } } } } }, new ScriptJob.FailureCallback() { @Override public void onReportFailure(String errormessage) { deferred.reject(new Throwable(errormessage)); } }); } else { deferred.reject(new Throwable("result is null")); } return deferred; } public void login() { ScriptJob.start(mScriptObject, "login", null, new ScriptJob.ResultsPrimitiveCallback() { @Override public void onReportResults(JsonPrimitive results) { onTestConfigFinished(results); } }); } public void logout() { ScriptJob.start(mScriptObject, "logout", null, new ScriptJob.ResultsPrimitiveCallback() { @Override public void onReportResults(JsonPrimitive results) { onTestConfigFinished(results); } }); } /** * @return this {@link ScriptResolver}'s id */ @Override public String getId() { return mId; } public String getName() { return mScriptAccount.getMetaData().name; } public void setConfig(Map<String, Object> config) { mScriptAccount.setConfig(config); } /** * @return the Map<String, String> containing the Config information of this resolver */ public Map<String, Object> getConfig() { return mScriptAccount.getConfig(); } /** * @return this {@link ScriptResolver}'s weight */ @Override public int getWeight() { return mWeight; } public String getDescription() { return mScriptAccount.getMetaData().description; } public List<ScriptResolverConfigUiField> getConfigUi() { return mConfigUi; } @Override public boolean isEnabled() { AuthenticatorUtils utils = AuthenticatorManager.get().getAuthenticatorUtils(mId); if (utils != null) { return utils.isLoggedIn(); } return mEnabled; } public void setEnabled(boolean enabled) { Log.d(TAG, this.mId + " has been " + (enabled ? "enabled" : "disabled")); mEnabled = enabled; Map<String, Object> config = getConfig(); config.put(ScriptAccount.ENABLED_KEY, enabled); setConfig(config); if (mEnabled) { // Re-init so that all plugins are being registered again settings(); init(); } else { mScriptAccount.unregisterAllPlugins(); } EventBus.getDefault().post(new EnabledStateChangedEvent()); } public void testConfig(Map<String, Object> config) { // Always wipe all cookies in the testingConfig cookie store beforehand mScriptAccount.getCookieManager(true).getCookieStore().removeAll(); ScriptJob.start(mScriptObject, "_adapter_testConfig", config, new ScriptJob.ResultsPrimitiveCallback() { @Override public void onReportResults(JsonPrimitive results) { onTestConfigFinished(results); } }); } private void onTestConfigFinished(JsonPrimitive results) { int type = -1; String message = null; if (results.isString()) { type = AuthenticatorManager.CONFIG_TEST_RESULT_TYPE_OTHER; message = results.getAsString(); } else if (results.isNumber() && results.getAsInt() > 0 && results.getAsInt() < 8) { type = results.getAsInt(); } Log.d(TAG, getName() + ": Config test result received. type: " + type + ", message:" + message); if (type == AuthenticatorManager.CONFIG_TEST_RESULT_TYPE_SUCCESS) { setEnabled(true); } else { setEnabled(false); } AuthenticatorManager.ConfigTestResultEvent event = new AuthenticatorManager.ConfigTestResultEvent(); event.mComponent = ScriptResolver.this; event.mType = type; event.mMessage = message; EventBus.getDefault().post(event); AuthenticatorManager.showToast(getPrettyName(), event); } public void getAccessToken(ScriptJob.ResultsCallback<ScriptResolverAccessTokenResult> cb) { ScriptJob.start(mScriptObject, "getAccessToken", cb); } }