import { spawn, ChildProcessWithoutNullStreams } from "child_process"; import * as fs from "fs"; export interface IZap { process: ChildProcessWithoutNullStreams | null, channel: string, adapter: 0 | 1 strength: { signal: string; cn: string; }, } export default class Zap { private zap0: IZap; private zap1: IZap; private channelNameList: string[]; private fileName: string; private regex = /Signal=\s*(-?\d+(\.\d+)?dBm)\s+C\/N=\s*(\d+(\.\d+)?dB)/; public constructor(fileName = "dvb_channel.conf", channel = "ION") { const zap0: IZap = { process: null, channel, adapter: 0, strength: { signal: "None", cn: "None" } } const zap1: IZap = { process: null, channel, adapter: 1, strength: { signal: "None", cn: "None" } } this.zap0 = zap0; this.zap1 = zap1; const file = fs.readFileSync('./' + fileName, "utf-8"); const lines = file.split("\n").map(line => line.trim()).filter(line => line && !line.startsWith("#")).map(line => line.split(":")[0]); this.channelNameList = lines; this.fileName = fileName; } public getChannels(): string[] { return this.channelNameList; } public getSignal(adapter: number) { if(adapter == 0){ return this.zap0.strength; } else if (adapter == 1){ return this.zap1.strength; } else { return {signal: 'N/A', cn: 'N/A'} } } private nextChannel(channel: string) :string { const size = this.channelNameList.length; const currentIndex = this.channelNameList.indexOf(channel); if (currentIndex >= 0) { return this.channelNameList[this.mod(currentIndex + 1, size)]; } else { return channel; } } private mod(n: number, m: number) :number{ return ((n % m) + m) % m; } private previousChannel(channel: string):string { const size = this.channelNameList.length; const currentIndex = this.channelNameList.indexOf(channel); if (currentIndex >= 0) { return this.channelNameList[this.mod(currentIndex - 1, size)]; } else { return channel; } } private cleanup(proc: ChildProcessWithoutNullStreams): Promise { proc.kill('SIGHUP'); return new Promise((resolve, reject) => { proc.once('exit', () => resolve() ) proc.on("error", (err: Error) => reject(err) ); }) } public async zapTo(channel: string, adapter: 0 | 1 = 0): Promise { let zap = adapter == 0 ? this.zap0 : this.zap1; let verifiedChannel: string; if (channel == '+') { verifiedChannel = this.nextChannel(zap.channel); } else if (channel == '-') { verifiedChannel = this.previousChannel(zap.channel); } else { if (!this.channelNameList.includes(channel)) { return Promise.reject(new Error("Invalid Channel name")); } else { verifiedChannel = channel; } } const cmd = 'dvbv5-zap' const args: string[] = ["-r", "-C", "US", "-I", "ZAP", "-c", this.fileName, "-a", adapter.toString(), verifiedChannel]; if (zap.process != null) { await this.cleanup(zap.process).catch(err => Promise.reject(err)); } zap.process = spawn(cmd, args); return new Promise((resolve, reject) => { let lockTimer: NodeJS.Timeout; zap.process.stderr.on("data", (data) => { const output = data.toString(); if (/Lock/.test(output)) { clearTimeout(lockTimer); const match = output.match(this.regex); zap.channel = verifiedChannel; zap.strength = { signal: match[1], cn: match[3] } resolve(zap); } if (/Not locked/.test(output)) { clearTimeout(lockTimer); zap.process.kill("SIGHUP"); } }); zap.process.on("exit", (code) => { reject(new Error("Unexpected exit of Zap")); }); lockTimer = setTimeout(() => { reject(new Error("Failed to Zap in time")); }, 5000); }) } }