Migrate to WebTorrent.

pull/18/head
Alex Kern 11 years ago
parent 9a6b1b8f8e
commit 75e646c4b9

@ -1,25 +0,0 @@
const rankSize = 16
function blobLength(b) {
if (typeof b.byteLength !== 'undefined') return b.byteLength
if (typeof b.size !== 'undefined') return b.size
return b.length
}
export default class ChunkedBlob {
constructor() {
this.size = 0
this.ranks = []
}
add(b) {
this.size += blobLength(b)
this.ranks.push(b)
}
toBlob() {
return new Blob(this.ranks)
}
}

@ -1,54 +0,0 @@
import ChunkedBlob from './ChunkedBlob'
export default class DownloadFile {
constructor(name, size, type) {
this.name = name
this.size = size
this.type = type
this.chunks = new ChunkedBlob()
}
addChunk(b) {
this.chunks.add(b)
}
clearChunks() {
this.chunks = new ChunkedBlob()
}
isComplete() {
return this.getProgress() === 1
}
getProgress() {
return this.chunks.size / this.size
}
download() {
let blob = this.chunks.toBlob()
let url = URL.createObjectURL(blob)
let a = document.createElement('a')
document.body.appendChild(a)
a.download = this.name
a.href = url
a.click()
setTimeout(() => {
URL.revokeObjectURL(url)
document.body.removeChild(a)
}, 0)
}
toJSON() {
return {
name: this.name,
size: this.size,
type: this.type
}
}
}

@ -1,33 +0,0 @@
const chunkSize = 256 * 1024
export default class UploadFile {
constructor(file) {
this.name = file.name
this.size = file.size
this.type = file.type
this.blob = file
}
countChunks() {
return Math.ceil(this.size / chunkSize)
}
getChunk(i) {
if (i < 0 || i >= this.countChunks())
throw new Error('Chunk out of bounds')
let start = i * chunkSize
let end = Math.min(start + chunkSize, this.size)
return this.blob.slice(start, end)
}
toJSON() {
return {
name: this.name,
size: this.size,
type: this.type
}
}
}

@ -3,8 +3,7 @@ import alt from '../alt'
export default alt.createActions(class DownloadActions {
constructor() {
this.generateActions(
'requestDownload',
'beginDownload'
'requestDownload'
)
}
})

