Skip to content

Commit

Permalink
Enhance host picker during launch (#512)
Browse files Browse the repository at this point in the history
* Fix and enhance the device picker

* Fix loading message

* Separate list building from list showing/picking

* better cleanup after host picker closes

* Fix some tests

* Use built-in busy spinner instead of changing text

* remove unused util functions
  • Loading branch information
TwitchBronBron authored Oct 31, 2023
1 parent f1e58d5 commit bfa161c
Show file tree
Hide file tree
Showing 7 changed files with 518 additions and 112 deletions.
45 changes: 39 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"brighterscript-formatter": "^1.6.34",
"debounce": "^1.2.0",
"dotenv": "^6.2.0",
"eventemitter3": "^5.0.1",
"fast-xml-parser": "^3.12.16",
"fs-extra": "^7.0.1",
"get-port": "^5.0.0",
Expand Down
97 changes: 83 additions & 14 deletions src/ActiveDeviceManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as backoff from 'backoff';
import { EventEmitter } from 'events';
import { EventEmitter } from 'eventemitter3';
import * as xmlParser from 'fast-xml-parser';
import * as http from 'http';
import * as NodeCache from 'node-cache';
Expand All @@ -8,13 +8,14 @@ import { Client } from 'node-ssdp';
import { URL } from 'url';
import { util } from './util';
import * as vscode from 'vscode';
import { firstBy } from 'thenby';
import type { Disposable } from 'vscode';

const DEFAULT_TIMEOUT = 10000;

export class ActiveDeviceManager extends EventEmitter {
export class ActiveDeviceManager {

constructor() {
super();
this.isRunning = false;
this.firstRequestForDevices = true;

Expand All @@ -38,23 +39,76 @@ export class ActiveDeviceManager extends EventEmitter {
this.deviceCache = new NodeCache({ stdTTL: 3600, checkperiod: 120 });
//anytime a device leaves the cache (either expired or manually deleted)
this.deviceCache.on('del', (deviceId, device) => {
this.emit('expiredDevice', deviceId, device);
void this.emit('device-expired', device);
});
this.processEnabledState();
}

private emitter = new EventEmitter();

public on(eventName: 'device-expired', handler: (device: RokuDeviceDetails) => void, disposables?: Disposable[]): () => void;
public on(eventName: 'device-found', handler: (device: RokuDeviceDetails) => void, disposables?: Disposable[]): () => void;
public on(eventName: string, handler: (payload: any) => void, disposables?: Disposable[]): () => void {
this.emitter.on(eventName, handler);
const unsubscribe = () => {
if (this.emitter !== undefined) {
this.emitter.removeListener(eventName, handler);
}
};

disposables?.push({
dispose: unsubscribe
});

return unsubscribe;
}

private async emit(eventName: 'device-expired', device: RokuDeviceDetails);
private async emit(eventName: 'device-found', device: RokuDeviceDetails);
private async emit(eventName: string, data?: any) {
//emit these events on next tick, otherwise they will be processed immediately which could cause issues
await util.sleep(0);
this.emitter?.emit(eventName, data);
}

public firstRequestForDevices: boolean;
public lastUsedDevice: string;
private enabled: boolean;
public lastUsedDevice: RokuDeviceDetails;
public enabled: boolean;
private showInfoMessages: boolean;
private deviceCache: NodeCache;
private exponentialBackoff: any;
private isRunning: boolean;

// Returns an object will all the active devices by device id
public getActiveDevices() {
/**
* Get a list of all devices discovered on the network
*/
public getActiveDevices(): RokuDeviceDetails[] {
this.firstRequestForDevices = false;
return this.deviceCache.mget(this.deviceCache.keys());
const devices = Object.values(
this.deviceCache.mget(this.deviceCache.keys()) as Record<string, RokuDeviceDetails>
).sort(firstBy((a: RokuDeviceDetails, b: RokuDeviceDetails) => {
return this.getPriorityForDeviceFormFactor(a) - this.getPriorityForDeviceFormFactor(b);
}).thenBy((a: RokuDeviceDetails, b: RokuDeviceDetails) => {
if (a.id < b.id) {
return -1;
}
if (a.id > b.id) {
return 1;
}
// ids must be equal
return 0;
}));
return devices;
}

private getPriorityForDeviceFormFactor(device: RokuDeviceDetails): number {
if (device.deviceInfo['is-stick']) {
return 0;
}
if (device.deviceInfo['is-tv']) {
return 2;
}
return 1;
}

// Returns the device cache statistics.
Expand Down Expand Up @@ -114,6 +168,18 @@ export class ActiveDeviceManager extends EventEmitter {
}
}

/**
* The number of milliseconds since a new device was discovered
*/
public get timeSinceLastDiscoveredDevice() {
if (!this.lastDiscoveredDeviceDate) {
return 0;
}
return Date.now() - this.lastDiscoveredDeviceDate.getTime();
}
private lastDiscoveredDeviceDate: Date;


// Discover all Roku devices on the network and watch for new ones that connect
private discoverAll(timeout: number = DEFAULT_TIMEOUT): Promise<string[]> {
return new Promise((resolve, reject) => {
Expand All @@ -122,13 +188,16 @@ export class ActiveDeviceManager extends EventEmitter {

finder.on('found', (device: RokuDeviceDetails) => {
if (!devices.includes(device.id)) {
if (this.showInfoMessages && this.deviceCache.get(device.id) === undefined) {
// New device found
void vscode.window.showInformationMessage(`Device found: ${device.deviceInfo['default-device-name']}`);
if (this.deviceCache.get(device.id) === undefined) {
this.lastDiscoveredDeviceDate = new Date();
if (this.showInfoMessages) {
// New device found
void vscode.window.showInformationMessage(`Device found: ${device.deviceInfo['default-device-name']}`);
}
}
this.deviceCache.set(device.id, device);
devices.push(device.id);
this.emit('foundDevice', device.id, device);
this.emit('device-found', device);
}
});

Expand Down Expand Up @@ -192,7 +261,7 @@ class RokuFinder extends EventEmitter {
const device: RokuDeviceDetails = {
location: url.origin,
ip: url.hostname,
id: info['device-info']['device-id'],
id: info['device-info']['device-id']?.toString?.(),
deviceInfo: info['device-info']
};
this.emit('found', device);
Expand Down
Loading

0 comments on commit bfa161c

Please sign in to comment.