Compare commits

..

3 Commits

Author SHA1 Message Date
Feross Aboukhadijeh
b65b186157 0.23.0 2020-07-15 20:08:06 -07:00
Feross Aboukhadijeh
0945901cc0 Update CHANGELOG.md 2020-07-15 20:07:32 -07:00
Feross Aboukhadijeh
e60c8a2144 update dep 2020-07-15 20:07:24 -07:00
73 changed files with 7344 additions and 22219 deletions

View File

@@ -1,21 +0,0 @@
name: ci
on: [push,pull_request]
jobs:
test:
name: Node ${{ matrix.node }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
node:
- '16'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm run build --if-present
- run: npm test

View File

@@ -1,69 +0,0 @@
name: package
on:
workflow_dispatch:
jobs:
package_linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm install
- run: npm run package -- linux
- uses: actions/upload-artifact@v3
with:
name: linux
path: |
dist/*.deb
dist/*.rpm
dist/*.zip
package_macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm install
- run: npm run package -- darwin
- uses: actions/upload-artifact@v3
with:
name: macos
path: |
dist/*.dmg
dist/*.zip
package_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm install
- run: npm run package -- win32
- uses: actions/upload-artifact@v3
with:
name: windows
path: |
dist/*.exe
dist/*.nupkg
dist/*.zip

View File

@@ -1,23 +0,0 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 12 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?'
stale-pr-message: 'Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?'
exempt-issue-labels: accepted,blocked,dependency,security,meta
exempt-pr-labels: accepted,blocked,bug,dependency,security,meta
stale-issue-label: 'stale'
stale-pr-label: 'stale'

1
.gitignore vendored
View File

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

3
.travis.yml Normal file
View File

@@ -0,0 +1,3 @@
language: node_js
node_js:
- lts/*

View File

@@ -77,7 +77,5 @@
- Ameet Kaustav (akaustav@users.noreply.github.com)
- gpatarin (gael.patarin@outlook.com)
- Gael Patarin (gael.patarin@outlook.com)
- Subin Siby (mail@subinsb.com)
- Hinara (hinara.turevel@gmail.com)
#### Generated by bin/update-authors.sh.

View File

@@ -1,38 +1,15 @@
# WebTorrent Desktop Version History
## v0.24.0 - 2020-08-28
### Added
- Support the `.m2ts` video container format ([hicom150](https://github.com/hicom150))
### Changed
- Update to Electron 10.1.0 [\#1864](https://github.com/webtorrent/webtorrent-desktop/pull/1864) ([feross](https://github.com/feross))
- Update the Windows installer loading image [\#1841](https://github.com/webtorrent/webtorrent-desktop/pull/1841) ([alxhotel](https://github.com/alxhotel))
### Fixed
- Fix music metadata not showing up [\#1847](https://github.com/webtorrent/webtorrent-desktop/pull/1847) ([Borewit](https://github.com/Borewit))
- Fix the "Play in VLC" functionality [\#1850](https://github.com/webtorrent/webtorrent-desktop/pull/1850) ([Hinara](https://github.com/Hinara))
- Prevent shortcuts from activating when user input elements are focused [\#1840](https://github.com/webtorrent/webtorrent-desktop/pull/1840) ([subins2000](https://github.com/subins2000))
## v0.23.0 - 2020-07-15
🔒 This release contains a critical security fix. Please update as soon as possible. [\#1837](https://github.com/webtorrent/webtorrent-desktop/issues/1837#issuecomment-729320901)
### Added
- Add macOS Notarization [\#1834](https://github.com/webtorrent/webtorrent-desktop/pull/1834) ([feross](https://github.com/feross))
This release contains a critical security fix.
### Changed
- Update to Electron 10 beta [\#1834](https://github.com/webtorrent/webtorrent-desktop/pull/1834) ([feross](https://github.com/feross))
- Update to Electron 10 beta [\#1834](https://github.com/webtorrent/webtorrent-desktop/pull/1834)
## v0.22.0 - 2020-07-15
❤️✨ A new version of WebTorrent Desktop is out! ❤️✨
### Added
- Linux `.rpm` packages and `arm64` builds are now available! [\#1694](https://github.com/webtorrent/webtorrent-desktop/pull/1694) ([hicom150](https://github.com/hicom150))
@@ -46,7 +23,7 @@
- Update to Electron 9 [\#1729](https://github.com/webtorrent/webtorrent-desktop/pull/1729) [\#1832](https://github.com/webtorrent/webtorrent-desktop/issues/1832)
- Update to music-metadata 4.8.0 [\#1719](https://github.com/webtorrent/webtorrent-desktop/pull/1719) ([Borewit](https://github.com/Borewit))
- Update Windows build documentation [\#1715](https://github.com/webtorrent/webtorrent-desktop/pull/1715) ([RecoX](https://github.com/RecoX))
- Remove unneeded dependencies ([feross](https://github.com/feross))
- Remove unneeded dependencies
### Fixed
@@ -55,8 +32,8 @@
- Fix percentage rounding error [\#1716](https://github.com/webtorrent/webtorrent-desktop/pull/1716) ([pR0Ps](https://github.com/pR0Ps))
- Fix path-selector in preferences page [\#1702](https://github.com/webtorrent/webtorrent-desktop/pull/1702) ([314eter](https://github.com/314eter))
- Fix path-selector in preferences page [\#1704](https://github.com/webtorrent/webtorrent-desktop/pull/1702) ([mathiasvr](https://github.com/mathiasvr))
- Fix: Increase height of 'About' window [\#1737](https://github.com/webtorrent/webtorrent-desktop/pull/1737) ([akaustav](https://github.com/akaustav))
- Fix "Save Torrent File As..." [\#1743](https://github.com/webtorrent/webtorrent-desktop/pull/1743) ([gpatarin](https://github.com/gpatarin))
- Fix: Increase height of 'About' window [\#1737](https://github.com/webtorrent/webtorrent-desktop/pull/1737)
- Fix "Save Torrent File As..." [\#1743](https://github.com/webtorrent/webtorrent-desktop/pull/1743)
## v0.21.0 - 2019-09-14

View File

@@ -35,7 +35,7 @@ Download the latest version of WebTorrent Desktop from
- Use [Homebrew-Cask](https://github.com/caskroom/homebrew-cask) to install from the command line:
```
$ brew install --cask webtorrent
$ brew cask install webtorrent
```
- Try the (unstable) development version by cloning the Git repository. See the
@@ -147,7 +147,7 @@ to be installed. For example on Mac, first install
[XQuartz](http://www.xquartz.org/), then run:
```
$ brew install wine mono
brew install wine mono
```
(Requires the [Homebrew](http://brew.sh/) package manager.)

View File

@@ -74,29 +74,17 @@
**This is the most important part.**
- Manually download the binaries for each platform from Github.
- Manually download the binaries for each platform from Github.
**Do not use your locally built binaries.** Modern OSs treat executables differently if they've
been downloaded, even though the files are byte for byte identical. This ensures that the
codesigning worked and is valid.
- Smoke test WebTorrent Desktop on each platform. Before a release, check that the following basic use cases work correctly:
- Smoke test WebTorrent Desktop on each platform.
1. Click "Play" to stream a built-in torrent (e.g. Sintel)
- Ensure that seeking to undownloaded region works and plays immediately.
- Ensure that sintel.mp4 gets downloaded to `~/Downloads`.
2. Check that the auto-updater works
- Open the console and check for the line "No update available" to indicate that the auto-updater is working. (If the auto updater does not run, users will successfully auto update to this new version, and then be stuck there forever.)
3. Add a new .torrent file via drag-and-drop.
- Ensure that it gets added to the list and starts downloading.
4. Remove a torrent from the client
- Ensure that the file is removed from `~/Downloads`
5. Create and seed a new a torrent via drag-and-drop.
- Ensure that the torrent gets created and seeding begins.
See Smoke Tests below for details. Open DevTools
on Windows and Mac, and ensure that the auto updater is running. If the auto updater does not
run, users will successfully auto update to this new version, and then be stuck there forever.
### 4. Ship it
@@ -105,5 +93,25 @@
Create a pull request in [webtorrent.io](https://github.com/webtorrent/webtorrent.io). Update
`config.js`, updating the desktop app version.
Once this PR is merged and Feross redeploys the WebTorrent website,
As soon as this PR is merged, Jenkins will automatically redeploy the WebTorrent website, and
hundreds of thousands of users around the world will start auto updating. **Merge with care.**
## Smoke Tests
Before a release, check that the following basic use cases work correctly:
1. Click "Play" to stream a built-in torrent (e.g. Sintel)
- Ensure that seeking to undownloaded region works and plays immediately.
- Ensure that sintel.mp4 gets downloaded to `~/Downloads`.
2. Check that the auto-updater works
- Open the console and check for the line "No update available" to indicate that the auto-updater is working.
3. Add a new .torrent file via drag-and-drop.
- Ensure that it gets added to the list and starts downloading.
4. Remove a torrent from the client
- Ensure that the file is removed from `~/Downloads`
5. Create and seed a new a torrent via drag-and-drop.
- Ensure that the torrent gets created and seeding begins.

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>

39
bin/extra-lint.js Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env node
const walkSync = require('walk-sync')
const fs = require('fs')
const path = require('path')
let hasErrors = false
// Find all Javascript source files
const files = walkSync('src', { globs: ['**/*.js'] })
console.log('Running extra-lint on ' + files.length + ' files...')
// Read each file, line by line
files.forEach(function (file) {
const filepath = path.join('src', file)
const lines = fs.readFileSync(filepath, 'utf8').split('\n')
lines.forEach(function (line, i) {
let error
// No lines over 100 characters
if (line.length > 100) {
error = 'Line >100 chars'
}
if (line.match(/^var /) || line.match(/ var /)) {
error = 'Use const or let'
}
if (error) {
const name = path.basename(file)
console.log('%s:%d - %s:\n%s', name, i + 1, error, line)
hasErrors = true
}
})
})
if (hasErrors) process.exit(1)
else console.log('Looks good!')

View File

@@ -172,8 +172,8 @@ const linux = {
// Build for Linux.
platform: 'linux',
// Build x64, armv7l, and arm64 binaries.
arch: ['x64', 'armv7l', 'arm64']
// Build x64 and arm64 binaries.
arch: ['x64', 'arm64']
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
}
@@ -266,7 +266,6 @@ function buildDarwin (cb) {
function signApp (cb) {
const sign = require('electron-osx-sign')
const { notarize } = require('electron-notarize')
/*
* Sign the app with Apple Developer ID certificates. We sign the app for 2 reasons:
@@ -282,37 +281,16 @@ function buildDarwin (cb) {
* - Membership in the Apple Developer Program
*/
const signOpts = {
verbose: true,
app: appPath,
platform: 'darwin',
identity: 'Developer ID Application: WebTorrent, LLC (5MAMC8G3L8)',
hardenedRuntime: true,
entitlements: path.join(config.ROOT_PATH, 'bin', 'darwin-entitlements.plist'),
'entitlements-inherit': path.join(config.ROOT_PATH, 'bin', 'darwin-entitlements.plist'),
'signature-flags': 'library'
}
const notarizeOpts = {
appBundleId: darwin.appBundleId,
appPath,
appleId: 'feross@feross.org',
appleIdPassword: '@keychain:AC_PASSWORD'
verbose: true
}
console.log('Mac: Signing app...')
sign(signOpts, function (err) {
if (err) return cb(err)
console.log('Mac: Signed app.')
console.log('Mac: Notarizing app...')
notarize(notarizeOpts).then(
function () {
console.log('Mac: Notarized app.')
cb(null)
},
function (err) {
cb(err)
})
cb(null)
})
}
@@ -545,17 +523,7 @@ function buildLinux (cb) {
},
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'),
lintianOverrides: [
'unstripped-binary-or-object',
'embedded-library',
'missing-dependency-on-libc',
'changelog-file-missing-in-native-package',
'description-synopsis-is-duplicated',
'setuid-binary',
'binary-without-manpage',
'shlib-with-executable-bit'
]
desktopTemplate: path.join(config.STATIC_PATH, 'linux/webtorrent-desktop.ejs')
}
installer(options).then(

27558
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,88 +1,74 @@
{
"name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
"version": "0.24.0",
"version": "0.23.0",
"author": {
"name": "WebTorrent, LLC",
"email": "feross@webtorrent.io",
"url": "https://webtorrent.io"
},
"babel": {
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"useBuiltIns": true
}
]
]
},
"bugs": {
"url": "https://github.com/webtorrent/webtorrent-desktop/issues"
},
"dependencies": {
"@electron/remote": "2.0.8",
"@material-ui/core": "4.12.4",
"airplayer": "github:webtorrent/airplayer#fix-security",
"application-config": "2.0.0",
"arch": "2.2.0",
"auto-launch": "5.0.5",
"bitfield": "4.1.0",
"capture-frame": "4.0.0",
"chokidar": "3.5.3",
"chromecasts": "1.10.2",
"create-torrent": "5.0.6",
"debounce": "1.2.1",
"dlnacasts": "0.1.0",
"drag-drop": "7.2.0",
"es6-error": "4.1.1",
"fn-getter": "1.0.0",
"iso-639-1": "2.1.15",
"languagedetect": "2.0.0",
"location-history": "1.1.2",
"music-metadata": "7.13.0",
"network-address": "1.1.2",
"parse-torrent": "9.1.5",
"prettier-bytes": "1.0.4",
"prop-types": "15.8.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"rimraf": "3.0.2",
"run-parallel": "1.2.0",
"semver": "7.3.8",
"simple-concat": "1.0.1",
"simple-get": "4.0.1",
"srt-to-vtt": "1.1.3",
"vlc-command": "1.2.0",
"webtorrent": "1.8.32",
"winreg": "1.2.4"
"application-config": "^2.0.0",
"arch": "^2.1.2",
"auto-launch": "^5.0.5",
"bitfield": "^3.0.0",
"capture-frame": "^3.0.2",
"chokidar": "^3.4.1",
"chromecasts": "^1.9.1",
"create-torrent": "^4.4.2",
"debounce": "^1.2.0",
"dlnacasts": "^0.1.0",
"drag-drop": "^6.0.2",
"es6-error": "^4.1.1",
"fn-getter": "^1.0.0",
"iso-639-1": "^2.1.3",
"languagedetect": "^2.0.0",
"location-history": "^1.1.2",
"material-ui": "^0.20.2",
"music-metadata": "6.3.6",
"network-address": "^1.1.2",
"parse-torrent": "^7.1.3",
"prettier-bytes": "^1.0.4",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rimraf": "^3.0.2",
"run-parallel": "^1.1.9",
"semver": "^7.3.2",
"simple-concat": "^1.0.0",
"simple-get": "^4.0.0",
"srt-to-vtt": "^1.1.3",
"vlc-command": "^1.2.0",
"webtorrent": ">=0.108.6",
"winreg": "^1.2.4"
},
"devDependencies": {
"@babel/cli": "7.19.3",
"@babel/core": "7.19.3",
"@babel/eslint-parser": "7.19.1",
"@babel/plugin-transform-react-jsx": "7.19.0",
"cross-zip": "4.0.0",
"depcheck": "1.4.3",
"electron": "15.5.7",
"electron-notarize": "1.2.1",
"electron-osx-sign": "0.6.0",
"electron-packager": "15.5.2",
"electron-winstaller": "5.0.0",
"gh-release": "6.0.4",
"minimist": "1.2.6",
"nodemon": "2.0.20",
"open": "8.4.0",
"plist": "3.0.6",
"pngjs": "6.0.0",
"run-series": "1.1.9",
"spectron": "19.0.0",
"standard": "17.0.0",
"tape": "5.6.1"
"babel-eslint": "^10.1.0",
"buble": "^0.20.0",
"cross-zip": "^3.1.0",
"depcheck": "^1.0.0",
"electron": "~9.1.0",
"electron-osx-sign": "^0.4.17",
"electron-packager": "^15.0.0",
"electron-winstaller": "^4.0.1",
"gh-release": "^3.5.0",
"minimist": "^1.2.5",
"nodemon": "^2.0.4",
"open": "^7.0.4",
"plist": "^3.0.1",
"pngjs": "^5.0.0",
"run-series": "^1.1.8",
"spectron": "~11.0.0",
"standard": "*",
"tape": "^5.0.1",
"walk-sync": "^2.2.0"
},
"engines": {
"node": "^16.0.0 || ^18.0.0",
"npm": "^7.10.0 || ^8.0.0"
"node": ">=4.0.0"
},
"homepage": "https://webtorrent.io",
"keywords": [
@@ -100,7 +86,7 @@
"optionalDependencies": {
"appdmg": "^0.6.0",
"electron-installer-debian": "^3.1.0",
"electron-installer-redhat": "^3.3.0"
"electron-installer-redhat": "^3.1.0"
},
"private": true,
"productName": "WebTorrent",
@@ -109,26 +95,19 @@
"url": "git://github.com/webtorrent/webtorrent-desktop.git"
},
"scripts": {
"build": "babel src --out-dir build",
"build": "buble src --target chrome:71 --output build",
"clean": "node ./bin/clean.js",
"gh-release": "gh-release",
"install-system-deps": "brew install fakeroot dpkg rpm",
"lint": "standard",
"lint:fix": "standard --fix",
"open-config": "node ./bin/open-config.js",
"package": "node ./bin/package.js",
"start": "npm run build && electron --no-sandbox .",
"test": "npm run lint && depcheck --ignores=standard,@babel/eslint-parser --ignore-dirs=build,dist",
"test": "standard && depcheck --ignores=standard,babel-eslint --ignore-dirs=build,dist && node ./bin/extra-lint.js",
"test-integration": "npm run build && node ./test",
"update-authors": "./bin/update-authors.sh",
"watch": "nodemon --exec \"npm run start\" --ext js,css --ignore build/ --ignore dist/"
},
"standard": {
"parser": "@babel/eslint-parser"
},
"renovate": {
"extends": [
"github>webtorrent/renovate-config"
]
"parser": "babel-eslint"
}
}

