From b690dc54743464670e64087179989c3b5522241e Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 27 Oct 2018 02:18:33 -0700 Subject: [PATCH] Rewrite reachability code and computer DB to bring it inline with other modern Moonlight clients --- app/src/main/java/com/limelight/PcView.java | 14 +- .../computers/ComputerDatabaseManager.java | 101 +++----- .../computers/ComputerManagerService.java | 232 +++++------------- .../computers/LegacyDatabaseReader.java | 113 +++++++++ .../com/limelight/grid/PcGridAdapter.java | 2 +- .../com/limelight/utils/ServerHelper.java | 6 +- moonlight-common | 2 +- 7 files changed, 224 insertions(+), 246 deletions(-) create mode 100644 app/src/main/java/com/limelight/computers/LegacyDatabaseReader.java diff --git a/app/src/main/java/com/limelight/PcView.java b/app/src/main/java/com/limelight/PcView.java index 30eb382f..2ce51d4f 100644 --- a/app/src/main/java/com/limelight/PcView.java +++ b/app/src/main/java/com/limelight/PcView.java @@ -313,8 +313,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position); // Inflate the context menu - if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE || - computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) { + if (computer.details.state == ComputerDetails.State.OFFLINE || + computer.details.state == ComputerDetails.State.UNKNOWN) { menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol)); menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc)); } @@ -346,7 +346,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { } private void doPair(final ComputerDetails computer) { - if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { + if (computer.state == ComputerDetails.State.OFFLINE) { Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show(); return; } @@ -478,7 +478,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { } private void doUnpair(final ComputerDetails computer) { - if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { + if (computer.state == ComputerDetails.State.OFFLINE) { Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); return; } @@ -530,7 +530,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { } private void doAppList(ComputerDetails computer) { - if (computer.reachability == ComputerDetails.Reachability.OFFLINE) { + if (computer.state == ComputerDetails.State.OFFLINE) { Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show(); return; } @@ -690,8 +690,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { public void onItemClick(AdapterView arg0, View arg1, int pos, long id) { ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos); - if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN || - computer.details.reachability == ComputerDetails.Reachability.OFFLINE) { + if (computer.details.state == ComputerDetails.State.UNKNOWN || + computer.details.state == ComputerDetails.State.OFFLINE) { // Open the context menu if a PC is offline or refreshing openContextMenu(arg1); } else if (computer.details.pairState != PairState.PAIRED) { diff --git a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java index 54a8d88f..4c523ce4 100644 --- a/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java +++ b/app/src/main/java/com/limelight/computers/ComputerDatabaseManager.java @@ -1,7 +1,5 @@ package com.limelight.computers; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -17,15 +15,14 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; public class ComputerDatabaseManager { - private static final String COMPUTER_DB_NAME = "computers.db"; + private static final String COMPUTER_DB_NAME = "computers2.db"; private static final String COMPUTER_TABLE_NAME = "Computers"; - private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName"; private static final String COMPUTER_UUID_COLUMN_NAME = "UUID"; - private static final String LOCAL_IP_COLUMN_NAME = "LocalIp"; - private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp"; - private static final String MAC_COLUMN_NAME = "Mac"; - - private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__"; + private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName"; + private static final String LOCAL_ADDRESS_COLUMN_NAME = "LocalAddress"; + private static final String REMOTE_ADDRESS_COLUMN_NAME = "RemoteAddress"; + private static final String MANUAL_ADDRESS_COLUMN_NAME = "ManualAddress"; + private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress"; private SQLiteDatabase computerDb; @@ -38,20 +35,27 @@ public class ComputerDatabaseManager { c.deleteDatabase(COMPUTER_DB_NAME); computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); } - initializeDb(); + initializeDb(c); } public void close() { computerDb.close(); } - private void initializeDb() { + private void initializeDb(Context c) { // Create tables if they aren't already there - computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," + - " %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL)", + computerDb.execSQL(String.format((Locale)null, + "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY, %s TEXT NOT NULL, %s TEXT, %s TEXT, %s TEXT, %s TEXT)", COMPUTER_TABLE_NAME, - COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME, - REMOTE_IP_COLUMN_NAME, MAC_COLUMN_NAME)); + COMPUTER_UUID_COLUMN_NAME, COMPUTER_NAME_COLUMN_NAME, + LOCAL_ADDRESS_COLUMN_NAME, REMOTE_ADDRESS_COLUMN_NAME, MANUAL_ADDRESS_COLUMN_NAME, + MAC_ADDRESS_COLUMN_NAME)); + + // Move all computers from the old DB (if any) to the new one + List oldComputers = LegacyDatabaseReader.migrateAllComputers(c); + for (ComputerDetails computer : oldComputers) { + updateComputer(computer); + } } public void deleteComputer(String name) { @@ -60,20 +64,19 @@ public class ComputerDatabaseManager { public boolean updateComputer(ComputerDetails details) { ContentValues values = new ContentValues(); - values.put(COMPUTER_NAME_COLUMN_NAME, details.name); values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString()); - values.put(LOCAL_IP_COLUMN_NAME, ADDRESS_PREFIX+details.localAddress); - values.put(REMOTE_IP_COLUMN_NAME, ADDRESS_PREFIX+details.remoteAddress); - values.put(MAC_COLUMN_NAME, details.macAddress); + values.put(COMPUTER_NAME_COLUMN_NAME, details.name); + values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress); + values.put(REMOTE_ADDRESS_COLUMN_NAME, details.remoteAddress); + values.put(MANUAL_ADDRESS_COLUMN_NAME, details.manualAddress); + values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress); return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); } private ComputerDetails getComputerFromCursor(Cursor c) { ComputerDetails details = new ComputerDetails(); - details.name = c.getString(0); - - String uuidStr = c.getString(1); + String uuidStr = c.getString(0); try { details.uuid = UUID.fromString(uuidStr); } catch (IllegalArgumentException e) { @@ -81,43 +84,14 @@ public class ComputerDatabaseManager { LimeLog.severe("DB: Corrupted UUID for "+details.name); } - // An earlier schema defined addresses as byte blobs. We'll - // gracefully migrate those to strings so we can store DNS names - // too. To disambiguate, we'll need to prefix them with a string - // greater than the allowable IP address length. - try { - details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress(); - LimeLog.warning("DB: Legacy local address for "+details.name); - } catch (UnknownHostException e) { - // This is probably a hostname/address with the prefix string - String stringData = c.getString(2); - if (stringData.startsWith(ADDRESS_PREFIX)) { - details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length()); - } - else { - LimeLog.severe("DB: Corrupted local address for "+details.name); - } - } - - try { - details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress(); - LimeLog.warning("DB: Legacy remote address for "+details.name); - } catch (UnknownHostException e) { - // This is probably a hostname/address with the prefix string - String stringData = c.getString(3); - if (stringData.startsWith(ADDRESS_PREFIX)) { - details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length()); - } - else { - LimeLog.severe("DB: Corrupted local address for "+details.name); - } - } - - details.macAddress = c.getString(4); + details.name = c.getString(1); + details.localAddress = c.getString(2); + details.remoteAddress = c.getString(3); + details.manualAddress = c.getString(4); + details.macAddress = c.getString(5); // This signifies we don't have dynamic state (like pair state) details.state = ComputerDetails.State.UNKNOWN; - details.reachability = ComputerDetails.Reachability.UNKNOWN; return details; } @@ -128,13 +102,11 @@ public class ComputerDatabaseManager { while (c.moveToNext()) { ComputerDetails details = getComputerFromCursor(c); - // If a field is corrupt or missing, skip the database entry - if (details.uuid == null || details.localAddress == null || details.remoteAddress == null || - details.macAddress == null) { + // If a critical field is corrupt or missing, skip the database entry + if (details.uuid == null) { continue; } - computerList.add(details); } @@ -143,8 +115,8 @@ public class ComputerDatabaseManager { return computerList; } - public ComputerDetails getComputerByName(String name) { - Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name}, null, null, null); + public ComputerDetails getComputerByUUID(UUID uuid) { + Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid.toString() }, null, null, null); if (!c.moveToFirst()) { // No matching computer c.close(); @@ -154,9 +126,8 @@ public class ComputerDatabaseManager { ComputerDetails details = getComputerFromCursor(c); c.close(); - // If a field is corrupt or missing, delete the database entry - if (details.uuid == null || details.localAddress == null || details.remoteAddress == null || - details.macAddress == null) { + // If a critical field is corrupt or missing, delete the database entry + if (details.uuid == null) { deleteComputer(details.name); return null; } diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerService.java b/app/src/main/java/com/limelight/computers/ComputerManagerService.java index d588dd0e..0bd8faee 100644 --- a/app/src/main/java/com/limelight/computers/ComputerManagerService.java +++ b/app/src/main/java/com/limelight/computers/ComputerManagerService.java @@ -3,10 +3,8 @@ package com.limelight.computers; import java.io.IOException; import java.io.OutputStream; import java.io.StringReader; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.UnknownHostException; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -95,7 +93,6 @@ public class ComputerManagerService extends Service { } details.state = ComputerDetails.State.OFFLINE; - details.reachability = ComputerDetails.Reachability.OFFLINE; } } catch (InterruptedException e) { releaseLocalDatabaseReference(); @@ -109,7 +106,7 @@ public class ComputerManagerService extends Service { if (!newPc) { // Check if it's in the database because it could have been // removed after this was issued - if (dbManager.getComputerByName(details.name) == null) { + if (dbManager.getComputerByUUID(details.uuid) == null) { // It's gone releaseLocalDatabaseReference(); return false; @@ -156,7 +153,7 @@ public class ComputerManagerService extends Service { } } }; - t.setName("Polling thread for " + tuple.computer.localAddress); + t.setName("Polling thread for " + tuple.computer.name); return t; } @@ -177,7 +174,6 @@ public class ComputerManagerService extends Service { if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) { LimeLog.info("Timing out polled state for "+tuple.computer.name); tuple.computer.state = ComputerDetails.State.UNKNOWN; - tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN; } // Report this computer initially @@ -253,7 +249,6 @@ public class ComputerManagerService extends Service { // from wiping this change out synchronized (tuple.networkLock) { tuple.computer.state = ComputerDetails.State.UNKNOWN; - tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN; } } } @@ -312,17 +307,8 @@ public class ComputerManagerService extends Service { for (PollingTuple tuple : pollingTuples) { // Check if this is the same computer if (tuple.computer.uuid.equals(details.uuid)) { - if (manuallyAdded) { - // Update details anyway in case this machine has been re-added by IP - // after not being reachable by our existing information - tuple.computer.localAddress = details.localAddress; - tuple.computer.remoteAddress = details.remoteAddress; - } - else { - // This indicates that mDNS discovered this address, so we - // should only apply the local address. - tuple.computer.localAddress = details.localAddress; - } + // Update the saved computer with potentially new details + tuple.computer.update(details); // Start a polling thread if polling is active if (pollingActive && tuple.thread == null) { @@ -350,8 +336,15 @@ public class ComputerManagerService extends Service { public boolean addComputerBlocking(String addr, boolean manuallyAdded) { // Setup a placeholder ComputerDetails fakeDetails = new ComputerDetails(); - fakeDetails.localAddress = addr; - fakeDetails.remoteAddress = addr; + + if (manuallyAdded) { + // Add PC UI + fakeDetails.manualAddress = addr; + } + else { + // mDNS + fakeDetails.localAddress = addr; + } // Block while we try to fill the details try { @@ -438,6 +431,9 @@ public class ComputerManagerService extends Service { return null; } + // Set the new active address + newDetails.activeAddress = address; + return newDetails; } catch (Exception e) { return null; @@ -447,6 +443,11 @@ public class ComputerManagerService extends Service { // Just try to establish a TCP connection to speculatively detect a running // GFE server private boolean fastPollIp(String address) { + if (address == null) { + // Don't bother if our address is null + return false; + } + Socket s = new Socket(); try { s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT); @@ -475,12 +476,14 @@ public class ComputerManagerService extends Service { t.start(); } - private ComputerDetails.Reachability fastPollPc(final String localAddress, final String remoteAddress) throws InterruptedException { + private String fastPollPc(final String localAddress, final String remoteAddress, final String manualAddress) throws InterruptedException { final boolean[] remoteInfo = new boolean[2]; final boolean[] localInfo = new boolean[2]; + final boolean[] manualInfo = new boolean[2]; startFastPollThread(localAddress, localInfo); startFastPollThread(remoteAddress, remoteInfo); + startFastPollThread(manualAddress, manualInfo); // Check local first synchronized (localInfo) { @@ -489,174 +492,67 @@ public class ComputerManagerService extends Service { } if (localInfo[1]) { - return ComputerDetails.Reachability.LOCAL; + return localAddress; } } - // Now remote + // Now manual + synchronized (manualInfo) { + while (!manualInfo[0]) { + manualInfo.wait(500); + } + + if (manualInfo[1]) { + return manualAddress; + } + } + + // And finally, remote synchronized (remoteInfo) { while (!remoteInfo[0]) { remoteInfo.wait(500); } if (remoteInfo[1]) { - return ComputerDetails.Reachability.REMOTE; + return remoteAddress; } } - return ComputerDetails.Reachability.OFFLINE; - } - - private static boolean isAddressLikelyLocal(String str) { - try { - // This will tend to be wrong for IPv6 but falling back to - // remote will be fine in that case. For IPv4, it should be - // pretty accurate due to NAT prevalence. - InetAddress addr = InetAddress.getByName(str); - return addr.isSiteLocalAddress() || addr.isLinkLocalAddress(); - } catch (UnknownHostException e) { - e.printStackTrace(); - return false; - } - } - - private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException { - ComputerDetails polledDetails; - ComputerDetails.Reachability reachability; - - if (details.localAddress.equals(details.remoteAddress)) { - reachability = isAddressLikelyLocal(details.localAddress) ? - ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE; - } - else { - // Do a TCP-level connection to the HTTP server to see if it's listening - LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +")"); - reachability = fastPollPc(details.localAddress, details.remoteAddress); - LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString()); - - // If no connection could be established to either IP address, there's nothing we can do - if (reachability == ComputerDetails.Reachability.OFFLINE) { - return null; - } - } - - boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL); - - if (localFirst) { - polledDetails = tryPollIp(details, details.localAddress); - } - else { - polledDetails = tryPollIp(details, details.remoteAddress); - } - - String reachableAddr = null; - if (polledDetails == null && !details.localAddress.equals(details.remoteAddress)) { - // Failed, so let's try the fallback - if (!localFirst) { - polledDetails = tryPollIp(details, details.localAddress); - } - else { - polledDetails = tryPollIp(details, details.remoteAddress); - } - - if (polledDetails != null) { - // The fallback poll worked - reachableAddr = !localFirst ? details.localAddress : details.remoteAddress; - } - } - else if (polledDetails != null) { - reachableAddr = localFirst ? details.localAddress : details.remoteAddress; - } - - if (reachableAddr == null) { - return null; - } - - // If both addresses are the same, guess whether we're local based on - // IP address heuristics. - if (reachableAddr.equals(polledDetails.localAddress) && - reachableAddr.equals(polledDetails.remoteAddress)) { - polledDetails.reachability = isAddressLikelyLocal(reachableAddr) ? - ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE; - } - else if (polledDetails.remoteAddress.equals(reachableAddr)) { - polledDetails.reachability = ComputerDetails.Reachability.REMOTE; - } - else if (polledDetails.localAddress.equals(reachableAddr)) { - polledDetails.reachability = ComputerDetails.Reachability.LOCAL; - } - else { - polledDetails.reachability = ComputerDetails.Reachability.UNKNOWN; - } - - return new ReachabilityTuple(polledDetails, reachableAddr); + return null; } private boolean pollComputer(ComputerDetails details) throws InterruptedException { - ReachabilityTuple initialReachTuple = pollForReachability(details); - if (initialReachTuple == null) { + ComputerDetails polledDetails; + + // Do a TCP-level connection to the HTTP server to see if it's listening + LimeLog.info("Starting fast poll for "+details.name+" ("+details.localAddress +", "+details.remoteAddress +", "+details.manualAddress +")"); + details.activeAddress = fastPollPc(details.localAddress, details.remoteAddress, details.manualAddress); + LimeLog.info("Fast poll for "+details.name+" returned active address: "+details.activeAddress); + + // If no connection could be established to either IP address, there's nothing we can do + if (details.activeAddress == null) { return false; } - if (initialReachTuple.computer.reachability == ComputerDetails.Reachability.UNKNOWN) { - // Neither IP address reported in the serverinfo response was the one we used. - // Poll again to see if we can contact this machine on either of its reported addresses. - ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer); - if (confirmationReachTuple == null) { - // Neither of those seem to work, so we'll hold onto the address that did work - initialReachTuple.computer.localAddress = initialReachTuple.reachableAddress; - initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL; - } - else { - // We got it on one of the returned addresses; replace the original reach tuple - // with the new one - initialReachTuple = confirmationReachTuple; - } + // Try using the active address from fast-poll + polledDetails = tryPollIp(details, details.activeAddress); + if (polledDetails == null && details.localAddress != null && !details.localAddress.equals(details.activeAddress)) { + polledDetails = tryPollIp(details, details.localAddress); + } + if (polledDetails == null && details.manualAddress != null && !details.manualAddress.equals(details.activeAddress)) { + polledDetails = tryPollIp(details, details.manualAddress); + } + if (polledDetails == null && details.remoteAddress != null && !details.remoteAddress.equals(details.activeAddress)) { + polledDetails = tryPollIp(details, details.remoteAddress); } - // Save some details about the old state of the PC that we may wish - // to restore later. - String savedMacAddress = details.macAddress; - String savedLocalAddress = details.localAddress; - String savedRemoteAddress = details.remoteAddress; - - // If we got here, it's reachable - details.update(initialReachTuple.computer); - - // If the new MAC address is empty, restore the old one (workaround for GFE bug) - if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) { - LimeLog.info("MAC address was empty; using existing value: "+savedMacAddress); - details.macAddress = savedMacAddress; + if (polledDetails != null) { + details.update(polledDetails); + return true; } - - // We never want to lose IP addresses by polling server info. If we get a poll back - // where localAddress == remoteAddress but savedLocalAddress != savedRemoteAddress, - // then we've lost an address in the polling and we should restore the one that's missing. - if (details.localAddress.equals(details.remoteAddress) && - !savedLocalAddress.equals(savedRemoteAddress)) { - if (details.localAddress.equals(savedLocalAddress)) { - // Local addresses are identical, so put the old remote address back - details.remoteAddress = savedRemoteAddress; - } - else if (details.remoteAddress.equals(savedRemoteAddress)) { - // Remote addresses are identical, so put the old local address back - details.localAddress = savedLocalAddress; - } - else { - // Neither IP address match. Let's restore the remote address to be safe. - details.remoteAddress = savedRemoteAddress; - } - - // Now update the reachability so the correct address is used - if (details.localAddress.equals(initialReachTuple.reachableAddress)) { - details.reachability = ComputerDetails.Reachability.LOCAL; - } - else { - details.reachability = ComputerDetails.Reachability.REMOTE; - } + else { + return false; } - - return true; } @Override @@ -841,7 +737,7 @@ public class ComputerManagerService extends Service { } while (waitPollingDelay()); } }; - thread.setName("App list polling thread for " + computer.localAddress); + thread.setName("App list polling thread for " + computer.name); thread.start(); } diff --git a/app/src/main/java/com/limelight/computers/LegacyDatabaseReader.java b/app/src/main/java/com/limelight/computers/LegacyDatabaseReader.java new file mode 100644 index 00000000..98af3556 --- /dev/null +++ b/app/src/main/java/com/limelight/computers/LegacyDatabaseReader.java @@ -0,0 +1,113 @@ +package com.limelight.computers; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; + +import com.limelight.LimeLog; +import com.limelight.nvstream.http.ComputerDetails; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +public class LegacyDatabaseReader { + private static final String COMPUTER_DB_NAME = "computers.db"; + private static final String COMPUTER_TABLE_NAME = "Computers"; + + private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__"; + + private static ComputerDetails getComputerFromCursor(Cursor c) { + ComputerDetails details = new ComputerDetails(); + + details.name = c.getString(0); + + String uuidStr = c.getString(1); + try { + details.uuid = UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + // We'll delete this entry + LimeLog.severe("DB: Corrupted UUID for " + details.name); + } + + // An earlier schema defined addresses as byte blobs. We'll + // gracefully migrate those to strings so we can store DNS names + // too. To disambiguate, we'll need to prefix them with a string + // greater than the allowable IP address length. + try { + details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress(); + LimeLog.warning("DB: Legacy local address for " + details.name); + } catch (UnknownHostException e) { + // This is probably a hostname/address with the prefix string + String stringData = c.getString(2); + if (stringData.startsWith(ADDRESS_PREFIX)) { + details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length()); + } else { + LimeLog.severe("DB: Corrupted local address for " + details.name); + } + } + + try { + details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress(); + LimeLog.warning("DB: Legacy remote address for " + details.name); + } catch (UnknownHostException e) { + // This is probably a hostname/address with the prefix string + String stringData = c.getString(3); + if (stringData.startsWith(ADDRESS_PREFIX)) { + details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length()); + } else { + LimeLog.severe("DB: Corrupted remote address for " + details.name); + } + } + + // On older versions of Moonlight, this is typically where manual addresses got stored, + // so let's initialize it just to be safe. + details.manualAddress = details.remoteAddress; + + details.macAddress = c.getString(4); + + // This signifies we don't have dynamic state (like pair state) + details.state = ComputerDetails.State.UNKNOWN; + + return details; + } + + private static List getAllComputers(SQLiteDatabase db) { + Cursor c = db.rawQuery("SELECT * FROM " + COMPUTER_TABLE_NAME, null); + LinkedList computerList = new LinkedList<>(); + while (c.moveToNext()) { + ComputerDetails details = getComputerFromCursor(c); + + // If a critical field is corrupt or missing, skip the database entry + if (details.uuid == null) { + continue; + } + + computerList.add(details); + } + + c.close(); + + return computerList; + } + + public static List migrateAllComputers(Context c) { + SQLiteDatabase computerDb = null; + try { + // Open the existing database + computerDb = SQLiteDatabase.openDatabase(c.getDatabasePath(COMPUTER_DB_NAME).getPath(), null, SQLiteDatabase.OPEN_READONLY); + return getAllComputers(computerDb); + } catch (SQLiteException e) { + return new LinkedList(); + } finally { + // Close and delete the old DB + if (computerDb != null) { + computerDb.close(); + } + c.deleteDatabase(COMPUTER_DB_NAME); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/limelight/grid/PcGridAdapter.java b/app/src/main/java/com/limelight/grid/PcGridAdapter.java index 1abcf515..c5c785a6 100644 --- a/app/src/main/java/com/limelight/grid/PcGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/PcGridAdapter.java @@ -46,7 +46,7 @@ public class PcGridAdapter extends GenericGridAdapter { imgView.setAlpha(0.4f); } - if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) { + if (obj.details.state == ComputerDetails.State.UNKNOWN) { prgView.setVisibility(View.VISIBLE); } else { diff --git a/app/src/main/java/com/limelight/utils/ServerHelper.java b/app/src/main/java/com/limelight/utils/ServerHelper.java index 658ac242..8ac924ef 100644 --- a/app/src/main/java/com/limelight/utils/ServerHelper.java +++ b/app/src/main/java/com/limelight/utils/ServerHelper.java @@ -18,8 +18,7 @@ import java.net.UnknownHostException; public class ServerHelper { public static String getCurrentAddressFromComputer(ComputerDetails computer) { - return computer.reachability == ComputerDetails.Reachability.LOCAL ? - computer.localAddress : computer.remoteAddress; + return computer.activeAddress; } public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer, @@ -30,8 +29,7 @@ public class ServerHelper { intent.putExtra(Game.EXTRA_APP_ID, app.getAppId()); intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported()); intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); - intent.putExtra(Game.EXTRA_STREAMING_REMOTE, - computer.reachability != ComputerDetails.Reachability.LOCAL); + intent.putExtra(Game.EXTRA_STREAMING_REMOTE, getCurrentAddressFromComputer(computer).equals(computer.remoteAddress)); intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString()); intent.putExtra(Game.EXTRA_PC_NAME, computer.name); return intent; diff --git a/moonlight-common b/moonlight-common index 31d6b4cb..dd6e76c0 160000 --- a/moonlight-common +++ b/moonlight-common @@ -1 +1 @@ -Subproject commit 31d6b4cb9f216c799c4fc2f01a96feb03c49c328 +Subproject commit dd6e76c0f272e509ece1602e30a6e71f51ae3205