Merge branch 'master' into master

This commit is contained in:
Дамјан Георгиевски
2019-09-15 18:25:43 +02:00
committed by GitHub
52 changed files with 2307 additions and 2150 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: feross
custom: https://paypal.me/borewit

4
.gitignore vendored
View File

@@ -2,3 +2,7 @@ node_modules/
build/ build/
dist/ dist/
npm-debug.log* npm-debug.log*
# JetBrains IntelliJ IDEA project files
.idea
*.iml

View File

@@ -43,9 +43,31 @@
- Borewit (borewit@users.noreply.github.com) - Borewit (borewit@users.noreply.github.com)
- greenkeeper[bot] (greenkeeper[bot]@users.noreply.github.com) - greenkeeper[bot] (greenkeeper[bot]@users.noreply.github.com)
- Auyer (rafa_auyer@icloud.com) - Auyer (rafa_auyer@icloud.com)
- Jon Koops (jonkoops@gmail.com)
- Michael George Attard (michaelgeorgeattard@gmail.com)
- SimplyAhmazing (ahmad19526@gmail.com) - SimplyAhmazing (ahmad19526@gmail.com)
- Cezar Carneiro (cezargcarneiro@gmail.com) - Cezar Carneiro (cezargcarneiro@gmail.com)
- Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
- Terry Hau (terryhau@gmail.com) - Terry Hau (terryhau@gmail.com)
- Vítor Galvão (info@vitorgalvao.com) - Vítor Galvão (info@vitorgalvao.com)
- Borewit (Borewit@users.noreply.github.com)
- Diego Rodríguez (diegorbaquero@gmail.com)
- Dan Flettre (flettre@gmail.com)
- Sibiraj (dev.sibiraj@outlook.com)
- clujin (clujin@gmail.com)
- Linus Unnebäck (linus@folkdatorn.se)
- Adrian Tombu (adrian@otso.fr)
- Lucas (5874806+RecoX@users.noreply.github.com)
- David Ernst (dsernst@users.noreply.github.com)
- David Ernst (git@dsernst.com)
- Jimmy Wärting (jimmy@warting.se)
- Recox (5874806+RecoX@users.noreply.github.com)
- greenkeeper[bot] (23040076+greenkeeper[bot]@users.noreply.github.com)
- hicom150 (hicom150@gmail.com)
- Jimmy Wärting (jimmy@warting.se)
- Julen Garcia Leunda (hicom150@gmail.com)
- Feross (feross@feross.org)
- Daniele Debernardi (drebrez@gmail.com)
- Chandan Chowdary Bhagam (chandandharana@gmail.com)
#### Generated by bin/update-authors.sh. #### Generated by bin/update-authors.sh.

View File

@@ -12,7 +12,7 @@
<h4 align="center">The streaming torrent app. For Mac, Windows, and Linux.</h4> <h4 align="center">The streaming torrent app. For Mac, Windows, and Linux.</h4>
<p align="center"> <p align="center">
<a href="https://gitter.im/webtorrent/webtorrent"><img src="https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg" alt="gitter"></a> <a href="https://discord.gg/cnXkm4Z"><img src="https://img.shields.io/discord/612575111718895616" alt="discord"></a>
<a href="https://travis-ci.org/webtorrent/webtorrent-desktop"><img src="https://img.shields.io/travis/webtorrent/webtorrent-desktop/master.svg" alt="travis"></a> <a href="https://travis-ci.org/webtorrent/webtorrent-desktop"><img src="https://img.shields.io/travis/webtorrent/webtorrent-desktop/master.svg" alt="travis"></a>
<a href="https://github.com/webtorrent/webtorrent-desktop/releases"><img src="https://img.shields.io/github/release/webtorrent/webtorrent-desktop.svg" alt="github release version"></a> <a href="https://github.com/webtorrent/webtorrent-desktop/releases"><img src="https://img.shields.io/github/release/webtorrent/webtorrent-desktop.svg" alt="github release version"></a>
<a href="https://github.com/webtorrent/webtorrent-desktop/releases"><img src="https://img.shields.io/github/downloads/webtorrent/webtorrent-desktop/total.svg" alt="github release downloads"></a> <a href="https://github.com/webtorrent/webtorrent-desktop/releases"><img src="https://img.shields.io/github/downloads/webtorrent/webtorrent-desktop/total.svg" alt="github release downloads"></a>
@@ -97,7 +97,7 @@ comparing each one to a reference. Why screenshots?
https://github.com/blog/817-behold-image-view-modes https://github.com/blog/817-behold-image-view-modes
For MacOS, you'll need a Retina screen for the integration tests to pass. Your screen should have For MacOS, you'll need a Retina screen for the integration tests to pass. Your screen should have
the same resolution as a 2016 12" Macbook. the same resolution as a 2018 MacBook Pro 13".
For Windows, you'll need Windows 10 with a 1366x768 screen. For Windows, you'll need Windows 10 with a 1366x768 screen.
@@ -158,6 +158,11 @@ The Mac app can only be packaged from **macOS**.
The Linux app can be packaged from **any** platform. The Linux app can be packaged from **any** platform.
If packaging from Mac, install system dependencies with Homebrew by running:
```
npm run install-system-deps
```
#### Recommended readings to start working in the app #### Recommended readings to start working in the app

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
const { CONFIG_PATH } = require('../src/config') const { CONFIG_PATH } = require('../src/config')
const opn = require('opn') const open = require('open')
opn(CONFIG_PATH) open(CONFIG_PATH)

View File

