org.sickbeard.SickBeard.java Source code

Java tutorial

Introduction

Here is the source code for org.sickbeard.SickBeard.java

Source

/*
 *    libSickBeard is a java library for communication with sickbeard
 *    Copyright (C) 2012  David Stocking dmstocking@gmail.com
 * 
 *    http://code.google.com/p/sick-stashe/
 *    
 *    libSickBeard 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 <http://www.gnu.org/licenses/>.
 */
package org.sickbeard;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.sickbeard.Episode.StatusEnum;
import org.sickbeard.FutureEpisode.TimeEnum;
import org.sickbeard.Show.QualityEnum;
import org.sickbeard.json.CommandsJson;
import org.sickbeard.json.EpisodeJson;
import org.sickbeard.json.GetQualityJson;
import org.sickbeard.json.HistoryJson;
import org.sickbeard.json.JsonResponse;
import org.sickbeard.json.MessageJson;
import org.sickbeard.json.OptionsJson;
import org.sickbeard.json.PingJson;
import org.sickbeard.json.RootDirJson;
import org.sickbeard.json.SeasonsJson;
import org.sickbeard.json.SeasonsListJson;
import org.sickbeard.json.ShowJson;
import org.sickbeard.json.ShowJson.CacheStatusJson;
import org.sickbeard.json.ShowWithFullSeasonListing;
import org.sickbeard.json.TvDbResultJson;
import org.sickbeard.json.TvDbResultsJson;
import org.sickbeard.json.deserializer.JsonBooleanDeserializer;
import org.sickbeard.json.type.JsonBoolean;
import org.sickbeard.net.SickAuthenticator;
import org.sickbeard.net.ssl.DefaultTrustManager;

import android.os.AsyncTask;
import android.util.Log;

import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

public class SickBeard {

    private static final String SUCCESS = "success";
    private static final String ERROR = "error";
    private static final String FAILURE = "failure";

    private boolean https = false;
    private String scheme;
    private String hostname;
    private String port;
    private String extraPath;
    private String path;

    private String user;
    private String password;

    private int apiVersion = 3;

