package com.sabdroidex.controllers.sabnzbd; import android.os.Handler; import android.os.Message; import android.util.Log; import com.sabdroidex.controllers.SABController; import com.sabdroidex.data.sabnzbd.Categories; import com.sabdroidex.data.sabnzbd.History; import com.sabdroidex.data.sabnzbd.Priorities; import com.sabdroidex.data.sabnzbd.Queue; import com.sabdroidex.data.sabnzbd.QueueElement; import com.sabdroidex.data.sabnzbd.SabnzbdConfig; import com.sabdroidex.data.sabnzbd.Scripts; import com.sabdroidex.utils.Preferences; import com.sabdroidex.utils.json.impl.JSONParser; import com.sabdroidex.utils.json.impl.JSONPojoMapper; import com.sabdroidex.utils.json.impl.SimpleJSONMarshaller; import com.utils.ApacheCredentialProvider; import com.utils.HttpUtil; import org.json.JSONObject; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** * @author Marc * */ public final class SABnzbdController extends SABController { public static enum MESSAGE { ADDFILE, ADDURL, HISTORY, PAUSE, QUEUE, REMOVE, RESUME, CONFIG, SET_CONFIG, GET_CONFIG, UPDATE, GET_CATS, GET_SCRIPTS, GET_PRIORITIES, CHANGE_CAT, CHANGE_SCRIPT, PRIORITY } private static final String TAG = "SABnzbdController"; private static boolean executingCommand = false; private static boolean executingRefreshHistory = false; private static boolean executingRefreshQuery = false; public static boolean paused = false; private static final String URL_TEMPLATE = "[SABNZBD_URL]/[SABNZBD_URL_EXTENTION]api?mode=[COMMAND]&output=json"; /** * This function sends a Nzb URL to Sabnzbd to add to the queue. * * @param messageHandler * The class that will handle the result message. * @param value * The URL to sent to the Sabnzbd server. */ public static void addByURL(final Handler messageHandler, final String value) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { makeApiCall(MESSAGE.ADDURL.toString().toLowerCase(), "name=" + value); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { sendUpdateMessageStatus(messageHandler, ""); } } }; sendUpdateMessageStatus(messageHandler, MESSAGE.ADDURL.toString()); thread.start(); } /** * This method sends a Nzb file to Sabnzbd to add to the queue. * * @param messageHandler * The class that will handle the result message. * @param name Defines the file name that was read. * @param file Is the read file in a char[] format. */ public static void addFile(final Handler messageHandler, final String name, final char[] file) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { makePostApiCall(MESSAGE.ADDFILE.toString().toLowerCase(), "application/x-nzb", URLEncoder.encode(name, "UTF-8"), file, "name=" + URLEncoder.encode(name, "UTF-8"), "cat=", "priority="); } catch (final Throwable e) { Log.w(TAG, " " + e.getLocalizedMessage()); } finally { sendUpdateMessageStatus(messageHandler, ""); } } }; sendUpdateMessageStatus(messageHandler, MESSAGE.ADDFILE.toString()); thread.start(); } /** * Gets all the configurations on the Sabnzbd Server * * @param messageHandler * The class that will handle the result message. */ public static void getAllConfigs(final Handler messageHandler) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { final String result = makeApiCall(MESSAGE.GET_CONFIG.toString().toLowerCase()); InputStream inputStream = new ByteArrayInputStream(result.getBytes()); JSONParser jsonParser = new JSONParser(); jsonParser.setBadFormat(true); Map<String, Object> jsonMap = (Map<String, Object>) jsonParser.parse(inputStream); if (jsonMap.get("error") != null) { sendUpdateMessageStatus(messageHandler, "SABnzbd : " + jsonMap.get("error")); } else { JSONPojoMapper jsonPojoMapper = new JSONPojoMapper(SabnzbdConfig.class); SabnzbdConfig config = (SabnzbdConfig) jsonPojoMapper.unMarshal(jsonMap); final Message message = new Message(); message.setTarget(messageHandler); message.what = MESSAGE.GET_CONFIG.hashCode(); message.obj = config; message.sendToTarget(); } } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); final Message message = new Message(); message.setTarget(messageHandler); message.what = MESSAGE.GET_CONFIG.hashCode(); message.obj = null; message.sendToTarget(); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; sendUpdateMessageStatus(messageHandler, ""); thread.start(); } /** * * @param messageHandler */ public static void getPriorities(final Handler messageHandler) { final Thread thread = new Thread() { @Override public void run() { try { final Message message = new Message(); message.setTarget(messageHandler); message.what = MESSAGE.GET_PRIORITIES.hashCode(); message.obj = new Priorities(); message.sendToTarget(); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; thread.start(); } /** * Sets the priority for a specific download * * @param messageHandler The class that will handle the result message. */ public static void setPriority(final Handler messageHandler, final String id, final String priority) { // Settings not ready if (!Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { Priorities.Priority p = Priorities.Priority.valueOf(priority.toUpperCase()); makeApiCall(MESSAGE.PRIORITY.toString().toLowerCase(), "value=" + id, "value2=" + p.getValue()); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; thread.start(); } /** * Gets the categories from the Sabnzbd Server * * @param messageHandler The class that will handle the result message. */ public static void getCategories(final Handler messageHandler) { // Settings not ready if (!Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { final String result = makeApiCall(MESSAGE.GET_CATS.toString().toLowerCase()); InputStream inputStream = new ByteArrayInputStream(result.getBytes()); JSONParser jsonParser = new JSONParser(); Map<String, Object> jsonMap = (Map<String, Object>) jsonParser.parse(inputStream); if (jsonMap.get("error") != null) { sendUpdateMessageStatus(messageHandler, "SABnzbd : " + jsonMap.get("error")); } else { JSONPojoMapper jsonPojoMapper = new JSONPojoMapper(Categories.class); Categories categories = (Categories) jsonPojoMapper.unMarshal(jsonMap); final Message message = new Message(); message.setTarget(messageHandler); message.what = MESSAGE.GET_CATS.hashCode(); message.obj = categories; message.sendToTarget(); } } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; thread.start(); } /** * Sets the category for a specific download * * @param messageHandler The class that will handle the result message. */ public static void setCategory(final Handler messageHandler, final String ... parameters) { // Settings not ready if (!Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { makeApiCall(MESSAGE.CHANGE_CAT.toString().toLowerCase(), "value=" +parameters[1], "value2=" + parameters[1]); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; thread.start(); } /** * Gets the scripts from the Sabnzbd Server * * @param messageHandler The class that will handle the result message. */ public static void getScripts(final Handler messageHandler) { // Settings not ready if (!Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { final String result = makeApiCall(MESSAGE.GET_SCRIPTS.toString().toLowerCase()); InputStream inputStream = new ByteArrayInputStream(result.getBytes()); JSONParser jsonParser = new JSONParser(); Map<String, Object> jsonMap = (Map<String, Object>) jsonParser.parse(inputStream); if (jsonMap.get("error") != null) { sendUpdateMessageStatus(messageHandler, "SABnzbd : " + jsonMap.get("error")); } else { JSONPojoMapper jsonPojoMapper = new JSONPojoMapper(Scripts.class); Scripts scripts = (Scripts) jsonPojoMapper.unMarshal(jsonMap); final Message message = new Message(); message.setTarget(messageHandler); message.what = MESSAGE.GET_SCRIPTS.hashCode(); message.obj = scripts; message.sendToTarget(); } } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; thread.start(); } /** * Sets the script for a specific download * * @param messageHandler The class that will handle the result message. */ public static void setScript(final Handler messageHandler, final String ... parameters) { // Settings not ready if (!Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { makeApiCall(MESSAGE.CHANGE_SCRIPT.toString().toLowerCase(), "value=" +parameters[1], "value2=" + parameters[1]); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; thread.start(); } /** * This function gets the URL used to connect to the Sabnzbd server * * @return url A {@link String} containing the URL of the Sabnzbd server */ private static String getFormattedUrl() { String url = URL_TEMPLATE; /** * Checking if there is a port to concatenate to the URL */ if ("".equals(Preferences.get(Preferences.SABNZBD_PORT))) { url = url.replace("[SABNZBD_URL]", Preferences.get(Preferences.SABNZBD_URL)); } else { url = url.replace("[SABNZBD_URL]", Preferences.get(Preferences.SABNZBD_URL) + ":" + Preferences.get(Preferences.SABNZBD_PORT)); } /** * Checking the URL extension */ if ("".equals(Preferences.get(Preferences.SABNZBD_URL_EXTENTION))) { url = url.replace("[SABNZBD_URL_EXTENTION]", Preferences.get(Preferences.SABNZBD_URL_EXTENTION)); } else { url = url.replace("[SABNZBD_URL_EXTENTION]", Preferences.get(Preferences.SABNZBD_URL_EXTENTION) + "/"); } if (!url.toUpperCase().startsWith("HTTP://") && !url.toUpperCase().startsWith("HTTPS://")) { if (Preferences.isEnabled(Preferences.SABNZBD_SSL)) { url = "https://" + url; } else { url = "http://" + url; } } /** * Checking if there is an API Key from Sabnzbd to concatenate to the * URL */ final String apiKey = Preferences.get(Preferences.SABNZBD_API_KEY); if (!apiKey.trim().equals("")) { url = url + "&apikey=" + apiKey; } return url; } /** * This function gets the Sabnzbd user name and password in an usable URL * format. * * @return The credentials used to connect to Sabnzbd */ private static String getPreferencesParams() { final String username = Preferences.get(Preferences.SABNZBD_USERNAME); final String password = Preferences.get(Preferences.SABNZBD_PASSWORD); String credentials = ""; if (username != null && !"".equals(username)) { credentials += "&ma_username=" + username; } if (password != null && !"".equals(password)) { credentials += "&ma_password=" + password; } return credentials; } /** * This function handle the API calls to Sabnzbd to define the URL and * parameters * * @param command * The type of command that will be sent to Sabnzbd * @return The result of the API call * @throws RuntimeException * Thrown if there is any unexpected problem during the * communication with the server */ public static String makeApiCall(final String command) throws Exception { return makeApiCall(command, ""); } /** * This function handle the API calls to Sabnzbd to define the URL and * parameters * * @param command * The type of command that will be sent to Sabnzbd * @param extraParams * Any parameter that will have to be part of the URL * @return The result of the API call * @throws RuntimeException * Thrown if there is any unexpected problem during the * communication with the server */ public static String makeApiCall(final String command, final String... extraParams) throws Exception { String url = getFormattedUrl(); url = url.replace("[COMMAND]", command); url = url + getPreferencesParams(); for (final String xTraParam : extraParams) { if (xTraParam != null && !xTraParam.trim().equals("")) { url = url + "&" + xTraParam; } } return new String(HttpUtil.getInstance().getDataAsCharArray(url, ApacheCredentialProvider.getCredentialsProvider())); } /** * This function handle the API calls to Sabnzbd to define the URL and * parameters * * * @param command * The type of command that will be sent to Sabnzbd * @param contentType * The type of the content that will be sent (application/x-nzb * or else). * @param content * The content that will be sent. The type should match what is * sent in contentType. * @param extraParams * Any parameter that will have to be part of the URL * @return The result of the API call * @throws RuntimeException * Thrown if there is any unexpected problem during the * communication with the server */ public static String makePostApiCall(final String command, final String contentType, String contentName, final char[] content, final String... extraParams) throws Exception { String url = getFormattedUrl(); url = url.replace("[COMMAND]", command); url = url + getPreferencesParams(); for (final String xTraParam : extraParams) { if (xTraParam != null && !xTraParam.trim().equals("")) { url = url + "&" + xTraParam; } } return new String(HttpUtil.getInstance().postDataAsCharArray(url, contentType, contentName, content)); } /** * Pauses or resumes a queue item depending on the current status * * @param messageHandler * @param item */ public static void pauseResumeItem(final Handler messageHandler, final QueueElement item) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { if ("Paused".equals(item.getStatus())) { makeApiCall(MESSAGE.QUEUE.toString().toLowerCase(), "name=resume", "value=" + item.getNzoId()); } else { makeApiCall(MESSAGE.QUEUE.toString().toLowerCase(), "name=pause", "value=" + item.getNzoId()); } Thread.sleep(100); SABnzbdController.refreshQueue(messageHandler); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { executingCommand = false; } } }; executingCommand = true; if (paused) { sendUpdateMessageStatus(messageHandler, MESSAGE.RESUME.toString()); } else { sendUpdateMessageStatus(messageHandler, MESSAGE.PAUSE.toString()); } thread.start(); } /** * Pauses or resumes the queue depending on the current status * * @param messageHandler */ public static void pauseResumeQueue(final Handler messageHandler) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { if (paused) { makeApiCall(MESSAGE.RESUME.toString().toLowerCase()); } else { makeApiCall(MESSAGE.PAUSE.toString().toLowerCase()); } Thread.sleep(100); } catch (final Throwable e) { Log.e(TAG, e.getLocalizedMessage()); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; if (paused) { sendUpdateMessageStatus(messageHandler, MESSAGE.RESUME.toString()); } else { sendUpdateMessageStatus(messageHandler, MESSAGE.PAUSE.toString()); } thread.start(); } /** * This function refreshes the elements from the history. * * @param messageHandler * The class that will handle the result message */ public static void refreshHistory(final Handler messageHandler) { // Already running or settings not ready if (executingRefreshHistory || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { final String result = makeApiCall(MESSAGE.HISTORY.toString().toLowerCase()); InputStream inputStream = new ByteArrayInputStream(result.getBytes()); JSONParser jsonParser = new JSONParser(); Map<String, Object> jsonMap = (Map<String, Object>) jsonParser.parse(inputStream); if (jsonMap.get("error") != null) { sendUpdateMessageStatus(messageHandler, "SABnzbd : " + jsonMap.get("error")); } else { JSONPojoMapper jsonPojoMapper = new JSONPojoMapper(History.class); History history = (History) jsonPojoMapper.unMarshal((Map<String, Object>) jsonMap.get("history")); final Message message = new Message(); message.setTarget(messageHandler); message.what = MESSAGE.HISTORY.hashCode(); message.obj = history; message.sendToTarget(); } } catch (final IOException e) { Log.e(TAG, " " + e.getLocalizedMessage()); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { executingRefreshHistory = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingRefreshHistory = true; sendUpdateMessageStatus(messageHandler, ""); thread.start(); } /** * This function refreshes the elements from the queue. * * @param messageHandler * The class that will handle the result message */ public static void refreshQueue(final Handler messageHandler) { // Already running or settings not ready if (executingRefreshQuery || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { String statusMessage = ""; try { final String result = makeApiCall(MESSAGE.QUEUE.toString().toLowerCase()); InputStream inputStream = new ByteArrayInputStream(result.getBytes()); JSONParser jsonParser = new JSONParser(); Map<String, Object> jsonMap = (Map<String, Object>) jsonParser.parse(inputStream); if (jsonMap.get("error") != null) { sendUpdateMessageStatus(messageHandler, "SABnzbd : " + jsonMap.get("error")); } else { JSONPojoMapper jsonPojoMapper = new JSONPojoMapper(Queue.class); Queue queue = (Queue) jsonPojoMapper.unMarshal((Map<String, Object>) jsonMap.get("queue")); paused = queue.getPaused(); final Message message = new Message(); message.setTarget(messageHandler); message.what = MESSAGE.QUEUE.hashCode(); message.obj = queue; message.sendToTarget(); } } catch (final IOException e) { Log.e(TAG, " " + e.getLocalizedMessage()); statusMessage = e.getLocalizedMessage(); } catch (final Throwable e) { Log.e(TAG, " " + e.getLocalizedMessage()); } finally { executingRefreshQuery = false; sendUpdateMessageStatus(messageHandler, statusMessage); } } }; executingRefreshQuery = true; sendUpdateMessageStatus(messageHandler, ""); thread.start(); } /** * Removes a history item * * @param messageHandler * The class that will handle the result message. * @param item * The item nzo_id to remove from the history. */ public static void removeHistoryItem(final Handler messageHandler, final String item) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { makeApiCall(MESSAGE.HISTORY.toString().toLowerCase(), "name=delete", "value=" + item); Thread.sleep(250); SABnzbdController.refreshHistory(messageHandler); } catch (final Throwable e) { Log.w(TAG, " " + e.getLocalizedMessage()); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; sendUpdateMessageStatus(messageHandler, ""); thread.start(); } /** * Removes a queue item * * @param messageHandler * The class that will handle the result message. * @param item * The item id to remove from the queue. */ public static void removeQueueItem(final Handler messageHandler, final QueueElement item) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { makeApiCall(MESSAGE.QUEUE.toString().toLowerCase(), "name=delete", "value=" + item.getNzoId()); Thread.sleep(250); SABnzbdController.refreshQueue(messageHandler); } catch (final Throwable e) { Log.w(TAG, " " + e.getLocalizedMessage()); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; sendUpdateMessageStatus(messageHandler, ""); thread.start(); } /** * Sets a specific configuration on the Sabnzbd Server * * @param messageHandler * The class that will handle the result message. * @param item * An array that contains the configuration section, the * configuration name and the new value. */ public static void setConfig(final Handler messageHandler, final Object[] item) { // Already running or settings not ready if (executingCommand || !Preferences.isSet(Preferences.SABNZBD_URL)) { return; } final Thread thread = new Thread() { @Override public void run() { try { makeApiCall(MESSAGE.SET_CONFIG.toString().toLowerCase(), "section=" + item[0], "keyword=" + item[1], "value=" + item[2]); } catch (final Throwable e) { Log.w(TAG, " " + e.getLocalizedMessage()); } finally { executingCommand = false; sendUpdateMessageStatus(messageHandler, ""); } } }; executingCommand = true; sendUpdateMessageStatus(messageHandler, ""); thread.start(); } }