@ -3,8 +3,6 @@ import alt from '../alt'
export default alt.createActions(class UploadActions {
constructor() {
this.generateActions(
'sendToDownloader',
'setUploadToken',
'uploadFile'
)
}

@ -4,7 +4,7 @@ import SupportStore from '../stores/SupportStore'
function getState() {
return {
active: SupportStore.getState().isChrome && DownloadStore.getState().file.size >= 500000000
active: SupportStore.getState().isChrome && DownloadStore.getState().fileSize >= 500000000
}
}

@ -2,10 +2,10 @@ import ChromeNotice from './ChromeNotice'
import DownloadActions from '../actions/DownloadActions'
import DownloadButton from './DownloadButton'
import DownloadStore from '../stores/DownloadStore'
import ErrorPage from './ErrorPage'
import ProgressBar from './ProgressBar'
import React from 'react'
import Spinner from './Spinner'
import peer from 'filepizza-peerjs'
export default class DownloadPage extends React.Component {
@ -16,20 +16,14 @@ export default class DownloadPage extends React.Component {
this._onChange = () => {
this.setState(DownloadStore.getState())
}
this._onConnection = (conn) => {
DownloadActions.beginDownload(conn)
}
}
componentDidMount() {
DownloadStore.listen(this._onChange)
peer.on('connection', this._onConnection)
}
componentDidUnmount() {
DownloadStore.unlisten(this._onChange)
peer.removeListener('connection', this._onConnection)
}
downloadFile() {
@ -43,8 +37,8 @@ export default class DownloadPage extends React.Component {
<h1>FilePizza</h1>
<Spinner dir="down"
name={this.state.file.name}
size={this.state.file.size} />
name={this.state.fileName}
size={this.state.fileSize} />
<ChromeNotice />
<DownloadButton onClick={this.downloadFile.bind(this)} />
@ -57,39 +51,29 @@ export default class DownloadPage extends React.Component {
<h1>FilePizza</h1>
<Spinner dir="down" animated
name={this.state.file.name}
size={this.state.file.size} />
name={this.state.fileName}
size={this.state.fileSize} />
<ChromeNotice />
<ProgressBar value={this.state.progress} />
</div>
case 'cancelled':
return <div className="page">
<h1>FilePizza</h1>
<Spinner dir="down"
name={this.state.file.name}
size={this.state.file.size} />
<ChromeNotice />
<ProgressBar value={-1} />
</div>
case 'done':
return <div className="page">
<h1>FilePizza</h1>
<Spinner dir="down"
name={this.state.file.name}
size={this.state.file.size} />
name={this.state.fileName}
size={this.state.fileSize} />
<ChromeNotice />
<ProgressBar value={1} />
</div>
default:
return <ErrorPage />
}
}

@ -1,5 +1,4 @@
import DropZone from './DropZone'
import ProgressBar from './ProgressBar'
import React from 'react'
import Spinner from './Spinner'
import Tempalink from './Tempalink'
@ -16,20 +15,14 @@ export default class UploadPage extends React.Component {
this._onChange = () => {
this.setState(UploadStore.getState())
}
this._onDownload = (peerID) => {
UploadActions.sendToDownloader(peerID)
}
}
componentDidMount() {
UploadStore.listen(this._onChange)
socket.on('download', this._onDownload)
}
componentDidUnmount() {
UploadStore.unlisten(this._onChange)
socket.removeListener('download', this._onDownload)
}
uploadFile(file) {
@ -37,9 +30,9 @@ export default class UploadPage extends React.Component {
}
handleSelectedFile(event) {
let files = event.target.files;
let files = event.target.files
if (files.length > 0) {
UploadActions.uploadFile(files[0]);
UploadActions.uploadFile(files[0])
}
}
@ -75,24 +68,17 @@ export default class UploadPage extends React.Component {
</div>
case 'uploading':
var keys = Object.keys(this.state.peerProgress)
keys.reverse()
return <div className="page">
<h1>FilePizza</h1>
<Spinner dir="up" animated {...this.state.file} />
<Spinner dir="up" animated
name={this.state.fileName}
size={this.state.fileSize} />
<p>Send someone this link to download.</p>
<p>This link will work as long as this page is open.</p>
<Tempalink token={this.state.token} />
{keys.length > 0 ? <p>Download Progress</p> : null}
<div className="data">
{keys.map((key) => {
return <ProgressBar small key={key} value={this.state.peerProgress[key]} />
})}
</div>
</div>
}
}

@ -35,5 +35,6 @@ export function find(token) {
}
export function remove(client) {
if (client == null) return
delete tokens[client.token]
}

@ -11,7 +11,10 @@ routes.get(/^\/([a-z]+-[a-z]+-[a-z]+-[a-z]+)$/, function (req, res, next) {
DownloadStore: {
status: 'ready',
token: uploader.token,
file: uploader.metadata
fileSize: uploader.fileSize,
fileName: uploader.fileName,
fileType: uploader.fileType,
infoHash: uploader.infoHash
}
}

@ -14,3 +14,4 @@ export default (
<NotFoundRoute handler={ErrorPage} />
</Route>
)

@ -3,7 +3,6 @@ var express = require('express')
var fs = require('fs')
var http = require('http')
var path = require('path')
var peer = require('peer')
var socketIO = require('socket.io')
var winston = require('winston')
var expressWinston = require('express-winston')
@ -32,8 +31,6 @@ server.listen(process.env.PORT || 3000, function () {
console.log('FilePizza listening on %s:%s', host, port)
})
app.use('/peer', peer.ExpressPeerServer(server))
app.use(expressWinston.logger({
winstonInstance: winston,
expressFormat: true
@ -55,22 +52,18 @@ io.on('connection', function (socket) {
socket.on('upload', function (metadata, res) {
if (upload) return
upload = true
db.create(socket).then((u) => {
upload = u
upload.metadata = metadata
upload.fileName = metadata.fileName
upload.fileSize = metadata.fileSize
upload.fileType = metadata.fileType
upload.infoHash = metadata.infoHash
res(upload.token)
})
})
socket.on('download', function (data) {
var uploader = db.find(data.token)
if (!uploader) return
uploader.socket.emit('download', data.peerID)
})
socket.on('disconnect', function () {
if (upload) db.remove(upload)
db.remove(upload)
})
})

@ -1,62 +1,57 @@
import DownloadActions from '../actions/DownloadActions'
import DownloadFile from '../DownloadFile'
import peer from 'filepizza-peerjs'
import WebTorrent from 'webtorrent'
import alt from '../alt'
import socket from 'filepizza-socket'
function downloadBlobURL(name, blobURL) {
let a = document.createElement('a')
document.body.appendChild(a)
a.download = name
a.href = blobURL
a.click()
}
export default alt.createStore(class DownloadStore {
constructor() {
this.bindActions(DownloadActions)
this.status = 'ready'
this.token = null
this.file = null
this.fileName = ''
this.fileSize = 0
this.fileType = ''
this.progress = 0
this.on('bootstrap', () => {
if (this.file && !(this.file instanceof DownloadFile)) {
this.file = new DownloadFile(this.file.name,
this.file.size,
this.file.type)
}
})
this.status = 'uninitialized'
this.token = null
this.infoHash = null
}
onRequestDownload() {
if (this.status !== 'ready') return
this.status = 'requesting'
socket.emit('download', {
peerID: peer.id,
token: this.token
})
}
onBeginDownload(conn) {
if (this.status !== 'requesting') return
this.status = 'downloading'
const client = new WebTorrent()
client.download(this.infoHash, (torrent) => {
this.setState({ status: 'downloading' })
conn.on('data', (chunk) => {
let downloaded = 0
const file = torrent.files[0]
const stream = file.createReadStream()
stream.on('data', (chunk) => {
if (this.status !== 'downloading') return
this.file.addChunk(chunk)
downloaded += chunk.length
if (this.file.isComplete()) {
if (downloaded === file.length) {
this.setState({ status: 'done', progress: 1 })
this.file.download()
conn.close()
file.getBlobURL((err, blobURL) => {
if (err) throw err
downloadBlobURL(this.fileName, blobURL)
})
} else {
this.setState({ progress: this.file.getProgress() })
conn.send('more')
this.setState({ progress: downloaded / file.length })
}
})
conn.on('close', () => {
if (this.status !== 'downloading') return
this.setState({ status: 'cancelled', progress: 0 })
this.file.clearChunks()
})
}

@ -1,70 +1,42 @@
import UploadActions from '../actions/UploadActions'
import UploadFile from '../UploadFile'
import alt from '../alt'
import peer from 'filepizza-peerjs'
import socket from 'filepizza-socket'
import WebTorrent from 'webtorrent'
export default alt.createStore(class UploadStore {
constructor() {
this.bindActions(UploadActions)
this.fileName = ''
this.fileSize = 0
this.fileType = ''
this.status = 'ready'
this.token = null
this.file = null
this.peerProgress = {}
this.infoHash = null
}
onUploadFile(file) {
if (this.status !== 'ready') return
this.status = 'processing'
this.file = new UploadFile(file)
const client = new WebTorrent()
client.seed(file, (torrent) => {
socket.emit('upload', {
name: this.file.name,
size: this.file.size,
type: this.file.type
fileName: file.name,
fileSize: file.size,
fileType: file.type,
infoHash: torrent.infoHash
}, (token) => {
this.setState({
status: 'uploading',
token: token
token: token,
fileName: file.name,
fileSize: file.size,
fileType: file.type,
infoHash: torrent.infoHash
})
})
}
onSendToDownloader(peerID) {
if (this.status !== 'uploading') return
let conn = peer.connect(peerID, {
reliable: true
})
let totalChunks = this.file.countChunks()
let i = 0
let sendNextChunk = () => {
if (i === totalChunks) return
let packet = this.file.getChunk(i)
conn.send(packet)
i++
this.peerProgress[peerID] = i/totalChunks
}
conn.on('open', () => {
sendNextChunk()
this.setState({ peerProgress: this.peerProgress })
})
conn.on('data', (data) => {
if (data === 'more') sendNextChunk()
this.setState({ peerProgress: this.peerProgress })
})
conn.on('close', () => {
if (this.peerProgress[peerID] < 1) {
this.peerProgress[peerID] = -1
}
this.setState({ peerProgress: this.peerProgress })
})
}

@ -1,6 +1,6 @@
{
"name": "filepizza",
"version": "0.1.2",
"version": "0.2.0",
"description": "Free peer-to-peer file transfers in your browser.",
"main": "index.js",
"scripts": {
@ -25,13 +25,10 @@
"classnames": "^1.2.0",
"express": "^4.12.0",
"express-winston": "^0.3.1",
"filepizza-peerjs": "^1.0.0",
"filepizza-socket": "^1.0.0",
"newrelic": "^1.21.1",
"nib": "^1.1.0",
"node-uuid": "^1.4.3",
"peer": "^0.2.8",
"peerjs": "^0.3.14",
"react": "^0.13.0",
"react-frozenhead": "^0.3.0",
"react-google-analytics": "^0.2.0",
@ -40,6 +37,7 @@
"socket.io-client": "^1.3.5",
"stylus": "^0.50.0",
"webrtcsupport": "^2.1.2",
"webtorrent": "^0.56.0",
"winston": "^1.0.1",
"xkcd-password": "^1.2.0"
},

Loading…
Cancel
Save