dcent-feeds
Version:
peer to peer camera feeds
180 lines (143 loc) • 5.61 kB
JavaScript
const path = require('path')
const os = require('os')
const { once } = require('events')
const fs = require('fs')
const fsProm = fs.promises
const { pipelinePromise } = require('streamx')
const { program } = require('commander')
const { ProxyClient } = require('p2proxy')
const IdEnc = require('hypercore-id-encoding')
const goodbye = require('graceful-goodbye')
const Corestore = require('corestore')
const Hyperswarm = require('hyperswarm')
const Hyperdrive = require('hyperdrive')
const DEFAULT_STORAGE = path.join(os.homedir(), '.dcent-feeds')
const DEFAULT_DOWNLOAD_DIR = path.join(os.homedir(), 'Downloads')
async function main () {
program.name('dcent-feeds').description('CLI for managing dcent feeds')
program
.command('stream')
.description('Stream a video feed')
.argument('<secret>', 'The secret giving access to the video feed')
.option('-p, --port <int>', 'Local port to serve the stream from', 4619)
.option('-h, --host <string>', 'Host to serve from', '127.0.0.1')
.action(runStream)
program
.command('list-feeds')
.description('List timestamps for which feeds are available')
.argument('<key>', 'The key of the hyperdrive containing the archived feeds')
.option('-s, --storage <string>', 'Corestore storage location', DEFAULT_STORAGE)
.action(runListFeeds)
program
.command('download')
.description('Download a feed')
.argument('<key>', 'The key of the hyperdrive containing the archived feeds')
.argument('<name>', 'The name of the feed (use the list-feeds commands to see the available names)')
.option('-s, --storage <string>', 'Corestore storage location', DEFAULT_STORAGE)
.option('-d, --download-dir <string>', 'Download folder location', DEFAULT_DOWNLOAD_DIR)
.action(runDownload)
await program.parseAsync(process.argv)
}
async function runStream (secret, { port, host }) {
secret = IdEnc.decode(secret)
const client = new ProxyClient(secret, port, host)
client.on('connection', ({ remoteAddress, remotePort, id }) => {
console.info(`Opened connection to ${remoteAddress}:${remotePort} (${id})`)
})
client.on('connection-close', ({ remoteAddress, remotePort, id }) => {
console.info(`Closed connection to ${remoteAddress}:${remotePort} (${id})`)
})
console.info('Setting up...')
goodbye(async () => {
console.info('Shutting down...')
if (client.opened) await client.close()
console.info('Shut down')
})
await client.ready()
const { address, port: listenPort } = client.address
console.info(`The video stream is served at http://${address}:${listenPort} (open this link in your browser)`)
}
async function runListFeeds (key, { storage }) {
key = IdEnc.decode(key)
const logger = console
const { store, drive, swarm } = setup(storage, key, logger)
goodbye(async () => {
console.info('Shutting down...')
await swarm.destroy()
await store.close()
console.info('Shut down')
})
console.info('Setting up...')
await drive.ready()
console.log(IdEnc.normalize(drive.key))
swarm.join(drive.discoveryKey, { client: true, server: false })
if (drive.db.core.peers.length === 0) {
logger.log('Waiting for connection to other peers...')
await once(drive.db.core, 'peer-add')
await drive.update({ wait: true })
}
for await (const entry of drive.list()) {
console.log(entry.key)
}
goodbye.exit()
}
async function runDownload (key, name, { storage, downloadDir }) {
key = IdEnc.decode(key)
const logger = console
const { store, drive, swarm } = setup(storage, key, logger)
const outputLoc = path.join(downloadDir, name)
// Imperfect check, but at least we avoid 99% of cases (checked again when opening the file)
if (fs.existsSync(outputLoc)) {
logger.error(`A file already exists at location: ${outputLoc}`)
process.exit(1)
}
logger.info(`Writing feed to: ${outputLoc}`)
let done = false
goodbye(async () => {
if (!done) console.info('Shutting down...')
await swarm.destroy()
await store.close()
if (!done) console.info('Shut down')
})
console.info('Setting up...')
await drive.ready()
console.log(IdEnc.normalize(drive.key))
swarm.join(drive.discoveryKey, { client: true, server: false })
if (drive.db.core.peers.length === 0) {
logger.log('Waiting for connection to other peers...')
await once(drive.db.core, 'peer-add')
await drive.update({ wait: true })
}
let file
try {
file = await fsProm.open(outputLoc, 'wx')
} catch (e) {
if (e.code === 'EEXIST') {
logger.error(`A file already exists at location: ${outputLoc}. Cancelling download.`)
process.exit(1)
}
throw e
}
const writeStream = file.createWriteStream()
const readStream = drive.createReadStream(name)
logger.info('Downloading feed...')
await pipelinePromise(readStream, writeStream)
logger.info(`Downloaded at: ${outputLoc}`)
done = true
goodbye.exit()
}
function setup (storage, key, logger) {
logger.info(`Using storage: ${storage}`)
const store = new Corestore(storage)
const drive = new Hyperdrive(store.namespace('feeds-drive'), key)
const swarm = new Hyperswarm()
swarm.on('connection', (conn, peer) => {
store.replicate(conn)
const address = `${conn.rawStream.remoteHost}:${conn.rawStream.remotePort}`
logger.info(`Opened connection to ${IdEnc.normalize(peer.publicKey)} (${address})`)
conn.on('close', () => { logger.info(`Closed connection to ${IdEnc.normalize(peer.publicKey)} (${address})`) })
})
return { store, drive, swarm }
}
main()