diff --git a/.travis.yml b/.travis.yml index e0357077..51fa54d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: node_js node_js: - "4" - "5" + - "6" sudo: false diff --git a/README.md b/README.md index a1f1daf9..29964c05 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,14 @@ Share a directory by typing `dat `: ``` $ dat my_data/ -Initializing Dat in my_data/ -[DONE] readme.txt (0.30 kB) -[DONE] data.csv (1.14 kB) -Items: 2 Size: 1.44 kB -Share Link 4f36c088e9687ddf53d36f785ab84c65f4d24d8c4161950519b96a57d65ae08a +Sharing /Users/joe/my_data/ + +Share Link: 2imqzxdu46jh2rrhm6xnd30bsjgu3oosgy52dphdbxlzxvz5sj The Share Link is secret and only those you share it with will be able to get the files -Sharing /Users/joe, connected to 2/4 sources -Uploading 28.62 kB/s, 765.08 kB Total + +[==============>] Added 2 files (1.44 kB/1.44 kB) + +Connected to 1 peers. Uploading 288.2 B/s. Watching for updates... ``` You are now publishing that data from your computer. It will be publicly accessible as long as your terminal is open. The hash is a **secret hash**, your data is visible to anyone you send the hash to. As you add more files to the folder, dat will update and share the new files. @@ -79,15 +79,15 @@ You are now publishing that data from your computer. It will be publicly accessi Your colleague can get data like this: ``` -$ dat 2bede435504c9482910b5d4e324e995a9bc4d6f068b98ae03d97e8d3ac5f80ea download_dir -Initializing Dat from 52d08a6d1ddc9b1f61b9862d2ae0d991676d489274bff6c5ebebecbfa3239f51 -[DONE] readme.txt (0.30 kB) -[DONE] data.csv (1.14 kB) -[DONE] 2 items (1.44 kB) -Share Link 52d08a6d1ddc9b1f61b9862d2ae0d991676d489274bff6c5ebebecbfa3239f51 +$ dat 2imqzxdu46jh2rrhm6xnd30bsjgu3oosgy52dphdbxlzxvz5sj download_dir +Downloading in /Users/joe/download_dir + +Share Link: 2imqzxdu46jh2rrhm6xnd30bsjgu3oosgy52dphdbxlzxvz5sj The Share Link is secret and only those you share it with will be able to get the files -Syncing live updates, connected to 1/2 sources -Download Finished, you may exit process + +[==============>] Downloaded 3 files (1.44 kB/1.44 kB) + +Connected to 1 peers. Downloading 1.44 kB/s. Watching for updates... ``` It will start downloading the data into the `download_dir` folder. Anyone who gets access to the unique dat-link will be able to download and re-host a copy of the data. It's distributed mad science! @@ -110,3 +110,80 @@ npm link This should add a `dat` command line command to your PATH. Now you can run the `dat` command to try it out. The contribution guide also has more tips on our [development workflow](https://github.com/maxogden/dat/blob/master/CONTRIBUTING.md#development-workflow). + + +### Internal API + +**Note: we are in the process of moving the main library to a separate module, [joehand/dat-js](https://github.com/joehand/dat-js). Temp documentation here for developers.** + +#### dat.download(cb) + +download `dat.key` to `dat.dir` + +#### dat.share(cb) + +share directory specified in `opts.dir` + +Swarm is automatically joined for key when it is available for share & download (`dat.joinSwarm()`). + +#### Events + +##### Initialization + +* `dat.on('ready')`: db created/read & hyperdrive archive created. +* `dat.on('error')`: init/database error + +##### Swarm + +Swarm events and stats are available from `dat.swarm`. + +* `dat.on('connecting')`: looking for peers +* `dat.on('swarm-update')`: peer number changed + +##### Share + +* `dat.on('key')`: key is available (this is at archive-finalized for snapshots) +* `dat.on('append-ready')`: file count available (`dat.appendStats`), about to start appending to hyperdrive +* `dat.on('file-added')`: file added to archive +* `dat.on('upload', data)`: piece of data uploaded +* `dat.on('archive-finalized')`: archive finalized, all files appended +* `dat.on('archive-updated')`: live archive changed + +##### Download + +* `dat.on('key')`: key is available +* `dat.on('file-downloaded', file)`: file downloaded +* `dat.on('download', data)`: piece of data downloaded +* `dat.on('upload', data)`: piece of data uploaded +* `dat.on('download-finished')`: archive download finished + +#### Other API + +* `dat.key`: key +* `dat.dir`: directory +* `dat.datPath`: path to .dat folder +* `dat.db`: database instance +* `dat.swarm`: hyperdrive-archive-swarm instance +* `dat.archive`: hyperdrive archive +* `dat.snapshot` (boolean): sharing snapshot archive + +#### Internal Stats +```javascript + +dat.stats = { + filesTotal: 0, // Latest archive size + bytesTotal: 0, + bytesUp: 0, + bytesDown: 0, + rateUp: speedometer(), + rateDown: speedometer() +} + +// Calculated on share before append starts. Used for append progress. +// Not updated for live. +dat.appendStats = { + files: 0, + bytes: 0, + dirs: 0 +} +```` diff --git a/bin/cli.js b/bin/cli.js new file mode 100755 index 00000000..f0b6955b --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +var args = require('minimist')(process.argv.splice(2), { + alias: {p: 'port', q: 'quiet', v: 'version'}, + boolean: ['snapshot', 'exit', 'list', 'quiet', 'version'] +}) + +process.title = 'dat' + +// set debug before requiring other modules +if (args.debug) { + var debug = args.debug + if (typeof args.debug === 'boolean') debug = '*' // default + process.env.DEBUG = debug +} + +if (args.version) { + var pkg = require('../package.json') + console.log(pkg.version) + process.exit(0) +} + +var isShare = false +var isDownload = false + +if (args.doctor || !args._[0]) run() +else getCommand() + +function run () { + if (args.doctor) require('./doctor')(args) + else if (isShare) require('../commands/share')(args) + else if (args.list && isDownload) require('../commands/list')(args) + else if (isDownload) require('../commands/download')(args) + else require('../usage')('root.txt') +} + +function getCommand () { + if (isDirectory(args._[0], true)) isShare = true + else if (isDatLink(args._[0])) isDownload = true + args.dir = isShare ? args._[0] : args._[1] + args.key = isDownload ? args._[0] : null + + if (isDirectory(args.dir)) run() // catch download dir error TODO: make optional + else onerror('Invalid Command') // Should never get here... +} + +function isDatLink (val, quiet) { + // TODO: switch to using dat-encoding here + var isLink = (val.length === 50 || val.length === 64) + if (quiet || isLink) return isLink + onerror('Invalid Dat Link') +} + +function isDirectory (val, quiet) { + try { + return require('fs').statSync(val).isDirectory() // TODO: support sharing single files + } catch (err) { + if (quiet) return false + onerror('Directory does not exist') + } +} + +function onerror (msg) { + console.error(msg + '\n') + process.exit(1) + // require('../usage')('root.txt') +} diff --git a/bin/download.js b/bin/download.js deleted file mode 100644 index 75a95b6c..00000000 --- a/bin/download.js +++ /dev/null @@ -1,134 +0,0 @@ -var hyperdrive = require('hyperdrive') -var each = require('stream-each') -var raf = require('random-access-file') -var path = require('path') -var chalk = require('chalk') -var prettyBytes = require('pretty-bytes') -var speedometer = require('speedometer') -var createSwarm = require('hyperdrive-archive-swarm') -var statusLogger = require('../lib/status-logger') -var swarmLogger = require('../lib/swarm-logger') - -module.exports = function (args) { - var db = args.level - var dir = args.dir - var key = args.key ? args.key : args._[0] - var pathName = dir === '.' ? process.cwd() : dir - - var noDataTimeout = null - var stats = { - filesTotal: 0, - bytesTotal: 0, - filesTransferred: 0, - bytesTransferred: 0, - transferRate: speedometer() - } - var logger = statusLogger(args) - - if (!dir) { - // TODO: create folder with dat name here - console.error('Please specify a directory.') - process.exit(1) - } else { - downloadArchive() - } - - function downloadArchive () { - var drive = hyperdrive(db) - var archive = drive.createArchive(Buffer(key, 'hex'), { - file: function (name) { - return raf(path.join(dir, name)) - } - }) - - logger.message('Initializing Dat from ' + chalk.blue.underline(archive.key.toString('hex'))) - - logger.status('', 0) // total progress and size - logger.status(printDatLink(archive.key), 1) - logger.status('The Share Link is secret and only those you share it with will be able to get the files', 2) - - var swarm = createSwarm(archive, args) - swarmLogger(swarm, logger, 'Downloading to ' + pathName) - - archive.on('download', function (data) { - if (noDataTimeout) clearInterval(noDataTimeout) - stats.bytesTransferred += data.length - stats.transferRate(data.length) - logger.status('Downloading ' + prettyBytes(stats.transferRate()) + '/s', -1) - printTotalStats() - noDataTimeout = setInterval(function () { - logger.status(chalk.blue('Waiting for Data...'), -1) - }, 1000) - }) - - archive.open(function (err) { - if (err) return onerror(err) - db.put('!dat!key', archive.key.toString('hex')) - logger.status('', 0) - - each(archive.list({live: archive.live}), function (data, next) { - var startBytes = stats.bytesTransferred - printTotalStats() - archive.download(data, function (err) { - if (err) return onerror(err) - var msg = chalk.green.dim('[DONE] ') + chalk.dim(data.name) - if (data.type === 'file') msg += chalk.dim(' (' + prettyBytes(data.length) + ')') - logger.message(msg) - stats.filesTransferred += 1 - if (startBytes === stats.bytesTransferred) stats.bytesTransferred += data.length // file already exists - printTotalStats() - if (stats.filesTransferred === stats.filesTotal) done() - else next() - }) - }, done) - }) - - function done () { - printTotalStats() - - if (args.exit) { - if (archive.live) logger.status('Download Finished, Run Again to Update', 3) - else logger.status('Download of Snapshot Finished', 3) - logger.status('', -1) // remove peer count - logger.logNow() - process.exit(0) - } - - logger.status('Download Finished, you may exit process', -1) - if (archive.live) swarmLogger(swarm, logger, 'Syncing live updates') - else swarmLogger(swarm, logger, 'Sharing data') - } - - function printDatLink (key) { - return chalk.bold('Share Link ') + chalk.blue.underline(key.toString('hex')) - } - - function printTotalStats () { - var msg = '' - var totalPer = 0 - var done = (stats.bytesTransferred === stats.bytesTotal) - - stats.filesTotal = archive.metadata.blocks - 1 // first block is header - stats.bytesTotal = archive.content.bytes - totalPer = Math.floor(100 * (stats.bytesTransferred / stats.bytesTotal)) - - if (done) msg += chalk.bold.green('[DONE] ') - else msg += chalk.bold('[' + (' ' + totalPer).slice(-3) + '%] ') - - if (done) { - msg += stats.filesTransferred + ' items' - msg += chalk.dim(' (' + prettyBytes(stats.bytesTransferred) + ')') - } else { - msg += stats.filesTransferred + ' of ' + stats.filesTotal + ' items' - msg += chalk.dim(' (' + prettyBytes(stats.bytesTransferred) + ' of ') - msg += chalk.dim(prettyBytes(stats.bytesTotal) + ')') - } - logger.status(msg, 0) - } - } - - function onerror (err) { - console.error(err.stack || err) - process.exit(1) - } -} diff --git a/bin/list.js b/bin/list.js deleted file mode 100644 index 5bbd4749..00000000 --- a/bin/list.js +++ /dev/null @@ -1,77 +0,0 @@ -var hyperdrive = require('hyperdrive') -var memdb = require('memdb') -var each = require('stream-each') -var raf = require('random-access-file') -var chalk = require('chalk') -var prettyBytes = require('pretty-bytes') -var createSwarm = require('hyperdrive-archive-swarm') -var statusLogger = require('../lib/status-logger') -var swarmLogger = require('../lib/swarm-logger') - -module.exports = function (args) { - var key = args.key ? args.key : args._[0] - var drive = hyperdrive(memdb()) - var archive = drive.createArchive(Buffer(key, 'hex'), { - file: function (name) { - return raf(name) - } - }) - var stats = { - filesTotal: 0, - bytesTotal: 0, - filesTransferred: 0 - } - var logger = statusLogger(args) - - logger.message('Initializing Dat from ' + chalk.blue.underline(archive.key.toString('hex'))) - - logger.status('', 0) // total progress and size - logger.status(printDatLink(archive.key), 1) - logger.status('The Share Link is secret and only those you share it with will be able to get the files', 2) - - var swarm = createSwarm(archive, args) - swarmLogger(swarm, logger, 'Getting File List') - - each(archive.list({live: args.live}), function (data, next) { - var msg = data.name - if (data.type === 'file') msg += ' ' + chalk.dim('(' + prettyBytes(data.length) + ')') - logger.message(msg) - stats.filesTransferred += 1 - printTotalStats() - if (stats.filesTransferred === stats.filesTotal) done() - else next() - }, done) - - function done () { - logger.status('') // remove messages - printTotalStats() - logger.status(printDatLink(archive.key), 1) - logger.logNow() - process.exit(0) - } - - function printDatLink (key) { - return chalk.bold('Share Link ') + chalk.blue.underline(key.toString('hex')) - } - - function printTotalStats () { - var msg = '' - var totalPer = 0 - var done = (stats.filesTransferred === stats.filesTotal) - - stats.filesTotal = archive.metadata.blocks - 1 // first block is header - stats.bytesTotal = archive.content.bytes - totalPer = Math.floor(100 * (stats.filesTransferred / stats.filesTotal)) - - if (done) msg += chalk.bold.green('[DONE] ') - else msg += chalk.bold('[' + (' ' + totalPer).slice(-3) + '%] ') - - if (done) { - msg += stats.filesTransferred + ' items' - msg += chalk.dim(' (' + prettyBytes(stats.bytesTotal) + ')') - } else { - msg += stats.filesTransferred + ' of ' + stats.filesTotal + ' items' - } - logger.status(msg, 0) - } -} diff --git a/bin/share.js b/bin/share.js deleted file mode 100644 index 4519849a..00000000 --- a/bin/share.js +++ /dev/null @@ -1,167 +0,0 @@ -var hyperdrive = require('hyperdrive') -var walker = require('folder-walker') -var each = require('stream-each') -var raf = require('random-access-file') -var path = require('path') -var chalk = require('chalk') -var prettyBytes = require('pretty-bytes') -var speedometer = require('speedometer') -var createSwarm = require('hyperdrive-archive-swarm') -var yoloWatch = require('yolowatch') -var statusLogger = require('../lib/status-logger') -var swarmLogger = require('../lib/swarm-logger') - -module.exports = function (args) { - var db = args.level - var dir = args.dir === '.' ? process.cwd() : args.dir - var key = args.key ? Buffer(args.key, 'hex') : null - var drive = hyperdrive(db) - var swarm = null - var logger = statusLogger(args) - var noDataTimeout = null - var stats = { - filesTotal: 0, - bytesTotal: 0, - bytesTransferred: 0, - transferRate: speedometer() - } - - var archive = drive.createArchive(key, { - name: path.basename(dir), // TODO: hyperdrive support for this - live: !args.snapshot, - file: function (name) { - return raf(path.join(dir, name), {readable: true, writable: false}) - } - }) - - archive.open(function (err) { - if (err) return onerror(err) - if (key && !archive.owner) { - logger.message('External Dat already exists in directory.') - logger.message('Run ' + chalk.bold('dat ' + key.toString('hex')) + ' to update.') - logger.logNow() - process.exit(0) - } - - logger.message('Initializing Dat in ' + dir) - - logger.status('', 0) // reserve total progress and size - if (key) logger.status(printDatLink(archive.key), 1) - else logger.status('Creating Share Link...', 1) // reserve for dat link - logger.status('The Share Link is secret and only those you share it with will be able to get the files', 2) - - if ((archive.live || archive.owner) && archive.key) { - if (!key) db.put('!dat!key', archive.key.toString('hex')) - logger.status(printDatLink(archive.key), 1) - swarm = createSwarm(archive, args) - swarmLogger(swarm, logger, 'Sharing ' + dir) - } - - archive.on('upload', function (data) { - stats.bytesTransferred += data.length - stats.transferRate(data.length) - var msg = 'Uploading ' + prettyBytes(stats.transferRate()) + '/s' - msg += ', ' + prettyBytes(stats.bytesTransferred) + ' Total' - logger.status(msg, -1) - if (noDataTimeout) clearInterval(noDataTimeout) - noDataTimeout = setInterval(function () { - var msg = 'Uploading ' + prettyBytes(stats.transferRate()) + '/s' - msg += ', ' + prettyBytes(stats.bytesTransferred) + ' Total' - logger.status(msg, -1) - }, 100) - }) - - if (args.resume) { - db.get('!dat!finalized', function (err, val) { - if (err || val !== 'true') return walkFolder(true) - else walkFolder(true) // TODO: check mtimes - }) - } else { - walkFolder() - } - }) - - function ignore (filepath) { - // TODO: split this out and make it composable/modular/optional/modifiable - return filepath.indexOf('.dat') === -1 && filepath.indexOf('.swp') === -1 - } - - function walkFolder (resume) { - var fileStream = walker(dir, {filter: ignore}) - if (resume) each(fileStream, checkAppend, done) - else each(fileStream, appendEntry, done) - } - - function checkAppend (data, next) { - archive.lookup(data.relname, function (err, result) { - if (!err && result) { - var msg = chalk.green.dim('[DONE] ') + chalk.dim(result.name) - if (data.type === 'file') msg += chalk.dim(' (' + prettyBytes(result.length) + ')') - logger.message(msg) - printTotalStats() - return next() - } - appendEntry(data, next) - }) - } - - function appendEntry (data, next) { - if (!ignore(data.filepath)) return next() - archive.append({type: data.type, name: data.relname}, function () { - var msg = chalk.green.dim('[DONE] ') + chalk.dim(data.relname) - if (data.type === 'file') msg += chalk.dim(' (' + prettyBytes(data.stat.size) + ')') - logger.message(msg) - next() - printTotalStats() - }) - } - - function printDatLink (key) { - return chalk.bold('Share Link ') + chalk.blue.underline(key.toString('hex')) - } - - function printTotalStats () { - stats.filesTotal = archive.metadata.blocks - 1 // first block is header - stats.bytesTotal = archive.content ? archive.content.bytes : 0 - var msg = 'Items: ' + chalk.bold(stats.filesTotal) - msg += ' Size: ' + chalk.bold(prettyBytes(stats.bytesTotal)) - logger.status(msg, 0) - } - - function done (err) { - if (err) return onerror(err) - - archive.finalize(function (err) { - if (err) return onerror(err) - db.put('!dat!finalized', true) - - if (args.snapshot) { - logger.status(printDatLink(archive.key), 1) - swarm = createSwarm(archive, args) - swarmLogger(swarm, logger, 'Sharing Snapshot ' + dir) - return - } - - var watcher = yoloWatch(dir) - watcher.on('changed', function (name, stat) { - // TODO: make yolowatch data consistent w/ folder-walker - if (name === dir) return - var data = { - filepath: name, - stat: stat, - relname: path.relative(dir, name), - basename: path.basename(name) - } - appendEntry(data, function () {}) - }) - watcher.on('added', function (file, stat) { - appendEntry(stat, function () {}) - }) - }) - } - - function onerror (err) { - console.error(err.stack || err) - process.exit(1) - } -} diff --git a/cli.js b/cli.js deleted file mode 100755 index 801ae273..00000000 --- a/cli.js +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env node -var path = require('path') -var level = require('level') -var mkdirp = require('mkdirp') - -var args = require('minimist')(process.argv.splice(2), { - alias: {p: 'port', q: 'quiet', v: 'version'}, - boolean: ['snapshot', 'exit', 'list', 'quiet', 'version'] -}) - -process.title = 'dat' - -// set debug before requiring other modules -if (args.debug) { - var debug = args.debug - if (typeof args.debug === 'boolean') debug = '*' // default - process.env.DEBUG = debug -} - -if (args.version) { - var pkg = require('./package.json') - console.log(pkg.version) - process.exit(0) -} - -var isShare = false -var isDownload = false - -if (args.doctor || !args._[0]) { - run() -} else { - getCommand() -} - -function run () { - if (args.doctor) { - require('./bin/doctor')(args) - } else if (isShare) { - require('./bin/share')(args) - } else if (args.list && isDownload) { - require('./bin/list')(args) - } else if (isDownload) { - require('./bin/download')(args) - } else { - require('./usage')('root.txt') - } -} - -function getCommand () { - if (isDirectory(args._[0], true)) isShare = true - else if (isDatLink(args._[0])) isDownload = true - args.dir = isShare ? args._[0] : args._[1] - args.key = isDownload ? args._[0] : null - - if (isShare || isDownload) getDatDb() - else run() -} - -function getDatDb () { - var dir = args.dir || '.' - args.datDb = args.datDb || path.join(dir, '.dat') - - var isNewDownload = (isDownload && !isDirectory(args.datDb, true)) - if (isNewDownload) { - if (dir !== '.') mkdirp.sync(args.datDb) - args.level = level(args.datDb) - run() - } else { - if (isDownload) args.dir = '.' // resume download in cwd - checkResume() - } - - function checkResume () { - var db = args.level = level(args.datDb) - if (args.port) db.put('!dat!port', args.port) - db.get('!dat!key', function (err, value) { - if (err) return run() - if (isDownload && args.key !== value) { - // TODO: prompt to overwrite existing dat - console.error('Existing .dat folder does not match key.') - process.exit(0) - } - args.key = value - args.resume = true - db.get('!dat!port', function (err, portVal) { - if (err) return run() - if (portVal) args.port = portVal - run() - }) - }) - } -} - -function isDatLink (val, quiet) { - // TODO: support dat.land link - var isLink = (val.length === 64) - if (quiet || isLink) return isLink - onerror('Invalid Dat Link') -} - -function isDirectory (val, quiet) { - try { - return require('fs').statSync(val).isDirectory() // TODO: support sharing single files - } catch (err) { - if (quiet) return false - onerror('Directory does not exist') - } -} - -function onerror (msg) { - console.error(msg + '\n') - require('./usage')('root.txt') -} diff --git a/commands/download.js b/commands/download.js new file mode 100644 index 00000000..6bfa3a30 --- /dev/null +++ b/commands/download.js @@ -0,0 +1,76 @@ +var chalk = require('chalk') +var prettyBytes = require('pretty-bytes') +var Dat = require('../lib/dat') +var logger = require('status-logger') +var ui = require('../lib/ui') + +module.exports = function (args) { + var dat = Dat(args) + var log = logger(args) + + var downloadTxt = 'Downloading ' + var swarmTimeout = null + + log.status('Starting Dat...\n', 0) + log.status('Connecting...', 1) + + dat.on('error', onerror) + + dat.once('ready', function () { + log.message('Downloading in ' + dat.dir + '\n') + dat.download(function (err) { + if (err) onerror(err) + }) + }) + + dat.once('key', function (key) { + log.message(ui.keyMsg(key)) + if (args.quiet) console.log(ui.keyMsg(key)) + }) + + dat.on('download', function (data) { + downloadTxt = 'Downloading ' + printStats() + printSwarm() + }) + + dat.on('download-finished', function () { + downloadTxt = 'Downloaded ' + printStats() + if (args.exit) { + log.status('', 1) + process.exit(0) + } + printSwarm() + }) + + dat.once('connecting', function () { + var msg = 'Waiting for connections. ' + if (dat.archive.live) msg += 'Watching for updates...' + log.status(msg, 1) + }) + + dat.on('swarm-update', printSwarm) + dat.on('upload', printSwarm) + + function printSwarm () { + log.status(ui.swarmMsg(dat), 1) + if (swarmTimeout) clearInterval(swarmTimeout) + swarmTimeout = setInterval(function () { + log.status(ui.swarmMsg(dat), 1) + }, 500) + } + + function printStats () { + var stats = dat.stats + var msg = ui.progress(stats.bytesDown / stats.bytesTotal) + msg += ' ' + downloadTxt + chalk.bold(stats.filesTotal) + ' files' + msg += chalk.dim(' (' + prettyBytes(stats.bytesDown) + '/' + prettyBytes(stats.bytesTotal) + ')') + log.status(msg + '\n', 0) + } +} + +function onerror (err) { + console.error(err.stack || err) + process.exit(1) +} diff --git a/commands/list.js b/commands/list.js new file mode 100644 index 00000000..a487712f --- /dev/null +++ b/commands/list.js @@ -0,0 +1,9 @@ +var Dat = require('../lib/dat') + +module.exports = function (args) { + var dat = Dat(args) + + dat.on('ready', function () { + console.error('not implemented') + }) +} diff --git a/commands/share.js b/commands/share.js new file mode 100644 index 00000000..bd9e6cdc --- /dev/null +++ b/commands/share.js @@ -0,0 +1,96 @@ +var chalk = require('chalk') +var prettyBytes = require('pretty-bytes') +var Dat = require('../lib/dat') +var logger = require('status-logger') +var ui = require('../lib/ui') + +module.exports = function (args) { + var dat = Dat(args) + var log = logger(args) + + var addText = 'Adding ' + var updated = false + var initFileCount = 0 + var swarmTimeout = null + + log.status('Starting Dat...\n', 0) + if (args.snapshot) log.status('Creating Link...', 1) + else log.status('Connecting...', 1) + + dat.on('error', onerror) + + dat.once('ready', function () { + log.message('Sharing ' + dat.dir + '\n') + dat.share(function (err) { + if (err) onerror(err) + }) + }) + + dat.on('file-counted', function () { + var msg = 'Calculating Size: ' + msg += dat.appendStats.files + ' files ' + msg += chalk.dim('(' + prettyBytes(dat.appendStats.bytes) + ')') + log.status(msg + '\n', 0) + }) + + dat.once('key', function (key) { + log.message(ui.keyMsg(key)) + if (args.quiet) console.log(ui.keyMsg(key)) + }) + + dat.on('file-added', printStats) + dat.on('file-exists', printStats) + + dat.once('append-ready', printStats) + + dat.once('archive-finalized', function () { + addText = 'Added ' + initFileCount = dat.stats.filesTotal + printStats() + }) + + dat.on('archive-updated', function () { + addText = 'Updated ' + updated = true + printStats() + }) + + dat.once('connecting', function () { + var msg = 'Waiting for connections. ' + if (dat.archive.live) msg += 'Watching for updates...' + log.status(msg, 1) + }) + + dat.on('swarm-update', printSwarm) + dat.on('upload', printSwarm) + + function printSwarm () { + log.status(ui.swarmMsg(dat), 1) + if (swarmTimeout) clearInterval(swarmTimeout) + swarmTimeout = setInterval(function () { + log.status(ui.swarmMsg(dat), 1) + }, 500) + } + + function printStats (data) { + var stats = dat.stats + var files = stats.filesTotal + var bytesTotal = dat.appendStats.bytes + var bytesProgress = stats.bytesTotal + if (updated) { + files = files - initFileCount + bytesProgress = stats.bytesTotal // TODO: update progress for live + bytesTotal = stats.bytesTotal + } + + var msg = ui.progress(bytesProgress / bytesTotal) + msg += ' ' + addText + chalk.bold(files) + ' files' + msg += chalk.dim(' (' + prettyBytes(bytesProgress) + '/' + prettyBytes(bytesTotal) + ')') + log.status(msg + '\n', 0) + } +} + +function onerror (err) { + console.error(err.stack || err) + process.exit(1) +} diff --git a/docs/meta/changelog.md b/docs/meta/changelog.md index 05a5382d..ff5429f2 100644 --- a/docs/meta/changelog.md +++ b/docs/meta/changelog.md @@ -5,6 +5,22 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] *Note: unreleased changes are added here.* +### Fixed +* Use yolowatch module for recursive live updates +* Improved stats for edge cases +* Print link with --quiet argument + +### Changed +* Simplified and clean up CLI output +* Improve modularity of library +* Move logger module into own npm package, status-logger + +### Removed +* List download option (will be re-added pending a hyperdrive update) + +### Added +* Use dat-encoding for 50 character links + ## 11.0.2 - 2016-06-23 ### Fixed * Live mode with recursive adding files! diff --git a/docs/usage.md b/docs/usage.md index 78b10659..65777eee 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -26,14 +26,14 @@ Share a directory by typing `dat `: ``` $ dat my_data/ -Initializing Dat in my_data/ -[DONE] readme.txt (0.30 kB) -[DONE] data.csv (1.14 kB) -Items: 2 Size: 1.44 kB -Share Link 4f36c088e9687ddf53d36f785ab84c65f4d24d8c4161950519b96a57d65ae08a +Sharing /Users/joe/my_data + +Share Link: 34p3ak4jwnfn6og184k4tqojngo76qj0nqbat2h03miu85x61t The Share Link is secret and only those you share it with will be able to get the files -Sharing /Users/joe, connected to 2/4 sources -Uploading 28.62 kB/s, 765.08 kB Total + +[==============>] Added 21 files (448.41 MB/448.41 MB) + +Connected to 2 peers. Uploading 5 mBd/s. Watching for updates... ``` You are now publishing that data from your computer. It will be publicly accessible as long as your terminal is open. The hash is a **secret hash**, your data is visible to anyone you send the hash to. As you add more files to the folder, dat will update and share the new files. @@ -43,15 +43,15 @@ You are now publishing that data from your computer. It will be publicly accessi You can download data by typing `dat `: ``` -$ dat 2bede435504c9482910b5d4e324e995a9bc4d6f068b98ae03d97e8d3ac5f80ea download_dir -Initializing Dat from 52d08a6d1ddc9b1f61b9862d2ae0d991676d489274bff6c5ebebecbfa3239f51 -[DONE] readme.txt (0.30 kB) -[DONE] data.csv (1.14 kB) -[DONE] 2 items (1.44 kB) -Share Link 52d08a6d1ddc9b1f61b9862d2ae0d991676d489274bff6c5ebebecbfa3239f51 +$ dat 34p3ak4jwnfn6og184k4tqojngo76qj0nqbat2h03miu85x61t download_dir +Downloading in /Users/joe/Downloads/download_dir + +Share Link: 34p3ak4jwnfn6og184k4tqojngo76qj0nqbat2h03miu85x61t The Share Link is secret and only those you share it with will be able to get the files -Syncing live updates, connected to 1/2 sources -Download Finished, you may exit process + +[===> ] Downloading 180 files (79.01 kB/498.4 MB) + +Connected to 0 peers. Downloading 10 mB/s. Watching for updates... ``` It will start downloading the data into the `download_dir` folder. Anyone who gets access to the unique dat-link will be able to download and re-host a copy of the data. It's distributed mad science! diff --git a/lib/append.js b/lib/append.js new file mode 100644 index 00000000..24d247d8 --- /dev/null +++ b/lib/append.js @@ -0,0 +1,69 @@ +var walker = require('folder-walker') +var each = require('stream-each') + +module.exports.initialAppend = function (dat, cb) { + dat.appendStats = { + files: 0, + dirs: 0, + bytes: 0 + } + each(walker(dat.dir, {filter: dat.ignore}), countFiles, function (err) { + if (err) cb(err) + dat.emit('append-ready') + + if (dat.resume) { + dat.db.get('!dat!finalized', function (err, val) { + if (err || val !== 'true') walkFolder(true) + else walkFolder(true) // TODO: check mtimes + }) + } else { + walkFolder() + } + }) + + function countFiles (data, next) { + dat.emit('file-counted') + if (data.type === 'directory') { + dat.appendStats.dirs += 1 + } else { + dat.appendStats.files += 1 + dat.appendStats.bytes += data.stat.size + } + next() + } + + function walkFolder (resume) { + var fileStream = walker(dat.dir, {filter: dat.ignore}) + if (resume) each(fileStream, resumeAppend, cb) + else each(fileStream, appendNew, cb) + } + + function appendNew (data, next) { + dat.archive.append({type: data.type, name: data.relname}, function () { + updateStats(dat, data) + next() + }) + } + + function resumeAppend (data, next) { + dat.archive.lookup(data.relname, function (err, result) { + if (err || !result) return appendNew(data, next) + updateStats(dat, data, true) + return next() + }) + } +} + +module.exports.liveAppend = function (dat, data) { + if (!dat.ignore(data.filepath)) return + dat.archive.append({type: data.type, name: data.relname}, function () { + updateStats(dat, data) + }) +} + +function updateStats (dat, data, existing) { + if (data.type === 'file') dat.stats.filesTotal += 1 + dat.stats.bytesTotal = dat.archive.content ? dat.archive.content.bytes : 0 + if (existing) dat.emit('file-exists', data) + else dat.emit('file-added', data) +} diff --git a/lib/dat.js b/lib/dat.js new file mode 100644 index 00000000..c0bb263d --- /dev/null +++ b/lib/dat.js @@ -0,0 +1,207 @@ +var events = require('events') +var fs = require('fs') +var path = require('path') +var util = require('util') +var encoding = require('dat-encoding') +var hyperdrive = require('hyperdrive') +var createSwarm = require('hyperdrive-archive-swarm') +var level = require('level') +var raf = require('random-access-file') +var speedometer = require('speedometer') +var each = require('stream-each') +var yoloWatch = require('yolowatch') +var append = require('./append') + +module.exports = Dat + +function Dat (opts) { + if (!(this instanceof Dat)) return new Dat(opts) + if (!opts) opts = {} + events.EventEmitter.call(this) + + var self = this + + this.dir = opts.dir === '.' ? process.cwd() : opts.dir + this.datPath = opts.datPath || path.join(self.dir, '.dat') + if (opts.key && opts.key.length === 50) opts.key = encoding.decode(opts.key) + else if (opts.key && opts.key.length === 64) opts.key = Buffer(opts.key, 'hex') + this.key = opts.key + this.snapshot = opts.snapshot + this.ignore = ignore + this.swarm = null + this.stats = { + filesTotal: 0, + bytesTotal: 0, + bytesUp: 0, + bytesDown: 0, + rateUp: speedometer(), + rateDown: speedometer() + } + getDb(function (err, db) { + if (err) return self.emit('error', err) + self.db = db + var drive = hyperdrive(db) + var isLive = opts.key ? null : !opts.snapshot + self.archive = drive.createArchive(self.key, { + live: isLive, + file: function (name) { + return raf(path.join(self.dir, name)) + } + }) + self.emit('ready') + }) + + function getDb (cb) { + if (!fs.existsSync(self.datPath)) fs.mkdirSync(self.datPath) + var db = level(self.datPath) + tryResume() + + function tryResume () { + if (opts.port) db.put('!dat!port', opts.port) + db.get('!dat!key', function (err, value) { + if (err || !value) return cb(null, db) + if (self.key && value !== encoding.encode(self.key)) return cb('Existing key does not match.') + self.key = encoding.decode(value) + self.resume = true + db.get('!dat!port', function (err, portVal) { + if (err || !portVal) return cb(null, db) + self.port = portVal + return cb(null, db) + }) + }) + } + } + + function ignore (filepath) { + // TODO: split this out and make it composable/modular/optional/modifiable + return filepath.indexOf('.dat') === -1 && filepath.indexOf('.swp') === -1 + } +} + +util.inherits(Dat, events.EventEmitter) + +Dat.prototype.share = function (cb) { + var self = this + var archive = self.archive + + archive.open(function (err) { + if (err) return cb(err) + + if (archive.key && !archive.owner) { + // TODO: allow this but change to download + cb('Dat previously downloaded. Run dat ' + encoding.encode(archive.key) + ' to resume') + } + + if ((archive.live || archive.owner) && archive.key) { + if (!self.key) self.db.put('!dat!key', encoding.encode(archive.key)) + self.joinSwarm() + self.emit('key', encoding.encode(archive.key)) + } + + append.initialAppend(self, done) + }) + + archive.on('upload', function (data) { + self.stats.bytesUp += data.length + self.stats.rateUp(data.length) + self.emit('upload', data) + }) + + function done (err) { + if (err) return cb(err) + + archive.finalize(function (err) { + if (err) return cb(err) + + if (self.snapshot) { + self.joinSwarm() + self.emit('key', encoding.encode(archive.key)) + self.emit('archive-finalized') + self.db.put('!dat!finalized', true, cb) + } + + self.db.put('!dat!finalized', true, function (err) { + if (err) return cb(err) + self.emit('archive-finalized') + watchLive() + }) + + function watchLive () { + var watch = yoloWatch(self.dir, {filter: self.ignore}) + watch.on('changed', function (name, data) { + if (name === self.dir) return + append.liveAppend(self, data) + self.emit('archive-updated') + }) + watch.on('added', function (name, data) { + append.liveAppend(self, data) + self.emit('archive-updated') + }) + } + }) + } +} + +Dat.prototype.download = function (cb) { + var self = this + var archive = self.archive + + self.stats.filesDown = 0 + self.joinSwarm() + self.emit('key', encoding.encode(self.key)) + + archive.open(function (err) { + if (err) return cb(err) + self.db.put('!dat!key', encoding.encode(archive.key)) + updateTotalStats() + + each(archive.list({live: archive.live}), function (data, next) { + var startBytes = self.stats.bytesDown + archive.download(data, function (err) { + if (err) return cb(err) + self.stats.filesDown += 1 + self.emit('file-downloaded', data) + if (startBytes === self.stats.bytesDown) self.stats.bytesDown += data.length // file already exists + if (self.stats.filesDown === self.stats.filesTotal) done() + else next() + }) + }, done) + }) + + archive.on('download', function (data) { + // TODO: better way to update totals on live updates? + updateTotalStats() + + self.stats.bytesDown += data.length + self.stats.rateDown(data.length) + self.emit('download', data) + }) + + archive.on('upload', function (data) { + self.stats.bytesUp += data.length + self.stats.rateUp(data.length) + self.emit('upload', data) + }) + + function updateTotalStats () { + self.stats.filesTotal = archive.metadata.blocks - 1 // first block is header. + self.stats.bytesTotal = archive.content ? archive.content.bytes : 0 + } + + function done () { + self.emit('download-finished') + if (!archive.live) cb(null) + } +} + +Dat.prototype.joinSwarm = function () { + var self = this + self.swarm = createSwarm(self.archive, {port: self.port}) + self.emit('connecting') + self.swarm.on('connection', function (peer) { + self.emit('swarm-update') + peer.on('close', function () { + self.emit('swarm-update') + }) + }) +} diff --git a/lib/status-logger.js b/lib/status-logger.js deleted file mode 100644 index 994b6872..00000000 --- a/lib/status-logger.js +++ /dev/null @@ -1,51 +0,0 @@ -var getLogger = require('../logger') - -module.exports = StatusLogger - -function StatusLogger (args) { - if (!(this instanceof StatusLogger)) return new StatusLogger(args) - - var logger = getLogger(args) - - var LOG_INTERVAL = (args.logspeed ? +args.logspeed : 200) - if (isNaN(LOG_INTERVAL)) LOG_INTERVAL = 200 - - var messageQueue = [] - var statusLines = [] - var statusLastLine = '' - - this.message = function (msg) { - messageQueue.push(msg) - } - - this.status = function (msg, lineNum) { - if (typeof lineNum === 'undefined') statusLines = [msg] - else if (lineNum === -1) statusLastLine = msg - else if (lineNum < statusLines.length) statusLines[lineNum] = msg - else statusLines.push(msg) - } - - this.logNow = function () { - // send last updates before exiting process - print() - } - - setInterval(function () { - print() - }, LOG_INTERVAL) - print() - - function print () { - logger.stdout() // Clear old stdout before printing messages - while (true) { - if (messageQueue.length === 0) break - logger.log(messageQueue[0]) - messageQueue.shift() - } - if (statusLines.length || statusLastLine.length) { - var msg = statusLines.join('\n') - msg += '\n' + statusLastLine - logger.stdout(msg) - } - } -} diff --git a/lib/swarm-logger.js b/lib/swarm-logger.js deleted file mode 100644 index 97260f75..00000000 --- a/lib/swarm-logger.js +++ /dev/null @@ -1,27 +0,0 @@ -var chalk = require('chalk') - -module.exports = swarmLogger - -function swarmLogger (swarm, logger, msg) { - var message = msg + ', waiting for connections...' - logger.status(message, 3) - updatePeers() - - swarm.on('connection', function (peer) { - updatePeers() - // TODO: if (peer.type === 'webrtc-swarm') - peer.on('close', function () { - updatePeers() - }) - }) - - function updatePeers () { - var count = '0' - var activePeers = swarm.connections - var totalPeers = swarm.node.totalConnections // TODO: browser connections - if (activePeers > 0) count = activePeers + '/' + totalPeers - if (activePeers > 0) message = msg + ', connected to ' + chalk.bold(count) + ' sources' - else message = msg + ', waiting for connections...' - logger.status(message, 3) - } -} diff --git a/lib/ui.js b/lib/ui.js new file mode 100644 index 00000000..8de07da8 --- /dev/null +++ b/lib/ui.js @@ -0,0 +1,35 @@ +var chalk = require('chalk') +var prettyBytes = require('pretty-bytes') + +module.exports.progress = function (percent) { + var width = 15 + var cap = '>' + var ends = ['[', ']'] + var spacer = Array(width).join(' ') + var progressVal = '' + var val = Math.round(percent * width) + + if (val && val > 0) { + progressVal = Array(val).join('=') + progressVal += cap + } + progressVal += spacer + progressVal = progressVal.substring(0, width) + + return ends[0] + progressVal + ends[1] +} + +module.exports.swarmMsg = function (dat) { + var msg = 'Connected to ' + dat.swarm.connections + ' peers. ' + if (dat.stats.bytesDown) msg += 'Downloading ' + prettyBytes(dat.stats.rateDown()) + '/s. ' + if (dat.stats.bytesUp) msg += 'Uploading ' + prettyBytes(dat.stats.rateUp()) + '/s. ' + if (dat.archive.live) msg += 'Watching for updates...' + return msg +} + +module.exports.keyMsg = function (key) { + var msg = 'Share Link: ' + msg += chalk.blue.underline(key) + '\n' + msg += 'The Share Link is secret and only those you share it with will be able to get the files' + return msg + '\n' +} diff --git a/logger.js b/logger.js deleted file mode 100644 index 367daa05..00000000 --- a/logger.js +++ /dev/null @@ -1,32 +0,0 @@ -var singleLineLog = require('single-line-log') - -module.exports = function getLogger (opts) { - if (opts.quiet) { - return { - stderr: logQuiet, - stdout: logQuiet, - log: logQuiet, - error: logQuiet - } - } - - if (opts.debug) { - return { - stderr: console.error.bind(console), - stdout: console.log.bind(console), - log: console.error.bind(console), - error: console.log.bind(console) - } - } - - return { - stderr: singleLineLog.stderr, - stdout: singleLineLog.stdout, - log: console.log.bind(console), - error: console.error.bind(console) - } -} - -function logQuiet () { - // do nothing -} diff --git a/package.json b/package.json index 8a3a70d7..09db8ab4 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "synchronization" ], "bin": { - "dat": "cli.js" + "dat": "bin/cli.js" }, "scripts": { "install-precommit": "echo ./node_modules/.bin/standard > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit", @@ -24,24 +24,23 @@ "license": "BSD-3-Clause", "dependencies": { "chalk": "^1.1.1", + "dat-encoding": "^2.0.2", "datland-swarm-defaults": "^1.0.0", "debug": "^2.2.0", "discovery-swarm": "^4.0.0", "dns-discovery": "^5.3.3", "folder-walker": "^3.0.0", - "hyperdrive": "^6.2.2", - "hyperdrive-archive-swarm": "^3.0.1", + "hyperdrive": "^6.6.1", + "hyperdrive-archive-swarm": "^3.1.0", "level": "^1.4.0", - "memdb": "^1.3.1", "minimist": "^1.2.0", - "mkdirp": "^0.5.1", "pretty-bytes": "^3.0.0", "pump": "^1.0.1", "random-access-file": "^1.2.0", - "single-line-log": "^1.1.0", "speedometer": "^1.0.0", + "status-logger": "^1.0.0", "stream-each": "^1.1.0", - "yolowatch": "^2.1.0" + "yolowatch": "^2.3.0" }, "bugs": { "url": "https://github.com/maxogden/dat/issues" @@ -51,7 +50,8 @@ "test": "tests" }, "devDependencies": { - "rimraf": "^2.5.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.3", "standard": "^6.0.7", "tap-spec": "^4.1.1", "tape": "^4.4.0", diff --git a/tests/download.js b/tests/download.js index 8879d415..22ab8587 100644 --- a/tests/download.js +++ b/tests/download.js @@ -6,19 +6,18 @@ var mkdirp = require('mkdirp') var rimraf = require('rimraf') var spawn = require('./helpers/spawn.js') -var dat = path.resolve(path.join(__dirname, '..', 'cli.js')) +var dat = path.resolve(path.join(__dirname, '..', 'bin', 'cli.js')) var fixtures = path.join(__dirname, 'fixtures') -var tmp = os.tmpdir() +var downloadDir = newTestFolder() // os x adds this if you view the fixtures in finder and breaks the file count assertions try { fs.unlinkSync(path.join(__dirname, 'fixtures', '.DS_Store')) } catch (e) { /* ignore error */ } test('starts looking for peers with correct hash', function (t) { - // cmd: dat tmp - rimraf.sync(path.join(tmp, '.dat')) - var st = spawn(t, dat + ' 9d011b6c9de26e53e9961c8d8ea840d33e0d8408318332c9502bad112cad9989 ' + tmp) + // cmd: dat downloadDir + var st = spawn(t, dat + ' 5hz25io80t0m1ttr332awpslmlfn1mc5bf1z8lvhh34a9r1ob3 ' + downloadDir) st.stdout.match(function (output) { - var downloading = output.indexOf('waiting for connections') > -1 + var downloading = output.indexOf('Waiting for connections') > -1 if (!downloading) return false t.ok(downloading, 'Started looking for Peers') st.kill() @@ -28,9 +27,9 @@ test('starts looking for peers with correct hash', function (t) { }) test('errors with invalid hash', function (t) { - // cmd: dat pizza tmp - rimraf.sync(path.join(tmp, '.dat')) - var st = spawn(t, dat + ' pizza ' + tmp) + // cmd: dat pizza downloadDir + rimraf.sync(path.join(downloadDir, '.dat')) + var st = spawn(t, dat + ' pizza ' + downloadDir) st.stderr.match(function (output) { var gotError = output.indexOf('Invalid Dat Link') > -1 t.ok(gotError, 'got error') @@ -42,9 +41,9 @@ test('errors with invalid hash', function (t) { test('errors on new download without directory', function (t) { // cmd: dat rimraf.sync(path.join(process.cwd(), '.dat')) // in case we have a .dat folder here - var st = spawn(t, dat + ' 9d011b6c9de26e53e9961c8d8ea840d33e0d8408318332c9502bad112cad9989') + var st = spawn(t, dat + ' 5hz25io80t0m1ttr332awpslmlfn1mc5bf1z8lvhh34a9r1ob3') st.stderr.match(function (output) { - var gotError = output.indexOf('Please specify a directory.') > -1 + var gotError = output.indexOf('Directory does not exist') > -1 t.ok(gotError, 'got error') if (gotError) return true }) @@ -69,7 +68,7 @@ test('download resumes with same key', function (t) { // cmd: dat tmpdir var downloader = spawn(t, dat + ' ' + link + ' ' + tmpdir, {end: false}) downloader.stdout.match(function (output) { - var contains = output.indexOf('DONE') > -1 + var contains = output.indexOf('Downloaded') > -1 if (!contains || !share) return false downloader.kill() spawnDownloaderTwo() @@ -79,14 +78,14 @@ test('download resumes with same key', function (t) { } function spawnDownloaderTwo () { - // cmd: dat (no dir required in cwd w/ dat folder) - var downloaderTwo = spawn(t, dat + ' ' + link, {cwd: tmpdir, end: false}) + // cmd: dat . + var downloaderTwo = spawn(t, dat + ' ' + link + ' ' + tmpdir, {end: false}) downloaderTwo.stdout.match(function (output) { - var contains = output.indexOf('Initializing') > -1 + var contains = output.indexOf('Downloaded') > -1 if (!contains || !share) return false downloaderTwo.kill() return true - }, 'download two resumed without dir argument') + }, 'download two resumed with same key') downloaderTwo.end(function () { t.end() }) @@ -112,10 +111,10 @@ test('download transfers files', function (t) { function startDownloader () { var downloader = spawn(t, dat + ' ' + link + ' ' + tmpdir, {end: false}) downloader.stdout.match(function (output) { - var contains = output.indexOf('Finished') > -1 + var contains = output.indexOf('Downloaded') > -1 if (!contains || !share) return false - var hasFiles = output.indexOf('3 items') > -1 + var hasFiles = output.indexOf('3 files') > -1 t.ok(hasFiles, 'file number is 3') var hasSize = output.indexOf('1.44 kB') > -1 @@ -146,8 +145,13 @@ test('download transfers files', function (t) { } }) +process.on('exit', function () { + console.log('cleaning up') + rimraf.sync(downloadDir) +}) + function newTestFolder () { - var tmpdir = tmp + '/dat-download-folder-test' + var tmpdir = path.join(os.tmpdir(), 'dat-download-folder') rimraf.sync(tmpdir) mkdirp.sync(tmpdir) return tmpdir @@ -155,7 +159,7 @@ function newTestFolder () { function matchDatLink (output) { // TODO: dat.land links - var match = output.match(/Link [A-Za-z0-9]{64}/) + var match = output.match(/Link: [A-Za-z0-9]{50}/) if (!match) return false - return match[0].split('Link ')[1].trim() + return match[0].split('Link: ')[1].trim() } diff --git a/tests/share.js b/tests/share.js index eaf8c65c..efb137bd 100644 --- a/tests/share.js +++ b/tests/share.js @@ -4,7 +4,7 @@ var test = require('tape') var rimraf = require('rimraf') var spawn = require('./helpers/spawn.js') -var dat = path.resolve(path.join(__dirname, '..', 'cli.js')) +var dat = path.resolve(path.join(__dirname, '..', 'bin', 'cli.js')) var fixtures = path.join(__dirname, 'fixtures') var fixturesStaticLink @@ -86,7 +86,7 @@ test('share prints shared directory', function (t) { // cmd: dat tests/fixtures var st = spawn(t, dat + ' ' + fixtures) st.stdout.match(function (output) { - var contains = output.indexOf('Initializing Dat') > -1 + var contains = output.indexOf('Sharing') > -1 if (!contains) return false t.ok(output.indexOf(path.resolve(fixtures)) > -1, 'prints directory name') st.kill() @@ -99,25 +99,12 @@ test('share prints shared directory', function (t) { test('prints file information (live)', function (t) { // cmd: dat tests/fixtures var st = spawn(t, dat + ' ' + fixtures) - var matchedFiles = 0 st.stdout.match(function (output) { - var finished = output.match('Sharing') + var finished = output.match('Added') if (!finished) return false - var fileList = output.split('\n').filter(function (line) { - return line.indexOf('[DONE]') > -1 - }) - fileList.forEach(function (file) { - file = file.split('[DONE]')[1] - if (file.match(/all_hour|empty/)) matchedFiles += 1 - }) - t.ok((matchedFiles === 2), 'Printed ' + matchedFiles + ' file names') - - var fileStats = output.split('\n').filter(function (line) { - return line.indexOf('Items') > -1 - })[0] - t.ok(fileStats.match(/Items: 3/), 'File count correct') - t.ok(fileStats.match(/Size: 1\.44 kB/), 'File size correct') + t.ok(output.match(/2 files/), 'File count correct') + t.ok(output.match(/1\.44 kB/), 'File size correct') st.kill() cleanDat() @@ -126,28 +113,15 @@ test('prints file information (live)', function (t) { st.end() }) -test('prints file information (static)', function (t) { +test('prints file information (snapshot)', function (t) { // cmd: dat tests/fixtures --snapshot var st = spawn(t, dat + ' ' + fixtures + ' --snapshot') - var matchedFiles = 0 st.stdout.match(function (output) { - var finished = output.match('Sharing Snapshot') + var finished = output.match('Added') if (!finished) return false - var fileList = output.split('\n').filter(function (line) { - return line.indexOf('[DONE]') > -1 - }) - fileList.forEach(function (file) { - file = file.split('[DONE]')[1] - if (file.match(/all_hour|empty/)) matchedFiles += 1 - }) - t.ok((matchedFiles === 2), 'Printed ' + matchedFiles + ' file names') - - var fileStats = output.split('\n').filter(function (line) { - return line.indexOf('Items') > -1 - })[0] - t.ok(fileStats.match(/Items: 2/), 'File count correct') // TODO: make this consitent w/ live - t.ok(fileStats.match(/Size: 1\.44 kB/), 'File size correct') + t.ok(output.match(/2 files/), 'File count correct') + t.ok(output.match(/1\.44 kB/), 'File size correct') st.kill() cleanDat() @@ -160,7 +134,7 @@ test('share with . arg defaults to cwd', function (t) { // cmd: dat . var st = spawn(t, dat + ' .', {cwd: fixtures}) st.stdout.match(function (output) { - var contains = output.indexOf('Initializing Dat') > -1 + var contains = output.indexOf('Sharing') > -1 if (!contains) return false t.ok(output.indexOf(path.resolve(fixtures)) > -1, 'prints directory name') st.kill() @@ -176,7 +150,7 @@ function cleanDat () { function matchDatLink (output) { // TODO: dat.land links - var match = output.match(/Link [A-Za-z0-9]{64}/) + var match = output.match(/Link: [A-Za-z0-9]{50}/) if (!match) return false - return match[0].split('Link ')[1].trim() + return match[0].split('Link: ')[1].trim() } diff --git a/usage/root.txt b/usage/root.txt index d2376527..195b64dd 100644 --- a/usage/root.txt +++ b/usage/root.txt @@ -9,7 +9,6 @@ dat download a dat-link into directory - --list print file list for dat-link --exit exit process after download finishes --port, -p set a specific inbound tcp port