package com.atomjack.vcfp.services; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Intent; import android.content.IntentFilter; import android.os.AsyncTask; import android.os.IBinder; import android.support.v4.content.LocalBroadcastManager; import com.atomjack.shared.Logger; import com.atomjack.shared.NewLogger; import com.atomjack.shared.Preferences; import com.atomjack.vcfp.VoiceControlForPlexApplication; import com.atomjack.vcfp.interfaces.ActiveConnectionHandler; import com.atomjack.vcfp.model.Connection; import com.atomjack.vcfp.model.Device; import com.atomjack.vcfp.model.MediaContainer; import com.atomjack.vcfp.model.PlexClient; import com.atomjack.vcfp.model.PlexServer; import com.atomjack.vcfp.net.PlexHttpClient; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import retrofit.Call; import retrofit.Callback; import retrofit.Response; import retrofit.Retrofit; import us.nineworlds.serenity.GDMReceiver; public class PlexScannerService extends Service { public static final String ACTION_SCAN_SERVERS = "com.atomjack.vcfp.plexscannerservice.action_scan_servers"; public static final String ACTION_SCAN_CLIENTS = "com.atomjack.vcfp.plexscannerservice.action_scan_clients"; public static final String ACTION_SERVER_SCAN_FINISHED = "com.atomjack.vcfp.plexscannerservice.action_server_scan_finished"; public static final String REMOTE_SERVER_SCAN_UNAUTHORIZED = "com.atomjack.vcfp.plexscannerservice.remote_server_scan_unauthorized"; // public static final String ACTION_LOCAL_SERVER_SCAN_FINISHED = "com.atomjack.vcfp.plexscannerservice.action_local_server_scan_finished"; public static final String ACTION_CLIENT_SCAN_FINISHED = "com.atomjack.vcfp.plexscannerservice.action_client_scan_finished"; public static final String SCAN_TYPE = "com.atomjack.vcfp.plexscannerservice.scan_type"; // The class that is doing a scan. This class will have an intent sent back to it when scanning is done. public static final String CLASS = "com.atomjack.vcfp.plexscannerservice.class"; public static final String CONNECT_TO_CLIENT = "com.atomjack.vcfp.plexscannerservice.connect_to_client"; public static final String CANCEL = "com.atomjack.vcfp.plexscannerservice.cancel"; private NewLogger logger; private Class callingClass; private BroadcastReceiver gdmReceiver; private boolean localServerScanFinished = false; private boolean remoteServerScanFinished = false; private static boolean cancel = false; private ConcurrentHashMap<String, PlexServer> servers = new ConcurrentHashMap<String, PlexServer>(); private List<PlexClient> clients = new ArrayList<>(); @Override public int onStartCommand(Intent intent, int flags, int startId) { String action = intent.getAction(); if(action != null) { Logger.d("[PlexScannerService] Action: %s", action); if(action.equals(ACTION_SCAN_SERVERS)) { remoteServerScanFinished = false; localServerScanFinished = false; cancel = false; servers = new ConcurrentHashMap<>(); setClass(intent); scanForServers(); } else if(action.equals(ACTION_SCAN_CLIENTS)) { setClass(intent); scanForClients(intent.getBooleanExtra(com.atomjack.shared.Intent.EXTRA_CONNECT_TO_CLIENT, false)); } else if(action.equals(ACTION_SERVER_SCAN_FINISHED)) { List<PlexServer> localServers = intent.getParcelableArrayListExtra(com.atomjack.shared.Intent.EXTRA_SERVERS); Logger.d("local server scan finished with %d servers", localServers.size()); Logger.d("is logged in: %s", VoiceControlForPlexApplication.getInstance().isLoggedIn()); Logger.d("remoteServerScanFinished: %s", remoteServerScanFinished); localServerScanFinished = true; for(PlexServer ls : localServers) { if(!servers.containsKey(ls.machineIdentifier)) servers.put(ls.machineIdentifier, ls); } if(remoteServerScanFinished || !VoiceControlForPlexApplication.getInstance().isLoggedIn()) { onServerScanFinished(); } } else if(action.equals(ACTION_CLIENT_SCAN_FINISHED)) { clients = intent.getParcelableArrayListExtra(com.atomjack.shared.Intent.EXTRA_CLIENTS); onClientScanFinished(); } else if(action.equals(CANCEL)) { cancel = true; } } return Service.START_NOT_STICKY; } private void setClass(Intent intent) { callingClass = (Class) intent.getSerializableExtra(CLASS); } private void onServerScanFinished() { onScanFinished(ACTION_SERVER_SCAN_FINISHED); } private void onClientScanFinished() { onScanFinished(ACTION_CLIENT_SCAN_FINISHED); } private void onScanFinished(String type) { onScanFinished(type, false); } private void onScanFinished(String type, final boolean foundUnauthorized) { if(cancel) return; if(type == ACTION_SERVER_SCAN_FINISHED) { final int[] serversScanned = new int[1]; serversScanned[0] = 0; final int numServers = servers.size(); for (final PlexServer server : servers.values()) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { server.findServerConnection(true, new ActiveConnectionHandler() { @Override public void onSuccess(Connection connection) { Logger.d("Found active connection for %s: %s", server.name, connection); serversScanned[0]++; PlexHttpClient.PlexHttpService service = PlexHttpClient.getService(connection); Call<MediaContainer> call = service.getLibrarySections(server.accessToken); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { MediaContainer mc = response.body(); server.movieSections = new ArrayList<>(); server.tvSections = new ArrayList<>(); server.musicSections = new ArrayList<>(); for(int i=0;i<mc.directories.size();i++) { if(mc.directories.get(i).type.equals("movie")) { server.addMovieSection(mc.directories.get(i).key); } if(mc.directories.get(i).type.equals("show")) { server.addTvSection(mc.directories.get(i).key); } if(mc.directories.get(i).type.equals("artist")) { server.addMusicSection(mc.directories.get(i).key); } } Logger.d("%s has %d directories.", server.name, mc.directories != null ? mc.directories.size() : 0); if(!server.name.equals("")) { // Finally, if this server is the current default server, save it in preferences so the access token gets transferred PlexServer defaultServer = VoiceControlForPlexApplication.gsonRead.fromJson(VoiceControlForPlexApplication.getInstance().prefs.get(Preferences.SERVER, ""), PlexServer.class); if(defaultServer != null && server.machineIdentifier.equals(defaultServer.machineIdentifier)) { VoiceControlForPlexApplication.getInstance().prefs.put(Preferences.SERVER, VoiceControlForPlexApplication.gsonWrite.toJson(server)); } Logger.d("Added %s.", server.name); } // since we're finding server connections concurrently, in separate threads, we have to wait until all servers are done being scanned. if (serversScanned[0] >= numServers) { sendServerScanFinishedIntent(foundUnauthorized); } } @Override public void onFailure(Throwable t) { if (serversScanned[0] >= numServers) { sendServerScanFinishedIntent(foundUnauthorized); } } }); } @Override public void onFailure(int statusCode) { Logger.d("Couldn't find active connection for %s", server.name); // Remove this server from the servers we found servers.remove(server.machineIdentifier); serversScanned[0]++; if (serversScanned[0] >= numServers) { sendServerScanFinishedIntent(foundUnauthorized); } } }); return null; } }.execute(); } } else if(type == ACTION_CLIENT_SCAN_FINISHED) { Intent intent = new Intent(this, callingClass); intent.setAction(type); intent.putExtra(com.atomjack.shared.Intent.EXTRA_CLIENTS, (ArrayList<PlexClient>)clients); intent.addFlags(Intent.FLAG_FROM_BACKGROUND); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (callingClass.getSuperclass() == Service.class) startService(intent); else startActivity(intent); } } private void sendServerScanFinishedIntent(boolean foundUnauthorized) { Intent intent = new Intent(this, callingClass); Logger.d("[PlexScannerService] onScanFinished, have %d servers", servers.size()); HashMap<String, PlexServer> s = new HashMap<>(); for(PlexServer server : servers.values()) s.put(server.name, server); intent.putExtra(com.atomjack.shared.Intent.EXTRA_SERVERS, s); intent.setAction(ACTION_SERVER_SCAN_FINISHED); intent.putExtra(REMOTE_SERVER_SCAN_UNAUTHORIZED, foundUnauthorized); intent.addFlags(Intent.FLAG_FROM_BACKGROUND); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (callingClass.getSuperclass() == Service.class) startService(intent); else startActivity(intent); } private void scanForServers() { VoiceControlForPlexApplication.getInstance().unauthorizedLocalServersFound.clear(); if(VoiceControlForPlexApplication.getInstance().isLoggedIn()) { refreshResources(VoiceControlForPlexApplication.getInstance().prefs.getString(Preferences.AUTHENTICATION_TOKEN), new RefreshResourcesResponseHandler() { @Override public void onSuccess() { remoteServerScanFinished = true; if (localServerScanFinished) onServerScanFinished(); } @Override public void onFailure(int statusCode) { Logger.d("[PlexScannerService] failure: %d", statusCode); if (statusCode == 401) { // Unauthorized onScanFinished(ACTION_SERVER_SCAN_FINISHED, true); } } }); } Logger.d("Doing local scan for servers"); Intent mServiceIntent = new Intent(this, GDMService.class); mServiceIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // mServiceIntent.putExtra(com.atomjack.shared.Intent.EXTRA_SILENT, silent); mServiceIntent.putExtra(com.atomjack.shared.Intent.EXTRA_CLASS, PlexScannerService.class); mServiceIntent.putExtra(com.atomjack.shared.Intent.SCAN_TYPE, com.atomjack.shared.Intent.SCAN_TYPE_SERVER); startService(mServiceIntent); } public void scanForClients() { scanForClients(false); } private void scanForClients(boolean connectToClient) { Intent mServiceIntent = new Intent(this, GDMService.class); mServiceIntent.putExtra(GDMService.PORT, 32412); // Port for clients mServiceIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mServiceIntent.putExtra(com.atomjack.shared.Intent.EXTRA_CLASS, PlexScannerService.class); mServiceIntent.putExtra(com.atomjack.shared.Intent.EXTRA_CONNECT_TO_CLIENT, connectToClient); mServiceIntent.putExtra(com.atomjack.shared.Intent.SCAN_TYPE, com.atomjack.shared.Intent.SCAN_TYPE_CLIENT); startService(mServiceIntent); VoiceControlForPlexApplication.hasDoneClientScan = true; } @Override public void onCreate() { super.onCreate(); logger = new NewLogger(this); gdmReceiver = new GDMReceiver(); IntentFilter filters = new IntentFilter(); filters.addAction(GDMService.MSG_RECEIVED); filters.addAction(GDMService.SOCKET_CLOSED); filters.addAction(GDMReceiver.ACTION_CANCEL); LocalBroadcastManager.getInstance(this).registerReceiver(gdmReceiver, filters); } @Override public void onDestroy() { super.onDestroy(); if(gdmReceiver != null) { LocalBroadcastManager.getInstance(this).unregisterReceiver(gdmReceiver); } } @Override public IBinder onBind(Intent intent) { return null; } // Remote Scan methods public interface RefreshResourcesResponseHandler { void onSuccess(); void onFailure(int statusCode); } public void refreshResources(String authToken, final RefreshResourcesResponseHandler responseHandler) { refreshResources(authToken, responseHandler, false); } public void refreshResources(String authToken) { refreshResources(authToken, null, true); } public void refreshResources(String authToken, final RefreshResourcesResponseHandler responseHandler, boolean silent) { Logger.d("Fetching resources from plex.tv"); cancel = false; VoiceControlForPlexApplication.hasDoneClientScan = true; PlexHttpClient.PlexHttpService service = PlexHttpClient.getService("https://plex.tv"); Call<MediaContainer> call = service.getResources(authToken); call.enqueue(new Callback<MediaContainer>() { @Override public void onResponse(Response<MediaContainer> response, Retrofit retrofit) { try { if(cancel) { cancel = false; return; } MediaContainer mediaContainer = response.body(); Logger.d("got %d devices", mediaContainer.devices.size()); // List<PlexServer> servers = new ArrayList<PlexServer>(); for(final Device device : mediaContainer.devices) { if(device.lastSeenAt < System.currentTimeMillis()/1000 - (60*60*24)) continue; if(device.provides.contains("server")) { PlexServer server = PlexServer.fromDevice(device); Logger.d("Device %s is a server, has %d connections (owned: %s)", server.name, server.connections.size(), server.owned); if(!servers.containsKey(server.machineIdentifier)) servers.put(server.machineIdentifier, server); if(VoiceControlForPlexApplication.getInstance().unauthorizedLocalServersFound.contains(server.machineIdentifier)) VoiceControlForPlexApplication.getInstance().unauthorizedLocalServersFound.remove(server.machineIdentifier); } else if(device.provides.contains("player")) { Logger.d("Device %s is a player", device.name); } } responseHandler.onSuccess(); /* final int[] serversScanned = new int[1]; serversScanned[0] = 0; final int numServers = servers.size(); for(final PlexServer server : servers) { VoiceControlForPlexApplication.addPlexServer(server, new Runnable() { @Override public void run() { Logger.d("Done scanning %s", server.name); serversScanned[0]++; Logger.d("%d out of %d servers scanned", serversScanned[0], numServers); if(serversScanned[0] >= numServers && responseHandler != null) responseHandler.onSuccess(); } }); } */ } catch (Exception e) { if(responseHandler != null) responseHandler.onFailure(0); } } @Override public void onFailure(Throwable t) { Logger.d("Failure getting resources."); t.printStackTrace(); if(cancel) { cancel = false; return; } if(responseHandler != null) responseHandler.onFailure(0); } }); } }