org.runbuddy.libtomahawk.resolver.ScriptResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.runbuddy.libtomahawk.resolver.ScriptResolver.java

Source

/* == 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);
    }
}