mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-19 19:13:03 +00:00
Rewrite reachability code and computer DB to bring it inline with other modern Moonlight clients
This commit is contained in:
parent
c2fbe6ad91
commit
b690dc5474
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user