const createTorrent = require('create-torrent') const path = require('path') const prettyBytes = require('prettier-bytes') const React = require('react') const { dispatch, dispatcher } = require('../lib/dispatcher') const FlatButton = require('material-ui/FlatButton').default const RaisedButton = require('material-ui/RaisedButton').default const TextField = require('material-ui/TextField').default const Checkbox = require('material-ui/Checkbox').default const CreateTorrentErrorPage = require('../components/create-torrent-error-page') const Heading = require('../components/heading') const ShowMore = require('../components/show-more') // Shows a basic UI to create a torrent, from an already-selected file or folder. // Includes a "Show Advanced..." button and more advanced UI. class CreateTorrentPage extends React.Component { constructor (props) { super(props) const state = this.props.state const info = state.location.current() // First, extract the base folder that the files are all in let pathPrefix = info.folderPath if (!pathPrefix) { pathPrefix = info.files.map((x) => x.path).reduce(findCommonPrefix) if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) { pathPrefix = path.dirname(pathPrefix) } } // Then, exclude .DS_Store and other dotfiles const files = info.files .filter((f) => !containsDots(f.path, pathPrefix)) .map((f) => ({ name: f.name, path: f.path, size: f.size })) if (files.length === 0) return () // Then, use the name of the base folder (or sole file, for a single file torrent) // as the default name. Show all files relative to the base folder. let defaultName, basePath if (files.length === 1) { // Single file torrent: /a/b/foo.jpg -> torrent name 'foo.jpg', path '/a/b' defaultName = files[0].name basePath = pathPrefix } else { // Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name 'b', path '/a' defaultName = path.basename(pathPrefix) basePath = path.dirname(pathPrefix) } // Default trackers const trackers = createTorrent.announceList.join('\n') this.state = { comment: '', isPrivate: false, pathPrefix, basePath, defaultName, files, trackers } // Create React event handlers only once this.handleSetIsPrivate = (_, isPrivate) => this.setState({ isPrivate }) this.handleSetComment = (_, comment) => this.setState({ comment }) this.handleSetTrackers = (_, trackers) => this.setState({ trackers }) this.handleSubmit = handleSubmit.bind(this) } render () { const files = this.state.files // Sanity check: show the number of files and total size const numFiles = files.length const totalBytes = files .map((f) => f.size) .reduce((a, b) => a + b, 0) const torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}` return (
Create torrent {this.state.defaultName}
{torrentInfo}
{this.state.pathPrefix}
{this.renderAdvanced()}
) } // Renders everything after clicking Show Advanced...: // * Is Private? (private torrents, not announced to DHT) // * Announce list (trackers) // * Comment renderAdvanced () { // Create file list const maxFileElems = 100 const files = this.state.files const fileElems = files.slice(0, maxFileElems).map((file, i) => { const relativePath = path.relative(this.state.pathPrefix, file.path) return (
{relativePath}
) }) if (files.length > maxFileElems) { fileElems.push(
+ {files.length - maxFileElems} more
) } // Align the text fields const textFieldStyle = { width: '' } const textareaStyle = { margin: 0 } return (
{fileElems}
) } } function handleSubmit () { const announceList = this.state.trackers .split('\n') .map((s) => s.trim()) .filter((s) => s !== '') const options = { // We can't let the user choose their own name if we want WebTorrent // to use the files in place rather than creating a new folder. name: this.state.defaultName, path: this.state.basePath, files: this.state.files, announce: announceList, comment: this.state.comment.trim() } // If torrent is not private, leave private flag unset. This ensures that // the torrent info hash will match the result generated by other tools, // including webtorrent-cli. if (this.state.isPrivate) options.private = true dispatch('createTorrent', options) } // Finds the longest common prefix function findCommonPrefix (a, b) { let i for (i = 0; i < a.length && i < b.length; i++) { if (a.charCodeAt(i) !== b.charCodeAt(i)) break } if (i === a.length) return a if (i === b.length) return b return a.substring(0, i) } function containsDots (path, pathPrefix) { const suffix = path.substring(pathPrefix.length).replace(/\\/g, '/') return ('/' + suffix).includes('/.') } module.exports = CreateTorrentPage