@@ -39,8 +39,7 @@ const argv = minimist(process.argv.slice(2), {
function build () { function build () {
console.log('Reinstalling node_modules...') console.log('Reinstalling node_modules...')
rimraf.sync(NODE_MODULES_PATH) rimraf.sync(NODE_MODULES_PATH)
cp.execSync('npm install', { stdio: 'inherit' }) cp.execSync('npm ci', { stdio: 'inherit' })
cp.execSync('npm dedupe', { stdio: 'inherit' })
console.log('Nuking dist/ and build/...') console.log('Nuking dist/ and build/...')
rimraf.sync(DIST_PATH) rimraf.sync(DIST_PATH)
@@ -119,7 +118,7 @@ const darwin = {
// Build for Mac // Build for Mac
platform: 'darwin', platform: 'darwin',
// Build x64 binaries only. // Build x64 binary only.
arch: 'x64', arch: 'x64',
// The bundle identifier to use in the application's plist (Mac only). // The bundle identifier to use in the application's plist (Mac only).
@@ -140,8 +139,8 @@ const win32 = {
// Build for Windows. // Build for Windows.
platform: 'win32', platform: 'win32',
// Build ia32 and x64 binaries. // Build x64 binary only.
arch: ['ia32', 'x64'], arch: 'x64',
// Object hash of application metadata to embed into the executable (Windows only) // Object hash of application metadata to embed into the executable (Windows only)
win32metadata: { win32metadata: {
@@ -174,8 +173,8 @@ const linux = {
// Build for Linux. // Build for Linux.
platform: 'linux', platform: 'linux',
// Build ia32 and x64 binaries. // Build x64 and arm64 binaries.
arch: ['ia32', 'x64', 'arm64'] arch: ['x64', 'arm64']
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option. // Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
} }
@@ -186,8 +185,7 @@ function buildDarwin (cb) {
const plist = require('plist') const plist = require('plist')
console.log('Mac: Packaging electron...') console.log('Mac: Packaging electron...')
electronPackager(Object.assign({}, all, darwin), function (err, buildPath) { electronPackager(Object.assign({}, all, darwin)).then(function (buildPath) {
if (err) return cb(err)
console.log('Mac: Packaged electron. ' + buildPath) console.log('Mac: Packaged electron. ' + buildPath)
const appPath = path.join(buildPath[0], config.APP_NAME + '.app') const appPath = path.join(buildPath[0], config.APP_NAME + '.app')
@@ -356,6 +354,8 @@ function buildDarwin (cb) {
cb(null) cb(null)
}) })
} }
}).catch(function (err) {
cb(err)
}) })
} }
@@ -376,8 +376,7 @@ function buildWin32 (cb) {
CERT_PATH = path.join(os.homedir(), 'Desktop') CERT_PATH = path.join(os.homedir(), 'Desktop')
} }
electronPackager(Object.assign({}, all, win32), function (err, buildPath) { electronPackager(Object.assign({}, all, win32)).then(function (buildPath) {
if (err) return cb(err)
console.log('Windows: Packaged electron. ' + buildPath) console.log('Windows: Packaged electron. ' + buildPath)
let signWithParams let signWithParams
@@ -396,21 +395,17 @@ function buildWin32 (cb) {
const tasks = [] const tasks = []
buildPath.forEach(function (filesPath) { buildPath.forEach(function (filesPath) {
const destArch = filesPath.split('-').pop()
if (argv.package === 'exe' || argv.package === 'all') { if (argv.package === 'exe' || argv.package === 'all') {
tasks.push((cb) => packageInstaller(filesPath, destArch, cb)) tasks.push((cb) => packageInstaller(filesPath, cb))
} }
if (argv.package === 'portable' || argv.package === 'all') { if (argv.package === 'portable' || argv.package === 'all') {
tasks.push((cb) => packagePortable(filesPath, destArch, cb)) tasks.push((cb) => packagePortable(filesPath, cb))
} }
}) })
series(tasks, cb) series(tasks, cb)
function packageInstaller (filesPath, destArch, cb) { function packageInstaller (filesPath, cb) {
console.log(`Windows: Creating ${destArch} installer...`) console.log('Windows: Creating installer...')
const archStr = destArch === 'ia32' ? '-ia32' : ''
installer.createWindowsInstaller({ installer.createWindowsInstaller({
appDirectory: filesPath, appDirectory: filesPath,
@@ -423,26 +418,17 @@ function buildWin32 (cb) {
noMsi: true, noMsi: true,
outputDirectory: DIST_PATH, outputDirectory: DIST_PATH,
productName: config.APP_NAME, productName: config.APP_NAME,
/**
* Only create delta updates for the Windows x64 build because 90% of our
* users have Windows x64 and the delta files take a *very* long time to
* generate. Also, the ia32 files on GitHub have non-standard Squirrel
* names (i.e. RELEASES-ia32 instead of RELEASES) and so Squirrel won't
* find them unless we proxy the requests.
*/
// TODO: Re-enable Windows 64-bit delta updates when we confirm that they // TODO: Re-enable Windows 64-bit delta updates when we confirm that they
// work correctly in the presence of the "ia32" .nupkg files. I // work correctly in the presence of the "ia32" .nupkg files. I
// (feross) noticed them listed in the 64-bit RELEASES file and // (feross) noticed them listed in the 64-bit RELEASES file and
// manually edited them out for the v0.17 release. Shipping only // manually edited them out for the v0.17 release. Shipping only
// full updates for now will work fine, with no ill-effects. // full updates for now will work fine, with no ill-effects.
// remoteReleases: destArch === 'x64' // remoteReleases: config.GITHUB_URL,
// ? config.GITHUB_URL
// : undefined,
/** /**
* If you hit a "GitHub API rate limit exceeded" error, set this token! * If you hit a "GitHub API rate limit exceeded" error, set this token!
*/ */
// remoteToken: process.env.WEBTORRENT_GITHUB_API_TOKEN, // remoteToken: process.env.WEBTORRENT_GITHUB_API_TOKEN,
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + archStr + '.exe', setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
setupIcon: config.APP_ICON + '.ico', setupIcon: config.APP_ICON + '.ico',
signWithParams: signWithParams, signWithParams: signWithParams,
title: config.APP_NAME, title: config.APP_NAME,
@@ -450,7 +436,7 @@ function buildWin32 (cb) {
version: pkg.version version: pkg.version
}) })
.then(function () { .then(function () {
console.log(`Windows: Created ${destArch} installer.`) console.log('Windows: Created installer.')
/** /**
* Delete extraneous Squirrel files (i.e. *.nupkg delta files for older * Delete extraneous Squirrel files (i.e. *.nupkg delta files for older
@@ -462,42 +448,13 @@ function buildWin32 (cb) {
fs.unlinkSync(path.join(DIST_PATH, filename)) fs.unlinkSync(path.join(DIST_PATH, filename))
}) })
if (destArch === 'ia32') {
console.log('Windows: Renaming ia32 installer files...')
// RELEASES -> RELEASES-ia32
const relPath = path.join(DIST_PATH, 'RELEASES-ia32')
fs.renameSync(
path.join(DIST_PATH, 'RELEASES'),
relPath
)
// WebTorrent-vX.X.X-full.nupkg -> WebTorrent-vX.X.X-ia32-full.nupkg
fs.renameSync(
path.join(DIST_PATH, `${config.APP_NAME}-${config.APP_VERSION}-full.nupkg`),
path.join(DIST_PATH, `${config.APP_NAME}-${config.APP_VERSION}-ia32-full.nupkg`)
)
// Change file name inside RELEASES-ia32 to match renamed file
const relContent = fs.readFileSync(relPath, 'utf8')
const relContent32 = relContent.replace('full.nupkg', 'ia32-full.nupkg')
fs.writeFileSync(relPath, relContent32)
if (relContent === relContent32) {
// Sanity check
throw new Error('Fixing RELEASES-ia32 failed. Replacement did not modify the file.')
}
console.log('Windows: Renamed ia32 installer files.')
}
cb(null) cb(null)
}) })
.catch(cb) .catch(cb)
} }
function packagePortable (filesPath, destArch, cb) { function packagePortable (filesPath, cb) {
console.log(`Windows: Creating ${destArch} portable app...`) console.log('Windows: Creating portable app...')
const portablePath = path.join(filesPath, 'Portable Settings') const portablePath = path.join(filesPath, 'Portable Settings')
mkdirp.sync(portablePath) mkdirp.sync(portablePath)
@@ -508,84 +465,76 @@ function buildWin32 (cb) {
const tempPath = path.join(portablePath, 'Temp') const tempPath = path.join(portablePath, 'Temp')
mkdirp.sync(tempPath) mkdirp.sync(tempPath)
const archStr = destArch === 'ia32' ? '-ia32' : ''
const inPath = path.join(DIST_PATH, path.basename(filesPath)) const inPath = path.join(DIST_PATH, path.basename(filesPath))
const outPath = path.join(DIST_PATH, BUILD_NAME + '-win' + archStr + '.zip') const outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
zip.zipSync(inPath, outPath) zip.zipSync(inPath, outPath)
console.log(`Windows: Created ${destArch} portable app.`) console.log('Windows: Created portable app.')
cb(null) cb(null)
} }
}).catch(function (err) {
cb(err)
}) })
} }
function buildLinux (cb) { function buildLinux (cb) {
console.log('Linux: Packaging electron...') console.log('Linux: Packaging electron...')
electronPackager(Object.assign({}, all, linux), function (err, buildPath) {
if (err) return cb(err) electronPackager(Object.assign({}, all, linux)).then(function (buildPath) {
console.log('Linux: Packaged electron. ' + buildPath) console.log('Linux: Packaged electron. ' + buildPath)
const tasks = [] const tasks = []
buildPath.forEach(function (filesPath) { buildPath.forEach(function (filesPath) {
const destArch = filesPath.split('-').pop()
if (argv.package === 'deb' || argv.package === 'all') { if (argv.package === 'deb' || argv.package === 'all') {
tasks.push((cb) => packageDeb(filesPath, destArch, cb)) tasks.push((cb) => packageDeb(filesPath, cb))
} }
if (argv.package === 'zip' || argv.package === 'all') { if (argv.package === 'zip' || argv.package === 'all') {
tasks.push((cb) => packageZip(filesPath, destArch, cb)) tasks.push((cb) => packageZip(filesPath, cb))
} }
}) })
series(tasks, cb) series(tasks, cb)
}).catch(function (err) {
cb(err)
}) })
function packageDeb (filesPath, destArch, cb) { function packageDeb (filesPath, cb) {
// Create .deb file for Debian-based platforms // Create .deb file for Debian-based platforms
console.log(`Linux: Creating ${destArch} deb...`) console.log('Linux: Creating deb...')
const deb = require('nobin-debian-installer')() const installer = require('electron-installer-debian')
const destPath = path.join('/opt', pkg.name)
deb.pack({ const options = {
package: pkg, src: filesPath + '/',
info: { dest: DIST_PATH,
arch: destArch === 'x64' ? 'amd64' : 'i386', arch: 'amd64',
targetDir: DIST_PATH, bin: 'WebTorrent',
depends: 'gconf2, libgtk2.0-0, libnss3, libxss1', icon: {
scripts: { '48x48': path.join(config.STATIC_PATH, 'linux/share/icons/hicolor/48x48/apps/webtorrent-desktop.png'),
postinst: path.join(config.STATIC_PATH, 'linux', 'postinst'), '256x256': path.join(config.STATIC_PATH, 'linux/share/icons/hicolor/256x256/apps/webtorrent-desktop.png')
prerm: path.join(config.STATIC_PATH, 'linux', 'prerm') },
categories: ['Network', 'FileTransfer', 'P2P'],
mimeType: ['application/x-bittorrent', 'x-scheme-handler/magnet', 'x-scheme-handler/stream-magnet'],
desktopTemplate: path.join(config.STATIC_PATH, 'linux/webtorrent-desktop.ejs')
} }
}
}, [{ installer(options).then(
src: ['./**'], () => {
dest: destPath, console.log('Linux: Created deb.')
expand: true,
cwd: filesPath
}, {
src: ['./**'],
dest: path.join('/usr', 'share'),
expand: true,
cwd: path.join(config.STATIC_PATH, 'linux', 'share')
}], function (err) {
if (err) return cb(err)
console.log(`Linux: Created ${destArch} deb.`)
cb(null) cb(null)
}) },
(err) => cb(err)
)
} }
function packageZip (filesPath, destArch, cb) { function packageZip (filesPath, cb) {
// Create .zip file for Linux // Create .zip file for Linux
console.log(`Linux: Creating ${destArch} zip...`) console.log('Linux: Creating zip...')
const archStr = destArch === 'ia32' ? '-ia32' : ''
const inPath = path.join(DIST_PATH, path.basename(filesPath)) const inPath = path.join(DIST_PATH, path.basename(filesPath))
const outPath = path.join(DIST_PATH, BUILD_NAME + '-linux' + archStr + '.zip') const outPath = path.join(DIST_PATH, BUILD_NAME + '-linux.zip')
zip.zipSync(inPath, outPath) zip.zipSync(inPath, outPath)
console.log(`Linux: Created ${destArch} zip.`) console.log('Linux: Created zip.')
cb(null) cb(null)
} }
} }

3785
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "webtorrent-desktop", "name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.", "description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
"version": "0.20.0", "version": "0.21.0",
"author": { "author": {
"name": "WebTorrent, LLC", "name": "WebTorrent, LLC",
"email": "feross@webtorrent.io", "email": "feross@webtorrent.io",
@@ -11,65 +11,62 @@
"url": "https://github.com/webtorrent/webtorrent-desktop/issues" "url": "https://github.com/webtorrent/webtorrent-desktop/issues"
}, },
"dependencies": { "dependencies": {
"airplayer": "^2.0.0", "airplayer": "github:webtorrent/airplayer#fix-security",
"application-config": "^1.0.0", "application-config": "^1.0.1",
"arch": "^2.0.0", "arch": "^2.1.1",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"bitfield": "^1.0.2", "bitfield": "^3.0.0",
"capture-frame": "^3.0.0", "capture-frame": "^3.0.0",
"chokidar": "^2.0.4", "chokidar": "^3.0.2",
"chromecasts": "^1.9.1", "chromecasts": "^1.9.1",
"cp-file": "^7.0.0", "create-torrent": "^4.4.1",
"create-torrent": "^4.0.0", "debounce": "^1.2.0",
"debounce": "^1.0.0", "deep-equal": "^1.1.0",
"deep-equal": "^1.0.1",
"dlnacasts": "^0.1.0", "dlnacasts": "^0.1.0",
"drag-drop": "^4.1.0", "drag-drop": "^5.0.1",
"es6-error": "^4.0.0", "es6-error": "^4.1.1",
"fn-getter": "^1.0.0", "fn-getter": "^1.0.0",
"iso-639-1": "^2.0.5", "iso-639-1": "^2.1.0",
"languagedetect": "^1.2.0", "languagedetect": "^1.2.0",
"location-history": "^1.0.0", "location-history": "^1.1.1",
"material-ui": "^0.20.2", "material-ui": "^0.20.2",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"music-metadata": "^4.2.0", "music-metadata": "^4.5.3",
"network-address": "^1.1.0", "network-address": "^1.1.2",
"parse-torrent": "^7.0.0", "parse-torrent": "^7.0.1",
"prettier-bytes": "^1.0.1", "prettier-bytes": "^1.0.4",
"prop-types": "^15.6.2", "prop-types": "^15.7.2",
"react": "^16.5.2", "react": "^16.9.0",
"react-dom": "^16.5.2", "react-dom": "^16.9.0",
"rimraf": "^3.0.0", "rimraf": "^3.0.0",
"run-parallel": "^1.1.6", "run-parallel": "^1.1.9",
"semver": "^6.0.0", "semver": "^6.3.0",
"simple-concat": "^1.0.0", "simple-concat": "^1.0.0",
"simple-get": "^3.0.3", "simple-get": "^3.0.3",
"srt-to-vtt": "^1.1.1", "srt-to-vtt": "^1.1.3",
"vlc-command": "^1.0.1", "vlc-command": "^1.2.0",
"webtorrent": "0.x", "webtorrent": ">=0.107.16",
"winreg": "^1.2.0", "winreg": "^1.2.4"
"zero-fill": "^2.2.3"
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^10.0.2", "babel-eslint": "^10.0.3",
"buble": "^0.19.6", "buble": "^0.19.8",
"cross-zip": "^2.0.1", "cross-zip": "^2.1.6",
"depcheck": "^0.8.0", "depcheck": "^0.8.3",
"electron": "~6.0.1", "electron": "~6.0.9",
"electron-osx-sign": "^0.4.11", "electron-osx-sign": "^0.4.13",
"electron-packager": "^14.0.4", "electron-packager": "^14.0.6",
"electron-winstaller": "^2.6.4", "electron-winstaller": "^4.0.0",
"gh-release": "^3.4.0", "gh-release": "^3.5.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"nobin-debian-installer": "0.0.10", "nodemon": "^1.19.2",
"nodemon": "^1.18.8", "open": "^6.4.0",
"opn": "^6.0.0",
"plist": "^3.0.1", "plist": "^3.0.1",
"pngjs": "^3.0.0", "pngjs": "^3.4.0",
"run-series": "^1.1.4", "run-series": "^1.1.8",
"spectron": "^3.3.0", "spectron": "^8.0.0",
"standard": "*", "standard": "*",
"tape": "^4.9.1", "tape": "^4.11.0",
"walk-sync": "^2.0.2" "walk-sync": "^2.0.2"
}, },
"engines": { "engines": {
@@ -89,7 +86,8 @@
"license": "MIT", "license": "MIT",
"main": "index.js", "main": "index.js",
"optionalDependencies": { "optionalDependencies": {
"appdmg": "^0.4.3" "appdmg": "^0.6.0",
"electron-installer-debian": "^2.0.0"
}, },
"private": true, "private": true,
"productName": "WebTorrent", "productName": "WebTorrent",
@@ -101,9 +99,9 @@
"build": "buble src --output build", "build": "buble src --output build",
"clean": "node ./bin/clean.js", "clean": "node ./bin/clean.js",
"gh-release": "gh-release", "gh-release": "gh-release",
"install-system-deps": "brew install fakeroot dpkg",
"open-config": "node ./bin/open-config.js", "open-config": "node ./bin/open-config.js",
"package": "node ./bin/package.js", "package": "node ./bin/package.js",
"prepublish": "npm run build",
"start": "npm run build && electron .", "start": "npm run build && electron .",
"test": "standard && depcheck --ignores=standard,babel-eslint --ignore-dirs=build,dist && node ./bin/extra-lint.js", "test": "standard && depcheck --ignores=standard,babel-eslint --ignore-dirs=build,dist && node ./bin/extra-lint.js",
"test-integration": "npm run build && node ./test", "test-integration": "npm run build && node ./test",

View File

@@ -73,8 +73,10 @@ module.exports = {
GITHUB_URL: 'https://github.com/webtorrent/webtorrent-desktop', GITHUB_URL: 'https://github.com/webtorrent/webtorrent-desktop',
GITHUB_URL_ISSUES: 'https://github.com/webtorrent/webtorrent-desktop/issues', GITHUB_URL_ISSUES: 'https://github.com/webtorrent/webtorrent-desktop/issues',
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/webtorrent/webtorrent-desktop/master', GITHUB_URL_RAW: 'https://raw.githubusercontent.com/webtorrent/webtorrent-desktop/master',
GITHUB_URL_RELEASES: 'https://github.com/webtorrent/webtorrent-desktop/releases',
HOME_PAGE_URL: 'https://webtorrent.io', HOME_PAGE_URL: 'https://webtorrent.io',
TWITTER_PAGE_URL: 'https://twitter.com/WebTorrentApp',
IS_PORTABLE: IS_PORTABLE, IS_PORTABLE: IS_PORTABLE,
IS_PRODUCTION: IS_PRODUCTION, IS_PRODUCTION: IS_PRODUCTION,

View File

@@ -12,8 +12,6 @@ function install () {
break break
case 'win32': installWin32() case 'win32': installWin32()
break break
case 'linux': installLinux()
break
} }
} }
@@ -23,8 +21,6 @@ function uninstall () {
break break
case 'win32': uninstallWin32() case 'win32': uninstallWin32()
break break
case 'linux': uninstallLinux()
break
} }
} }
@@ -269,100 +265,3 @@ function uninstallWin32 () {
function commandToArgs (command) { function commandToArgs (command) {
return command.map((arg) => `"${arg}"`).join(' ') return command.map((arg) => `"${arg}"`).join(' ')
} }
function installLinux () {
const fs = require('fs')
const os = require('os')
const path = require('path')
const config = require('../config')
const log = require('./log')
// Do not install in user dir if running on system
if (/^\/opt/.test(process.execPath)) return
installDesktopFile()
installIconFile()
function installDesktopFile () {
const templatePath = path.join(
config.STATIC_PATH, 'linux', 'webtorrent-desktop.desktop'
)
fs.readFile(templatePath, 'utf8', writeDesktopFile)
}
function writeDesktopFile (err, desktopFile) {
if (err) return log.error(err.message)
const appPath = config.IS_PRODUCTION
? path.dirname(process.execPath)
: config.ROOT_PATH
desktopFile = desktopFile
.replace(/\$APP_NAME/g, config.APP_NAME)
.replace(/\$APP_PATH/g, appPath)
.replace(/\$EXEC_PATH/g, EXEC_COMMAND.join(' '))
.replace(/\$TRY_EXEC_PATH/g, process.execPath)
const desktopFilePath = path.join(
os.homedir(),
'.local',
'share',
'applications',
'webtorrent-desktop.desktop'
)
fs.mkdirp(path.dirname(desktopFilePath))
fs.writeFile(desktopFilePath, desktopFile, err => {
if (err) return log.error(err.message)
})
}
function installIconFile () {
const iconStaticPath = path.join(config.STATIC_PATH, 'WebTorrent.png')
fs.readFile(iconStaticPath, writeIconFile)
}
function writeIconFile (err, iconFile) {
if (err) return log.error(err.message)
const mkdirp = require('mkdirp')
const iconFilePath = path.join(
os.homedir(),
'.local',
'share',
'icons',
'webtorrent-desktop.png'
)
mkdirp(path.dirname(iconFilePath), err => {
if (err) return log.error(err.message)
fs.writeFile(iconFilePath, iconFile, err => {
if (err) log.error(err.message)
})
})
}
}
function uninstallLinux () {
const os = require('os')
const path = require('path')
const rimraf = require('rimraf')
const desktopFilePath = path.join(
os.homedir(),
'.local',
'share',
'applications',
'webtorrent-desktop.desktop'
)
rimraf.sync(desktopFilePath)
const iconFilePath = path.join(
os.homedir(),
'.local',
'share',
'icons',
'webtorrent-desktop.png'
)
rimraf.sync(iconFilePath)
}

View File

@@ -302,6 +302,13 @@ function getMenuTemplate () {
shell.openExternal(config.HOME_PAGE_URL) shell.openExternal(config.HOME_PAGE_URL)
} }
}, },
{
label: 'Release Notes',
click: () => {
const shell = require('./shell')
shell.openExternal(config.GITHUB_URL_RELEASES)
}
},
{ {
label: 'Contribute on GitHub', label: 'Contribute on GitHub',
click: () => { click: () => {
@@ -318,6 +325,13 @@ function getMenuTemplate () {
const shell = require('./shell') const shell = require('./shell')
shell.openExternal(config.GITHUB_URL_ISSUES) shell.openExternal(config.GITHUB_URL_ISSUES)
} }
},
{
label: 'Follow us on Twitter',
click: () => {
const shell = require('./shell')
shell.openExternal(config.TWITTER_PAGE_URL)
}
} }
] ]
} }

View File

@@ -1,8 +1,10 @@
const React = require('react') const React = require('react')
const TextField = require('material-ui/TextField').default const TextField = require('material-ui/TextField').default
const { clipboard } = require('electron')
const ModalOKCancel = require('./modal-ok-cancel') const ModalOKCancel = require('./modal-ok-cancel')
const { dispatch, dispatcher } = require('../lib/dispatcher') const { dispatch, dispatcher } = require('../lib/dispatcher')
const { isMagnetLink } = require('../lib/torrent-player')
module.exports = class OpenTorrentAddressModal extends React.Component { module.exports = class OpenTorrentAddressModal extends React.Component {
render () { render () {
@@ -30,6 +32,12 @@ module.exports = class OpenTorrentAddressModal extends React.Component {
componentDidMount () { componentDidMount () {
this.torrentURL.input.focus() this.torrentURL.input.focus()
const clipboardContent = clipboard.readText()
if (isMagnetLink(clipboardContent)) {
this.torrentURL.input.value = clipboardContent
this.torrentURL.input.select()
}
} }
} }

View File

@@ -1,3 +1,5 @@
const path = require('path')
const colors = require('material-ui/styles/colors') const colors = require('material-ui/styles/colors')
const electron = require('electron') const electron = require('electron')
const React = require('react') const React = require('react')
@@ -16,7 +18,6 @@ class PathSelector extends React.Component {
return { return {
className: PropTypes.string, className: PropTypes.string,
dialog: PropTypes.object, dialog: PropTypes.object,
displayValue: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
@@ -31,7 +32,7 @@ class PathSelector extends React.Component {
handleClick () { handleClick () {
const opts = Object.assign({ const opts = Object.assign({
defaultPath: this.props.value, defaultPath: this.props.value && path.dirname(this.props.value),
properties: ['openFile', 'openDirectory'] properties: ['openFile', 'openDirectory']
}, this.props.dialog) }, this.props.dialog)
@@ -65,8 +66,7 @@ class PathSelector extends React.Component {
const textFieldStyle = { const textFieldStyle = {
flex: '1' flex: '1'
} }
const text = this.props.value || ''
const text = this.props.displayValue || this.props.value || ''
const buttonStyle = { const buttonStyle = {
marginLeft: 10 marginLeft: 10
} }

View File

@@ -269,6 +269,7 @@ module.exports = class PlaybackController {
// update state // update state
state.playing.infoHash = infoHash state.playing.infoHash = infoHash
state.playing.fileIndex = index state.playing.fileIndex = index
state.playing.fileName = fileSummary.name
state.playing.type = TorrentPlayer.isVideo(fileSummary) ? 'video' state.playing.type = TorrentPlayer.isVideo(fileSummary) ? 'video'
: TorrentPlayer.isAudio(fileSummary) ? 'audio' : TorrentPlayer.isAudio(fileSummary) ? 'audio'
: 'other' : 'other'

View File

@@ -10,7 +10,7 @@ module.exports = class TorrentController {
this.state = state this.state = state
} }
torrentInfoHash (torrentKey, infoHash) { torrentParsed (torrentKey, infoHash, magnetURI) {
let torrentSummary = this.getTorrentSummary(torrentKey) let torrentSummary = this.getTorrentSummary(torrentKey)
console.log('got infohash for %s torrent %s', console.log('got infohash for %s torrent %s',
torrentSummary ? 'existing' : 'new', torrentKey) torrentSummary ? 'existing' : 'new', torrentKey)
@@ -33,6 +33,7 @@ module.exports = class TorrentController {
} }
torrentSummary.infoHash = infoHash torrentSummary.infoHash = infoHash
torrentSummary.magnetURI = magnetURI
dispatch('update') dispatch('update')
} }
@@ -62,7 +63,6 @@ module.exports = class TorrentController {
torrentSummary.status = 'downloading' torrentSummary.status = 'downloading'
torrentSummary.name = torrentSummary.displayName || torrentInfo.name torrentSummary.name = torrentSummary.displayName || torrentInfo.name
torrentSummary.path = torrentInfo.path torrentSummary.path = torrentInfo.path
torrentSummary.magnetURI = torrentInfo.magnetURI
// TODO: make torrentInfo immutable, save separately as torrentSummary.info // TODO: make torrentInfo immutable, save separately as torrentSummary.info
// For now, check whether torrentSummary.files has already been set: // For now, check whether torrentSummary.files has already been set:
const hasDetailedFileInfo = torrentSummary.files && torrentSummary.files[0].path const hasDetailedFileInfo = torrentSummary.files && torrentSummary.files[0].path

View File

@@ -35,7 +35,7 @@ function run (state) {
} }
function migrate_0_7_0 (saved) { function migrate_0_7_0 (saved) {
const cpFile = require('cp-file') const { copyFileSync } = require('fs')
const path = require('path') const path = require('path')
saved.torrents.forEach(function (ts) { saved.torrents.forEach(function (ts) {
@@ -57,7 +57,7 @@ function migrate_0_7_0 (saved) {
dst = path.join(config.TORRENT_PATH, infoHash + '.torrent') dst = path.join(config.TORRENT_PATH, infoHash + '.torrent')
// Synchronous FS calls aren't ideal, but probably OK in a migration // Synchronous FS calls aren't ideal, but probably OK in a migration
// that only runs once // that only runs once
if (src !== dst) cpFile.sync(src, dst) if (src !== dst) copyFileSync(src, dst)
delete ts.torrentPath delete ts.torrentPath
ts.torrentFileName = infoHash + '.torrent' ts.torrentFileName = infoHash + '.torrent'
@@ -72,7 +72,7 @@ function migrate_0_7_0 (saved) {
dst = path.join(config.POSTER_PATH, infoHash + extension) dst = path.join(config.POSTER_PATH, infoHash + extension)
// Synchronous FS calls aren't ideal, but probably OK in a migration // Synchronous FS calls aren't ideal, but probably OK in a migration
// that only runs once // that only runs once
if (src !== dst) cpFile.sync(src, dst) if (src !== dst) copyFileSync(src, dst)
delete ts.posterURL delete ts.posterURL
ts.posterFileName = infoHash + extension ts.posterFileName = infoHash + extension
@@ -156,7 +156,7 @@ function migrate_0_17_2 (saved) {
// folders/files that end in a trailing dot (.) or space are not deletable from // folders/files that end in a trailing dot (.) or space are not deletable from
// Windows Explorer. See: https://github.com/webtorrent/webtorrent-desktop/issues/905 // Windows Explorer. See: https://github.com/webtorrent/webtorrent-desktop/issues/905
const cpFile = require('cp-file') const { copyFileSync } = require('fs')
const rimraf = require('rimraf') const rimraf = require('rimraf')
const OLD_NAME = 'The WIRED CD - Rip. Sample. Mash. Share.' const OLD_NAME = 'The WIRED CD - Rip. Sample. Mash. Share.'
@@ -191,7 +191,7 @@ function migrate_0_17_2 (saved) {
ts.posterFileName = NEW_HASH + '.jpg' ts.posterFileName = NEW_HASH + '.jpg'
rimraf.sync(path.join(config.TORRENT_PATH, ts.torrentFileName)) rimraf.sync(path.join(config.TORRENT_PATH, ts.torrentFileName))
cpFile.sync( copyFileSync(
path.join(config.STATIC_PATH, 'wiredCd.torrent'), path.join(config.STATIC_PATH, 'wiredCd.torrent'),
path.join(config.TORRENT_PATH, NEW_HASH + '.torrent') path.join(config.TORRENT_PATH, NEW_HASH + '.torrent')
) )

View File

@@ -38,7 +38,8 @@ function getPreviousIndex (state) {
function getCurrentLocalURL (state) { function getCurrentLocalURL (state) {
return state.server return state.server
? state.server.localURL + '/' + state.playing.fileIndex ? state.server.localURL + '/' + state.playing.fileIndex + '/' +
encodeURIComponent(state.playing.fileName)
: '' : ''
} }

View File

@@ -55,6 +55,10 @@ function init (appState) {
} }
function play (name) { function play (name) {
if (state == null) {
return
}
if (!state.saved.prefs.soundNotifications) { if (!state.saved.prefs.soundNotifications) {
return return
} }

View File

@@ -88,6 +88,7 @@ function getDefaultPlayState () {
return { return {
infoHash: null, /* the info hash of the torrent we're playing */ infoHash: null, /* the info hash of the torrent we're playing */
fileIndex: null, /* the zero-based index within the torrent */ fileIndex: null, /* the zero-based index within the torrent */
fileName: null, /* name of the file that is playing */
location: 'local', /* 'local', 'chromecast', 'airplay' */ location: 'local', /* 'local', 'chromecast', 'airplay' */
type: null, /* 'audio' or 'video', could be 'other' if ever support eg streaming to VLC */ type: null, /* 'audio' or 'video', could be 'other' if ever support eg streaming to VLC */
currentTime: 0, /* seconds */ currentTime: 0, /* seconds */
@@ -109,9 +110,8 @@ function getDefaultPlayState () {
} }
/* If the saved state file doesn't exist yet, here's what we use instead */ /* If the saved state file doesn't exist yet, here's what we use instead */
function setupStateSaved (cb) { function setupStateSaved () {
const cpFile = require('cp-file') const { copyFileSync, mkdirSync, readFileSync } = require('fs')
const fs = require('fs')
const parseTorrent = require('parse-torrent') const parseTorrent = require('parse-torrent')
const saved = { const saved = {
@@ -131,31 +131,28 @@ function setupStateSaved (cb) {
version: config.APP_VERSION /* make sure we can upgrade gracefully later */ version: config.APP_VERSION /* make sure we can upgrade gracefully later */
} }
const tasks = [] // TODO: Doing several sync calls during first startup is not ideal
mkdirSync(config.POSTER_PATH, { recursive: true })
mkdirSync(config.TORRENT_PATH, { recursive: true })
config.DEFAULT_TORRENTS.forEach((t, i) => { config.DEFAULT_TORRENTS.forEach((t, i) => {
const infoHash = saved.torrents[i].infoHash const infoHash = saved.torrents[i].infoHash
tasks.push( // TODO: Doing several sync calls during first startup is not ideal
cpFile( copyFileSync(
path.join(config.STATIC_PATH, t.posterFileName), path.join(config.STATIC_PATH, t.posterFileName),
path.join(config.POSTER_PATH, infoHash + path.extname(t.posterFileName)) path.join(config.POSTER_PATH, infoHash + path.extname(t.posterFileName))
) )
) copyFileSync(
tasks.push(
cpFile(
path.join(config.STATIC_PATH, t.torrentFileName), path.join(config.STATIC_PATH, t.torrentFileName),
path.join(config.TORRENT_PATH, infoHash + '.torrent') path.join(config.TORRENT_PATH, infoHash + '.torrent')
) )
)
}) })
Promise.all(tasks) return saved
.then(() => cb(null, saved))
.catch(err => cb(err))
function createTorrentObject (t) { function createTorrentObject (t) {
// TODO: Doing several fs.readFileSync calls during first startup is not ideal // TODO: Doing several sync calls during first startup is not ideal
const torrent = fs.readFileSync(path.join(config.STATIC_PATH, t.torrentFileName)) const torrent = readFileSync(path.join(config.STATIC_PATH, t.torrentFileName))
const parsedTorrent = parseTorrent(torrent) const parsedTorrent = parseTorrent(torrent)
return { return {
@@ -204,10 +201,14 @@ function load (cb) {
appConfig.read(function (err, saved) { appConfig.read(function (err, saved) {
if (err || !saved.version) { if (err || !saved.version) {
console.log('Missing config file: Creating new one') console.log('Missing config file: Creating new one')
setupStateSaved(onSavedState) try {
} else { saved = setupStateSaved()
onSavedState(null, saved) } catch (err) {
onSavedState(err)
return
} }
}
onSavedState(null, saved)
}) })
function onSavedState (err, saved) { function onSavedState (err, saved) {

View File

@@ -3,6 +3,7 @@ module.exports = {
isVideo, isVideo,
isAudio, isAudio,
isTorrent, isTorrent,
isMagnetLink,
isPlayableTorrentSummary isPlayableTorrentSummary
} }
@@ -31,9 +32,15 @@ function isAudio (file) {
// - a file object where obj.name is ends in .torrent // - a file object where obj.name is ends in .torrent
// - a string that's a magnet link (magnet://...) // - a string that's a magnet link (magnet://...)
function isTorrent (file) { function isTorrent (file) {
const isTorrentFile = getFileExtension(file) === '.torrent' return isTorrentFile(file) || isMagnetLink(file)
const isMagnet = typeof file === 'string' && /^(stream-)?magnet:/.test(file) }
return isTorrentFile || isMagnet
function isTorrentFile (file) {
return getFileExtension(file) === '.torrent'
}
function isMagnetLink (link) {
return typeof link === 'string' && /^(stream-)?magnet:/.test(link)
} }
function getFileExtension (file) { function getFileExtension (file) {

View File

@@ -360,7 +360,7 @@ function setupIpc () {
ipcRenderer.on('windowBoundsChanged', onWindowBoundsChanged) ipcRenderer.on('windowBoundsChanged', onWindowBoundsChanged)
const tc = controllers.torrent() const tc = controllers.torrent()
ipcRenderer.on('wt-infohash', (e, ...args) => tc.torrentInfoHash(...args)) ipcRenderer.on('wt-parsed', (e, ...args) => tc.torrentParsed(...args))
ipcRenderer.on('wt-metadata', (e, ...args) => tc.torrentMetadata(...args)) ipcRenderer.on('wt-metadata', (e, ...args) => tc.torrentMetadata(...args))
ipcRenderer.on('wt-done', (e, ...args) => tc.torrentDone(...args)) ipcRenderer.on('wt-done', (e, ...args) => tc.torrentDone(...args))
ipcRenderer.on('wt-done', () => controllers.torrentList().resumePausedTorrents()) ipcRenderer.on('wt-done', () => controllers.torrentList().resumePausedTorrents())

View File

@@ -65,9 +65,9 @@ class CreateTorrentPage extends React.Component {
} }
// Create React event handlers only once // Create React event handlers only once
this.setIsPrivate = (_, isPrivate) => this.setState({ isPrivate }) this.handleSetIsPrivate = (_, isPrivate) => this.setState({ isPrivate })
this.setComment = (_, comment) => this.setState({ comment }) this.handleSetComment = (_, comment) => this.setState({ comment })
this.setTrackers = (_, trackers) => this.setState({ trackers }) this.handleSetTrackers = (_, trackers) => this.setState({ trackers })
this.handleSubmit = handleSubmit.bind(this) this.handleSubmit = handleSubmit.bind(this)
} }
@@ -146,7 +146,7 @@ class CreateTorrentPage extends React.Component {
className='torrent-is-private control' className='torrent-is-private control'
style={{ display: '' }} style={{ display: '' }}
checked={this.state.isPrivate} checked={this.state.isPrivate}
onCheck={this.setIsPrivate} onCheck={this.handleSetIsPrivate}
/> />
</div> </div>
<div key='trackers' className='torrent-attribute'> <div key='trackers' className='torrent-attribute'>
@@ -159,7 +159,7 @@ class CreateTorrentPage extends React.Component {
rows={2} rows={2}
rowsMax={10} rowsMax={10}
value={this.state.trackers} value={this.state.trackers}
onChange={this.setTrackers} onChange={this.handleSetTrackers}
/> />
</div> </div>
<div key='comment' className='torrent-attribute'> <div key='comment' className='torrent-attribute'>
@@ -173,7 +173,7 @@ class CreateTorrentPage extends React.Component {
rows={2} rows={2}
rowsMax={10} rowsMax={10}
value={this.state.comment} value={this.state.comment}
onChange={this.setComment} onChange={this.handleSetComment}
/> />
</div> </div>
<div key='files' className='torrent-attribute'> <div key='files' className='torrent-attribute'>

View File

@@ -3,7 +3,6 @@
const React = require('react') const React = require('react')
const Bitfield = require('bitfield') const Bitfield = require('bitfield')
const prettyBytes = require('prettier-bytes') const prettyBytes = require('prettier-bytes')
const zeroFill = require('zero-fill')
const TorrentSummary = require('../lib/torrent-summary') const TorrentSummary = require('../lib/torrent-summary')
const Playlist = require('../lib/playlist') const Playlist = require('../lib/playlist')
@@ -300,7 +299,7 @@ function renderAudioMetadata (state) {
} }
elems.push(( elems.push((
<div key='release' className='audio-release'> <div key='release' className='audio-release'>
<label>Release</label>{ releaseInfo.join(', ') } <label>Release</label>{releaseInfo.join(', ')}
</div> </div>
)) ))
} }
@@ -327,7 +326,7 @@ function renderAudioMetadata (state) {
if (format.length > 0) { if (format.length > 0) {
elems.push(( elems.push((
<div key='format' className='audio-format'> <div key='format' className='audio-format'>
<label>Format</label>{ format.join(', ') } <label>Format</label>{format.join(', ')}
</div> </div>
)) ))
} }
@@ -762,10 +761,10 @@ function formatTime (time, total) {
const totalMinutes = Math.floor(total / 60) const totalMinutes = Math.floor(total / 60)
const hours = Math.floor(time / 3600) const hours = Math.floor(time / 3600)
let minutes = Math.floor(time % 3600 / 60) let minutes = Math.floor(time % 3600 / 60)
if (totalMinutes > 9) { if (totalMinutes > 9 && minutes < 10) {
minutes = zeroFill(2, minutes) minutes = '0' + minutes
} }
const seconds = zeroFill(2, Math.floor(time % 60)) const seconds = `0${Math.floor(time % 60)}`.slice(-2)
return (totalHours > 0 ? hours + ':' : '') + minutes + ':' + seconds return (totalHours > 0 ? hours + ':' : '') + minutes + ':' + seconds
} }

View File

@@ -1,4 +1,3 @@
const path = require('path')
const React = require('react') const React = require('react')
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
@@ -57,7 +56,7 @@ class PreferencesPage extends React.Component {
<Checkbox <Checkbox
className='control' className='control'
checked={!this.props.state.saved.prefs.openExternalPlayer} checked={!this.props.state.saved.prefs.openExternalPlayer}
label={'Play torrent media files using WebTorrent'} label='Play torrent media files using WebTorrent'
onCheck={this.handleOpenExternalPlayerChange} onCheck={this.handleOpenExternalPlayerChange}
/> />
</Preference> </Preference>
@@ -74,7 +73,7 @@ class PreferencesPage extends React.Component {
<Checkbox <Checkbox
className='control' className='control'
checked={this.props.state.saved.prefs.highestPlaybackPriority} checked={this.props.state.saved.prefs.highestPlaybackPriority}
label={'Highest Playback Priority'} label='Highest Playback Priority'
onCheck={this.handleHighestPlaybackPriorityChange} onCheck={this.handleHighestPlaybackPriorityChange}
/> />
<p>Pauses all active torrents to allow playback to use all of the available bandwidth.</p> <p>Pauses all active torrents to allow playback to use all of the available bandwidth.</p>
@@ -102,10 +101,9 @@ class PreferencesPage extends React.Component {
title: 'Select media player app', title: 'Select media player app',
properties: ['openFile'] properties: ['openFile']
}} }}
displayValue={playerName}
onChange={this.handleExternalPlayerPathChange} onChange={this.handleExternalPlayerPathChange}
title='External player' title='External player'
value={playerPath ? path.dirname(playerPath) : null} value={playerPath}
/> />
</Preference> </Preference>
) )
@@ -121,7 +119,7 @@ class PreferencesPage extends React.Component {
<Checkbox <Checkbox
className='control' className='control'
checked={this.props.state.saved.prefs.autoAddTorrents} checked={this.props.state.saved.prefs.autoAddTorrents}
label={'Watch for new .torrent files and add them immediately'} label='Watch for new .torrent files and add them immediately'
onCheck={(e, value) => { this.handleAutoAddTorrentsChange(e, value) }} onCheck={(e, value) => { this.handleAutoAddTorrentsChange(e, value) }}
/> />
</Preference> </Preference>
@@ -139,11 +137,11 @@ class PreferencesPage extends React.Component {
dispatch('updatePreferences', 'autoAddTorrents', isChecked) dispatch('updatePreferences', 'autoAddTorrents', isChecked)
if (isChecked) { if (isChecked) {
dispatch('startFolderWatcher', null) dispatch('startFolderWatcher')
return return
} }
dispatch('stopFolderWatcher', null) dispatch('stopFolderWatcher')
} }
torrentsFolderPathSelector () { torrentsFolderPathSelector () {
@@ -156,16 +154,15 @@ class PreferencesPage extends React.Component {
title: 'Select folder to watch for new torrents', title: 'Select folder to watch for new torrents',
properties: ['openDirectory'] properties: ['openDirectory']
}} }}
displayValue={torrentsFolderPath || ''} onChange={this.handleTorrentsFolderPathChange}
onChange={this.handletorrentsFolderPathChange}
title='Folder to watch' title='Folder to watch'
value={torrentsFolderPath ? path.dirname(torrentsFolderPath) : null} value={torrentsFolderPath}
/> />
</Preference> </Preference>
) )
} }
handletorrentsFolderPathChange (filePath) { handleTorrentsFolderPathChange (filePath) {
dispatch('updatePreferences', 'torrentsFolderPath', filePath) dispatch('updatePreferences', 'torrentsFolderPath', filePath)
} }
@@ -204,7 +201,7 @@ class PreferencesPage extends React.Component {
<Checkbox <Checkbox
className='control' className='control'
checked={this.props.state.saved.prefs.startup} checked={this.props.state.saved.prefs.startup}
label={'Open WebTorrent on startup'} label='Open WebTorrent on startup'
onCheck={this.handleStartupChange} onCheck={this.handleStartupChange}
/> />
</Preference> </Preference>
@@ -217,7 +214,7 @@ class PreferencesPage extends React.Component {
<Checkbox <Checkbox
className='control' className='control'
checked={this.props.state.saved.prefs.soundNotifications} checked={this.props.state.saved.prefs.soundNotifications}
label={'Enable sounds'} label='Enable sounds'
onCheck={this.handleSoundNotificationsChange} onCheck={this.handleSoundNotificationsChange}
/> />
</Preference> </Preference>

View File

@@ -234,7 +234,7 @@ module.exports = class TorrentList extends React.Component {
<i <i
key='play-button' key='play-button'
title='Start streaming' title='Start streaming'
className={'icon play'} className='icon play'
onClick={dispatcher('playFile', infoHash)} onClick={dispatcher('playFile', infoHash)}
> >
play_circle_outline play_circle_outline

View File

@@ -12,7 +12,6 @@ const mm = require('music-metadata')
const networkAddress = require('network-address') const networkAddress = require('network-address')
const path = require('path') const path = require('path')
const WebTorrent = require('webtorrent') const WebTorrent = require('webtorrent')
const zeroFill = require('zero-fill')
const crashReporter = require('../crash-reporter') const crashReporter = require('../crash-reporter')
const config = require('../config') const config = require('../config')
@@ -41,10 +40,9 @@ const VERSION = require('../../package.json').version
* '0.16.1' -> '0016' * '0.16.1' -> '0016'
* '1.2.5' -> '0102' * '1.2.5' -> '0102'
*/ */
const VERSION_STR = VERSION.match(/([0-9]+)/g) const VERSION_STR = VERSION
.slice(0, 2) .replace(/\d*./g, v => `0${v % 100}`.slice(-2))
.map((v) => zeroFill(2, v)) .slice(0, 4)
.join('')
/** /**
* Version prefix string (used in peer ID). WebTorrent uses the Azureus-style * Version prefix string (used in peer ID). WebTorrent uses the Azureus-style
@@ -149,7 +147,7 @@ function addTorrentEvents (torrent) {
torrent.on('error', (err) => torrent.on('error', (err) =>
ipc.send('wt-error', torrent.key, err.message)) ipc.send('wt-error', torrent.key, err.message))
torrent.on('infoHash', () => torrent.on('infoHash', () =>
ipc.send('wt-infohash', torrent.key, torrent.infoHash)) ipc.send('wt-parsed', torrent.key, torrent.infoHash, torrent.magnetURI))
torrent.on('metadata', torrentMetadata) torrent.on('metadata', torrentMetadata)
torrent.on('ready', torrentReady) torrent.on('ready', torrentReady)
torrent.on('done', torrentDone) torrent.on('done', torrentDone)
@@ -360,11 +358,15 @@ function getAudioMetadata (infoHash, index) {
: mm.parseStream(file.createReadStream(), file.name, options) : mm.parseStream(file.createReadStream(), file.name, options)
onMetaData onMetaData
.then(() => { .then(
console.log(`metadata for file='${file.name}' completed.`) () => console.log(`metadata for file='${file.name}' completed.`),
}).catch(function (err) { err => {
return console.log('error getting audio metadata for ' + infoHash + ':' + index, err) console.log(
}) `error getting audio metadata for ${infoHash}:${index}`,
err
)
}
)
} }
function selectFiles (torrentOrInfoHash, selections) { function selectFiles (torrentOrInfoHash, selections) {

View File

@@ -1,4 +0,0 @@
#!/bin/sh
set -e
chmod +x /opt/webtorrent-desktop/WebTorrent
ln -s -f /opt/webtorrent-desktop/WebTorrent /usr/bin/webtorrent-desktop

View File

@@ -1,3 +0,0 @@
#!/bin/sh
set -e
rm /usr/bin/webtorrent-desktop

View File

@@ -1,29 +0,0 @@
[Desktop Entry]
Name=$APP_NAME
Version=1.0
GenericName=BitTorrent Client
X-GNOME-FullName=$APP_NAME
Comment=Download and share files over BitTorrent
Type=Application
Icon=webtorrent-desktop
Terminal=false
Path=$APP_PATH
Exec=$EXEC_PATH %U
TryExec=$TRY_EXEC_PATH
StartupNotify=false
Categories=Network;FileTransfer;P2P;
MimeType=application/x-bittorrent;x-scheme-handler/magnet;x-scheme-handler/stream-magnet;
Actions=CreateNewTorrent;OpenTorrentFile;OpenTorrentAddress;
[Desktop Action CreateNewTorrent]
Name=Create New Torrent...
Exec=$EXEC_PATH -n
[Desktop Action OpenTorrentFile]
Name=Open Torrent File...
Exec=$EXEC_PATH -o
[Desktop Action OpenTorrentAddress]
Name=Open Torrent Address...
Exec=$EXEC_PATH -u

View File

@@ -0,0 +1,26 @@
[Desktop Entry]
Type=Application
<% if (version) { %>Version=<%= version %><% } %>
Name=<%= productName %>
<% if (genericName) { %>GenericName=<%= genericName %><% } %>
<% if (description) { %>Comment=<%= description %><% } %>
Icon=<%= name %>
<% if (name) { %>Exec=<%= name %> %U<% } %>
Terminal=false
Actions=CreateNewTorrent;OpenTorrentFile;OpenTorrentAddress;
<% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>;<% } %>
<% if (categories && categories.length) { %>Categories=<%= categories.join(';') %>;<% } %>
StartupNotify=true
<% if (name) { %>StartupWMClass=<%= name %> <% } %>
[Desktop Action CreateNewTorrent]
Name=Create New Torrent...
<% if (name) { %>Exec=<%= name %> -n <% } %>
[Desktop Action OpenTorrentFile]
Name=Open Torrent File...
<% if (name) { %>Exec=<%= name %> -o <% } %>
[Desktop Action OpenTorrentAddress]
Name=Open Torrent Address...
<% if (name) { %>Exec=<%= name %> -u <% } %>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 705 KiB

After

Width:  |  Height:  |  Size: 737 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 KiB

After

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

After

Width:  |  Height:  |  Size: 873 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 KiB

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 705 KiB

After

Width:  |  Height:  |  Size: 737 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -1,5 +1,5 @@
const Application = require('spectron').Application const Application = require('spectron').Application
const cpFile = require('cp-file') const { copyFileSync } = require('fs')
const fs = require('fs') const fs = require('fs')
const mkdirp = require('mkdirp') const mkdirp = require('mkdirp')
const parseTorrent = require('parse-torrent') const parseTorrent = require('parse-torrent')
@@ -28,10 +28,15 @@ module.exports = {
// Returns a promise that resolves to a Spectron Application once the app has loaded. // Returns a promise that resolves to a Spectron Application once the app has loaded.
// Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly. // Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly.
function createApp (t) { function createApp (t) {
const userDataDir = process.platform === 'win32'
? path.join('C:\\Windows\\Temp', 'WebTorrentTest')
: path.join('/tmp', 'WebTorrentTest')
return new Application({ return new Application({
path: path.join(__dirname, '..', 'node_modules', '.bin', path: path.join(__dirname, '..', 'node_modules', '.bin',
'electron' + (process.platform === 'win32' ? '.cmd' : '')), 'electron' + (process.platform === 'win32' ? '.cmd' : '')),
args: ['-r', path.join(__dirname, 'mocks.js'), path.join(__dirname, '..')], args: ['-r', path.join(__dirname, 'mocks.js'), path.join(__dirname, '..')],
chromeDriverArgs: [`--user-data-dir=${userDataDir}`],
env: { NODE_ENV: 'test' }, env: { NODE_ENV: 'test' },
waitTimeout: 10e3 waitTimeout: 10e3
}) })
@@ -78,6 +83,12 @@ function endTest (app, t, err) {
// Otherwise, create the reference screenshot: test/screenshots/<platform>/<name>.png // Otherwise, create the reference screenshot: test/screenshots/<platform>/<name>.png
function screenshotCreateOrCompare (app, t, name) { function screenshotCreateOrCompare (app, t, name) {
const ssDir = path.join(__dirname, 'screenshots', process.platform) const ssDir = path.join(__dirname, 'screenshots', process.platform)
// check that path exists otherwise create it
if (!fs.existsSync(ssDir)) {
fs.mkdirSync(ssDir)
}
const ssPath = path.join(ssDir, name + '.png') const ssPath = path.join(ssDir, name + '.png')
let ssBuf let ssBuf
@@ -210,7 +221,7 @@ function extractImportantFields (parsedTorrent) {
function copy (pathFrom, pathTo) { function copy (pathFrom, pathTo) {
try { try {
cpFile.sync(pathFrom, pathTo) copyFileSync(pathFrom, pathTo)
} catch (err) { } catch (err) {
// Windows lets us create files and folders under C:\Windows\Temp, // Windows lets us create files and folders under C:\Windows\Temp,
// but when you try to `copySync` into one of those folders, you get EPERM // but when you try to `copySync` into one of those folders, you get EPERM

View File

@@ -47,10 +47,8 @@ test('create-torrent', function (t) {
'wss://tracker.fastcast.nz', 'wss://tracker.fastcast.nz',
'wss://tracker.openwebtorrent.com' 'wss://tracker.openwebtorrent.com'
], ],
comment: undefined, infoHash: 'b31a80b3dd807c2fdde4c4da1a0db6123fa35883',
infoHash: '4b087858a32e31a0d313b5f9e0a2e13c08c5403f',
name: 'tmp.jpg', name: 'tmp.jpg',
private: false,
urlList: [] urlList: []
} }