Skip to content

Commit

Permalink
Merge pull request #100 from hoobs-org/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
mkellsy authored Sep 7, 2021
2 parents 03385a1 + b1cf3ef commit 832e259
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 104 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hoobsd",
"version": "4.0.100",
"version": "4.0.103",
"description": "Server component for the certified HOOBS smart home stack.",
"license": "GPL-3.0",
"repository": {
Expand Down Expand Up @@ -38,6 +38,7 @@
"http-terminator": "^3.0.0",
"lodash": "^4.17.21",
"macaddress": "^0.5.1",
"n-readlines": "^1.0.1",
"node-cache": "^5.1.2",
"node-uname": "^3.0.4",
"sanitize-filename": "^1.6.3",
Expand All @@ -63,6 +64,7 @@
"@types/http-terminator": "^2.0.1",
"@types/lodash": "^4.14.168",
"@types/lodash.debounce": "^4.0.6",
"@types/n-readlines": "^1.0.2",
"@types/node": "^14.14.31",
"@types/node-cache": "^4.2.5",
"@types/node-persist": "^3.1.1",
Expand Down
44 changes: 34 additions & 10 deletions src/hub/controllers/accessories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ export default class AccessoriesController {
const id = sanitize(request.params.id);
const working = AccessoriesController.layout;
const index = working.rooms.findIndex((item: { [key: string]: any }) => item.id === id);
const waits: Promise<void>[] = [];

if (index === -1) return response.send({ error: "room not found" });

Expand Down Expand Up @@ -583,13 +584,20 @@ export default class AccessoriesController {
value: 0,
});

await State.ipc?.fetch(room.accessories[i].bridge, "accessory:set", { id: room.accessories[i].accessory_identifier, service: "on" }, { value: 0 });
waits.push(new Promise((resolve) => {
State.ipc?.fetch(room.accessories[i].bridge, "accessory:set", { id: room.accessories[i].accessory_identifier, service: "on" }, { value: 0 }).finally(() => {
resolve();
});
}));

await Promise.allSettled(waits);
}
}

break;

default:
console.log("HERE");
room = this.properties(working.rooms[index], (await this.accessories()).filter((item) => item.type !== "bridge"), true, true);
room.accessories = room.accessories || [];
value = request.body.value;
Expand All @@ -603,7 +611,13 @@ export default class AccessoriesController {
value,
});

await State.ipc?.fetch(room.accessories[i].bridge, "accessory:set", { id: room.accessories[i].accessory_identifier, service: request.params.service }, { value });
waits.push(new Promise((resolve) => {
State.ipc?.fetch(room.accessories[i].bridge, "accessory:set", { id: room.accessories[i].accessory_identifier, service: request.params.service }, { value }).finally(() => {
resolve();
});
}));

await Promise.allSettled(waits);
}
}