View File

@@ -1,6 +1,6 @@
const appConfig = require('application-config')('WebTorrent')
const path = require('path')
const { app } = require('electron')
const electron = require('electron')
const arch = require('arch')
const APP_NAME = 'WebTorrent'
@@ -126,10 +126,10 @@ function getPath (key) {
return ''
} else if (process.type === 'renderer') {
// Electron renderer process
return require('@electron/remote').app.getPath(key)
return electron.remote.app.getPath(key)
} else {
// Electron main process
return app.getPath(key)
return electron.app.getPath(key)
}
}

View File

@@ -4,12 +4,11 @@ module.exports = {
function init () {
const config = require('./config')
const { crashReporter } = require('electron')
const electron = require('electron')
crashReporter.start({
electron.crashReporter.start({
companyName: config.APP_NAME,
productName: config.APP_NAME,
submitURL: config.CRASH_REPORT_URL,
globalExtra: { _companyName: config.APP_NAME },
compress: true
submitURL: config.CRASH_REPORT_URL
})
}

View File

@@ -2,7 +2,7 @@ module.exports = {
init
}
const { dialog } = require('electron')
const electron = require('electron')
const config = require('../config')
const log = require('./log')
@@ -44,7 +44,7 @@ function onResponse (err, res, data) {
}
}
dialog.showMessageBox({
electron.dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
title: data.title,

View File

@@ -6,7 +6,7 @@ module.exports = {
openFiles
}
const { dialog } = require('electron')
const electron = require('electron')
const log = require('./log')
const windows = require('./windows')
@@ -34,13 +34,13 @@ function openSeedDirectory () {
log('openSeedDirectory')
const opts = process.platform === 'darwin'
? {
title: 'Select a file or folder for the torrent.',
properties: ['openFile', 'openDirectory']
}
title: 'Select a file or folder for the torrent.',
properties: ['openFile', 'openDirectory']
}
: {
title: 'Select a folder for the torrent.',
properties: ['openDirectory']
}
title: 'Select a folder for the torrent.',
properties: ['openDirectory']
}
showOpenSeed(opts)
}
@@ -53,15 +53,15 @@ function openFiles () {
log('openFiles')
const opts = process.platform === 'darwin'
? {
title: 'Select a file or folder to add.',
properties: ['openFile', 'openDirectory']
}
title: 'Select a file or folder to add.',
properties: ['openFile', 'openDirectory']
}
: {
title: 'Select a file to add.',
properties: ['openFile']
}
title: 'Select a file to add.',
properties: ['openFile']
}
setTitle(opts.title)
const selectedPaths = dialog.showOpenDialogSync(windows.main.win, opts)
const selectedPaths = electron.dialog.showOpenDialogSync(windows.main.win, opts)
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('onOpen', selectedPaths)
@@ -79,10 +79,10 @@ function openTorrentFile () {
properties: ['openFile', 'multiSelections']
}
setTitle(opts.title)
const selectedPaths = dialog.showOpenDialogSync(windows.main.win, opts)
const selectedPaths = electron.dialog.showOpenDialogSync(windows.main.win, opts)
resetTitle()
if (!Array.isArray(selectedPaths)) return
selectedPaths.forEach(selectedPath => {
selectedPaths.forEach(function (selectedPath) {
windows.main.dispatch('addTorrent', selectedPath)
})
}
@@ -114,7 +114,7 @@ function resetTitle () {
*/
function showOpenSeed (opts) {
setTitle(opts.title)
const selectedPaths = dialog.showOpenDialogSync(windows.main.win, opts)
const selectedPaths = electron.dialog.showOpenDialogSync(windows.main.win, opts)
resetTitle()
if (!Array.isArray(selectedPaths)) return
windows.main.dispatch('showCreateTorrent', selectedPaths)

View File

@@ -17,12 +17,12 @@ let proc = null
function checkInstall (playerPath, cb) {
// check for VLC if external player has not been specified by the user
// otherwise assume the player is installed
if (!playerPath) return vlcCommand(cb)
if (playerPath == null) return vlcCommand(cb)
process.nextTick(() => cb(null))
}
function spawn (playerPath, url, title) {
if (playerPath) return spawnExternal(playerPath, [url])
if (playerPath != null) return spawnExternal(playerPath, [url])
// Try to find and use VLC if external player is not specified
vlcCommand((err, vlcPath) => {

View File

@@ -25,7 +25,8 @@ function uninstall () {
}
function installDarwin () {
const { app } = require('electron')
const electron = require('electron')
const app = electron.app
// On Mac, only protocols that are listed in `Info.plist` can be set as the
// default handler at runtime.

View File

@@ -1,15 +1,12 @@
console.time('init')
require('@electron/remote/main').initialize()
const { app, ipcMain } = require('electron')
// Start crash reporter early, so it takes effect for child processes
const crashReporter = require('../crash-reporter')
crashReporter.init()
const electron = require('electron')
const app = electron.app
const parallel = require('run-parallel')
const config = require('../config')
const crashReporter = require('../crash-reporter')
const ipc = require('./ipc')
const log = require('./log')
const menu = require('./menu')
@@ -67,6 +64,8 @@ function init () {
app.setPath('temp', path.join(config.CONFIG_PATH, 'Temp'))
}
const ipcMain = electron.ipcMain
let isReady = false // app ready, windows can be created
app.ipcReady = false // main window has finished loading and IPC is ready
app.isQuitting = false
@@ -82,9 +81,9 @@ function init () {
isReady = true
const state = results.state
menu.init()
windows.main.init(state, { hidden })
windows.webtorrent.init()
menu.init()
// To keep app startup fast, some code is delayed.
setTimeout(() => {
@@ -110,13 +109,17 @@ function init () {
ipc.init()
app.once('ipcReady', () => {
app.once('will-finish-launching', function () {
crashReporter.init()
})
app.once('ipcReady', function () {
log('Command line args:', argv)
processArgv(argv)
console.timeEnd('init')
})
app.on('before-quit', e => {
app.on('before-quit', function (e) {
if (app.isQuitting) return
app.isQuitting = true
@@ -129,7 +132,7 @@ function init () {
}, 4000) // quit after 4 secs, at most
})
app.on('activate', () => {
app.on('activate', function () {
if (isReady) windows.main.show()
})
}
@@ -196,18 +199,14 @@ function onAppOpen (newArgv) {
// Development: 2 args, eg: electron .
// Test: 4 args, eg: electron -r .../mocks.js .
function sliceArgv (argv) {
return argv.slice(
config.IS_PRODUCTION
? 1
: config.IS_TEST
? 4
: 2
)
return argv.slice(config.IS_PRODUCTION ? 1
: config.IS_TEST ? 4
: 2)
}
function processArgv (argv) {
const torrentIds = []
argv.forEach(arg => {
argv.forEach(function (arg) {
if (arg === '-n' || arg === '-o' || arg === '-u') {
// Critical path: Only load the 'dialog' package if it is needed
const dialog = require('./dialog')

View File

@@ -3,7 +3,9 @@ module.exports = {
setModule
}
const { app, ipcMain } = require('electron')
const electron = require('electron')
const app = electron.app
const log = require('./log')
const menu = require('./menu')
@@ -21,16 +23,18 @@ function setModule (name, module) {
}
function init () {
ipcMain.once('ipcReady', e => {
const ipc = electron.ipcMain
ipc.once('ipcReady', function (e) {
app.ipcReady = true
app.emit('ipcReady')
})
ipcMain.once('ipcReadyWebTorrent', e => {
ipc.once('ipcReadyWebTorrent', function (e) {
app.ipcReadyWebTorrent = true
log('sending %d queued messages from the main win to the webtorrent window',
messageQueueMainToWebTorrent.length)
messageQueueMainToWebTorrent.forEach(message => {
messageQueueMainToWebTorrent.forEach(function (message) {
windows.webtorrent.send(message.name, ...message.args)
log('webtorrent: sent queued %s', message.name)
})
@@ -40,11 +44,11 @@ function init () {
* Dialog
*/
ipcMain.on('openTorrentFile', () => {
ipc.on('openTorrentFile', () => {
const dialog = require('./dialog')
dialog.openTorrentFile()
})
ipcMain.on('openFiles', () => {
ipc.on('openFiles', () => {
const dialog = require('./dialog')
dialog.openFiles()
})
@@ -53,11 +57,11 @@ function init () {
* Dock
*/
ipcMain.on('setBadge', (e, ...args) => {
ipc.on('setBadge', (e, ...args) => {
const dock = require('./dock')
dock.setBadge(...args)
})
ipcMain.on('downloadFinished', (e, ...args) => {
ipc.on('downloadFinished', (e, ...args) => {
const dock = require('./dock')
dock.downloadFinished(...args)
})
@@ -66,7 +70,7 @@ function init () {
* Player Events
*/
ipcMain.on('onPlayerOpen', () => {
ipc.on('onPlayerOpen', function () {
const powerSaveBlocker = require('./power-save-blocker')
const shortcuts = require('./shortcuts')
const thumbar = require('./thumbar')
@@ -77,14 +81,14 @@ function init () {
thumbar.enable()
})
ipcMain.on('onPlayerUpdate', (e, ...args) => {
ipc.on('onPlayerUpdate', function (e, ...args) {
const thumbar = require('./thumbar')
menu.onPlayerUpdate(...args)
thumbar.onPlayerUpdate(...args)
})
ipcMain.on('onPlayerClose', () => {
ipc.on('onPlayerClose', function () {
const powerSaveBlocker = require('./power-save-blocker')
const shortcuts = require('./shortcuts')
const thumbar = require('./thumbar')
@@ -95,7 +99,7 @@ function init () {
thumbar.disable()
})
ipcMain.on('onPlayerPlay', () => {
ipc.on('onPlayerPlay', function () {
const powerSaveBlocker = require('./power-save-blocker')
const thumbar = require('./thumbar')
@@ -103,7 +107,7 @@ function init () {
thumbar.onPlayerPlay()
})
ipcMain.on('onPlayerPause', () => {
ipc.on('onPlayerPause', function () {
const powerSaveBlocker = require('./power-save-blocker')
const thumbar = require('./thumbar')
@@ -115,7 +119,7 @@ function init () {
* Folder Watcher Events
*/
ipcMain.on('startFolderWatcher', () => {
ipc.on('startFolderWatcher', function () {
if (!modules.folderWatcher) {
log('IPC ERR: folderWatcher module is not defined.')
return
@@ -124,7 +128,7 @@ function init () {
modules.folderWatcher.start()
})
ipcMain.on('stopFolderWatcher', () => {
ipc.on('stopFolderWatcher', function () {
if (!modules.folderWatcher) {
log('IPC ERR: folderWatcher module is not defined.')
return
@@ -137,15 +141,15 @@ function init () {
* Shell
*/
ipcMain.on('openPath', (e, ...args) => {
ipc.on('openPath', (e, ...args) => {
const shell = require('./shell')
shell.openPath(...args)
})
ipcMain.on('showItemInFolder', (e, ...args) => {
ipc.on('showItemInFolder', (e, ...args) => {
const shell = require('./shell')
shell.showItemInFolder(...args)
})
ipcMain.on('moveItemToTrash', (e, ...args) => {
ipc.on('moveItemToTrash', (e, ...args) => {
const shell = require('./shell')
shell.moveItemToTrash(...args)
})
@@ -154,7 +158,7 @@ function init () {
* File handlers
*/
ipcMain.on('setDefaultFileHandler', (e, flag) => {
ipc.on('setDefaultFileHandler', (e, flag) => {
const handlers = require('./handlers')
if (flag) handlers.install()
@@ -165,7 +169,7 @@ function init () {
* Auto start on login
*/
ipcMain.on('setStartup', (e, flag) => {
ipc.on('setStartup', (e, flag) => {
const startup = require('./startup')
if (flag) startup.install()
@@ -178,38 +182,36 @@ function init () {
const main = windows.main
ipcMain.on('setAspectRatio', (e, ...args) => main.setAspectRatio(...args))
ipcMain.on('setBounds', (e, ...args) => main.setBounds(...args))
ipcMain.on('setProgress', (e, ...args) => main.setProgress(...args))
ipcMain.on('setTitle', (e, ...args) => main.setTitle(...args))
ipcMain.on('show', () => main.show())
ipcMain.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args))
ipcMain.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args))
ipc.on('setAspectRatio', (e, ...args) => main.setAspectRatio(...args))
ipc.on('setBounds', (e, ...args) => main.setBounds(...args))
ipc.on('setProgress', (e, ...args) => main.setProgress(...args))
ipc.on('setTitle', (e, ...args) => main.setTitle(...args))
ipc.on('show', () => main.show())
ipc.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args))
ipc.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args))
/**
* External Media Player
*/
ipcMain.on('checkForExternalPlayer', (e, path) => {
ipc.on('checkForExternalPlayer', function (e, path) {
const externalPlayer = require('./external-player')
externalPlayer.checkInstall(path, err => {
externalPlayer.checkInstall(path, function (err) {
windows.main.send('checkForExternalPlayer', !err)
})
})
ipcMain.on('openExternalPlayer', (e, ...args) => {
ipc.on('openExternalPlayer', (e, ...args) => {
const externalPlayer = require('./external-player')
const shortcuts = require('./shortcuts')
const thumbar = require('./thumbar')
menu.togglePlaybackControls(false)
shortcuts.disable()
thumbar.disable()
externalPlayer.spawn(...args)
})
ipcMain.on('quitExternalPlayer', () => {
ipc.on('quitExternalPlayer', () => {
const externalPlayer = require('./external-player')
externalPlayer.kill()
})
@@ -218,12 +220,11 @@ function init () {
* Message passing
*/
const oldEmit = ipcMain.emit
ipcMain.emit = (name, e, ...args) => {
const oldEmit = ipc.emit
ipc.emit = function (name, e, ...args) {
// Relay messages between the main window and the WebTorrent hidden window
if (name.startsWith('wt-') && !app.isQuitting) {
console.dir(e.sender.getTitle())
if (e.sender.getTitle() === 'WebTorrent Hidden Window') {
if (e.sender.browserWindowOptions.title === 'webtorrent-hidden-window') {
// Send message to main window
windows.main.send(name, ...args)
log('webtorrent: got %s', name)
@@ -243,6 +244,6 @@ function init () {
}
// Emit all other events normally
oldEmit.call(ipcMain, name, e, ...args)
oldEmit.call(ipc, name, e, ...args)
}
}

View File

@@ -8,9 +8,11 @@ module.exports.error = error
* where they can be viewed in Developer Tools.
*/
const { app } = require('electron')
const electron = require('electron')
const windows = require('./windows')
const app = electron.app
function log (...args) {
if (app.ipcReady) {
windows.main.send('log', ...args)

View File

@@ -8,7 +8,9 @@ module.exports = {
onToggleFullScreen
}
const { app, Menu } = require('electron')
const electron = require('electron')
const app = electron.app
const config = require('../config')
const windows = require('./windows')
@@ -16,8 +18,8 @@ const windows = require('./windows')
let menu = null
function init () {
menu = Menu.buildFromTemplate(getMenuTemplate())
Menu.setApplicationMenu(menu)
menu = electron.Menu.buildFromTemplate(getMenuTemplate())
electron.Menu.setApplicationMenu(menu)
}
function togglePlaybackControls (flag) {
@@ -68,11 +70,12 @@ function onToggleFullScreen (flag) {
}
function getMenuItem (label) {
for (const menuItem of menu.items) {
const submenuItem = menuItem.submenu.items.find(item => item.label === label)
if (submenuItem) return submenuItem
for (let i = 0; i < menu.items.length; i++) {
const menuItem = menu.items[i].submenu.items.find(function (item) {
return item.label === label
})
if (menuItem) return menuItem
}
return {}
}
function getMenuTemplate () {

View File

@@ -3,7 +3,7 @@ module.exports = {
disable
}
const { powerSaveBlocker } = require('electron')
const electron = require('electron')
const log = require('./log')
let blockId = 0
@@ -13,11 +13,11 @@ let blockId = 0
* display.
*/
function enable () {
if (powerSaveBlocker.isStarted(blockId)) {
if (electron.powerSaveBlocker.isStarted(blockId)) {
// If a power saver block already exists, do nothing.
return
}
blockId = powerSaveBlocker.start('prevent-display-sleep')
blockId = electron.powerSaveBlocker.start('prevent-display-sleep')
log(`powerSaveBlocker.enable: ${blockId}`)
}
@@ -25,10 +25,10 @@ function enable () {
* Stop blocking the system from entering low-power mode.
*/
function disable () {
if (!powerSaveBlocker.isStarted(blockId)) {
if (!electron.powerSaveBlocker.isStarted(blockId)) {
// If a power saver block does not exist, do nothing.
return
}
powerSaveBlocker.stop(blockId)
electron.powerSaveBlocker.stop(blockId)
log(`powerSaveBlocker.disable: ${blockId}`)
}

View File

@@ -5,7 +5,7 @@ module.exports = {
moveItemToTrash
}
const { shell } = require('electron')
const electron = require('electron')
const log = require('./log')
/**
@@ -13,16 +13,15 @@ const log = require('./log')
*/
function openExternal (url) {
log(`openExternal: ${url}`)
shell.openExternal(url)
electron.shell.openExternal(url)
}
/**
* Open the given file in the desktops default manner.
*/
function openPath (path) {
log(`openPath: ${path}`)
shell.openPath(path)
electron.shell.openPath(path)
}
/**
@@ -30,7 +29,7 @@ function openPath (path) {
*/
function showItemInFolder (path) {
log(`showItemInFolder: ${path}`)
shell.showItemInFolder(path)
electron.shell.showItemInFolder(path)
}
/**
@@ -38,5 +37,5 @@ function showItemInFolder (path) {
*/
function moveItemToTrash (path) {
log(`moveItemToTrash: ${path}`)
shell.trashItem(path)
electron.shell.moveItemToTrash(path)
}

View File

@@ -3,20 +3,20 @@ module.exports = {
enable
}
const { globalShortcut } = require('electron')
const electron = require('electron')
const windows = require('./windows')
function enable () {
// Register play/pause media key, available on some keyboards.
globalShortcut.register(
electron.globalShortcut.register(
'MediaPlayPause',
() => windows.main.dispatch('playPause')
)
globalShortcut.register(
electron.globalShortcut.register(
'MediaNextTrack',
() => windows.main.dispatch('nextTrack')
)
globalShortcut.register(
electron.globalShortcut.register(
'MediaPreviousTrack',
() => windows.main.dispatch('previousTrack')
)
@@ -24,7 +24,7 @@ function enable () {
function disable () {
// Return the media key to the OS, so other apps can use it.
globalShortcut.unregister('MediaPlayPause')
globalShortcut.unregister('MediaNextTrack')
globalShortcut.unregister('MediaPreviousTrack')
electron.globalShortcut.unregister('MediaPlayPause')
electron.globalShortcut.unregister('MediaNextTrack')
electron.globalShortcut.unregister('MediaPreviousTrack')
}

View File

@@ -2,39 +2,150 @@ module.exports = {
handleEvent
}
const { app } = require('electron')
const cp = require('child_process')
const electron = require('electron')
const fs = require('fs')
const os = require('os')
const path = require('path')
const spawn = require('child_process').spawn
const app = electron.app
const handlers = require('./handlers')
const EXE_NAME = path.basename(process.execPath)
const UPDATE_EXE = path.join(process.execPath, '..', '..', 'Update.exe')
const run = (args, done) => {
spawn(UPDATE_EXE, args, { detached: true })
.on('close', done)
}
function handleEvent (cmd) {
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
run([`--createShortcut=${EXE_NAME}`], app.quit)
if (cmd === '--squirrel-install') {
// App was installed. Install desktop/start menu shortcuts.
createShortcuts(function () {
// Ensure user sees install splash screen so they realize that Setup.exe actually
// installed an application and isn't the application itself.
setTimeout(function () {
app.quit()
}, 3000)
})
return true
}
if (cmd === '--squirrel-updated') {
// App was updated. (Called on new version of app)
updateShortcuts(function () {
app.quit()
})
return true
}
if (cmd === '--squirrel-uninstall') {
// App was just uninstalled. Undo anything we did in the --squirrel-install and
// --squirrel-updated handlers
// Uninstall .torrent file and magnet link handlers
handlers.uninstall()
run([`--removeShortcut=${EXE_NAME}`], app.quit)
// Remove desktop/start menu shortcuts.
// HACK: add a callback to handlers.uninstall() so we can remove this setTimeout
setTimeout(function () {
removeShortcuts(function () {
app.quit()
})
}, 1000)
return true
}
if (cmd === '--squirrel-obsolete') {
// App will be updated. (Called on outgoing version of app)
app.quit()
return true
}
if (cmd === '--squirrel-firstrun') {
// App is running for the first time. Do not quit, allow startup to continue.
return false
}
return false
}
/**
* Spawn a command and invoke the callback when it completes with an error and
* the output from standard out.
*/
function spawn (command, args, cb) {
let stdout = ''
let error = null
let child = null
try {
child = cp.spawn(command, args)
} catch (err) {
// Spawn can throw an error
process.nextTick(function () {
cb(error, stdout)
})
return
}
child.stdout.on('data', function (data) {
stdout += data
})
child.on('error', function (processError) {
error = processError
})
child.on('close', function (code, signal) {
if (code !== 0 && !error) error = new Error('Command failed: #{signal || code}')
if (error) error.stdout = stdout
cb(error, stdout)
})
}
/**
* Spawn the Squirrel `Update.exe` command with the given arguments and invoke
* the callback when the command completes.
*/
function spawnUpdate (args, cb) {
spawn(UPDATE_EXE, args, cb)
}
/**
* Create desktop and start menu shortcuts using the Squirrel `Update.exe`
* command.
*/
function createShortcuts (cb) {
spawnUpdate(['--createShortcut', EXE_NAME], cb)
}
/**
* Update desktop and start menu shortcuts using the Squirrel `Update.exe`
* command.
*/
function updateShortcuts (cb) {
const homeDir = os.homedir()
if (homeDir) {
const desktopShortcutPath = path.join(homeDir, 'Desktop', 'WebTorrent.lnk')
// If the desktop shortcut was deleted by the user, then keep it deleted.
fs.access(desktopShortcutPath, function (err) {
const desktopShortcutExists = !err
createShortcuts(function () {
if (desktopShortcutExists) {
cb()
} else {
// Remove the unwanted desktop shortcut that was recreated
fs.unlink(desktopShortcutPath, cb)
}
})
})
} else {
createShortcuts(cb)
}
}
/**
* Remove desktop and start menu shortcuts using the Squirrel `Update.exe`
* command.
*/
function removeShortcuts (cb) {
spawnUpdate(['--removeShortcut', EXE_NAME], cb)
}

View File

@@ -4,7 +4,9 @@ module.exports = {
setWindowFocus
}
const { app, Tray, Menu } = require('electron')
const electron = require('electron')
const app = electron.app
const config = require('../config')
const windows = require('./windows')
@@ -34,7 +36,7 @@ function setWindowFocus (flag) {
}
function initLinux () {
checkLinuxTraySupport(err => {
checkLinuxTraySupport(function (err) {
if (!err) createTray()
})
}
@@ -44,20 +46,28 @@ function initWin32 () {
}
/**
* Check for libappindicator support before creating tray icon.
* Check for libappindicator1 support before creating tray icon
*/
function checkLinuxTraySupport (cb) {
const cp = require('child_process')
// Check that libappindicator libraries are installed in system.
cp.exec('ldconfig -p | grep libappindicator', (err, stdout) => {
// Check that we're on Ubuntu (or another debian system) and that we have
// libappindicator1. If WebTorrent was installed from the deb file, we should
// always have it. If it was installed from the zip file, we might not.
cp.exec('dpkg --get-selections libappindicator1', function (err, stdout) {
if (err) return cb(err)
cb(null)
// Unfortunately there's no cleaner way, as far as I can tell, to check
// whether a debian package is installed:
if (stdout.endsWith('\tinstall\n')) {
cb(null)
} else {
cb(new Error('debian package not installed'))
}
})
}
function createTray () {
tray = new Tray(getIconPath())
tray = new electron.Tray(getIconPath())
// On Windows, left click opens the app, right click opens the context menu.
// On Linux, any click (left or right) opens the context menu.
@@ -68,7 +78,7 @@ function createTray () {
}
function updateTrayMenu () {
const contextMenu = Menu.buildFromTemplate(getMenuTemplate())
const contextMenu = electron.Menu.buildFromTemplate(getMenuTemplate())
tray.setContextMenu(contextMenu)
}

View File

@@ -2,7 +2,7 @@ module.exports = {
init
}
const { autoUpdater } = require('electron')
const electron = require('electron')
const get = require('simple-get')
const config = require('../config')
@@ -47,31 +47,31 @@ function onResponse (err, res, data) {
}
function initDarwinWin32 () {
autoUpdater.on(
electron.autoUpdater.on(
'error',
(err) => log.error(`Update error: ${err.message}`)
)
autoUpdater.on(
electron.autoUpdater.on(
'checking-for-update',
() => log('Checking for update')
)
autoUpdater.on(
electron.autoUpdater.on(
'update-available',
() => log('Update available')
)
autoUpdater.on(
electron.autoUpdater.on(
'update-not-available',
() => log('No update available')
)
autoUpdater.on(
electron.autoUpdater.on(
'update-downloaded',
(e, notes, name, date, url) => log(`Update downloaded: ${name}: ${url}`)
)
autoUpdater.setFeedURL({ url: AUTO_UPDATE_URL })
autoUpdater.checkForUpdates()
electron.autoUpdater.setFeedURL({ url: AUTO_UPDATE_URL })
electron.autoUpdater.checkForUpdates()
}

View File

@@ -2,7 +2,9 @@ module.exports = {
init
}
const { app } = require('electron')
const electron = require('electron')
const app = electron.app
/**
* Add a user task menu to the app icon on right-click. (Windows)

View File

@@ -4,14 +4,14 @@ const about = module.exports = {
}
const config = require('../../config')
const { BrowserWindow } = require('electron')
const electron = require('electron')
function init () {
if (about.win) {
return about.win.show()
}
const win = about.win = new BrowserWindow({
const win = about.win = new electron.BrowserWindow({
backgroundColor: '#ECECEC',
center: true,
fullscreen: false,
@@ -26,18 +26,14 @@ function init () {
useContentSize: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableBlinkFeatures: 'AudioVideoTracks',
enableRemoteModule: true,
backgroundThrottling: false
enableBlinkFeatures: 'AudioVideoTracks'
},
width: 300
})
require('@electron/remote/main').enable(win.webContents)
win.loadURL(config.WINDOW_ABOUT)
win.once('ready-to-show', () => {
win.once('ready-to-show', function () {
win.show()
// No menu on the About window
// Hack: BrowserWindow removeMenu method not working on electron@7
@@ -45,7 +41,7 @@ function init () {
win.setMenuBarVisibility(false)
})
win.once('closed', () => {
win.once('closed', function () {
about.win = null
})
}

View File

@@ -14,9 +14,11 @@ const main = module.exports = {
win: null
}
const { app, BrowserWindow, screen } = require('electron')
const electron = require('electron')
const debounce = require('debounce')
const app = electron.app
const config = require('../../config')
const log = require('../log')
const menu = require('../menu')
@@ -28,7 +30,7 @@ function init (state, options) {
const initialBounds = Object.assign(config.WINDOW_INITIAL_BOUNDS, state.saved.bounds)
const win = main.win = new BrowserWindow({
const win = main.win = new electron.BrowserWindow({
backgroundColor: '#282828',
darkTheme: true, // Forces dark theme (GTK+3)
height: initialBounds.height,
@@ -42,15 +44,11 @@ function init (state, options) {
width: initialBounds.width,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableBlinkFeatures: 'AudioVideoTracks',
enableRemoteModule: true,
backgroundThrottling: false
enableBlinkFeatures: 'AudioVideoTracks'
},
x: initialBounds.x,
y: initialBounds.y
})
require('@electron/remote/main').enable(win.webContents)
win.loadURL(config.WINDOW_MAIN)
@@ -66,7 +64,7 @@ function init (state, options) {
menu.onToggleFullScreen(main.win.isFullScreen())
})
win.webContents.on('will-navigate', (e) => {
win.webContents.on('will-navigate', (e, url) => {
// Prevent drag-and-drop from navigating the Electron window, which can happen
// before our drag-and-drop handlers have been initialized.
e.preventDefault()
@@ -152,7 +150,7 @@ function setBounds (bounds, maximize) {
log('setBounds: maximizing')
main.win.maximize()
} else if (maximize === false && main.win.isMaximized()) {
log('setBounds: minimizing')
log('setBounds: unmaximizing')
main.win.unmaximize()
}
@@ -162,7 +160,7 @@ function setBounds (bounds, maximize) {
log(`setBounds: setting bounds to ${JSON.stringify(bounds)}`)
if (bounds.x === null && bounds.y === null) {
// X and Y not specified? By default, center on current screen
const scr = screen.getDisplayMatching(main.win.getBounds())
const scr = electron.screen.getDisplayMatching(main.win.getBounds())
bounds.x = Math.round(scr.bounds.x + (scr.bounds.width / 2) - (bounds.width / 2))
bounds.y = Math.round(scr.bounds.y + (scr.bounds.height / 2) - (bounds.height / 2))
log(`setBounds: centered to ${JSON.stringify(bounds)}`)

View File

@@ -6,12 +6,12 @@ const webtorrent = module.exports = {
win: null
}
const { app, BrowserWindow } = require('electron')
const electron = require('electron')
const config = require('../../config')
function init () {
const win = webtorrent.win = new BrowserWindow({
const win = webtorrent.win = new electron.BrowserWindow({
backgroundColor: '#1E1E1E',
center: true,
fullscreen: false,
@@ -26,20 +26,16 @@ function init () {
useContentSize: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableBlinkFeatures: 'AudioVideoTracks',
enableRemoteModule: true,
backgroundThrottling: false
enableBlinkFeatures: 'AudioVideoTracks'
},
width: 150
})
require('@electron/remote/main').enable(win.webContents)
win.loadURL(config.WINDOW_WEBTORRENT)
// Prevent killing the WebTorrent process
win.on('close', e => {
if (app.isQuitting) {
win.on('close', function (e) {
if (electron.app.isQuitting) {
return
}
e.preventDefault()

View File

@@ -16,7 +16,6 @@ module.exports = class DeleteAllTorrentsModal extends React.Component {
<p><strong>{message}</strong></p>
<ModalOKCancel
cancelText='CANCEL'
color='secondary'
onCancel={dispatcher('exitModal')}
okText={buttonText}
onOK={handleRemove}

View File

@@ -11,7 +11,6 @@ class Header extends React.Component {
onMouseMove={dispatcher('mediaMouseMoved')}
onMouseEnter={dispatcher('mediaControlsMouseEnter')}
onMouseLeave={dispatcher('mediaControlsMouseLeave')}
role='navigation'
>
{this.getTitle()}
<div className='nav left float-left'>
@@ -19,9 +18,6 @@ class Header extends React.Component {
className={'icon back ' + (loc.hasBack() ? '' : 'disabled')}
title='Back'
onClick={dispatcher('back')}
role='button'
aria-disabled={!loc.hasBack()}
aria-label='Back'
>
chevron_left
</i>
@@ -29,9 +25,6 @@ class Header extends React.Component {
className={'icon forward ' + (loc.hasForward() ? '' : 'disabled')}
title='Forward'
onClick={dispatcher('forward')}
role='button'
aria-disabled={!loc.hasForward()}
aria-label='Forward'
>
chevron_right
</i>
@@ -57,7 +50,6 @@ class Header extends React.Component {
className='icon add'
title='Add torrent'
onClick={dispatcher('openFiles')}
role='button'
>
add
</i>

View File

@@ -1,7 +1,7 @@
const React = require('react')
const PropTypes = require('prop-types')
const grey = require('@material-ui/core/colors/grey').default
const colors = require('material-ui/styles/colors')
class Heading extends React.Component {
static get propTypes () {
@@ -19,7 +19,7 @@ class Heading extends React.Component {
render () {
const HeadingTag = 'h' + this.props.level
const style = {
color: grey['100'],
color: colors.grey100,
fontSize: 20,
marginBottom: 15,
marginTop: 30

View File

@@ -1,28 +1,26 @@
const React = require('react')
const Button = require('@material-ui/core/Button').default
const FlatButton = require('material-ui/FlatButton').default
const RaisedButton = require('material-ui/RaisedButton').default
module.exports = class ModalOKCancel extends React.Component {
render () {
const cancelStyle = { marginRight: 10, color: 'black' }
const { cancelText, color = 'primary', onCancel, okText, onOK } = this.props
const { cancelText, onCancel, okText, onOK } = this.props
return (
<div className='float-right'>
<Button
<FlatButton
className='control cancel'
onClick={onCancel}
style={cancelStyle}
>
{cancelText}
</Button>
<Button
autoFocus
label={cancelText}
onClick={onCancel}
/>
<RaisedButton
className='control ok'
primary
label={okText}
onClick={onOK}
color={color}
variant='contained'
>
{okText}
</Button>
autoFocus
/>
</div>
)
}

View File

@@ -1,5 +1,5 @@
const React = require('react')
const TextField = require('@material-ui/core/TextField').default
const TextField = require('material-ui/TextField').default
const { clipboard } = require('electron')
const ModalOKCancel = require('./modal-ok-cancel')

View File

@@ -1,12 +1,14 @@
const path = require('path')
const remote = require('@electron/remote')
const colors = require('material-ui/styles/colors')
const electron = require('electron')
const React = require('react')
const PropTypes = require('prop-types')
const Button = require('@material-ui/core/Button').default
const TextField = require('@material-ui/core/TextField').default
const grey = require('@material-ui/core/colors/grey').default
const remote = electron.remote
const RaisedButton = require('material-ui/RaisedButton').default
const TextField = require('material-ui/TextField').default
// Lets you pick a file or directory.
// Uses the system Open File dialog.
@@ -53,8 +55,10 @@ class PathSelector extends React.Component {
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
const textareaStyle = {
color: colors.grey50
}
const textFieldStyle = {
color: grey['50'],
flex: '1'
}
const text = this.props.value || ''
@@ -68,20 +72,13 @@ class PathSelector extends React.Component {
{this.props.title}:
</div>
<TextField
className='control'
disabled
id={id}
style={textFieldStyle}
value={text}
className='control' disabled id={id} value={text}
inputStyle={textareaStyle} style={textFieldStyle}
/>
<Button
className='control'
onClick={this.handleClick}
<RaisedButton
className='control' label='Change' onClick={this.handleClick}
style={buttonStyle}
variant='contained'
>
Change
</Button>
/>
</div>
)
}

View File

@@ -16,7 +16,6 @@ module.exports = class RemoveTorrentModal extends React.Component {
<p><strong>{message}</strong></p>
<ModalOKCancel
cancelText='CANCEL'
color='secondary'
onCancel={dispatcher('exitModal')}
okText={buttonText}
onOK={handleRemove}

View File

@@ -1,7 +1,7 @@
const React = require('react')
const PropTypes = require('prop-types')
const Button = require('@material-ui/core/Button').default
const RaisedButton = require('material-ui/RaisedButton').default
class ShowMore extends React.Component {
static get propTypes () {
@@ -42,13 +42,11 @@ class ShowMore extends React.Component {
return (
<div className='show-more' style={this.props.style}>
{this.state.expanded ? this.props.children : null}
<Button
<RaisedButton
className='control'
onClick={this.handleClick}
variant='contained'
>
{label}
</Button>
label={label}
/>
</div>
)
}

View File

@@ -1,5 +1,5 @@
const React = require('react')
const { shell } = require('electron')
const electron = require('electron')
const ModalOKCancel = require('./modal-ok-cancel')
const { dispatcher } = require('../lib/dispatcher')
@@ -36,7 +36,7 @@ module.exports = class UnsupportedMediaModal extends React.Component {
}
onInstall () {
shell.openExternal('https://www.videolan.org/vlc/')
electron.shell.openExternal('http://www.videolan.org/vlc/')
// TODO: dcposch send a dispatch rather than modifying state directly
const state = this.props.state

View File

@@ -1,5 +1,5 @@
const React = require('react')
const { shell } = require('electron')
const electron = require('electron')
const ModalOKCancel = require('./modal-ok-cancel')
const { dispatch } = require('../lib/dispatcher')
@@ -25,7 +25,7 @@ module.exports = class UpdateAvailableModal extends React.Component {
function handleShow () {
// TODO: use the GitHub urls from config.js
shell.openExternal('https://github.com/webtorrent/webtorrent-desktop/releases')
electron.shell.openExternal('https://github.com/webtorrent/webtorrent-desktop/releases')
dispatch('exitModal')
}

View File

@@ -23,7 +23,7 @@ module.exports = class MediaController {
telemetry.logPlayAttempt('error')
state.playing.location = 'error'
ipcRenderer.send('checkForExternalPlayer', state.saved.prefs.externalPlayerPath)
ipcRenderer.once('checkForExternalPlayer', (e, isInstalled) => {
ipcRenderer.once('checkForExternalPlayer', function (e, isInstalled) {
state.modal = {
id: 'unsupported-media-modal',
error,
@@ -56,7 +56,7 @@ module.exports = class MediaController {
const state = this.state
state.playing.location = 'external'
const onServerRunning = () => {
const onServerRunning = function () {
state.playing.isReady = true
telemetry.logPlayAttempt('external')

View File

@@ -1,4 +1,4 @@
const { ipcRenderer } = require('electron')
const electron = require('electron')
const path = require('path')
const Cast = require('../lib/cast')
@@ -11,6 +11,8 @@ const TorrentSummary = require('../lib/torrent-summary')
const Playlist = require('../lib/playlist')
const State = require('../lib/state')
const ipcRenderer = electron.ipcRenderer
// Controls playback of torrents and files within torrents
// both local (<video>,<audio>,external player) and remote (cast)
module.exports = class PlaybackController {
@@ -156,20 +158,6 @@ module.exports = class PlaybackController {
else this.state.playing.jumpToTime = time
}
// Show video preview
preview (x) {
if (!Number.isFinite(x)) {
console.error('Tried to preview a non-finite position ' + x)
return console.trace()
}
this.state.playing.previewXCoord = x
}
// Hide video preview
clearPreview () {
this.state.playing.previewXCoord = null
}
// Change playback speed. 1 = faster, -1 = slower
// Playback speed ranges from 16 (fast forward) to 1 (normal playback)
// to 0.25 (quarter-speed playback), then goes to -0.25, -0.5, -1, -2, etc
@@ -282,10 +270,8 @@ module.exports = class PlaybackController {
state.playing.infoHash = infoHash
state.playing.fileIndex = index
state.playing.fileName = fileSummary.name
state.playing.type = TorrentPlayer.isVideo(fileSummary)
? 'video'
: TorrentPlayer.isAudio(fileSummary)
? 'audio'
state.playing.type = TorrentPlayer.isVideo(fileSummary) ? 'video'
: TorrentPlayer.isAudio(fileSummary) ? 'audio'
: 'other'
// pick up where we left off

View File

@@ -1,5 +1,5 @@
const { dispatch } = require('../lib/dispatcher')
const { ipcRenderer } = require('electron')
const ipcRenderer = require('electron').ipcRenderer
// Controls the Preferences screen
module.exports = class PrefsController {
@@ -13,7 +13,7 @@ module.exports = class PrefsController {
const state = this.state
state.location.go({
url: 'preferences',
setup (cb) {
setup: function (cb) {
// initialize preferences
state.window.title = 'Preferences'
ipcRenderer.send('setAllowNav', false)

View File

@@ -1,8 +1,10 @@
const remote = require('@electron/remote')
const electron = require('electron')
const fs = require('fs')
const path = require('path')
const parallel = require('run-parallel')
const remote = electron.remote
const { dispatch } = require('../lib/dispatcher')
module.exports = class SubtitlesController {
@@ -37,11 +39,12 @@ module.exports = class SubtitlesController {
// Read the files concurrently, then add all resulting subtitle tracks
const tasks = files.map((file) => (cb) => loadSubtitle(file, cb))
parallel(tasks, (err, tracks) => {
parallel(tasks, function (err, tracks) {
if (err) return dispatch('error', err)
// No dupes allowed
tracks.forEach((track, i) => {
for (let i = 0; i < tracks.length; i++) {
// No dupes allowed
const track = tracks[i]
let trackIndex = subtitles.tracks.findIndex((t) =>
track.filePath === t.filePath)
@@ -54,7 +57,7 @@ module.exports = class SubtitlesController {
if (autoSelect && (i === 0 || isSystemLanguage(track.language))) {
subtitles.selectedIndex = trackIndex
}
})
}
// Finally, make sure no two tracks have the same label
relabelSubtitles(subtitles)
@@ -93,7 +96,7 @@ function loadSubtitle (file, cb) {
const vttStream = fs.createReadStream(filePath).pipe(srtToVtt())
concat(vttStream, (err, buf) => {
concat(vttStream, function (err, buf) {
if (err) return dispatch('error', 'Can\'t parse subtitles file.')
// Detect what language the subtitles are in
@@ -126,7 +129,7 @@ function isSystemLanguage (language) {
// Labels each track by language, eg 'German', 'English', 'English 2', ...
function relabelSubtitles (subtitles) {
const counts = {}
subtitles.tracks.forEach(track => {
subtitles.tracks.forEach(function (track) {
const lang = track.language
counts[lang] = (counts[lang] || 0) + 1
track.label = counts[lang] > 1 ? (lang + ' ' + counts[lang]) : lang

View File

@@ -1,5 +1,5 @@
const path = require('path')
const { ipcRenderer } = require('electron')
const ipcRenderer = require('electron').ipcRenderer
const TorrentSummary = require('../lib/torrent-summary')
const sound = require('../lib/sound')
@@ -177,7 +177,7 @@ function showDoneNotification (torrent) {
silent: true
})
notif.onclick = () => {
notif.onclick = function () {
ipcRenderer.send('show')
}

View File

@@ -1,13 +1,14 @@
const fs = require('fs')
const path = require('path')
const { ipcRenderer, clipboard } = require('electron')
const remote = require('@electron/remote')
const electron = require('electron')
const { dispatch } = require('../lib/dispatcher')
const { TorrentKeyNotFoundError } = require('../lib/errors')
const sound = require('../lib/sound')
const TorrentSummary = require('../lib/torrent-summary')
const ipcRenderer = electron.ipcRenderer
const instantIoRegex = /^(https:\/\/)?instant\.io\/#/
// Controls the torrent list: creating, adding, deleting, & manipulating torrents
@@ -94,7 +95,7 @@ module.exports = class TorrentListController {
if (!fileOrFolder) return start()
// Existing torrent: check that the path is still there
fs.stat(fileOrFolder, err => {
fs.stat(fileOrFolder, function (err) {
if (err) {
s.error = 'path-missing'
dispatch('backToList')
@@ -113,10 +114,6 @@ module.exports = class TorrentListController {
}
}
setGlobalTrackers (globalTrackers) {
ipcRenderer.send('wt-set-global-trackers', globalTrackers)
}
// TODO: use torrentKey, not infoHash
toggleTorrent (infoHash) {
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
@@ -160,8 +157,10 @@ module.exports = class TorrentListController {
prioritizeTorrent (infoHash) {
this.state.saved.torrents
.filter(torrent => ['downloading', 'seeding'].includes(torrent.status)) // Active torrents only.
.forEach((torrent) => { // Pause all active torrents except the one that started playing.
.filter((torrent) => { // We're interested in active torrents only.
return (['downloading', 'seeding'].indexOf(torrent.status) !== -1)
})
.map((torrent) => { // Pause all active torrents except the one that started playing.
if (infoHash === torrent.infoHash) return
// Pause torrent without playing sounds.
@@ -176,7 +175,7 @@ module.exports = class TorrentListController {
resumePausedTorrents () {
console.log('Playback Priority: resuming paused torrents')
if (!this.state.saved.torrentsToResume || !this.state.saved.torrentsToResume.length) return
this.state.saved.torrentsToResume.forEach((infoHash) => {
this.state.saved.torrentsToResume.map((infoHash) => {
this.toggleTorrent(infoHash)
})
@@ -230,11 +229,6 @@ module.exports = class TorrentListController {
}
deleteAllTorrents (deleteData) {
// Go back to list before the current playing torrent is deleted
if (this.state.location.url() === 'player') {
dispatch('backToList')
}
this.state.saved.torrents.forEach((summary) => deleteTorrentFile(summary, deleteData))
this.state.saved.torrents = []
@@ -255,59 +249,59 @@ module.exports = class TorrentListController {
openTorrentContextMenu (infoHash) {
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
const menu = new remote.Menu()
const menu = new electron.remote.Menu()
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
label: 'Remove From List',
click: () => dispatch('confirmDeleteTorrent', torrentSummary.infoHash, false)
}))
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
label: 'Remove Data File',
click: () => dispatch('confirmDeleteTorrent', torrentSummary.infoHash, true)
}))
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
type: 'separator'
}))
if (torrentSummary.files) {
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
label: process.platform === 'darwin' ? 'Show in Finder' : 'Show in Folder',
click: () => showItemInFolder(torrentSummary)
}))
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
type: 'separator'
}))
}
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
label: 'Copy Magnet Link to Clipboard',
click: () => clipboard.writeText(torrentSummary.magnetURI)
click: () => electron.clipboard.writeText(torrentSummary.magnetURI)
}))
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
label: 'Copy Instant.io Link to Clipboard',
click: () => clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
click: () => electron.clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
}))
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
label: 'Save Torrent File As...',
click: () => dispatch('saveTorrentFileAs', torrentSummary.torrentKey),
enabled: torrentSummary.torrentFileName != null
}))
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
type: 'separator'
}))
const sortedByName = this.state.saved.prefs.sortByName
menu.append(new remote.MenuItem({
menu.append(new electron.remote.MenuItem({
label: `${sortedByName ? '✓ ' : ''}Sort by Name`,
click: () => dispatch('updatePreferences', 'sortByName', !sortedByName)
}))
menu.popup({ window: remote.getCurrentWindow() })
menu.popup({ window: electron.remote.getCurrentWindow() })
}
// Takes a torrentSummary or torrentKey
@@ -317,7 +311,7 @@ module.exports = class TorrentListController {
if (!torrentSummary) throw new TorrentKeyNotFoundError(torrentKey)
const downloadPath = this.state.saved.prefs.downloadPath
const newFileName = path.parse(torrentSummary.name).name + '.torrent'
const win = remote.getCurrentWindow()
const win = electron.remote.getCurrentWindow()
const opts = {
title: 'Save Torrent File',
defaultPath: path.join(downloadPath, newFileName),
@@ -328,14 +322,14 @@ module.exports = class TorrentListController {
buttonLabel: 'Save'
}
const savePath = remote.dialog.showSaveDialogSync(win, opts)
const savePath = electron.remote.dialog.showSaveDialogSync(win, opts)
if (!savePath) return // They clicked Cancel
console.log('Saving torrent ' + torrentKey + ' to ' + savePath)
const torrentPath = TorrentSummary.getTorrentPath(torrentSummary)
fs.readFile(torrentPath, (err, torrentFile) => {
fs.readFile(torrentPath, function (err, torrentFile) {
if (err) return dispatch('error', err)
fs.writeFile(savePath, torrentFile, err => {
fs.writeFile(savePath, torrentFile, function (err) {
if (err) return dispatch('error', err)
})
})
@@ -348,8 +342,8 @@ function findFilesRecursive (paths, cb_) {
if (paths.length > 1) {
let numComplete = 0
const ret = []
paths.forEach(path => {
findFilesRecursive([path], fileObjs => {
paths.forEach(function (path) {
findFilesRecursive([path], function (fileObjs) {
ret.push(...fileObjs)
if (++numComplete === paths.length) {
ret.sort((a, b) => a.path < b.path ? -1 : Number(a.path > b.path))
@@ -361,7 +355,7 @@ function findFilesRecursive (paths, cb_) {
}
const fileOrFolder = paths[0]
fs.stat(fileOrFolder, (err, stat) => {
fs.stat(fileOrFolder, function (err, stat) {
if (err) return dispatch('error', err)
// Files: return name, path, and size
@@ -376,7 +370,7 @@ function findFilesRecursive (paths, cb_) {
// Folders: recurse, make a list of all the files
const folderPath = fileOrFolder
fs.readdir(folderPath, (err, fileNames) => {
fs.readdir(folderPath, function (err, fileNames) {
if (err) return dispatch('error', err)
const paths = fileNames.map((fileName) => path.join(folderPath, fileName))
findFilesRecursive(paths, cb_)
@@ -386,7 +380,7 @@ function findFilesRecursive (paths, cb_) {
function deleteFile (path) {
if (!path) return
fs.unlink(path, err => {
fs.unlink(path, function (err) {
if (err) dispatch('error', err)
})
}

View File

@@ -54,17 +54,17 @@ function init (appState, callback) {
state.devices.airplay = airplayPlayer()
// Listen for devices: Chromecast, DLNA and Airplay
chromecasts.on('update', device => {
chromecasts.on('update', function (device) {
// TODO: how do we tell if there are *no longer* any Chromecasts available?
// From looking at the code, chromecasts.players only grows, never shrinks
state.devices.chromecast.addDevice(device)
})
dlnacasts.on('update', device => {
dlnacasts.on('update', function (device) {
state.devices.dlna.addDevice(device)
})
airplayer.on('update', device => {
airplayer.on('update', function (device) {
state.devices.airplay.addDevice(device)
})
}
@@ -116,7 +116,7 @@ function chromecastPlayer () {
}
function addDevice (device) {
device.on('error', err => {
device.on('error', function (err) {
if (device !== ret.device) return
state.playing.location = 'local'
state.errors.push({
@@ -125,7 +125,7 @@ function chromecastPlayer () {
})
update()
})
device.on('disconnect', () => {
device.on('disconnect', function () {
if (device !== ret.device) return
state.playing.location = 'local'
update()
@@ -144,8 +144,8 @@ function chromecastPlayer () {
'Access-Control-Allow-Origin': '*',
'Transfer-Encoding': 'chunked'
})
res.end(Buffer.from(selectedSubtitle.buffer.slice(21), 'base64'))
}).listen(0, () => {
res.end(Buffer.from(selectedSubtitle.buffer.substr(21), 'base64'))
}).listen(0, function () {
const port = ret.subServer.address().port
const subtitlesUrl = 'http://' + state.server.networkAddress + ':' + port + '/'
callback(subtitlesUrl)
@@ -155,13 +155,13 @@ function chromecastPlayer () {
function open () {
const torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
serveSubtitles(subtitlesUrl => {
serveSubtitles(function (subtitlesUrl) {
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, {
type: 'video/mp4',
title: config.APP_NAME + ' - ' + torrentSummary.name,
subtitles: subtitlesUrl ? [subtitlesUrl] : [],
autoSubtitles: !!subtitlesUrl
}, err => {
}, function (err) {
if (err) {
state.playing.location = 'local'
state.errors.push({
@@ -221,7 +221,7 @@ function airplayPlayer () {
return ret
function addDevice (player) {
player.on('event', event => {
player.on('event', function (event) {
switch (event.state) {
case 'loading':
break
@@ -243,7 +243,7 @@ function airplayPlayer () {
}
function open () {
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, (err, res) => {
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, function (err, res) {
if (err) {
state.playing.location = 'local'
state.errors.push({
@@ -270,7 +270,7 @@ function airplayPlayer () {
}
function status () {
ret.device.playbackInfo((err, res, status) => {
ret.device.playbackInfo(function (err, res, status) {
if (err) {
state.playing.location = 'local'
state.errors.push({
@@ -317,7 +317,7 @@ function dlnaPlayer (player) {
}
function addDevice (device) {
device.on('error', err => {
device.on('error', function (err) {
if (device !== ret.device) return
state.playing.location = 'local'
state.errors.push({
@@ -326,7 +326,7 @@ function dlnaPlayer (player) {
})
update()
})
device.on('disconnect', () => {
device.on('disconnect', function () {
if (device !== ret.device) return
state.playing.location = 'local'
update()
@@ -339,7 +339,7 @@ function dlnaPlayer (player) {
type: 'video/mp4',
title: config.APP_NAME + ' - ' + torrentSummary.name,
seek: state.playing.currentTime > 10 ? state.playing.currentTime : 0
}, err => {
}, function (err) {
if (err) {
state.playing.location = 'local'
state.errors.push({
@@ -374,7 +374,7 @@ function dlnaPlayer (player) {
}
function volume (volume, callback) {
ret.device.volume(volume, err => {
ret.device.volume(volume, function (err) {
// quick volume update
state.playing.volume = volume
callback(err)
@@ -396,7 +396,7 @@ function handleStatus (err, status) {
// Start polling cast device state, whenever we're connected
function startStatusInterval () {
statusInterval = setInterval(() => {
statusInterval = setInterval(function () {
const player = getPlayer()
if (player) player.status()
}, 1000)
@@ -454,7 +454,7 @@ function selectDevice (index) {
function stop () {
const player = getPlayer()
if (player) {
player.stop(() => {
player.stop(function () {
player.device = null
stoppedCasting()
})
@@ -522,6 +522,6 @@ function setVolume (volume) {
if (player) player.volume(volume, castCallback)
}
function castCallback (...args) {
console.log('%s callback: %o', state.playing.location, args)
function castCallback () {
console.log('%s callback: %o', state.playing.location, arguments)
}

View File

@@ -5,7 +5,7 @@ module.exports = {
}
const dispatchers = {}
let _dispatch = () => {}
let _dispatch = function () {}
function setDispatch (dispatch) {
_dispatch = dispatch
@@ -23,7 +23,7 @@ function dispatcher (...args) {
const str = JSON.stringify(args)
let handler = dispatchers[str]
if (!handler) {
handler = dispatchers[str] = e => {
handler = dispatchers[str] = function (e) {
// Do not propagate click to elements below the button
e.stopPropagation()

View File

@@ -5,7 +5,7 @@ const mediaExtensions = {
'.ogg', '.opus', '.spx', '.wma', '.wav', '.wv', '.wvp'],
video: [
'.avi', '.mp4', '.m4v', '.webm', '.mov', '.mkv', '.mpg', '.mpeg',
'.ogv', '.webm', '.wmv', '.m2ts'],
'.ogv', '.webm', '.wmv'],
image: ['.gif', '.jpg', '.jpeg', '.png']
}

View File

@@ -31,28 +31,15 @@ function run (state) {
if (semver.lt(version, '0.21.0')) migrate_0_21_0(saved)
if (semver.lt(version, '0.22.0')) migrate_0_22_0(saved)
if (semver.lt(version, config.APP_VERSION)) {
installHandlers(state.saved)
}
// Config is now on the new version
state.saved.version = config.APP_VERSION
}
// Whenever the app is updated, re-install default handlers if the user has
// enabled them.
function installHandlers (saved) {
if (saved.prefs.isFileHandler) {
const ipcRenderer = require('electron').ipcRenderer
ipcRenderer.send('setDefaultFileHandler', true)
}
}
function migrate_0_7_0 (saved) {
const { copyFileSync } = require('fs')
const path = require('path')
saved.torrents.forEach(ts => {
saved.torrents.forEach(function (ts) {
const infoHash = ts.infoHash
// Replace torrentPath with torrentFileName
@@ -134,7 +121,7 @@ function migrate_0_12_0 (saved) {
'02767050e0be2fd4db9a2ad6c12416ac806ed6ed.torrent',
'3ba219a8634bf7bae3d848192b2da75ae995589d.torrent'
]
saved.torrents.forEach(torrentSummary => {
saved.torrents.forEach(function (torrentSummary) {
if (!defaultTorrentFiles.includes(torrentSummary.torrentFileName)) return
const fileOrFolder = TorrentSummary.getFileOrFolder(torrentSummary)
if (!fileOrFolder) return
@@ -148,16 +135,16 @@ function migrate_0_12_0 (saved) {
}
function migrate_0_14_0 (saved) {
saved.torrents.forEach(ts => {
saved.torrents.forEach(function (ts) {
delete ts.defaultPlayFileIndex
})
}
function migrate_0_17_0 (saved) {
// Fix a sad, sad bug that resulted in 100MB+ config.json files
saved.torrents.forEach(ts => {
saved.torrents.forEach(function (ts) {
if (!ts.files) return
ts.files.forEach(file => {
ts.files.forEach(function (file) {
if (!file.audioInfo || !file.audioInfo.picture) return
// This contained a Buffer, which 30x'd in size when serialized to JSON
delete file.audioInfo.picture
@@ -179,7 +166,9 @@ function migrate_0_17_2 (saved) {
const OLD_HASH = '3ba219a8634bf7bae3d848192b2da75ae995589d'
const NEW_HASH = 'a88fda5954e89178c372716a6a78b8180ed4dad3'
const ts = saved.torrents.find(ts => ts.infoHash === OLD_HASH)
const ts = saved.torrents.find((ts) => {
return ts.infoHash === OLD_HASH
})
if (!ts) return // Wired CD torrent does not exist

View File

@@ -3,7 +3,6 @@ const path = require('path')
const { EventEmitter } = require('events')
const config = require('../../config')
const defaultAnnounceList = require('create-torrent').announceList.map((arr) => arr[0])
const SAVE_DEBOUNCE_INTERVAL = 1000
@@ -13,13 +12,13 @@ const State = module.exports = Object.assign(new EventEmitter(), {
getDefaultPlayState,
load,
// state.save() calls are rate-limited. Use state.saveImmediate() to skip limit.
save (...args) {
save: function () {
// Perf optimization: Lazy-require debounce (and it's dependencies)
const debounce = require('debounce')
// After first State.save() invokation, future calls go straight to the
// debounced function
State.save = debounce(saveImmediate, SAVE_DEBOUNCE_INTERVAL)
State.save(...args)
State.save(...arguments)
},
saveImmediate
})
@@ -80,7 +79,6 @@ function getDefaultState () {
getPlayingTorrentSummary,
getPlayingFileSummary,
getExternalPlayerName,
getGlobalTrackers,
shouldHidePlayerControls
}
}
@@ -131,8 +129,7 @@ function setupStateSaved () {
soundNotifications: true,
autoAddTorrents: false,
torrentsFolderPath: '',
highestPlaybackPriority: true,
globalTrackers: defaultAnnounceList
highestPlaybackPriority: true
},
torrents: config.DEFAULT_TORRENTS.map(createTorrentObject),
torrentsToResume: [],
@@ -201,15 +198,8 @@ function shouldHidePlayerControls () {
new Date().getTime() - this.playing.mouseStationarySince > 2000 &&
!this.playing.mouseInControls &&
!this.playing.isPaused &&
this.playing.location === 'local'
}
function getGlobalTrackers () {
const trackers = this.saved.prefs.globalTrackers
if (!trackers) {
return defaultAnnounceList
}
return trackers
this.playing.location === 'local' &&
this.playing.playbackRate === 1
}
async function load (cb) {
@@ -252,7 +242,7 @@ async function saveImmediate (state, cb) {
// reading the torrent file or file(s) to seed & don't have an infohash
copy.torrents = copy.torrents
.filter((x) => x.infoHash)
.map(x => {
.map(function (x) {
const torrent = {}
for (const key in x) {
if (key === 'progress' || key === 'torrentKey') {

View File

@@ -7,7 +7,7 @@ module.exports = {
logPlayAttempt
}
const remote = require('@electron/remote')
const electron = require('electron')
const config = require('../../config')
@@ -50,7 +50,7 @@ function send (state) {
json: true
}
get.post(opts, (err, res) => {
get.post(opts, function (err, res) {
if (err) return console.error('Error sending telemetry', err)
if (res.statusCode !== 200) {
return console.error(`Error sending telemetry, status code: ${res.statusCode}`)
@@ -74,7 +74,7 @@ function reset () {
// Track screen resolution
function getScreenInfo () {
return remote.screen.getAllDisplays().map((screen) => ({
return electron.remote.screen.getAllDisplays().map((screen) => ({
width: screen.size.width,
height: screen.size.height,
scaleFactor: screen.scaleFactor
@@ -106,14 +106,15 @@ function getTorrentStats (state) {
}
// First, count torrents & total file size
for (const torrent of state.saved.torrents) {
const stat = byStatus[torrent.status]
if (!torrent || !torrent.files || !stat) continue
for (let i = 0; i < count; i++) {
const t = state.saved.torrents[i]
const stat = byStatus[t.status]
if (!t || !t.files || !stat) continue
stat.count++
for (const file of torrent.files) {
if (!file || !file.length) continue
const fileSizeMB = file.length / (1 << 20)
for (let j = 0; j < t.files.length; j++) {
const f = t.files[j]
if (!f || !f.length) continue
const fileSizeMB = f.length / (1 << 20)
sizeMB += fileSizeMB
stat.sizeMB += fileSizeMB
}
@@ -144,7 +145,7 @@ function roundPow2 (n) {
if (n <= 0) return 0
// Otherwise, return 1, 2, 4, 8, etc by rounding in log space
const log2 = Math.log(n) / Math.log(2)
return 2 ** Math.round(log2)
return Math.pow(2, Math.round(log2))
}
// An uncaught error happened in the main process or in one of the windows

View File

@@ -1,20 +0,0 @@
module.exports = {
calculateEta
}
function calculateEta (missing, downloadSpeed) {
if (downloadSpeed === 0 || missing === 0) return
const rawEta = missing / downloadSpeed
const hours = Math.floor(rawEta / 3600) % 24
const minutes = Math.floor(rawEta / 60) % 60
const seconds = Math.floor(rawEta % 60)
// Only display hours and minutes if they are greater than 0 but always
// display minutes if hours is being displayed
const hoursStr = hours ? hours + ' h' : ''
const minutesStr = (hours || minutes) ? minutes + ' min' : ''
const secondsStr = seconds + ' s'
return `${hoursStr} ${minutesStr} ${secondsStr} remaining`
}

View File

@@ -9,14 +9,20 @@ const msgNoSuitablePoster = 'Cannot generate a poster from any files in the torr
function torrentPoster (torrent, cb) {
// First, try to use a poster image if available
const posterFile = torrent.files.filter(file => /^poster\.(jpg|png|gif)$/.test(file.name))[0]
const posterFile = torrent.files.filter(function (file) {
return /^poster\.(jpg|png|gif)$/.test(file.name)
})[0]
if (posterFile) return extractPoster(posterFile, cb)
// 'score' each media type based on total size present in torrent
const bestScore = ['audio', 'video', 'image'].map(mediaType => ({
type: mediaType,
size: calculateDataLengthByExtension(torrent, mediaExtensions[mediaType])
})).sort((a, b) => b.size - a.size)[0] // sort descending on size
const bestScore = ['audio', 'video', 'image'].map(mediaType => {
return {
type: mediaType,
size: calculateDataLengthByExtension(torrent, mediaExtensions[mediaType])
}
}).sort((a, b) => { // sort descending on size
return b.size - a.size
})[0]
if (bestScore.size === 0) {
// Admit defeat, no video, audio or image had a significant presence
@@ -45,7 +51,9 @@ function calculateDataLengthByExtension (torrent, extensions) {
if (files.length === 0) return 0
return files
.map(file => file.length)
.reduce((a, b) => a + b)
.reduce((a, b) => {
return a + b
})
}
/**
@@ -57,7 +65,9 @@ function calculateDataLengthByExtension (torrent, extensions) {
function getLargestFileByExtension (torrent, extensions) {
const files = filterOnExtension(torrent, extensions)
if (files.length === 0) return undefined
return files.reduce((a, b) => a.length > b.length ? a : b)
return files.reduce((a, b) => {
return a.length > b.length ? a : b
})
}
/**
@@ -105,10 +115,12 @@ function torrentPosterFromAudio (torrent, cb) {
if (imageFiles.length === 0) return cb(new Error(msgNoSuitablePoster))
const bestCover = imageFiles.map(file => ({
file,
score: scoreAudioCoverFile(file)
})).reduce((a, b) => {
const bestCover = imageFiles.map(file => {
return {
file,
score: scoreAudioCoverFile(file)
}
}).reduce((a, b) => {
if (a.score > b.score) {
return a
}
@@ -154,8 +166,7 @@ function torrentPosterFromVideo (torrent, cb) {
function onSeeked () {
video.removeEventListener('seeked', onSeeked)
const frame = captureFrame(video)
const buf = frame && frame.image
const buf = captureFrame(video)
// unload video element
video.pause()
@@ -178,5 +189,5 @@ function torrentPosterFromImage (torrent, cb) {
function extractPoster (file, cb) {
const extname = path.extname(file.name)
file.getBuffer((err, buf) => cb(err, buf, extname))
file.getBuffer((err, buf) => { return cb(err, buf, extname) })
}

View File

@@ -14,6 +14,9 @@ Module.prototype.require = function (id) {
console.time('init')
const crashReporter = require('../crash-reporter')
crashReporter.init()
// Perf optimization: Start asynchronously read on config file before all the
// blocking require() calls below.
@@ -104,7 +107,9 @@ function onState (err, _state) {
const TorrentController = require('./controllers/torrent-controller')
return new TorrentController(state)
}),
torrentList: createGetter(() => new TorrentListController(state)),
torrentList: createGetter(() => {
return new TorrentListController(state)
}),
update: createGetter(() => {
const UpdateController = require('./controllers/update-controller')
return new UpdateController(state)
@@ -124,17 +129,11 @@ function onState (err, _state) {
}
})
// Give global trackers
setGlobalTrackers()
// Restart everything we were torrenting last time the app ran
resumeTorrents()
// Initialize ReactDOM
ReactDOM.render(
<App state={state} ref={elem => { app = elem }} />,
document.querySelector('#body')
)
app = ReactDOM.render(<App state={state} />, document.querySelector('#body'))
// Calling update() updates the UI given the current state
// Do this at least once a second to give every file in every torrentSummary
@@ -156,11 +155,11 @@ function onState (err, _state) {
// Add YouTube style hotkey shortcuts
window.addEventListener('keydown', onKeydown)
const debouncedFullscreenToggle = debounce(() => {
const debouncedFullscreenToggle = debounce(function () {
dispatch('toggleFullScreen')
}, 1000, true)
document.addEventListener('wheel', event => {
document.addEventListener('wheel', function (event) {
// ctrlKey detects pinch to zoom, http://crbug.com/289887
if (event.ctrlKey) {
event.preventDefault()
@@ -256,7 +255,6 @@ const dispatchHandlers = {
controllers.torrentList().confirmDeleteTorrent(infoHash, deleteData),
deleteTorrent: (infoHash, deleteData) =>
controllers.torrentList().deleteTorrent(infoHash, deleteData),
openTorrentListContextMenu: () => onPaste(),
confirmDeleteAllTorrents: (deleteData) =>
controllers.torrentList().confirmDeleteAllTorrents(deleteData),
deleteAllTorrents: (deleteData) =>
@@ -279,8 +277,6 @@ const dispatchHandlers = {
previousTrack: () => controllers.playback().previousTrack(),
skip: (time) => controllers.playback().skip(time),
skipTo: (time) => controllers.playback().skipTo(time),
preview: (x) => controllers.playback().preview(x),
clearPreview: () => controllers.playback().clearPreview(),
changePlaybackRate: (dir) => controllers.playback().changePlaybackRate(dir),
changeVolume: (delta) => controllers.playback().changeVolume(delta),
setVolume: (vol) => controllers.playback().setVolume(vol),
@@ -317,7 +313,6 @@ const dispatchHandlers = {
preferences: () => controllers.prefs().show(),
updatePreferences: (key, value) => controllers.prefs().update(key, value),
checkDownloadPath,
updateGlobalTrackers: (trackers) => setGlobalTrackers(trackers),
startFolderWatcher: () => controllers.folderWatcher().start(),
stopFolderWatcher: () => controllers.folderWatcher().stop(),
@@ -402,7 +397,7 @@ function setupIpc () {
function backToList () {
// Exit any modals and screens with a back button
state.modal = null
state.location.backToFirst(() => {
state.location.backToFirst(function () {
// If we were already on the torrent list, scroll to the top
const contentTag = document.querySelector('.content')
if (contentTag) contentTag.scrollTop = 0
@@ -420,10 +415,6 @@ function escapeBack () {
}
}
function setGlobalTrackers () {
controllers.torrentList().setGlobalTrackers(state.getGlobalTrackers())
}
// Starts all torrents that aren't paused on program startup
function resumeTorrents () {
state.saved.torrents
@@ -526,16 +517,13 @@ function onError (err) {
const editableHtmlTags = new Set(['input', 'textarea'])
function onPaste (e) {
if (e && editableHtmlTags.has(e.target.tagName.toLowerCase())) return
if (editableHtmlTags.has(e.target.tagName.toLowerCase())) return
controllers.torrentList().addTorrent(electron.clipboard.readText())
update()
}
function onKeydown (e) {
// prevent event fire on user input elements
if (editableHtmlTags.has(e.target.tagName.toLowerCase())) return
const key = e.key
if (key === 'ArrowLeft') {
@@ -596,7 +584,7 @@ function onWindowBoundsChanged (e, newBounds) {
}
function checkDownloadPath () {
fs.stat(state.saved.prefs.downloadPath, (err, stat) => {
fs.stat(state.saved.prefs.downloadPath, function (err, stat) {
if (err) {
state.downloadPathStatus = 'missing'
return console.error(err)

View File

@@ -1,9 +1,10 @@
const colors = require('material-ui/styles/colors')
const createGetter = require('fn-getter')
const React = require('react')
const { ThemeProvider, createTheme } = require('@material-ui/core/styles')
const grey = require('@material-ui/core/colors/grey').default
const red = require('@material-ui/core/colors/red').default
const darkBaseTheme = require('material-ui/styles/baseThemes/darkBaseTheme').default
const getMuiTheme = require('material-ui/styles/getMuiTheme').default
const MuiThemeProvider = require('material-ui/styles/MuiThemeProvider').default
const Header = require('../components/header')
@@ -32,8 +33,17 @@ const fontFamily = process.platform === 'win32'
? '"Segoe UI", sans-serif'
: 'BlinkMacSystemFont, "Helvetica Neue", Helvetica, sans-serif'
let darkTheme
let lightTheme
darkBaseTheme.fontFamily = fontFamily
darkBaseTheme.userAgent = false
darkBaseTheme.palette.primary1Color = colors.grey50
darkBaseTheme.palette.primary2Color = colors.grey50
darkBaseTheme.palette.primary3Color = colors.grey600
darkBaseTheme.palette.accent1Color = colors.redA200
darkBaseTheme.palette.accent2Color = colors.redA400
darkBaseTheme.palette.accent3Color = colors.redA100
let darkMuiTheme
let lightMuiTheme
class App extends React.Component {
render () {
@@ -54,66 +64,19 @@ class App extends React.Component {
if (state.window.isFocused) cls.push('is-focused')
if (hideControls) cls.push('hide-video-controls')
if (!darkTheme) {
darkTheme = createTheme({
overrides: {
MuiButton: {
contained: {
backgroundColor: '#303030',
color: 'white',
'&:hover': {
backgroundColor: '#414141',
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: '#303030',
color: 'white'
}
}
}
},
MuiCheckbox: {
root: {
padding: '0px',
marginRight: '16px'
}
},
MuiFormControlLabel: {
label: {
color: 'white',
fontSize: '0.875rem'
},
root: {
marginLeft: '0px',
marginRight: '0px'
}
}
},
palette: {
primary: {
main: grey['50']
},
secondary: {
main: grey['50']
},
type: 'dark'
},
typography: {
fontFamily
}
}, {
userAgent: false
})
if (!darkMuiTheme) {
darkMuiTheme = getMuiTheme(darkBaseTheme)
}
return (
<ThemeProvider theme={darkTheme}>
<MuiThemeProvider muiTheme={darkMuiTheme}>
<div className={'app ' + cls.join(' ')}>
<Header state={state} />
{this.getErrorPopover()}
<div key='content' className='content'>{this.getView()}</div>
{this.getModal()}
</div>
</ThemeProvider>
</MuiThemeProvider>
)
}
@@ -123,7 +86,9 @@ class App extends React.Component {
const recentErrors = state.errors.filter((x) => now - x.time < 5000)
const hasErrors = recentErrors.length > 0
const errorElems = recentErrors.map((error, i) => <div key={i} className='error'>{error.message}</div>)
const errorElems = recentErrors.map(function (error, i) {
return (<div key={i} className='error'>{error.message}</div>)
})
return (
<div
key='errors'
@@ -139,32 +104,23 @@ class App extends React.Component {
const state = this.props.state
if (!state.modal) return
if (!lightTheme) {
lightTheme = createTheme({
palette: {
secondary: {
main: red.A200
},
type: 'light'
},
typography: {
fontFamily
}
}, {
userAgent: false
})
if (!lightMuiTheme) {
const lightBaseTheme = require('material-ui/styles/baseThemes/lightBaseTheme').default
lightBaseTheme.fontFamily = fontFamily
lightBaseTheme.userAgent = false
lightMuiTheme = getMuiTheme(lightBaseTheme)
}
const ModalContents = Modals[state.modal.id]()
return (
<ThemeProvider theme={lightTheme}>
<MuiThemeProvider muiTheme={lightMuiTheme}>
<div key='modal' className='modal'>
<div key='modal-background' className='modal-background' />
<div key='modal-content' className='modal-content'>
<ModalContents state={state} />
</div>
</div>
</ThemeProvider>
</MuiThemeProvider>
)
}

View File

@@ -3,16 +3,17 @@ const path = require('path')
const prettyBytes = require('prettier-bytes')
const React = require('react')
const Button = require('@material-ui/core/Button').default
const TextField = require('@material-ui/core/TextField').default
const Checkbox = require('@material-ui/core/Checkbox').default
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')
const { dispatch, dispatcher } = require('../lib/dispatcher')
// 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 {
@@ -98,23 +99,20 @@ class CreateTorrentPage extends React.Component {
{this.renderAdvanced()}
</ShowMore>
<div className='float-right'>
<Button
<FlatButton
className='control cancel'
onClick={dispatcher('cancel')}
label='Cancel'
style={{
marginRight: 10
}}
>
Cancel
</Button>
<Button
onClick={dispatcher('cancel')}
/>
<RaisedButton
className='control create-torrent-button'
label='Create Torrent'
primary
onClick={this.handleSubmit}
color='primary'
variant='contained'
>
Create Torrent
</Button>
/>
</div>
</div>
)
@@ -136,6 +134,10 @@ class CreateTorrentPage extends React.Component {
fileElems.push(<div key='more'>+ {files.length - maxFileElems} more</div>)
}
// Align the text fields
const textFieldStyle = { width: '' }
const textareaStyle = { margin: 0 }
return (
<div key='advanced' className='create-torrent-advanced'>
<div key='private' className='torrent-attribute'>
@@ -144,32 +146,34 @@ class CreateTorrentPage extends React.Component {
className='torrent-is-private control'
style={{ display: '' }}
checked={this.state.isPrivate}
onChange={this.handleSetIsPrivate}
onCheck={this.handleSetIsPrivate}
/>
</div>
<div key='trackers' className='torrent-attribute'>
<label>Trackers:</label>
<TextField
className='torrent-trackers control'
fullWidth
maxRows={10}
minRows={2}
multiline
onChange={this.handleSetTrackers}
style={textFieldStyle}
textareaStyle={textareaStyle}
multiLine
rows={2}
rowsMax={10}
value={this.state.trackers}
onChange={this.handleSetTrackers}
/>
</div>
<div key='comment' className='torrent-attribute'>
<label>Comment:</label>
<TextField
className='torrent-comment control'
fullWidth
maxRows={10}
minRows={2}
multiline
onChange={this.handleSetComment}
placeholder='Optionally describe your torrent...'
style={textFieldStyle}
textareaStyle={textareaStyle}
hintText='Optionally describe your torrent...'
multiLine
rows={2}
rowsMax={10}
value={this.state.comment}
onChange={this.handleSetComment}
/>
</div>
<div key='files' className='torrent-attribute'>

View File

@@ -1,14 +1,11 @@
/* globals MediaMetadata */
const React = require('react')
const BitField = require('bitfield').default
const Bitfield = require('bitfield')
const prettyBytes = require('prettier-bytes')
const TorrentSummary = require('../lib/torrent-summary')
const Playlist = require('../lib/playlist')
const { dispatch, dispatcher } = require('../lib/dispatcher')
const config = require('../../config')
const { calculateEta } = require('../lib/time')
// Shows a streaming video player. Standard features + Chromecast + Airplay
module.exports = class Player extends React.Component {
@@ -37,7 +34,6 @@ module.exports = class Player extends React.Component {
tag.pause()
tag.src = ''
tag.load()
navigator.mediaSession.metadata = null
}
}
@@ -54,28 +50,6 @@ function renderMedia (state) {
// Get the <video> or <audio> tag
const mediaElement = document.querySelector(state.playing.type)
if (mediaElement !== null) {
if (navigator.mediaSession.metadata === null && mediaElement.played.length !== 0) {
navigator.mediaSession.metadata = new MediaMetadata({
title: state.playing.fileName
})
navigator.mediaSession.setActionHandler('pause', () => {
dispatch('playPause')
})
navigator.mediaSession.setActionHandler('play', () => {
dispatch('playPause')
})
if (Playlist.hasNext(state)) {
navigator.mediaSession.setActionHandler('nexttrack', () => {
dispatch('nextTrack')
})
}
if (Playlist.hasPrevious(state)) {
navigator.mediaSession.setActionHandler('previoustrack', () => {
dispatch('previousTrack')
})
}
}
if (state.playing.isPaused && !mediaElement.paused) {
mediaElement.pause()
} else if (!state.playing.isPaused && mediaElement.paused) {
@@ -134,7 +108,8 @@ function renderMedia (state) {
// Add subtitles to the <video> tag
const trackTags = []
if (state.playing.subtitles.selectedIndex >= 0) {
state.playing.subtitles.tracks.forEach((track, i) => {
for (let i = 0; i < state.playing.subtitles.tracks.length; i++) {
const track = state.playing.subtitles.tracks[i]
const isSelected = state.playing.subtitles.selectedIndex === i
trackTags.push(
<track
@@ -145,7 +120,7 @@ function renderMedia (state) {
src={track.buffer}
/>
)
})
}
}
// Create the <audio> or <video> tag
@@ -154,7 +129,6 @@ function renderMedia (state) {
<MediaTagName
src={Playlist.getCurrentLocalURL(state)}
onDoubleClick={dispatcher('toggleFullScreen')}
onClick={dispatcher('playPause')}
onLoadedMetadata={onLoadedMetadata}
onEnded={onEnded}
onStalled={dispatcher('mediaStalled')}
@@ -224,7 +198,7 @@ function renderMedia (state) {
}
}
function onEnded () {
function onEnded (e) {
if (Playlist.hasNext(state)) {
dispatch('nextTrack')
} else {
@@ -366,13 +340,13 @@ function renderAudioMetadata (state) {
format.push(fileSummary.audioInfo.format.codec)
}
if (fileSummary.audioInfo.format.bitrate) {
format.push(Math.round(fileSummary.audioInfo.format.bitrate / 1000) + ' kbit/s') // 128 kbit/s
format.push(Math.round(fileSummary.audioInfo.format.bitrate / 1000) + ' kbps') // 128 kbps
}
if (fileSummary.audioInfo.format.sampleRate) {
format.push(Math.round(fileSummary.audioInfo.format.sampleRate / 100) / 10 + ' kHz')
}
if (fileSummary.audioInfo.format.bitsPerSample) {
format.push(fileSummary.audioInfo.format.bitsPerSample + '-bit')
format.push(fileSummary.audioInfo.format.bitsPerSample + ' bit')
}
if (format.length > 0) {
elems.push((
@@ -453,70 +427,22 @@ function renderCastScreen (state) {
const isStarting = state.playing.location.endsWith('-pending')
const castName = state.playing.castName
const fileName = state.getPlayingFileSummary().name || ''
let castStatus
if (isCast && isStarting) castStatus = 'Connecting to ' + castName + '...'
else if (isCast && !isStarting) castStatus = 'Connected to ' + castName
else castStatus = ''
const prog = state.getPlayingTorrentSummary().progress || {}
// Show a nice title image, if possible
const style = {
backgroundImage: cssBackgroundImagePoster(state)
}
function renderEta (total, downloaded) {
const missing = (total || 0) - (downloaded || 0)
const downloadSpeed = prog.downloadSpeed || 0
if (downloadSpeed === 0 || missing === 0) return
const etaStr = calculateEta(missing, downloadSpeed)
return (<span>{etaStr}</span>)
}
function renderDownloadProgress () {
if (!prog.files) return
const fileProg = prog.files[state.playing.fileIndex]
const fileProgress = fileProg.numPiecesPresent / fileProg.numPieces
const fileLength = state.getPlayingFileSummary().length
const fileDownloaded = fileProgress * fileLength
const progress = Math.round(100 * fileProgress)
const total = prettyBytes(fileLength)
const completed = prettyBytes(fileDownloaded)
const downloadSpeed = prettyBytes(prog.downloadSpeed || 0)
const uploadSpeed = prettyBytes(prog.uploadSpeed || 0)
let sizes
if (fileProgress < 1) {
sizes = <span> | {completed} / {total}</span>
} else {
sizes = <span> | {completed}</span>
}
return (
<div key='download-progress'>
<span className='progress'>{progress}% downloaded {sizes}</span>
<br />
<span>↓ {downloadSpeed}/s ↑ {uploadSpeed}/s | {prog.numPeers || 0} peer(s)</span>
<br />
{renderEta(fileLength, fileDownloaded)}
</div>
)
}
return (
<div key='cast' className='letterbox' style={style}>
<div className='cast-screen'>
<i className='icon'>{castIcon}</i>
<div key='type' className='cast-type'>{castType}</div>
<div key='status' className='cast-status'>{castStatus}</div>
<div key='name' className='name'>{fileName}</div>
{renderDownloadProgress()}
</div>
</div>
)
@@ -528,7 +454,7 @@ function renderCastOptions (state) {
const { location, devices } = state.devices.castMenu
const player = state.devices[location]
const items = devices.map((device, ix) => {
const items = devices.map(function (device, ix) {
const isSelected = player.device === device
const name = device.name
return (
@@ -551,7 +477,7 @@ function renderSubtitleOptions (state) {
const subtitles = state.playing.subtitles
if (!subtitles.tracks.length || !subtitles.showMenu) return
const items = subtitles.tracks.map((track, ix) => {
const items = subtitles.tracks.map(function (track, ix) {
const isSelected = state.playing.subtitles.selectedIndex === ix
return (
<li key={ix} onClick={dispatcher('selectSubtitle', ix)}>
@@ -578,7 +504,7 @@ function renderAudioTrackOptions (state) {
const audioTracks = state.playing.audioTracks
if (!audioTracks.tracks.length || !audioTracks.showMenu) return
const items = audioTracks.tracks.map((track, ix) => {
const items = audioTracks.tracks.map(function (track, ix) {
const isSelected = state.playing.audioTracks.selectedIndex === ix
return (
<li key={ix} onClick={dispatcher('selectAudioTrack', ix)}>
@@ -610,8 +536,6 @@ function renderPlayerControls (state) {
const nextClass = Playlist.hasNext(state) ? '' : 'disabled'
const elements = [
renderPreview(state),
<div key='playback-bar' className='playback-bar'>
{renderLoadingBar(state)}
<div
@@ -622,9 +546,7 @@ function renderPlayerControls (state) {
<div
key='scrub-bar'
className='scrub-bar'
draggable='true'
onMouseMove={handleScrubPreview}
onMouseOut={clearPreview}
draggable
onDragStart={handleDragStart}
onClick={handleScrub}
onDrag={handleScrub}
@@ -635,8 +557,6 @@ function renderPlayerControls (state) {
key='skip-previous'
className={'icon skip-previous float-left ' + prevClass}
onClick={dispatcher('previousTrack')}
role='button'
aria-label='Previous track'
>
skip_previous
</i>,
@@ -645,8 +565,6 @@ function renderPlayerControls (state) {
key='play'
className='icon play-pause float-left'
onClick={dispatcher('playPause')}
role='button'
aria-label={state.playing.isPaused ? 'Play' : 'Pause'}
>
{state.playing.isPaused ? 'play_arrow' : 'pause'}
</i>,
@@ -655,8 +573,6 @@ function renderPlayerControls (state) {
key='skip-next'
className={'icon skip-next float-left ' + nextClass}
onClick={dispatcher('nextTrack')}
role='button'
aria-label='Next track'
>
skip_next
</i>,
@@ -665,8 +581,6 @@ function renderPlayerControls (state) {
key='fullscreen'
className='icon fullscreen float-right'
onClick={dispatcher('toggleFullScreen')}
role='button'
aria-label={state.window.isFullScreen ? 'Exit full screen' : 'Enter full screen'}
>
{state.window.isFullScreen ? 'fullscreen_exit' : 'fullscreen'}
</i>
@@ -679,8 +593,6 @@ function renderPlayerControls (state) {
key='subtitles'
className={'icon closed-caption float-right ' + captionsClass}
onClick={handleSubtitles}
role='button'
aria-label='Closed captions'
>
closed_caption
</i>
@@ -706,7 +618,7 @@ function renderPlayerControls (state) {
airplay: { true: 'airplay', false: 'airplay' },
dlna: { true: 'tv', false: 'tv' }
}
castTypes.forEach(castType => {
castTypes.forEach(function (castType) {
// Do we show this button (eg. the Chromecast button) at all?
const isCasting = state.playing.location.startsWith(castType)
const player = state.devices[castType]
@@ -743,14 +655,10 @@ function renderPlayerControls (state) {
// Render volume slider
const volume = state.playing.volume
const volumeIcon = 'volume_' + (
volume === 0
? 'off'
: volume < 0.3
? 'mute'
: volume < 0.6
? 'down'
: 'up'
)
volume === 0 ? 'off'
: volume < 0.3 ? 'mute'
: volume < 0.6 ? 'down'
: 'up')
const volumeStyle = {
background: '-webkit-gradient(linear, left top, right top, ' +
'color-stop(' + (volume * 100) + '%, #eee), ' +
@@ -762,8 +670,6 @@ function renderPlayerControls (state) {
<i
className='icon volume-icon float-left'
onMouseDown={handleVolumeMute}
role='button'
aria-label='Mute'
>
{volumeIcon}
</i>
@@ -795,31 +701,27 @@ function renderPlayerControls (state) {
))
}
const emptyImage = new window.Image(0, 0)
emptyImage.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'
return (
<div
key='controls' className='controls'
onMouseEnter={dispatcher('mediaControlsMouseEnter')}
onMouseLeave={dispatcher('mediaControlsMouseLeave')}
>
{elements}
{renderCastOptions(state)}
{renderSubtitleOptions(state)}
{renderAudioTrackOptions(state)}
</div>
)
function handleDragStart (e) {
// Prevent the cursor from changing, eg to a green + icon on Mac
if (e.dataTransfer) {
const dt = e.dataTransfer
// Prevent the cursor from changing, eg to a green + icon on Mac
dt.effectAllowed = 'none'
// Prevent ghost image
dt.setDragImage(emptyImage, 0, 0)
}
}
// Handles a scrub hover (preview another position in the video)
function handleScrubPreview (e) {
// Only show for videos
if (!e.clientX || state.playing.type !== 'video') return
dispatch('mediaMouseMoved')
dispatch('preview', e.clientX)
}
function clearPreview () {
if (state.playing.type !== 'video') return
dispatch('clearPreview')
}
// Handles a click or drag to scrub (jump to another position in the video)
function handleScrub (e) {
if (!e.clientX) return
@@ -831,7 +733,7 @@ function renderPlayerControls (state) {
}
// Handles volume muting and Unmuting
function handleVolumeMute () {
function handleVolumeMute (e) {
if (state.playing.volume === 0.0) {
dispatch('setVolume', 1.0)
} else {
@@ -853,72 +755,9 @@ function renderPlayerControls (state) {
}
}
function handleAudioTracks () {
function handleAudioTracks (e) {
dispatch('toggleAudioTracksMenu')
}
return (
<div
key='controls' className='controls'
onMouseEnter={dispatcher('mediaControlsMouseEnter')}
onMouseLeave={dispatcher('mediaControlsMouseLeave')}
>
{elements}
{renderCastOptions(state)}
{renderSubtitleOptions(state)}
{renderAudioTrackOptions(state)}
</div>
)
}
function renderPreview (state) {
const { previewXCoord = null } = state.playing
// Calculate time from x-coord as fraction of track width
const windowWidth = document.querySelector('body').clientWidth
const fraction = previewXCoord / windowWidth
const time = fraction * state.playing.duration /* seconds */
const height = 70
let width = 0
const previewEl = document.querySelector('video#preview')
if (previewEl !== null && previewXCoord !== null) {
previewEl.currentTime = time
// Auto adjust width to maintain video aspect ratio
width = Math.floor((previewEl.videoWidth / previewEl.videoHeight) * height)
}
// Center preview window on mouse cursor,
// while avoiding falling off the left or right edges
const xPos = Math.min(Math.max(previewXCoord - (width / 2), 5), windowWidth - width - 5)
return (
<div
key='preview' style={{
position: 'absolute',
bottom: 50,
left: xPos,
display: previewXCoord == null && 'none' // Hide preview when XCoord unset
}}
>
<div style={{ width, height, backgroundColor: 'black' }}>
<video
src={Playlist.getCurrentLocalURL(state)}
id='preview'
style={{ border: '1px solid lightgrey', borderRadius: 2 }}
/>
</div>
<p
style={{
textAlign: 'center', margin: 5, textShadow: '0 0 2px rgba(0,0,0,.5)', color: '#eee'
}}
>
{formatTime(time, state.playing.duration)}
</p>
</div>
)
}
// Renders the loading bar. Shows which parts of the torrent are loaded, which
@@ -940,7 +779,7 @@ function renderLoadingBar (state) {
const parts = []
let lastPiecePresent = false
for (let i = fileProg.startPiece; i <= fileProg.endPiece; i++) {
const partPresent = BitField.prototype.get.call(prog.bitfield, i)
const partPresent = Bitfield.prototype.get.call(prog.bitfield, i)
if (partPresent && !lastPiecePresent) {
parts.push({ start: i - fileProg.startPiece, count: 1 })
} else if (partPresent) {
@@ -950,7 +789,7 @@ function renderLoadingBar (state) {
}
// Output some bars to show which parts of the file are loaded
const loadingBarElems = parts.map((part, i) => {
const loadingBarElems = parts.map(function (part, i) {
const style = {
left: (100 * part.start / fileProg.numPieces) + '%',
width: (100 * part.count / fileProg.numPieces) + '%'

View File

@@ -1,12 +1,9 @@
const React = require('react')
const PropTypes = require('prop-types')
const Button = require('@material-ui/core/Button').default
const Checkbox = require('@material-ui/core/Checkbox').default
const FormControlLabel = require('@material-ui/core/FormControlLabel').default
const TextField = require('@material-ui/core/TextField').default
const grey = require('@material-ui/core/colors/grey').default
const colors = require('material-ui/styles/colors')
const Checkbox = require('material-ui/Checkbox').default
const RaisedButton = require('material-ui/RaisedButton').default
const Heading = require('../components/heading')
const PathSelector = require('../components/path-selector')
@@ -31,15 +28,6 @@ class PreferencesPage extends React.Component {
this.handleSoundNotificationsChange =
this.handleSoundNotificationsChange.bind(this)
this.handleSetGlobalTrackers =
this.handleSetGlobalTrackers.bind(this)
const globalTrackers = this.props.state.getGlobalTrackers().join('\n')
this.state = {
globalTrackers
}
}
downloadPathSelector () {
@@ -65,15 +53,11 @@ class PreferencesPage extends React.Component {
openExternalPlayerCheckbox () {
return (
<Preference>
<FormControlLabel
control={
<Checkbox
className='control'
checked={!this.props.state.saved.prefs.openExternalPlayer}
onChange={this.handleOpenExternalPlayerChange}
/>
}
<Checkbox
className='control'
checked={!this.props.state.saved.prefs.openExternalPlayer}
label='Play torrent media files using WebTorrent'
onCheck={this.handleOpenExternalPlayerChange}
/>
</Preference>
)
@@ -86,15 +70,11 @@ class PreferencesPage extends React.Component {
highestPlaybackPriorityCheckbox () {
return (
<Preference>
<FormControlLabel
control={
<Checkbox
className='control'
checked={this.props.state.saved.prefs.highestPlaybackPriority}
onChange={this.handleHighestPlaybackPriorityChange}
/>
}
<Checkbox
className='control'
checked={this.props.state.saved.prefs.highestPlaybackPriority}
label='Highest Playback Priority'
onCheck={this.handleHighestPlaybackPriorityChange}
/>
<p>Pauses all active torrents to allow playback to use all of the available bandwidth.</p>
</Preference>
@@ -136,15 +116,11 @@ class PreferencesPage extends React.Component {
autoAddTorrentsCheckbox () {
return (
<Preference>
<FormControlLabel
control={
<Checkbox
className='control'
checked={this.props.state.saved.prefs.autoAddTorrents}
onChange={(e, value) => { this.handleAutoAddTorrentsChange(e, value) }}
/>
}
<Checkbox
className='control'
checked={this.props.state.saved.prefs.autoAddTorrents}
label='Watch for new .torrent files and add them immediately'
onCheck={(e, value) => { this.handleAutoAddTorrentsChange(e, value) }}
/>
</Preference>
)
@@ -202,13 +178,11 @@ class PreferencesPage extends React.Component {
return (
<Preference>
<p>WebTorrent is not currently the default torrent app.</p>
<Button
<RaisedButton
className='control'
onClick={this.handleSetDefaultApp}
variant='contained'
>
Make WebTorrent the default
</Button>
label='Make WebTorrent the default'
/>
</Preference>
)
}
@@ -224,15 +198,11 @@ class PreferencesPage extends React.Component {
return (
<Preference>
<FormControlLabel
control={
<Checkbox
className='control'
checked={this.props.state.saved.prefs.startup}
onChange={this.handleStartupChange}
/>
}
<Checkbox
className='control'
checked={this.props.state.saved.prefs.startup}
label='Open WebTorrent on startup'
onCheck={this.handleStartupChange}
/>
</Preference>
)
@@ -241,15 +211,11 @@ class PreferencesPage extends React.Component {
soundNotificationsCheckbox () {
return (
<Preference>
<FormControlLabel
control={
<Checkbox
className='control'
checked={this.props.state.saved.prefs.soundNotifications}
onChange={this.handleSoundNotificationsChange}
/>
}
<Checkbox
className='control'
checked={this.props.state.saved.prefs.soundNotifications}
label='Enable sounds'
onCheck={this.handleSoundNotificationsChange}
/>
</Preference>
)
@@ -263,37 +229,9 @@ class PreferencesPage extends React.Component {
dispatch('updatePreferences', 'isFileHandler', true)
}
setGlobalTrackers () {
return (
<Preference>
<TextField
className='torrent-trackers control'
fullWidth
maxRows={10}
minRows={2}
multiline
onChange={this.handleSetGlobalTrackers}
value={this.state.globalTrackers}
/>
</Preference>
)
}
handleSetGlobalTrackers (e, globalTrackers) {
this.setState({ globalTrackers })
const announceList = globalTrackers
.split('\n')
.map((s) => s.trim())
.filter((s) => s !== '')
dispatch('updatePreferences', 'globalTrackers', announceList)
dispatch('updateGlobalTrackers', announceList)
}
render () {
const style = {
color: grey['400'],
color: colors.grey400,
marginLeft: 25,
marginRight: 25
}
@@ -316,9 +254,6 @@ class PreferencesPage extends React.Component {
{this.setStartupCheckbox()}
{this.soundNotificationsCheckbox()}
</PreferencesSection>
<PreferencesSection title='Trackers'>
{this.setGlobalTrackers()}
</PreferencesSection>
</div>
)
}

View File

@@ -1,13 +1,12 @@
const React = require('react')
const prettyBytes = require('prettier-bytes')
const Checkbox = require('@material-ui/core/Checkbox').default
const LinearProgress = require('@material-ui/core/LinearProgress').default
const Checkbox = require('material-ui/Checkbox').default
const LinearProgress = require('material-ui/LinearProgress').default
const TorrentSummary = require('../lib/torrent-summary')
const TorrentPlayer = require('../lib/torrent-player')
const { dispatcher } = require('../lib/dispatcher')
const { calculateEta } = require('../lib/time')
module.exports = class TorrentList extends React.Component {
render () {
@@ -36,11 +35,7 @@ module.exports = class TorrentList extends React.Component {
)
return (
<div
key='torrent-list'
className='torrent-list'
onContextMenu={dispatcher('openTorrentListContextMenu')}
>
<div key='torrent-list' className='torrent-list'>
{contents}
</div>
)
@@ -124,19 +119,19 @@ module.exports = class TorrentList extends React.Component {
const isActive = ['downloading', 'seeding'].includes(torrentSummary.status)
return (
<Checkbox
checked={isActive}
className={'control download ' + torrentSummary.status}
key='download-button'
onChange={dispatcher('toggleTorrent', infoHash)}
onClick={stopPropagation}
className={'control download ' + torrentSummary.status}
style={{
display: 'inline-block',
marginRight: '12px',
'& .MuiSvgIconRoot': {
width: '20px',
height: '20px'
}
width: 32
}}
iconStyle={{
width: 20,
height: 20
}}
checked={isActive}
onClick={stopPropagation}
onCheck={dispatcher('toggleTorrent', infoHash)}
/>
)
}
@@ -155,7 +150,7 @@ module.exports = class TorrentList extends React.Component {
}
return (
<div key='progress-bar' style={styles.wrapper}>
<LinearProgress style={styles.progress} value={progress} variant='determinate' />
<LinearProgress style={styles.progress} mode='determinate' value={progress} />
</div>
)
}
@@ -196,9 +191,18 @@ module.exports = class TorrentList extends React.Component {
const downloadSpeed = prog.downloadSpeed
if (downloadSpeed === 0 || missing === 0) return
const etaStr = calculateEta(missing, downloadSpeed)
const rawEta = missing / downloadSpeed
const hours = Math.floor(rawEta / 3600) % 24
const minutes = Math.floor(rawEta / 60) % 60
const seconds = Math.floor(rawEta % 60)
return (<span key='eta'>{etaStr}</span>)
// Only display hours and minutes if they are greater than 0 but always
// display minutes if hours is being displayed
const hoursStr = hours ? hours + 'h' : ''
const minutesStr = (hours || minutes) ? minutes + 'm' : ''
const secondsStr = seconds + 's'
return (<span key='eta'>{hoursStr} {minutesStr} {secondsStr} remaining</span>)
}
function renderTorrentStatus () {

View File

@@ -4,17 +4,30 @@ console.time('init')
const crypto = require('crypto')
const util = require('util')
const { ipcRenderer } = require('electron')
const defaultAnnounceList = require('create-torrent').announceList
const electron = require('electron')
const fs = require('fs')
const mm = require('music-metadata')
const networkAddress = require('network-address')
const path = require('path')
const WebTorrent = require('webtorrent')
const crashReporter = require('../crash-reporter')
const config = require('../config')
const { TorrentKeyNotFoundError } = require('./lib/errors')
const torrentPoster = require('./lib/torrent-poster')
// Report when the process crashes
crashReporter.init()
// Send & receive messages from the main window
const ipc = electron.ipcRenderer
// Force use of webtorrent trackers on all torrents
global.WEBTORRENT_ANNOUNCE = defaultAnnounceList
.map((arr) => arr[0])
.filter((url) => url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0)
/**
* WebTorrent version.
*/
@@ -59,31 +72,29 @@ init()
function init () {
listenToClientEvents()
ipcRenderer.on('wt-set-global-trackers', (e, globalTrackers) =>
setGlobalTrackers(globalTrackers))
ipcRenderer.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
ipcRenderer.on('wt-stop-torrenting', (e, infoHash) =>
ipc.on('wt-stop-torrenting', (e, infoHash) =>
stopTorrenting(infoHash))
ipcRenderer.on('wt-create-torrent', (e, torrentKey, options) =>
ipc.on('wt-create-torrent', (e, torrentKey, options) =>
createTorrent(torrentKey, options))
ipcRenderer.on('wt-save-torrent-file', (e, torrentKey) =>
ipc.on('wt-save-torrent-file', (e, torrentKey) =>
saveTorrentFile(torrentKey))
ipcRenderer.on('wt-generate-torrent-poster', (e, torrentKey) =>
ipc.on('wt-generate-torrent-poster', (e, torrentKey) =>
generateTorrentPoster(torrentKey))
ipcRenderer.on('wt-get-audio-metadata', (e, infoHash, index) =>
ipc.on('wt-get-audio-metadata', (e, infoHash, index) =>
getAudioMetadata(infoHash, index))
ipcRenderer.on('wt-start-server', (e, infoHash) =>
ipc.on('wt-start-server', (e, infoHash) =>
startServer(infoHash))
ipcRenderer.on('wt-stop-server', () =>
ipc.on('wt-stop-server', (e) =>
stopServer())
ipcRenderer.on('wt-select-files', (e, infoHash, selections) =>
ipc.on('wt-select-files', (e, infoHash, selections) =>
selectFiles(infoHash, selections))
ipcRenderer.send('ipcReadyWebTorrent')
ipc.send('ipcReadyWebTorrent')
window.addEventListener('error', (e) =>
ipcRenderer.send('wt-uncaught-error', { message: e.error.message, stack: e.error.stack }),
ipc.send('wt-uncaught-error', { message: e.error.message, stack: e.error.stack }),
true)
setInterval(updateTorrentProgress, 1000)
@@ -91,13 +102,8 @@ function init () {
}
function listenToClientEvents () {
client.on('warning', (err) => ipcRenderer.send('wt-warning', null, err.message))
client.on('error', (err) => ipcRenderer.send('wt-error', null, err.message))
}
// Sets the default trackers
function setGlobalTrackers (globalTrackers) {
globalThis.WEBTORRENT_ANNOUNCE = globalTrackers
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
client.on('error', (err) => ipc.send('wt-error', null, err.message))
}
// Starts a given TorrentID, which can be an infohash, magnet URI, etc.
@@ -131,44 +137,44 @@ function createTorrent (torrentKey, options) {
const torrent = client.seed(paths, options)
torrent.key = torrentKey
addTorrentEvents(torrent)
ipcRenderer.send('wt-new-torrent')
ipc.send('wt-new-torrent')
}
function addTorrentEvents (torrent) {
torrent.on('warning', (err) =>
ipcRenderer.send('wt-warning', torrent.key, err.message))
ipc.send('wt-warning', torrent.key, err.message))
torrent.on('error', (err) =>
ipcRenderer.send('wt-error', torrent.key, err.message))
ipc.send('wt-error', torrent.key, err.message))
torrent.on('infoHash', () =>
ipcRenderer.send('wt-parsed', torrent.key, torrent.infoHash, torrent.magnetURI))
ipc.send('wt-parsed', torrent.key, torrent.infoHash, torrent.magnetURI))
torrent.on('metadata', torrentMetadata)
torrent.on('ready', torrentReady)
torrent.on('done', torrentDone)
function torrentMetadata () {
const info = getTorrentInfo(torrent)
ipcRenderer.send('wt-metadata', torrent.key, info)
ipc.send('wt-metadata', torrent.key, info)
updateTorrentProgress()
}
function torrentReady () {
const info = getTorrentInfo(torrent)
ipcRenderer.send('wt-ready', torrent.key, info)
ipcRenderer.send('wt-ready-' + torrent.infoHash, torrent.key, info)
ipc.send('wt-ready', torrent.key, info)
ipc.send('wt-ready-' + torrent.infoHash, torrent.key, info)
updateTorrentProgress()
}
function torrentDone () {
const info = getTorrentInfo(torrent)
ipcRenderer.send('wt-done', torrent.key, info)
ipc.send('wt-done', torrent.key, info)
updateTorrentProgress()
torrent.getFileModtimes((err, fileModtimes) => {
torrent.getFileModtimes(function (err, fileModtimes) {
if (err) return onError(err)
ipcRenderer.send('wt-file-modtimes', torrent.key, fileModtimes)
ipc.send('wt-file-modtimes', torrent.key, fileModtimes)
})
}
}
@@ -201,19 +207,19 @@ function saveTorrentFile (torrentKey) {
const torrent = getTorrent(torrentKey)
const torrentPath = path.join(config.TORRENT_PATH, torrent.infoHash + '.torrent')
fs.access(torrentPath, fs.constants.R_OK, err => {
fs.access(torrentPath, fs.constants.R_OK, function (err) {
const fileName = torrent.infoHash + '.torrent'
if (!err) {
// We've already saved the file
return ipcRenderer.send('wt-file-saved', torrentKey, fileName)
return ipc.send('wt-file-saved', torrentKey, fileName)
}
// Otherwise, save the .torrent file, under the app config folder
fs.mkdir(config.TORRENT_PATH, { recursive: true }, _ => {
fs.writeFile(torrentPath, torrent.torrentFile, err => {
fs.mkdir(config.TORRENT_PATH, { recursive: true }, function (_) {
fs.writeFile(torrentPath, torrent.torrentFile, function (err) {
if (err) return console.log('error saving torrent file %s: %o', torrentPath, err)
console.log('saved torrent file %s', torrentPath)
return ipcRenderer.send('wt-file-saved', torrentKey, fileName)
return ipc.send('wt-file-saved', torrentKey, fileName)
})
})
})
@@ -223,17 +229,17 @@ function saveTorrentFile (torrentKey) {
// Auto chooses either a frame from a video file, an image, etc
function generateTorrentPoster (torrentKey) {
const torrent = getTorrent(torrentKey)
torrentPoster(torrent, (err, buf, extension) => {
torrentPoster(torrent, function (err, buf, extension) {
if (err) return console.log('error generating poster: %o', err)
// save it for next time
fs.mkdir(config.POSTER_PATH, { recursive: true }, err => {
fs.mkdir(config.POSTER_PATH, { recursive: true }, function (err) {
if (err) return console.log('error creating poster dir: %o', err)
const posterFileName = torrent.infoHash + extension
const posterFilePath = path.join(config.POSTER_PATH, posterFileName)
fs.writeFile(posterFilePath, buf, err => {
fs.writeFile(posterFilePath, buf, function (err) {
if (err) return console.log('error saving poster: %o', err)
// show the poster
ipcRenderer.send('wt-poster', torrentKey, posterFileName)
ipc.send('wt-poster', torrentKey, posterFileName)
})
})
})
@@ -245,20 +251,22 @@ function updateTorrentProgress () {
if (prevProgress && util.isDeepStrictEqual(progress, prevProgress)) {
return /* don't send heavy object if it hasn't changed */
}
ipcRenderer.send('wt-progress', progress)
ipc.send('wt-progress', progress)
prevProgress = progress
}
function getTorrentProgress () {
// First, track overall progress
const progress = client.progress
const hasActiveTorrents = client.torrents.some(torrent => torrent.progress !== 1)
const hasActiveTorrents = client.torrents.some(function (torrent) {
return torrent.progress !== 1
})
// Track progress for every file in each torrent
// TODO: ideally this would be tracked by WebTorrent, which could do it
// more efficiently than looping over torrent.bitfield
const torrentProg = client.torrents.map(torrent => {
const fileProg = torrent.files && torrent.files.map(file => {
const torrentProg = client.torrents.map(function (torrent) {
const fileProg = torrent.files && torrent.files.map(function (file, index) {
const numPieces = file._endPiece - file._startPiece + 1
let numPiecesPresent = 0
for (let piece = file._startPiece; piece <= file._endPiece; piece++) {
@@ -298,12 +306,12 @@ function startServer (infoHash) {
else torrent.once('ready', () => startServerFromReadyTorrent(torrent))
}
function startServerFromReadyTorrent (torrent) {
function startServerFromReadyTorrent (torrent, cb) {
if (server) return
// start the streaming torrent-to-http server
server = torrent.createServer()
server.listen(0, () => {
server.listen(0, function () {
const port = server.address().port
const urlSuffix = ':' + port
const info = {
@@ -313,8 +321,8 @@ function startServerFromReadyTorrent (torrent) {
networkAddress: networkAddress()
}
ipcRenderer.send('wt-server-running', info)
ipcRenderer.send('wt-server-' + torrent.infoHash, info)
ipc.send('wt-server-running', info)
ipc.send('wt-server-' + torrent.infoHash, info)
})
}
@@ -332,17 +340,14 @@ function getAudioMetadata (infoHash, index) {
// Set initial matadata to display the filename first.
const metadata = { title: file.name }
ipcRenderer.send('wt-audio-metadata', infoHash, index, metadata)
ipc.send('wt-audio-metadata', infoHash, index, metadata)
const options = {
native: false,
skipCovers: true,
fileSize: file.length,
observer: () => {
ipcRenderer.send('wt-audio-metadata', infoHash, index, {
common: metadata.common,
format: metadata.format
})
observer: event => {
ipc.send('wt-audio-metadata', infoHash, index, event.metadata)
}
}
const onMetadata = file.done
@@ -354,7 +359,7 @@ function getAudioMetadata (infoHash, index) {
onMetadata
.then(
metadata => {
ipcRenderer.send('wt-audio-metadata', infoHash, index, metadata)
ipc.send('wt-audio-metadata', infoHash, index, metadata)
console.log(`metadata for file='${file.name}' completed.`)
},
err => {
@@ -383,7 +388,7 @@ function selectFiles (torrentOrInfoHash, selections) {
// selection with individual selections for each file, so we can
// select/deselect files later on
if (!selections) {
selections = new Array(torrent.files.length).fill(true)
selections = torrent.files.map((x) => true)
}
// Selections specified incorrectly?
@@ -396,15 +401,15 @@ function selectFiles (torrentOrInfoHash, selections) {
torrent.deselect(0, torrent.pieces.length - 1, false)
// Add selections (individual files)
selections.forEach((selection, i) => {
for (let i = 0; i < selections.length; i++) {
const file = torrent.files[i]
if (selection) {
if (selections[i]) {
file.select()
} else {
console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
file.deselect()
}
})
}
}
// Gets a WebTorrent handle by torrentKey
@@ -422,7 +427,7 @@ function onError (err) {
// TODO: remove this once the following bugs are fixed:
// https://bugs.chromium.org/p/chromium/issues/detail?id=490143
// https://github.com/electron/electron/issues/7212
window.testOfflineMode = () => {
window.testOfflineMode = function () {
console.log('Test, going OFFLINE')
client = window.client = new WebTorrent({
peerId: PEER_ID,

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,6 @@
[Desktop Entry]
Type=Application
<% if (version) { %>Version=<%= version %><% } %>
Name=<%= productName %>
<% if (genericName) { %>GenericName=<%= genericName %><% } %>
<% if (description) { %>Comment=<%= description %><% } %>
@@ -10,7 +11,7 @@ Actions=CreateNewTorrent;OpenTorrentFile;OpenTorrentAddress;
<% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>;<% } %>
<% if (categories && categories.length) { %>Categories=<%= categories.join(';') %>;<% } %>
StartupNotify=true
StartupWMClass=<%= productName %>
<% if (name) { %>StartupWMClass=<%= name %> <% } %>
[Desktop Action CreateNewTorrent]
Name=Create New Torrent...

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -322,10 +322,6 @@ textarea,
* TORRENT LIST
*/
.torrent-list {
height: 100%;
}
.torrent {
background: linear-gradient(to bottom right, #4B79A1, #283E51);
background-repeat: no-repeat;
@@ -354,7 +350,7 @@ textarea,
text-shadow: rgba(0, 0, 0, 0.5) 0 0 4px;
}
.torrent .metadata .ellipsis > span:not(:last-child):not(.control)::after {
.torrent .metadata span:not(:last-child)::after {
content: ' • ';
opacity: 0.7;
padding-left: 4px;
@@ -562,13 +558,6 @@ body.drag .app::after {
width: 100%;
}
.player .name {
font-size: 20px;
font-weight: bold;
line-height: 1.5em;
margin-bottom: 6px;
}
/*
* PLAYER CONTROLS
*/

Binary file not shown.

Binary file not shown.

Binary file not shown.