UNPKG

pauls-dat-api

Version:

Library of functions that make working with dat / hyperdrive easier.

227 lines (156 loc) 7.32 kB
'use strict';function _asyncToGenerator(fn) {return function () {var gen = fn.apply(this, arguments);return new Promise(function (resolve, reject) {function step(key, arg) {try {var info = gen[key](arg);var value = info.value;} catch (error) {reject(error);return;}if (info.done) {resolve(value);} else {return Promise.resolve(value).then(function (value) {step("next", value);}, function (err) {step("throw", err);});}}return step("next");});};}var emitStream = require('emit-stream'); var EventEmitter = require('events').EventEmitter; var match = require('anymatch');var _require = require('path'),sep = _require.sep;var _require2 = require('./common'),findEntryByContentBlock = _require2.findEntryByContentBlock,tonix = _require2.tonix; function watch(archive, path) { // options if (typeof path === 'string') { path = [path]; } // handle by type if (!archive.key) { return watchFilesystem(archive, path); } else if (archive.writable) { return watchLocal(archive, path); } else { return watchRemote(archive, path); } } function watchFilesystem(fs, paths) { // create new emitter and stream var emitter = new EventEmitter(); var stream = emitStream(emitter); // wire up events var stopwatch = fs.watch(sep, onFileChange); stream.on('close', function () { try {stopwatch();} catch (e) {/* ignore - this can happen if fs's path was invalid */} }); function onFileChange(path) { // apply path matching path = tonix(path); path = temporaryWindowsPathFix(path, fs.base); if (paths && !match(paths, path)) { return; } emitter.emit('changed', { path }); } return stream; } function watchLocal(archive, paths) { // create new emitter and stream var emitter = new EventEmitter(); var stream = emitStream(emitter); // wire up events archive.metadata.on('append', onMetaAppend); stream.on('close', function () { archive.metadata.removeListener('append', onMetaAppend); }); function onMetaAppend() { var block = archive.metadata.length - 1; archive.tree._getAndDecode(block, {}, function (err, entry) { if (err || !entry) return; // apply path matching if (paths && !match(paths, entry.name)) { return; } // local archive, emit both events immediately emitter.emit('invalidated', { path: entry.name }); emitter.emit('changed', { path: entry.name }); }); } return stream; } function watchRemote(archive, paths) {var onContentDownload = function () {var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark( function _callee(block) {var range;return regeneratorRuntime.wrap(function _callee$(_context) {while (1) {switch (_context.prev = _context.next) {case 0:_context.next = 2;return ( findEntryByContentBlock(archive, block));case 2:range = _context.sent; // emit 'changed' if downloaded if (range && (!paths || match(paths, range.name)) && isDownloaded(archive, range)) { setImmediate(function () {return emitter.emit('changed', { path: range.name });}); }case 4:case 'end':return _context.stop();}}}, _callee, this);}));return function onContentDownload(_x) {return _ref.apply(this, arguments);};}(); // create new emitter and stream var emitter = new EventEmitter();var stream = emitStream(emitter); // wire up events archive.metadata.on('download', onMetaDownload);if (archive.content) {wireContent();} else {archive.on('content', wireContent);}function wireContent() {archive.content.on('download', onContentDownload);}stream.on('close', function () {// unlisten events archive.metadata.removeListener('download', onMetaDownload);if (archive.content) {archive.content.removeListener('download', onContentDownload);}}); // handlers function onMetaDownload(block) {archive.tree._getAndDecode(block, {}, function (err, entry) {if (err || !entry) return; // apply path matching if (paths && !match(paths, entry.name)) {return;} // emit emitter.emit('invalidated', { path: entry.name }); // check if we can emit 'changed' now var isChanged = false;if (!entry.value) {isChanged = true; // a deletion } else {var st = archive.tree._codec.decode(entry.value);var range = { start: st.offset, end: st.offset + st.blocks };isChanged = isDownloaded(archive, range);}if (isChanged) {emitter.emit('changed', { path: entry.name });}});}return stream;}function isDownloaded(archive, range) {if (!archive.content || !archive.content.opened) return false;for (var i = range.start; i < range.end; i++) { if (!archive.content.has(i)) return false; } return true; } function createNetworkActivityStream(archive, path) { // create new emitter and stream var emitter = new EventEmitter(); var stream = emitStream(emitter); stream.on('close', function () { // unlisten events archive.metadata.removeListener('peer-add', onNetworkChanged); archive.metadata.removeListener('peer-remove', onNetworkChanged); untrack(archive.metadata, handlers.metadata); untrack(archive.content, handlers.content); }); // handlers function onNetworkChanged() { emitter.emit('network-changed', { connections: archive.metadata.peers.length }); } var handlers = { metadata: { onDownload(block, data) { emitter.emit('download', { feed: 'metadata', block, bytes: data.length }); }, onUpload(block, data) { emitter.emit('upload', { feed: 'metadata', block, bytes: data.length }); }, onSync() { emitter.emit('sync', { feed: 'metadata' }); } }, content: { onDownload(block, data) { emitter.emit('download', { feed: 'content', block, bytes: data.length }); }, onUpload(block, data) { emitter.emit('upload', { feed: 'content', block, bytes: data.length }); }, onSync() { emitter.emit('sync', { feed: 'content' }); } } // initialize all trackers };track(archive.metadata, 'metadata'); if (archive.content) track(archive.content, 'content');else archive.on('content', function () {return track(archive.content, 'content');}); archive.metadata.on('peer-add', onNetworkChanged); archive.metadata.on('peer-remove', onNetworkChanged); function track(feed, name) { if (!feed) return; var h = handlers[name]; feed.on('download', h.onDownload); feed.on('upload', h.onUpload); feed.on('sync', h.onSync); } function untrack(feed, handlers) { if (!feed) return; feed.removeListener('download', handlers.onDownload); feed.removeListener('upload', handlers.onUpload); feed.removeListener('sync', handlers.onSync); } return stream; } // HACK // workaround for a bug in libuv (https://github.com/nodejs/node/issues/19170) // paths will sometimes have some of the parent dir in them // if so, remove that bit // -prf function temporaryWindowsPathFix(path, parentPath) { if (process.platform === 'win32') { var secondSlashIndex = path.indexOf('/', 1); var firstSegment = path.slice(1, secondSlashIndex); if (parentPath.endsWith(firstSegment)) { return path.slice(secondSlashIndex); } } return path; } module.exports = { watch, createNetworkActivityStream };