Expand Down Expand Up @@ -642,23 +656,33 @@ export default class AccessoriesController {

private async accessories(bridge?: string): Promise<any[]> {
const working = AccessoriesController.layout;

let results: any[] = [];
const waits: Promise<void>[] = [];
const results: any[] = [];

if (bridge) {
const accessories = (await State.ipc?.fetch(bridge, "accessories:list")) || [];

results = results.concat(accessories);
waits.push(new Promise((resolve) => {
State.ipc?.fetch(bridge, "accessories:list").then((accessories) => {
if (Array.isArray(accessories) && accessories.length > 0) results.push(...accessories);
}).finally(() => {
resolve();
});
}));
} else {
for (let i = 0; i < State.bridges.length; i += 1) {
if (State.bridges[i].type !== "hub") {
const accessories = (await State.ipc?.fetch(State.bridges[i].id, "accessories:list")) || [];

results = results.concat(accessories);
waits.push(new Promise((resolve) => {
State.ipc?.fetch(State.bridges[i].id, "accessories:list").then((accessories) => {
if (Array.isArray(accessories) && accessories.length > 0) results.push(...accessories);
}).finally(() => {
resolve();
});
}));
}
}
}

await Promise.allSettled(waits);

for (let i = 0; i < results.length; i += 1) {
if (working.accessories[results[i].accessory_identifier]) _.extend(results[i], working.accessories[results[i].accessory_identifier]);
}
Expand Down
23 changes: 15 additions & 8 deletions src/hub/controllers/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,28 @@ export default class BridgeController {
}

async all(_request: Request, response: Response): Promise<Response> {
const results = [];
const results: { [key: string]: any } = [];
const waits: Promise<void>[] = [];

for (let i = 0; i < State.bridges.length; i += 1) {
if (State.bridges[i].type !== "hub") {
const status = await State.ipc?.fetch(State.bridges[i].id, "status:get");

if (status) {
results.push({
bridge: State.bridges[i].id,
status,
waits.push(new Promise((resolve) => {
State.ipc?.fetch(State.bridges[i].id, "status:get").then((status) => {
if (status) {
results.push({
bridge: State.bridges[i].id,
status,
});
}
}).finally(() => {
resolve();
});
}
}));
}
}

await Promise.allSettled(waits);

return response.send(results);
}

Expand Down
52 changes: 28 additions & 24 deletions src/hub/controllers/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,41 @@ export default class StatusController {
async status(_request: Request, response: Response): Promise<Response> {
const key = "system/status";
const results: { [key: string]: any } = {};
const waits: Promise<void>[] = [];

for (let i = 0; i < State.bridges.length; i += 1) {
if (State.bridges[i].type !== "hub") {
const status = await State.ipc?.fetch(State.bridges[i].id, "status:get");

if (status) {
results[State.bridges[i].id] = {
version: status.version,
running: status.running,
status: status.status,
uptime: status.uptime,
product: status.product,
bridge_name: status.bridge_name,
bridge_username: status.bridge_username,
bridge_port: status.bridge_port,
setup_pin: status.setup_pin,
setup_id: status.setup_id,
bridge_path: status.bridge_path,
};
} else {
results[State.bridges[i].id] = {
running: false,
status: "unavailable",
uptime: 0,
};
}
waits.push(new Promise((resolve) => {
State.ipc?.fetch(State.bridges[i].id, "status:get").then((status) => {
if (status) {
results[State.bridges[i].id] = {
version: status.version,
running: status.running,
status: status.status,
uptime: status.uptime,
product: status.product,
bridge_name: status.bridge_name,
bridge_username: status.bridge_username,
bridge_port: status.bridge_port,
setup_pin: status.setup_pin,
setup_id: status.setup_id,
bridge_path: status.bridge_path,
};
} else {
results[State.bridges[i].id] = {
running: false,
status: "unavailable",
uptime: 0,
};
}

resolve();
});
}));
}
}

const system = System.info();
const waits: Promise<void>[] = [];
const applications: { [key: string]: any } = State.cache?.get<{ [key: string]: any }>(key) || {};

if (!applications.cli) {
Expand Down
36 changes: 18 additions & 18 deletions src/hub/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import Monitor from "./services/monitor";
import Pipe from "../services/pipe";
import Bridges, { BridgeRecord, BridgeProcess } from "../services/bridges";
import { Console, Events, NotificationType } from "../services/logger";
import { cloneJson } from "../services/json";
import { cloneJson, compressJson } from "../services/json";

import IndexController from "./controllers/index";
import AuthController from "./controllers/auth";
Expand All @@ -70,14 +70,6 @@ const BRIDGE_LAUNCH_DELAY = 1 * 1000;
const BRIDGE_TEARDOWN_DELAY = 3 * 1000;
const BRIDGE_RELAUNCH_DELAY = 7 * 1000;

function running(pid: number): boolean {
try {
return process.kill(pid, 0) || false;
} catch (_error) {
return false;
}
}

export default class API extends EventEmitter {
declare time: number;

Expand Down Expand Up @@ -109,7 +101,7 @@ export default class API extends EventEmitter {
State.ipc = new IPC(this.bridges);

State.ipc.on(Events.LOG, (data: any) => Console.log(LogLevel.INFO, data));
State.ipc.on(Events.NOTIFICATION, (data: any) => State.io?.sockets.emit(Events.NOTIFICATION, data));
State.ipc.on(Events.NOTIFICATION, (data: any) => State.io?.sockets.emit(Events.NOTIFICATION, compressJson(data)));

State.ipc.on(Events.ACCESSORY_CHANGE, (data: any) => {
const working = AccessoriesController.layout;
Expand All @@ -120,7 +112,7 @@ export default class API extends EventEmitter {
}

data.data.accessory = accessory;
State.io?.sockets.emit(Events.ACCESSORY_CHANGE, data);
State.io?.sockets.emit(Events.ACCESSORY_CHANGE, compressJson(data));
});

State.ipc.on(Events.RESTART, async (data: string) => {
Expand Down Expand Up @@ -257,7 +249,7 @@ export default class API extends EventEmitter {
if (!State.orphans) flags.push("--orphans");

let waits: Promise<void>[] = [];
const keys = Object.keys(this.bridges).filter((item) => item !== bridge.id && this.bridges[item].port === bridge.port && running(this.bridges[item].process.pid));
const keys = Object.keys(this.bridges).filter((item) => item !== bridge.id && this.bridges[item].port === bridge.port && Bridges.running(this.bridges[item].process.pid));

for (let i = 0; i < keys.length; i += 1) {
waits.push(this.teardown(keys[i]));
Expand Down Expand Up @@ -320,7 +312,7 @@ export default class API extends EventEmitter {
});

setTimeout(() => {
if (running(this.bridges[bridge.id].process.pid)) {
if (Bridges.running(this.bridges[bridge.id].process.pid)) {
this.bridges[bridge.id].process.once("exit", () => {
setTimeout(() => this.launch(bridge), BRIDGE_RELAUNCH_DELAY);
});
Expand All @@ -331,9 +323,9 @@ export default class API extends EventEmitter {
this.bridges[bridge.id].process.stderr?.pipe(stderr);

Console.notify(
typeof bridge === "string" ? bridge : bridge.id,
bridge.id,
"Bridge Started",
`${typeof bridge === "string" ? bridge : bridge.display} has started.`,
`${bridge.display || bridge.id} has started.`,
NotificationType.SUCCESS,
"layers",
);
Expand All @@ -344,15 +336,23 @@ export default class API extends EventEmitter {
const id = typeof bridge === "string" ? bridge : bridge.id;

return new Promise((resolve) => {
if (this.bridges[id] && running(this.bridges[id].process.pid)) {
if (this.bridges[id] && Bridges.running(this.bridges[id].process.pid)) {
Console.info(`${typeof bridge === "string" ? bridge : bridge.display} stopping`);

let display = "";

if (typeof bridge === "string") {
display = (State.bridges.find((item) => item.id === bridge) || {}).display || bridge;
} else {
display = bridge.display;
}

const handler = () => {
setTimeout(() => {
Console.notify(
typeof bridge === "string" ? bridge : bridge.id,
"Bridge Stopped",
`${typeof bridge === "string" ? bridge : bridge.display} has stopped.`,
`${display} has stopped.`,
NotificationType.ERROR,
);

Expand Down Expand Up @@ -419,7 +419,7 @@ export default class API extends EventEmitter {
}

for (let i = 0; i < bridges.length; i += 1) {
if (!this.bridges[bridges[i].id] || !running(this.bridges[bridges[i].id].process.pid)) {
if (!this.bridges[bridges[i].id] || !Bridges.running(this.bridges[bridges[i].id].process.pid)) {
this.launch(bridges[i]);
}
}
Expand Down
22 changes: 14 additions & 8 deletions src/hub/services/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,20 @@ export default class Socket {
resolve(data);
});

this.forked.send(this.format("fetch", {
path,
session,
params,
body,
}));

timeout = setTimeout(() => resolve(undefined), 10 * 1000);
try {
this.forked.send(this.format("fetch", {
path,
session,
params,
body,
}));

timeout = setTimeout(() => resolve(undefined), 10 * 1000);
} catch (_error) {
if (timeout) clearTimeout(timeout);

resolve(undefined);
}
} else {
resolve(undefined);
}
Expand Down
11 changes: 10 additions & 1 deletion src/services/bridges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ export interface BridgeProcess {
}

export default class Bridges {
static running(pid: number): boolean {
try {
return process.kill(pid, 0) || false;
} catch (_error) {
return false;
}
}

static locate() {
const paths = (process.env.PATH || "").split(":");

Expand Down Expand Up @@ -259,6 +267,7 @@ export default class Bridges {

const id = sanitize(name);
const index = State.bridges.findIndex((n: BridgeRecord) => n.id === id);
const display = index >= 0 ? State.bridges[index].display : name;

if (index >= 0) {
State.bridges.splice(index, 1);
Expand All @@ -272,7 +281,7 @@ export default class Bridges {
Console.notify(
"hub",
"Bridge Removed",
`${name} removed.`,
`${display} removed.`,
NotificationType.WARN,
"layers",
);
Expand Down
Loading

0 comments on commit 832e259

Please sign in to comment.