├── .gitignore ├── LICENSE ├── README.md ├── airtar.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Simon Kusterer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # airtar 2 | 3 | airtar is a tiny wrapper around [airpaste](https://github.com/mafintosh/airpaste) and [tar-fs](https://github.com/mafintosh/tar-fs). It can be used 4 | to send multiple files within your local network without knowing the IP or hostname of the receiver. 5 | Basicly it does the same as calling `tar c . | airpaste` on the sender-side and `airpaste | tar x` 6 | on the receiver-side. On top it adds some sugar like displaying the transfer rate. 7 | 8 | ### usage on the sender side: 9 | 10 | ``` 11 | // transfer the files from the current directory 12 | airtar . 13 | 14 | // but you can also choose which files and directories you want to transfer 15 | airtar file1.txt file2.txt 16 | 17 | // ...or use globs 18 | airtar *.js 19 | 20 | // call airpaste with a namespace 21 | airtar -n foo . 22 | ``` 23 | 24 | ### usage on the receiver side: 25 | 26 | ``` 27 | // receive files and save them in the current directory 28 | airtar -r . 29 | 30 | // shortcut for 'airtar -r .' 31 | airtar 32 | 33 | // or define a target dir 34 | airtar -r ./target 35 | 36 | // ...use a namespace 37 | airtar -r -n foo . 38 | 39 | // explicity overwrite existing files 40 | airtar -r -o . 41 | ``` 42 | 43 | ### security notice 44 | 45 | `airtar` is meant to be run in a trusted network. The transfered data is not crypted in 46 | anyway and you won't be able to verify who sent it. 47 | 48 | ### installation 49 | 50 | `npm install airtar -g` 51 | 52 | 53 | ## License 54 | Copyright (c) 2015 Simon Kusterer 55 | Licensed under the MIT license. 56 | -------------------------------------------------------------------------------- /airtar.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var minimist = require('minimist') 4 | var log = require('single-line-log').stdout 5 | var prettysize = require('prettysize') 6 | var isAbsolute = require('absolute-path') 7 | var progress = require('progress-stream') 8 | var through = require('through2') 9 | var circulate = require('array-loop') 10 | var airpaste = require('airpaste') 11 | var tarfs = require('tar-fs') 12 | var fs = require('fs') 13 | var glob = require('glob') 14 | var chalk = require('chalk') 15 | var cwd = process.cwd() 16 | var dots = circulate(['.', '..', '...', '...']) 17 | 18 | var opts = minimist(process.argv.slice(2), { 19 | boolean: ['r', 'o', 'help', 'h', '?'] 20 | }) 21 | 22 | if (opts.help || opts.h || opts['?']) { 23 | console.log([ 24 | '', 25 | 'Usage', 26 | ' Send : airtar [-n ] [, ...]', 27 | ' Receive : airtar [-r] [-n ] [-o] []', 28 | '', 29 | 'Option Meaning', 30 | '-r receive a file (defaults true if no args are specified)', 31 | '-n define a namespace', 32 | '-o overwrite existing files', 33 | '-h view usage', 34 | '' 35 | ].join('\n')) 36 | process.exit() 37 | } 38 | 39 | var measure = function (fileStream, header) { 40 | var prog = progress({ 41 | length: header.size, 42 | time: 200 43 | }) 44 | 45 | prog.on('progress', function (info) { 46 | var str = ' ' + header.name + ' ' + chalk.gray('(' + prettysize(header.size) + ')') 47 | if (info.percentage === 100) { 48 | log('') 49 | console.log(chalk.green('[ DONE ]') + str) 50 | } else { 51 | log(chalk.gray('[ ' + info.percentage.toFixed(0) + ' % ] ') + str + '\n') 52 | } 53 | }) 54 | 55 | return fileStream.pipe(prog) 56 | } 57 | 58 | var counter = function () { 59 | var total = 0 60 | return { 61 | through: through(function (chunk, enc, next) { 62 | total += chunk.length 63 | this.push(chunk) 64 | next() 65 | }), 66 | total: function () { 67 | return total 68 | } 69 | } 70 | } 71 | 72 | var wait = function (msg) { 73 | var inter = setInterval(function () { 74 | log(msg + dots()) 75 | }, 300) 76 | return function () { 77 | log('') 78 | clearInterval(inter) 79 | } 80 | } 81 | 82 | var send = function () { 83 | var source = opts._.reduce(function (memo, entry) { 84 | return memo.concat(glob.sync(entry)) 85 | }, []) 86 | 87 | if (source.filter(isAbsolute).length) { 88 | console.log(chalk.red('Absolute paths are not allowed.')) 89 | process.exit() 90 | } 91 | 92 | var stream = airpaste(opts.namespace) 93 | var found = wait('waiting for receiver') 94 | var count = counter() 95 | 96 | stream.once('uncork', function () { 97 | found() 98 | tarfs 99 | .pack(cwd, { entries: source, mapStream: measure }) 100 | .pipe(count.through) 101 | .pipe(stream) 102 | }) 103 | 104 | stream.once('finish', function () { 105 | console.log(chalk.gray(prettysize(count.total()) + ' sent')) 106 | }) 107 | } 108 | 109 | var receive = function () { 110 | var stream = airpaste(opts.namespace) 111 | 112 | var ignore = function (name) { 113 | if (!opts.o && fs.existsSync(name) && fs.statSync(name).isFile()) { 114 | console.log(chalk.red('[ EXISTS ]') + ' ' + name) 115 | return true 116 | } 117 | return false 118 | } 119 | 120 | var dir = opts._[0] || process.cwd() 121 | var found = wait('waiting for sender', dir) 122 | var count = counter() 123 | var target = tarfs.extract(dir, { ignore: ignore, mapStream: measure }) 124 | 125 | stream.once('uncork', function () { 126 | found() 127 | stream 128 | .pipe(count.through) 129 | .pipe(target) 130 | }) 131 | 132 | target.once('finish', function () { 133 | console.log(chalk.gray(prettysize(count.total()) + ' received')) 134 | }) 135 | } 136 | 137 | process.on('uncaughtException', function () { 138 | log('') 139 | console.log(chalk.red('oooops, something went wrong')) 140 | }) 141 | 142 | if (opts._.length === 0) opts.r = true 143 | 144 | if (!opts.r) { 145 | send() 146 | } else { 147 | receive() 148 | } 149 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "airtar", 3 | "version": "5.0.0", 4 | "description": "send files over the local network using airpaste and tar", 5 | "main": "", 6 | "bin": { 7 | "airtar": "./airtar.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:xat/airtar.git" 12 | }, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "author": "Simon Kusterer", 17 | "license": "MIT", 18 | "dependencies": { 19 | "absolute-path": "0.0.0", 20 | "airpaste": "^1.0.8", 21 | "array-loop": "^1.0.0", 22 | "chalk": "^1.0.0", 23 | "glob": "^5.0.3", 24 | "minimist": "^1.1.1", 25 | "prettysize": "0.0.3", 26 | "progress-stream": "^1.0.1", 27 | "single-line-log": "^0.4.1", 28 | "tar-fs": "^1.5.0", 29 | "through2": "^0.6.3" 30 | } 31 | } 32 | --------------------------------------------------------------------------------