    public SickBeard(String hostname, String port, String api, boolean https, String extraPath, String user,
            String password, boolean trustAll, String trustMe) {
        this.hostname = hostname;
        this.port = port;
        this.extraPath = "/" + extraPath + "/";
        this.path = this.extraPath + "/api/" + api + "/";
        try {
            this.https = https;
            this.scheme = https ? "https" : "http";

            Authenticator.setDefault(new SickAuthenticator(user, password, hostname));
            HostnameVerifier verifier;
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager(trustAll, trustMe) },
                    new SecureRandom());
            if (trustAll) {
                verifier = new AllowAllHostnameVerifier();
            } else {
                verifier = new StrictHostnameVerifier();
            }
            HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(verifier);
        } catch (Exception e) {
            ;
        }
        /***********************************************************
         * ANDROID SPECIFIC START                                  *
         ***********************************************************/
        // start a AsyncTask to try and find the actual api version number
        AsyncTask<Void, Void, CommandsJson> task = new AsyncTask<Void, Void, CommandsJson>() {
            @Override
            protected CommandsJson doInBackground(Void... arg0) {
                try {
                    return SickBeard.this.sbGetCommands();
                } catch (Exception e) {
                    Log.e("SickBeard", e.getMessage(), e);
                    return null;
                }
            }

            @Override
            protected void onPostExecute(CommandsJson result) {
                // do nothing because this is a network error
                if (result == null)
                    return;
                try {
                    // if we get a version use it
                    SickBeard.this.apiVersion = Integer.valueOf(result.api_version);
                } catch (NumberFormatException e) {
                    // 2 was the odd float so assume its 2 if we cant get an int
                    SickBeard.this.apiVersion = 2;
                }
            }
        };
        task.execute();
        /***********************************************************
         * ANDROID SPECIFIC END                                    *
         ***********************************************************/
    }

    public int getApiVersion() {
        return apiVersion;
    }

    public URI getServerUri() throws URISyntaxException {
        return new URI(scheme, null, hostname, Integer.parseInt(port), path, null, null);
    }

    public URI getServerUri(String command) throws URISyntaxException {
        return new URI(scheme, null, hostname, Integer.parseInt(port), path, "cmd=" + command, null);
    }

    public Episode episode(String tvdbid, String season, String episode) throws Exception {
        return this.episode(tvdbid, season, episode, false);
    }

    public Episode episode(String tvdbid, String season, String episode, boolean full_path) throws Exception {
        StringBuilder builder = new StringBuilder("episode");
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        builder.append("&season=");
        builder.append(season);
        builder.append("&episode=");
        builder.append(episode);
        builder.append("&full_path=");
        builder.append((full_path ? "1" : "0"));
        EpisodeJson json = this.<EpisodeJson>commandData(builder.toString(),
                new TypeToken<JsonResponse<EpisodeJson>>() {
                }.getType());
        return new Episode(json);
    }

    public boolean episodeSearch(String tvdbid, String season, String episode) throws Exception {
        StringBuilder builder = new StringBuilder("episode.search");
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        builder.append("&season=");
        builder.append(season);
        builder.append("&episode=");
        builder.append(episode);
        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean episodeSetStatus(String tvdbid, String season, String episode, StatusEnum status)
            throws Exception {
        StringBuilder builder = new StringBuilder("episode.setstatus");
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        builder.append("&season=");
        builder.append(season);
        builder.append("&episode=");
        builder.append(episode);
        builder.append("&status=");
        builder.append(status.toJson());
        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean episodeSetStatus(String tvdbid, List<SeasonEpisodePair> episodes, StatusEnum status)
            throws Exception {
        StringBuilder builder = new StringBuilder();
        builder.append("episode.setstatus_0");
        for (int i = 1; i < episodes.size(); i++) {
            builder.append("|episode.setstatus_");
            builder.append(i);
        }
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        builder.append("&status=");
        builder.append(status.toJson());
        for (int i = 0; i < episodes.size(); i++) {
            SeasonEpisodePair p = episodes.get(i);
            builder.append("&episode.setstatus_");
            builder.append(i);
            builder.append(".season=");
            builder.append(p.season);
            builder.append("&episode.setstatus_");
            builder.append(i);
            builder.append(".episode=");
            builder.append(p.episode);
        }
        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public ArrayList<String> exceptions(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("exceptions");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        return this.<ArrayList<String>>commandData(builder.toString(),
                new TypeToken<JsonResponse<ArrayList<String>>>() {
                }.getType());
    }

    public FutureEpisodes future(FutureEpisodes.SortEnum sort) throws Exception {
        StringBuilder builder = new StringBuilder("future");
        builder.append("&sort=");
        switch (sort) {
        case DATE:
            builder.append("date");
            break;
        case NETWORK:
            builder.append("network");
            break;
        case NAME:
            builder.append("name");
            break;
        default:
            builder.append("date");
        }

        FutureEpisodes ret = this.<FutureEpisodes>commandData(builder.toString(),
                new TypeToken<JsonResponse<FutureEpisodes>>() {
                }.getType());
        for (FutureEpisode ep : ret.missed) {
            ep.when = TimeEnum.MISSED;
        }
        for (FutureEpisode ep : ret.today) {
            ep.when = TimeEnum.TODAY;
        }
        for (FutureEpisode ep : ret.soon) {
            ep.when = TimeEnum.SOON;
        }
        for (FutureEpisode ep : ret.later) {
            ep.when = TimeEnum.LATER;
        }
        return ret;
    }

    public History history() throws Exception {
        ArrayList<HistoryJson> result = this.<ArrayList<HistoryJson>>commandData("history",
                new TypeToken<JsonResponse<ArrayList<HistoryJson>>>() {
                }.getType());
        return new History(result);
    }

    public History history(int limit) throws Exception {
        StringBuilder builder = new StringBuilder("history");
        builder.append("&limit=");
        builder.append(limit);
        ArrayList<HistoryJson> result = this.<ArrayList<HistoryJson>>commandData(builder.toString(),
                new TypeToken<JsonResponse<ArrayList<HistoryJson>>>() {
                }.getType());
        return new History(result);
    }

    public boolean historyClear() throws Exception {
        return this.<Object>commandSuccessful("history.clear", new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean historyTrim() throws Exception {
        return this.<Object>commandSuccessful("history.trim", new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public Logs logs() throws Exception {
        ArrayList<String> result = this.<ArrayList<String>>commandData("logs",
                new TypeToken<JsonResponse<ArrayList<String>>>() {
                }.getType());
        return new Logs(result);
    }

    public Logs logs(Logs.LevelEnum minLevel) throws Exception {
        StringBuilder builder = new StringBuilder("logs");
        builder.append("&min_level=");
        switch (minLevel) {
        case DEBUG:
            builder.append("debug");
            break;
        case INFO:
            builder.append("info");
            break;
        case WARNING:
            builder.append("warning");
            break;
        case ERROR:
            builder.append("error");
            break;
        default:
            builder.append("error");
            break;
        }
        ArrayList<String> result = this.<ArrayList<String>>commandData(builder.toString(),
                new TypeToken<JsonResponse<ArrayList<String>>>() {
                }.getType());
        return new Logs(result);
    }

    public Show show(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        return new Show(this.<ShowJson>commandData(builder.toString(), new TypeToken<JsonResponse<ShowJson>>() {
        }.getType()));
    }

    public Show show(String tvdbid, boolean fullSeasonListing) throws Exception {
        if (fullSeasonListing) {
            StringBuilder builder = new StringBuilder("show|show.seasons");
            builder.append("&tvdbid=");
            builder.append(tvdbid);

            ShowWithFullSeasonListing results = this.<ShowWithFullSeasonListing>commandData(builder.toString(),
                    new TypeToken<JsonResponse<ShowWithFullSeasonListing>>() {
                    }.getType());
            Show ret = new Show(results.show.data);
            ret.seasonList.clear();
            for (Map.Entry<String, SeasonsJson> season : results.seasons.data.entrySet()) {
                ret.seasonList.add(new Season(season.getKey(), season.getValue()));
            }
            return ret;
        } else {
            return show(tvdbid);
        }
    }

    public boolean showAddNew(String tvdbid) throws Exception {
        return showAddNew(tvdbid, null, null, null, null, null);
    }

    public boolean showAddNew(String tvdbid, LanguageEnum language, Boolean seasonFolders, StatusEnum status,
            EnumSet<QualityEnum> initial, EnumSet<QualityEnum> archive) throws Exception {
        StringBuilder builder = new StringBuilder("show.addnew");
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        if (language != null) {
            builder.append("&lang=");
            builder.append(language.getAbbrev());
        }
        if (seasonFolders != null) {
            // the option isnt called season folders anymore
            if (apiVersion >= 3) {
                builder.append("&flatten_folders=");
            } else {
                builder.append("&season_folder=");
            }
            // if you pass me a boolean you better damn well have checked the version number
            builder.append(seasonFolders ? "1" : "0");
        }
        if (status != null) {
            builder.append("&status=");
            builder.append(status.toJson());
        }
        if (initial != null) {
            builder.append("&initial=");
            Iterator<QualityEnum> iter = initial.iterator();
            if (iter.hasNext()) {
                builder.append(iter.next().toString().toLowerCase());
                while (iter.hasNext()) {
                    builder.append("|");
                    builder.append(iter.next().toString().toLowerCase());
                }
            }
        }
        if (archive != null) {
            builder.append("&archive=");
            Iterator<QualityEnum> iter = archive.iterator();
            if (iter.hasNext()) {
                builder.append(iter.next().toString().toLowerCase());
                while (iter.hasNext()) {
                    builder.append("|");
                    builder.append(iter.next().toString().toLowerCase());
                }
            }
        }

        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public CacheStatus showCache(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.cache");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        return new CacheStatus(this.<CacheStatusJson>commandData(builder.toString(),
                new TypeToken<JsonResponse<CacheStatusJson>>() {
                }.getType()));
    }

    public boolean showDelete(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.delete");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public URI showGetBanner(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.getbanner");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        //      URI uri = new URI( serverUri.toString() + builder.toString() );
        //      HttpURLConnection server = (HttpURLConnection)uri.toURL().openConnection();
        //      server.connect();
        //      return server.getInputStream();
        return this.getServerUri(builder.toString());
    }

    public URI showGetPoster(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.getposter");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        //      URI uri = new URI( serverUri.toString() + builder.toString() );
        //      HttpURLConnection server = (HttpURLConnection)uri.toURL().openConnection();
        //      server.connect();
        //      return server.getInputStream();
        return this.getServerUri(builder.toString());
    }

    public GetQuality showGetQuality(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.getquality");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        GetQualityJson result = this.<GetQualityJson>commandData(builder.toString(),
                new TypeToken<JsonResponse<GetQualityJson>>() {
                }.getType());

        return new GetQuality(result);
    }

    public boolean showPause(String tvdbid, Boolean pause) throws Exception {
        StringBuilder builder = new StringBuilder("show.pause");
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        if (pause != null) {
            builder.append("&pause=");
            builder.append(pause ? "1" : "0");
        }

        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean showPause(String[] tvdbids, Boolean pause) throws Exception {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < tvdbids.length; i++) {
            builder.append("show.pause_");
            builder.append(i);
            if (i < tvdbids.length - 1)
                builder.append("|");
        }
        for (int i = 0; i < tvdbids.length; i++) {
            builder.append("&show.pause_");
            builder.append(i);
            builder.append(".tvdbid=");
            builder.append(tvdbids[i]);
        }
        if (pause != null) {
            builder.append("&pause=");
            builder.append(pause ? "1" : "0");
        }
        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean showRefresh(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.refresh");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean showRefresh(String[] tvdbids) throws Exception {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < tvdbids.length; i++) {
            builder.append("show.refresh_");
            builder.append(i);
            if (i < tvdbids.length - 1)
                builder.append("|");
        }
        for (int i = 0; i < tvdbids.length; i++) {
            builder.append("&show.refresh_");
            builder.append(i);
            builder.append(".tvdbid=");
            builder.append(tvdbids[i]);
        }
        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public List<Integer> showSeasonList(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.seasonlist");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        ArrayList<Integer> result = this.<ArrayList<Integer>>commandData(builder.toString(),
                new TypeToken<JsonResponse<ArrayList<Integer>>>() {
                }.getType());

        return result;
    }

    public List<Season> showSeasons(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.seasons");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        SeasonsListJson result = this.<SeasonsListJson>commandData(builder.toString(),
                new TypeToken<JsonResponse<SeasonsListJson>>() {
                }.getType());
        List<Season> ret = new ArrayList<Season>();
        for (Map.Entry<String, SeasonsJson> entry : result.entrySet()) {
            ret.add(new Season(entry.getKey(), entry.getValue()));
        }
        return ret;
    }

    public Season showSeasons(String tvdbid, String season) throws Exception {
        StringBuilder builder = new StringBuilder("show.seasons");
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        builder.append("&season=");
        builder.append(season);

        SeasonsJson result = this.<SeasonsJson>commandData(builder.toString(),
                new TypeToken<JsonResponse<SeasonsJson>>() {
                }.getType());
        return new Season(season, result);
    }

    public boolean showSetQuality(String tvdbid, EnumSet<QualityEnum> initial, EnumSet<QualityEnum> archive)
            throws Exception {
        StringBuilder builder = new StringBuilder("show.setquality");
        builder.append("&tvdbid=");
        builder.append(tvdbid);
        if (initial != null) {
            builder.append("&initial=");
            Iterator<QualityEnum> iter = initial.iterator();
            if (iter.hasNext()) {
                builder.append(iter.next().toString().toLowerCase());
                while (iter.hasNext()) {
                    builder.append("|");
                    builder.append(iter.next().toString().toLowerCase());
                }
            }
        }
        if (archive != null) {
            builder.append("&archive=");
            Iterator<QualityEnum> iter = archive.iterator();
            if (iter.hasNext()) {
                builder.append(iter.next().toString().toLowerCase());
                while (iter.hasNext()) {
                    builder.append("|");
                    builder.append(iter.next().toString().toLowerCase());
                }
            }
        }

        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean showUpdate(String tvdbid) throws Exception {
        StringBuilder builder = new StringBuilder("show.update");
        builder.append("&tvdbid=");
        builder.append(tvdbid);

        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean showUpdate(String[] tvdbids) throws Exception {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < tvdbids.length; i++) {
            builder.append("show.update_");
            builder.append(i);
            if (i < tvdbids.length - 1)
                builder.append("|");
        }
        for (int i = 0; i < tvdbids.length; i++) {
            builder.append("&show.update_");
            builder.append(i);
            builder.append(".tvdbid=");
            builder.append(tvdbids[i]);
        }
        return this.<Object>commandSuccessful(builder.toString(), new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public ArrayList<Show> shows() throws Exception {
        HashMap<String, ShowJson> result = this.commandData("shows",
                new TypeToken<JsonResponse<HashMap<String, ShowJson>>>() {
                }.getType());
        for (Map.Entry<String, ShowJson> entry : result.entrySet()) {
            entry.getValue().id = entry.getKey();
        }
        ArrayList<Show> ret = new ArrayList<Show>();
        for (ShowJson j : result.values()) {
            ret.add(new Show(j));
        }
        Collections.sort(ret, new Comparator<Show>() {
            public int compare(Show a, Show b) {
                return a.showName.compareTo(a.showName);
            }
        });
        return ret;
    }

    public boolean sbForceSearch() throws Exception {
        return this.<Object>commandSuccessful("sb.forcesearch", new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public CommandsJson sbGetCommands() throws Exception {
        return this.<CommandsJson>commandData("sb", new TypeToken<JsonResponse<CommandsJson>>() {
        }.getType());
    }

    public OptionsJson sbGetDefaults() throws Exception {
        return this.<OptionsJson>commandData("sb", new TypeToken<JsonResponse<OptionsJson>>() {
        }.getType());
    }

    public ArrayList<MessageJson> sbGetMessages() throws Exception {
        return this.<ArrayList<MessageJson>>commandData("sb.getmessages",
                new TypeToken<JsonResponse<ArrayList<MessageJson>>>() {
                }.getType());
    }

    public ArrayList<RootDirJson> sbGetRootDirs() throws Exception {
        return this.<ArrayList<RootDirJson>>commandData("sb.getrootdirs",
                new TypeToken<JsonResponse<ArrayList<RootDirJson>>>() {
                }.getType());
    }

    public boolean sbPauseBackLogSearch() throws Exception {
        return this.<Object>commandSuccessful("sb.pausebacklog", new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public boolean sbPing() {
        try {
            return this.<PingJson>commandSuccessful("sb.ping", new TypeToken<JsonResponse<PingJson>>() {
            }.getType());
        } catch (Exception e) {
            return false;
        }
    }

    public boolean sbRestart() throws Exception {
        return this.<Object>commandSuccessful("sb.restart", new TypeToken<JsonResponse<Object>>() {
        }.getType());
    }

    public SearchResults sbSearchTvDb(String query, LanguageEnum language) throws Exception {
        StringBuilder builder = new StringBuilder("sb.searchtvdb");
        builder.append("&name=");
        builder.append(query);
        if (language != null) {
            builder.append("&lang=");
            builder.append(language.getAbbrev());
        }
        // having to use all these generics is kind of annoying
        TvDbResultsJson results = this.<TvDbResultsJson>commandData(builder.toString(),
                new TypeToken<JsonResponse<TvDbResultsJson>>() {
                }.getType());
        SearchResults ret = new SearchResults();
        for (TvDbResultJson json : results.results) { // lol results of results :)
            ret.results.add(new SearchResultTvDb(json));
        }
        return ret;
    }

    public boolean sbSetDefaults(OptionsJson defaults) throws Exception {
        return false;
    }

    // hard to test function XD
    public boolean sbShutdown() throws Exception {
        return this.<String>commandSuccessful("sb.shutdown", new TypeToken<JsonResponse<String>>() {
        }.getType());
    }

    private <T> T commandData(String command, Type type) throws Exception {
        JsonResponse<T> response = this.commandResponse(command, type);
        return (response.result.equals(SUCCESS) ? response.data : null);
    }

    private <T> JsonResponse<T> commandResponse(String command, Type type) throws Exception {
        URI uri = this.getServerUri(command);
        //      URI uri = new URI( serverUri.toString() + URLEncoder.encode(command) );
        HttpURLConnection server = (HttpURLConnection) uri.toURL().openConnection();

        server.setConnectTimeout(30000);
        BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream()));
        // TypeToken cannot figure out T so instead it must be supplied
        //Type type = new TypeToken< JSONResponse<T> >() {}.getType();
        GsonBuilder build = new GsonBuilder();
        StringBuilder sBuild = new StringBuilder();
        String input;
        while ((input = reader.readLine()) != null)
            sBuild.append(input);
        reader.close();
        input = sBuild.toString();
        build.registerTypeAdapter(JsonBoolean.class, new JsonBooleanDeserializer());
        JsonResponse<T> response = null;
        try {
            response = build.create().fromJson(input, type);
            tryExtractError(response);
            return response;
        } catch (Exception e) {
            // well something messed up
            // if this part messes up then something REALLY bad happened
            response = build.create().fromJson(input, new TypeToken<JsonResponse<Object>>() {
            }.getType());
            tryExtractError(response);
            // DO NOT RETURN AN ACTUAL OBJECT!!!!!
            // this makes the code in the UI confused
            return null;
        }
    }

    private <T> boolean commandSuccessful(String command, Type type) throws Exception {
        return this.commandResponse(command, type).result.equals(SUCCESS);
    }

    private <T> void tryExtractError(JsonResponse<T> response) throws Exception {
        if (response.result.compareTo(ERROR) == 0 || response.result.compareTo(FAILURE) == 0) {
            if (response.message != null && response.message.length() > 0)
                throw new Exception(response.message);
            else if (response.data != null && response.data.toString().length() > 0)
                throw new Exception(response.data.toString());
            throw new Exception("Unknown Error occurred ... Ut Oh.");
        }
    }
}