Rewrite reachability code and computer DB to bring it inline with other modern Moonlight clients

This commit is contained in:
Cameron Gutman 2018-10-27 02:18:33 -07:00
parent c2fbe6ad91
commit b690dc5474
7 changed files with 224 additions and 246 deletions

View File

@ -313,8 +313,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
// Inflate the context menu // Inflate the context menu
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE || if (computer.details.state == ComputerDetails.State.OFFLINE ||
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) { 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, 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)); 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) { 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(); Toast.makeText(PcView.this, getResources().getString(R.string.pair_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
@ -478,7 +478,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
private void doUnpair(final ComputerDetails computer) { 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(); Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
@ -530,7 +530,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
} }
private void doAppList(ComputerDetails computer) { 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(); Toast.makeText(PcView.this, getResources().getString(R.string.error_pc_offline), Toast.LENGTH_SHORT).show();
return; return;
} }
@ -690,8 +690,8 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
public void onItemClick(AdapterView<?> arg0, View arg1, int pos, public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) { long id) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos); ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN || if (computer.details.state == ComputerDetails.State.UNKNOWN ||
computer.details.reachability == ComputerDetails.Reachability.OFFLINE) { computer.details.state == ComputerDetails.State.OFFLINE) {
// Open the context menu if a PC is offline or refreshing // Open the context menu if a PC is offline or refreshing
openContextMenu(arg1); openContextMenu(arg1);
} else if (computer.details.pairState != PairState.PAIRED) { } else if (computer.details.pairState != PairState.PAIRED) {

View File

@ -1,7 +1,5 @@
package com.limelight.computers; package com.limelight.computers;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -17,15 +15,14 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
public class ComputerDatabaseManager { 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_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 COMPUTER_UUID_COLUMN_NAME = "UUID";
private static final String LOCAL_IP_COLUMN_NAME = "LocalIp"; private static final String COMPUTER_NAME_COLUMN_NAME = "ComputerName";
private static final String REMOTE_IP_COLUMN_NAME = "RemoteIp"; private static final String LOCAL_ADDRESS_COLUMN_NAME = "LocalAddress";
private static final String MAC_COLUMN_NAME = "Mac"; private static final String REMOTE_ADDRESS_COLUMN_NAME = "RemoteAddress";
private static final String MANUAL_ADDRESS_COLUMN_NAME = "ManualAddress";
private static final String ADDRESS_PREFIX = "ADDRESS_PREFIX__"; private static final String MAC_ADDRESS_COLUMN_NAME = "MacAddress";
private SQLiteDatabase computerDb; private SQLiteDatabase computerDb;
@ -38,20 +35,27 @@ public class ComputerDatabaseManager {
c.deleteDatabase(COMPUTER_DB_NAME); c.deleteDatabase(COMPUTER_DB_NAME);
computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null); computerDb = c.openOrCreateDatabase(COMPUTER_DB_NAME, 0, null);
} }
initializeDb(); initializeDb(c);
} }
public void close() { public void close() {
computerDb.close(); computerDb.close();
} }
private void initializeDb() { private void initializeDb(Context c) {
// Create tables if they aren't already there // Create tables if they aren't already there
computerDb.execSQL(String.format((Locale)null, "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY," + computerDb.execSQL(String.format((Locale)null,
" %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT NULL, %s TEXT NOT 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_TABLE_NAME,
COMPUTER_NAME_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, LOCAL_IP_COLUMN_NAME, COMPUTER_UUID_COLUMN_NAME, COMPUTER_NAME_COLUMN_NAME,
REMOTE_IP_COLUMN_NAME, MAC_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<ComputerDetails> oldComputers = LegacyDatabaseReader.migrateAllComputers(c);
for (ComputerDetails computer : oldComputers) {
updateComputer(computer);
}
} }
public void deleteComputer(String name) { public void deleteComputer(String name) {
@ -60,20 +64,19 @@ public class ComputerDatabaseManager {
public boolean updateComputer(ComputerDetails details) { public boolean updateComputer(ComputerDetails details) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString()); values.put(COMPUTER_UUID_COLUMN_NAME, details.uuid.toString());
values.put(LOCAL_IP_COLUMN_NAME, ADDRESS_PREFIX+details.localAddress); values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
values.put(REMOTE_IP_COLUMN_NAME, ADDRESS_PREFIX+details.remoteAddress); values.put(LOCAL_ADDRESS_COLUMN_NAME, details.localAddress);
values.put(MAC_COLUMN_NAME, details.macAddress); 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); return -1 != computerDb.insertWithOnConflict(COMPUTER_TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
private ComputerDetails getComputerFromCursor(Cursor c) { private ComputerDetails getComputerFromCursor(Cursor c) {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
details.name = c.getString(0); String uuidStr = c.getString(0);
String uuidStr = c.getString(1);
try { try {
details.uuid = UUID.fromString(uuidStr); details.uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -81,43 +84,14 @@ public class ComputerDatabaseManager {
LimeLog.severe("DB: Corrupted UUID for "+details.name); LimeLog.severe("DB: Corrupted UUID for "+details.name);
} }
// An earlier schema defined addresses as byte blobs. We'll details.name = c.getString(1);
// gracefully migrate those to strings so we can store DNS names details.localAddress = c.getString(2);
// too. To disambiguate, we'll need to prefix them with a string details.remoteAddress = c.getString(3);
// greater than the allowable IP address length. details.manualAddress = c.getString(4);
try { details.macAddress = c.getString(5);
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);
// This signifies we don't have dynamic state (like pair state) // This signifies we don't have dynamic state (like pair state)
details.state = ComputerDetails.State.UNKNOWN; details.state = ComputerDetails.State.UNKNOWN;
details.reachability = ComputerDetails.Reachability.UNKNOWN;
return details; return details;
} }
@ -128,13 +102,11 @@ public class ComputerDatabaseManager {
while (c.moveToNext()) { while (c.moveToNext()) {
ComputerDetails details = getComputerFromCursor(c); ComputerDetails details = getComputerFromCursor(c);
// If a field is corrupt or missing, skip the database entry // If a critical field is corrupt or missing, skip the database entry
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null || if (details.uuid == null) {
details.macAddress == null) {
continue; continue;
} }
computerList.add(details); computerList.add(details);
} }
@ -143,8 +115,8 @@ public class ComputerDatabaseManager {
return computerList; return computerList;
} }
public ComputerDetails getComputerByName(String name) { public ComputerDetails getComputerByUUID(UUID uuid) {
Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_NAME_COLUMN_NAME+"=?", new String[]{name}, null, null, null); Cursor c = computerDb.query(COMPUTER_TABLE_NAME, null, COMPUTER_UUID_COLUMN_NAME+"=?", new String[]{ uuid.toString() }, null, null, null);
if (!c.moveToFirst()) { if (!c.moveToFirst()) {
// No matching computer // No matching computer
c.close(); c.close();
@ -154,9 +126,8 @@ public class ComputerDatabaseManager {
ComputerDetails details = getComputerFromCursor(c); ComputerDetails details = getComputerFromCursor(c);
c.close(); c.close();
// If a field is corrupt or missing, delete the database entry // If a critical field is corrupt or missing, delete the database entry
if (details.uuid == null || details.localAddress == null || details.remoteAddress == null || if (details.uuid == null) {
details.macAddress == null) {
deleteComputer(details.name); deleteComputer(details.name);
return null; return null;
} }

View File

@ -3,10 +3,8 @@ package com.limelight.computers;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.StringReader; import java.io.StringReader;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -95,7 +93,6 @@ public class ComputerManagerService extends Service {
} }
details.state = ComputerDetails.State.OFFLINE; details.state = ComputerDetails.State.OFFLINE;
details.reachability = ComputerDetails.Reachability.OFFLINE;
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
releaseLocalDatabaseReference(); releaseLocalDatabaseReference();
@ -109,7 +106,7 @@ public class ComputerManagerService extends Service {
if (!newPc) { if (!newPc) {
// Check if it's in the database because it could have been // Check if it's in the database because it could have been
// removed after this was issued // removed after this was issued
if (dbManager.getComputerByName(details.name) == null) { if (dbManager.getComputerByUUID(details.uuid) == null) {
// It's gone // It's gone
releaseLocalDatabaseReference(); releaseLocalDatabaseReference();
return false; 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; return t;
} }
@ -177,7 +174,6 @@ public class ComputerManagerService extends Service {
if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) { if (System.currentTimeMillis() - tuple.lastSuccessfulPollMs > POLL_DATA_TTL_MS) {
LimeLog.info("Timing out polled state for "+tuple.computer.name); LimeLog.info("Timing out polled state for "+tuple.computer.name);
tuple.computer.state = ComputerDetails.State.UNKNOWN; tuple.computer.state = ComputerDetails.State.UNKNOWN;
tuple.computer.reachability = ComputerDetails.Reachability.UNKNOWN;
} }
// Report this computer initially // Report this computer initially
@ -253,7 +249,6 @@ public class ComputerManagerService extends Service {
// from wiping this change out // from wiping this change out
synchronized (tuple.networkLock) { synchronized (tuple.networkLock) {
tuple.computer.state = ComputerDetails.State.UNKNOWN; 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) { for (PollingTuple tuple : pollingTuples) {
// Check if this is the same computer // Check if this is the same computer
if (tuple.computer.uuid.equals(details.uuid)) { if (tuple.computer.uuid.equals(details.uuid)) {
if (manuallyAdded) { // Update the saved computer with potentially new details
// Update details anyway in case this machine has been re-added by IP tuple.computer.update(details);
// 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;
}
// Start a polling thread if polling is active // Start a polling thread if polling is active
if (pollingActive && tuple.thread == null) { if (pollingActive && tuple.thread == null) {
@ -350,8 +336,15 @@ public class ComputerManagerService extends Service {
public boolean addComputerBlocking(String addr, boolean manuallyAdded) { public boolean addComputerBlocking(String addr, boolean manuallyAdded) {
// Setup a placeholder // Setup a placeholder
ComputerDetails fakeDetails = new ComputerDetails(); 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 // Block while we try to fill the details
try { try {
@ -438,6 +431,9 @@ public class ComputerManagerService extends Service {
return null; return null;
} }
// Set the new active address
newDetails.activeAddress = address;
return newDetails; return newDetails;
} catch (Exception e) { } catch (Exception e) {
return null; return null;
@ -447,6 +443,11 @@ public class ComputerManagerService extends Service {
// Just try to establish a TCP connection to speculatively detect a running // Just try to establish a TCP connection to speculatively detect a running
// GFE server // GFE server
private boolean fastPollIp(String address) { private boolean fastPollIp(String address) {
if (address == null) {
// Don't bother if our address is null
return false;
}
Socket s = new Socket(); Socket s = new Socket();
try { try {
s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT); s.connect(new InetSocketAddress(address, NvHTTP.HTTPS_PORT), FAST_POLL_TIMEOUT);
@ -475,12 +476,14 @@ public class ComputerManagerService extends Service {
t.start(); 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[] remoteInfo = new boolean[2];
final boolean[] localInfo = new boolean[2]; final boolean[] localInfo = new boolean[2];
final boolean[] manualInfo = new boolean[2];
startFastPollThread(localAddress, localInfo); startFastPollThread(localAddress, localInfo);
startFastPollThread(remoteAddress, remoteInfo); startFastPollThread(remoteAddress, remoteInfo);
startFastPollThread(manualAddress, manualInfo);
// Check local first // Check local first
synchronized (localInfo) { synchronized (localInfo) {
@ -489,174 +492,67 @@ public class ComputerManagerService extends Service {
} }
if (localInfo[1]) { 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) { synchronized (remoteInfo) {
while (!remoteInfo[0]) { while (!remoteInfo[0]) {
remoteInfo.wait(500); remoteInfo.wait(500);
} }
if (remoteInfo[1]) { if (remoteInfo[1]) {
return ComputerDetails.Reachability.REMOTE; return remoteAddress;
} }
} }
return ComputerDetails.Reachability.OFFLINE; return null;
}
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);
} }
private boolean pollComputer(ComputerDetails details) throws InterruptedException { private boolean pollComputer(ComputerDetails details) throws InterruptedException {
ReachabilityTuple initialReachTuple = pollForReachability(details); ComputerDetails polledDetails;
if (initialReachTuple == null) {
// 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; return false;
} }
if (initialReachTuple.computer.reachability == ComputerDetails.Reachability.UNKNOWN) { // Try using the active address from fast-poll
// Neither IP address reported in the serverinfo response was the one we used. polledDetails = tryPollIp(details, details.activeAddress);
// Poll again to see if we can contact this machine on either of its reported addresses. if (polledDetails == null && details.localAddress != null && !details.localAddress.equals(details.activeAddress)) {
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer); polledDetails = tryPollIp(details, details.localAddress);
if (confirmationReachTuple == null) { }
// Neither of those seem to work, so we'll hold onto the address that did work if (polledDetails == null && details.manualAddress != null && !details.manualAddress.equals(details.activeAddress)) {
initialReachTuple.computer.localAddress = initialReachTuple.reachableAddress; polledDetails = tryPollIp(details, details.manualAddress);
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL; }
} if (polledDetails == null && details.remoteAddress != null && !details.remoteAddress.equals(details.activeAddress)) {
else { polledDetails = tryPollIp(details, details.remoteAddress);
// We got it on one of the returned addresses; replace the original reach tuple
// with the new one
initialReachTuple = confirmationReachTuple;
}
} }
// Save some details about the old state of the PC that we may wish if (polledDetails != null) {
// to restore later. details.update(polledDetails);
String savedMacAddress = details.macAddress; return true;
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;
} }
else {
// We never want to lose IP addresses by polling server info. If we get a poll back return false;
// 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;
}
} }
return true;
} }
@Override @Override
@ -841,7 +737,7 @@ public class ComputerManagerService extends Service {
} while (waitPollingDelay()); } while (waitPollingDelay());
} }
}; };
thread.setName("App list polling thread for " + computer.localAddress); thread.setName("App list polling thread for " + computer.name);
thread.start(); thread.start();
} }

View File

@ -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<ComputerDetails> getAllComputers(SQLiteDatabase db) {
Cursor c = db.rawQuery("SELECT * FROM " + COMPUTER_TABLE_NAME, null);
LinkedList<ComputerDetails> 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<ComputerDetails> 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<ComputerDetails>();
} finally {
// Close and delete the old DB
if (computerDb != null) {
computerDb.close();
}
c.deleteDatabase(COMPUTER_DB_NAME);
}
}
}

View File

@ -46,7 +46,7 @@ public class PcGridAdapter extends GenericGridAdapter<PcView.ComputerObject> {
imgView.setAlpha(0.4f); imgView.setAlpha(0.4f);
} }
if (obj.details.reachability == ComputerDetails.Reachability.UNKNOWN) { if (obj.details.state == ComputerDetails.State.UNKNOWN) {
prgView.setVisibility(View.VISIBLE); prgView.setVisibility(View.VISIBLE);
} }
else { else {

View File

@ -18,8 +18,7 @@ import java.net.UnknownHostException;
public class ServerHelper { public class ServerHelper {
public static String getCurrentAddressFromComputer(ComputerDetails computer) { public static String getCurrentAddressFromComputer(ComputerDetails computer) {
return computer.reachability == ComputerDetails.Reachability.LOCAL ? return computer.activeAddress;
computer.localAddress : computer.remoteAddress;
} }
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer, 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_ID, app.getAppId());
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported()); intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_STREAMING_REMOTE, intent.putExtra(Game.EXTRA_STREAMING_REMOTE, getCurrentAddressFromComputer(computer).equals(computer.remoteAddress));
computer.reachability != ComputerDetails.Reachability.LOCAL);
intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString()); intent.putExtra(Game.EXTRA_PC_UUID, computer.uuid.toString());
intent.putExtra(Game.EXTRA_PC_NAME, computer.name); intent.putExtra(Game.EXTRA_PC_NAME, computer.name);
return intent; return intent;

@ -1 +1 @@
Subproject commit 31d6b4cb9f216c799c4fc2f01a96feb03c49c328 Subproject commit dd6e76c0f272e509ece1602e30a6e71f51ae3205