package org.sugr.gearshift.service; import android.annotation.TargetApi; import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.DocumentsContract; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import org.sugr.gearshift.G; import org.sugr.gearshift.core.Torrent; import org.sugr.gearshift.core.TransmissionProfile; import org.sugr.gearshift.core.TransmissionSession; import org.sugr.gearshift.datasource.DataSource; import org.sugr.gearshift.datasource.TorrentStatus; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; public class DataService extends IntentService { public static final class Requests { public static final String REMOVE_PROFILE = "remove_profile"; public static final String GET_SESSION = "get_session"; public static final String SET_SESSION = "set_session"; public static final String GET_TORRENTS = "get_torrents"; public static final String ADD_TORRENT = "add_torrent"; public static final String REMOVE_TORRENT = "remove_torrents"; public static final String SET_TORRENT = "set_torrent"; public static final String SET_TORRENT_LOCATION = "set_torrent_location"; public static final String SET_TORRENT_ACTION = "set_torrent_action"; public static final String SET_TORRENT_QUEUE_ACTION = "set_torrent_queue_action"; public static final String GET_FREE_SPACE = "get_free_space"; public static final String TEST_PORT = "test_port"; public static final String UPDATE_BLOCKLIST = "blocklist_update"; } public static final class Args { public static final String SESSION = "session"; public static final String SESSION_FIELDS = "session_fields"; public static final String ALL_TORRENT_FIELDS = "all_torrent_fields"; public static final String DETAIL_FIELDS = "detail_fields"; public static final String UPDATE_ACTIVE = "update_active"; public static final String TORRENTS_TO_UPDATE = "torrents_to_update"; public static final String REMOVE_OBSOLETE = "remove_obsolete"; public static final String MAGNET_URI = "magnet_uri"; public static final String TORRENT_DATA_PATH = "torrent_data_path"; public static final String TORRENT_FILE = "torrent_file"; public static final String LOCATION = "location"; public static final String MOVE_DATA = "move_data"; public static final String ADD_PAUSED = "add_paused"; public static final String DOCUMENT_URI = "document_uri"; public static final String HASH_STRINGS = "hash_strings"; public static final String DELETE_DATA = "delete_data"; public static final String TORRENT_FIELD = "torrent_field"; public static final String TORRENT_FIELD_VALUE = "torrent_field_value"; public static final String TORRENT_ACTION = "torrent_action"; public static final String TORRENT_QUEUE_ACTION = "torrent_queue_action"; } private DataSource dataSource; private TransmissionSessionManager manager; private TransmissionProfile profile; public DataService() { super("DataService"); } @TargetApi(Build.VERSION_CODES.KITKAT) @Override protected void onHandleIntent(Intent intent) { String profileId = intent.getStringExtra(G.ARG_PROFILE_ID); String requestType = intent.getStringExtra(G.ARG_REQUEST_TYPE); Bundle args = intent.getBundleExtra(G.ARG_REQUEST_ARGS); Intent response = null; if (dataSource == null) { dataSource = new DataSource(this); } dataSource.open(); try { if (requestType == null) { throw new IllegalArgumentException("Invalid request type"); } if (profileId == null) { throw new IllegalArgumentException("No profile specified"); } if (args == null) { throw new IllegalArgumentException("No arguments bundle"); } if (profile == null || !profile.getId().equals(profileId)) { profile = new TransmissionProfile(profileId, PreferenceManager.getDefaultSharedPreferences(this)); } if (requestType.equals(Requests.REMOVE_PROFILE)) { profile.delete(); dataSource.clearTorrentsForProfile(profileId); response = createResponse(requestType, profileId); } else { if (profile.getHost().equals("")) { throw new IllegalArgumentException("No profile specified"); } if (manager != null) { manager.setProfile(profile); } if (manager == null) { manager = new TransmissionSessionManager( (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE), PreferenceManager.getDefaultSharedPreferences(this), profile, dataSource, new ConnectionProvider()); } switch (requestType) { case Requests.GET_SESSION: manager.updateSession(); response = createResponse(requestType, profileId); break; case Requests.SET_SESSION: TransmissionSession session = args.getParcelable(Args.SESSION); String[] keys = args.getStringArray(Args.SESSION_FIELDS); if (session == null) { throw new IllegalArgumentException("No session object given"); } if (keys == null || keys.length == 0) { throw new IllegalArgumentException("No session fields given"); } manager.setSession(session, keys); response = createResponse(requestType, profileId); break; case Requests.GET_TORRENTS: { TorrentStatus status; String[] fields = Torrent.Fields.STATS; if (args.getBoolean(Args.ALL_TORRENT_FIELDS, false)) { fields = G.concat(fields, Torrent.Fields.METADATA, Torrent.Fields.STATS_EXTRA, Torrent.Fields.INFO_EXTRA); } else { if (!dataSource.hasCompleteMetadata(profileId)) { fields = G.concat(Torrent.Fields.METADATA, fields); } if (args.getBoolean(Args.DETAIL_FIELDS, false)) { fields = G.concat(fields, Torrent.Fields.STATS_EXTRA); if (!dataSource.hasExtraInfo(profileId)) { fields = G.concat(fields, Torrent.Fields.INFO_EXTRA); } } } String[] hashStrings = args.getStringArray(Args.TORRENTS_TO_UPDATE); if (hashStrings != null) { status = manager.getTorrents(fields, hashStrings, false); } else if (args.getBoolean(Args.UPDATE_ACTIVE, false) && !args.getBoolean(Args.DETAIL_FIELDS, false)) { status = manager.getActiveTorrents(fields); } else { status = manager.getTorrents(fields, null, args.getBoolean(Args.REMOVE_OBSOLETE, false)); } String[] unnamed = dataSource.getUnnamedTorrentHashStrings(profileId); if (unnamed != null && unnamed.length > 0) { manager.getTorrents( G.concat(new String[]{Torrent.Fields.hashString}, Torrent.Fields.METADATA), unnamed, false); } response = createResponse(requestType, profileId) .putExtra(G.ARG_ADDED, status.hasAdded) .putExtra(G.ARG_REMOVED, status.hasRemoved) .putExtra(G.ARG_STATUS_CHANGED, status.hasStatusChanged) .putExtra(G.ARG_INCOMPLETE_METADATA, status.hasIncompleteMetadata); break; } case Requests.ADD_TORRENT: { String uri = args.getString(Args.MAGNET_URI); String temporaryFile = args.getString(Args.TORRENT_DATA_PATH); String torrentFile = args.getString(Args.TORRENT_FILE); String location = args.getString(Args.LOCATION); boolean paused = args.getBoolean(Args.ADD_PAUSED, false); Uri document = args.getParcelable(Args.DOCUMENT_URI); String meta = null; if (TextUtils.isEmpty(uri) && TextUtils.isEmpty(temporaryFile)) { throw new IllegalArgumentException( "Either a uri or the torrent data needs to be specified"); } if (!TextUtils.isEmpty(temporaryFile)) { BufferedReader reader = null; try { File file = new File(new URI(temporaryFile)); reader = new BufferedReader(new FileReader(file)); StringBuilder fileData = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { fileData.append(line).append("\n"); } meta = fileData.toString(); if (!file.delete()) { G.logD("Couldn't remove torrent " + file.getName()); } } catch (Exception e) { throw new IllegalArgumentException("Unable to process torrent data " + temporaryFile); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } String addedHash = manager.addTorrent(uri, meta, location, paused); if (document != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (!DocumentsContract.deleteDocument(getContentResolver(), document)) { G.logD("Couldn't remove torrent " + document.toString()); } } if (!TextUtils.isEmpty(torrentFile)) { File file = new File(torrentFile); if (!file.delete()) { G.logD("Couldn't remove torrent " + file.getName()); } } profile.setLastDownloadDirectory(location); profile.setDeleteLocal(torrentFile != null || document != null); profile.setStartPaused(paused); response = createResponse(requestType, profileId) .putExtra(G.ARG_ADDED_HASH, addedHash); break; } case Requests.REMOVE_TORRENT: { String[] hashStrings = args.getStringArray(Args.HASH_STRINGS); boolean delete = args.getBoolean(Args.DELETE_DATA, false); if (hashStrings == null || hashStrings.length == 0) { throw new IllegalArgumentException("No hash strings provided"); } manager.removeTorrent(hashStrings, delete); response = createResponse(requestType, profileId); break; } case Requests.SET_TORRENT: { String[] hashStrings = args.getStringArray(Args.HASH_STRINGS); String field = args.getString(Args.TORRENT_FIELD); Object value = args.get(Args.TORRENT_FIELD_VALUE); if (hashStrings == null || hashStrings.length == 0) { throw new IllegalArgumentException("No hash strings provided"); } if (TextUtils.isEmpty(field)) { throw new IllegalArgumentException("No torrent field provided"); } manager.setTorrentProperty(hashStrings, field, value); response = createResponse(requestType, profileId) .putExtra(G.ARG_TORRENT_FIELD, field); break; } case Requests.SET_TORRENT_LOCATION: { String[] hashStrings = args.getStringArray(Args.HASH_STRINGS); String location = args.getString(Args.LOCATION); boolean move = args.getBoolean(Args.MOVE_DATA, false); if (hashStrings == null || hashStrings.length == 0) { throw new IllegalArgumentException("No hash strings provided"); } if (TextUtils.isEmpty(location)) { throw new IllegalArgumentException("No torrent location provided"); } manager.setTorrentLocation(hashStrings, location, move); profile.setLastDownloadDirectory(location); profile.setMoveData(move); response = createResponse(requestType, profileId); break; } case Requests.SET_TORRENT_ACTION: { String[] hashStrings = args.getStringArray(Args.HASH_STRINGS); String action = args.getString(Args.TORRENT_ACTION); if (hashStrings == null || hashStrings.length == 0) { throw new IllegalArgumentException("No hash strings provided"); } if (TextUtils.isEmpty(action)) { throw new IllegalArgumentException("No action provided"); } manager.setTorrentAction(hashStrings, action); response = createResponse(requestType, profileId); break; } case Requests.SET_TORRENT_QUEUE_ACTION: { String[] hashStrings = args.getStringArray(Args.HASH_STRINGS); String action = args.getString(Args.TORRENT_QUEUE_ACTION); if (hashStrings == null || hashStrings.length == 0) { throw new IllegalArgumentException("No hash strings provided"); } if (TextUtils.isEmpty(action)) { throw new IllegalArgumentException("No action provided"); } manager.setTorrentQueueAction(hashStrings, action); response = createResponse(requestType, profileId); break; } case Requests.GET_FREE_SPACE: { String location = args.getString(Args.LOCATION); if (TextUtils.isEmpty(location)) { throw new IllegalArgumentException("No torrent location provided"); } long freeSpace = manager.getFreeSpace(location); response = createResponse(requestType, profileId) .putExtra(G.ARG_FREE_SPACE, freeSpace); break; } case Requests.TEST_PORT: boolean isOpen = manager.testPort(); response = createResponse(requestType, profileId) .putExtra(G.ARG_PORT_IS_OPEN, isOpen); break; case Requests.UPDATE_BLOCKLIST: long size = manager.updateBlocklist(); response = createResponse(requestType, profileId) .putExtra(G.ARG_BLOCKLIST_SIZE, size); break; default: throw new IllegalArgumentException("Invalid request type"); } } } catch (IllegalArgumentException e) { G.logE("Error while processing service request!", e); } catch (TransmissionSessionManager.ManagerException e) { G.logE("Error while processing service request!", e); response = handleError(e, requestType, profileId); } finally { dataSource.close(); if (response != null) { LocalBroadcastManager.getInstance(this).sendBroadcast(response); } } } private Intent createResponse(String requestType, String profileId) { return new Intent(G.INTENT_SERVICE_ACTION_COMPLETE) .putExtra(G.ARG_REQUEST_TYPE, requestType) .putExtra(G.ARG_PROFILE_ID, profileId); } private Intent handleError(TransmissionSessionManager.ManagerException e, String requestType, String profileId) { Intent intent = createResponse(requestType, profileId); G.logD("Got an error while fetching data: " + e.getMessage() + " and this code: " + e.getCode()); int error; switch(e.getCode()) { case HttpURLConnection.HTTP_UNAUTHORIZED: case HttpURLConnection.HTTP_FORBIDDEN: error = Errors.ACCESS_DENIED; break; case HttpURLConnection.HTTP_OK: if (e.getMessage().equals("no-json")) { error = Errors.NO_JSON; } else { return null; } break; case -1: if (e.getMessage().equals("timeout")) { error = Errors.TIMEOUT; } else if (e.getMessage().equals("connectivity")) { error = Errors.NO_CONNECTIVITY; } else { error = Errors.NO_CONNECTION; } break; case -2: if (e.getMessage().equals("duplicate torrent")) { error = Errors.DUPLICATE_TORRENT; } else if (e.getMessage().equals("invalid or corrupt torrent file")) { error = Errors.INVALID_TORRENT; } else { error = Errors.RESPONSE_ERROR; G.logE("Transmission Daemon Error!", e); } break; case -3: error = Errors.OUT_OF_MEMORY; break; case -4: error = Errors.JSON_PARSE_ERROR; G.logE("JSON parse error!", e); break; default: error = Errors.GENERIC_HTTP; break; } return intent.putExtra(G.ARG_ERROR, error) .putExtra(G.ARG_ERROR_CODE, e.getCode()) .putExtra(G.ARG_ERROR_STRING, e.getMessage()); } public static class Errors { public static final int NO_CONNECTIVITY = 1; public static final int ACCESS_DENIED = 1 << 1; public static final int NO_JSON = 1 << 2; public static final int NO_CONNECTION = 1 << 3; public static final int GENERIC_HTTP = 1 << 4; public static final int THREAD_ERROR = 1 << 5; public static final int RESPONSE_ERROR = 1 << 6; public static final int DUPLICATE_TORRENT = 1 << 7; public static final int INVALID_TORRENT = 1 << 8; public static final int TIMEOUT = 1 << 9; public static final int OUT_OF_MEMORY = 1 << 10; public static final int JSON_PARSE_ERROR = 1 << 11; } }