Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfb3a01239 | ||
|
|
736d575ab1 | ||
|
|
34a9508483 | ||
|
|
21ed8797c2 | ||
|
|
454491572a | ||
|
|
6518a1535c | ||
|
|
0095687bf5 | ||
|
|
d466ed085a | ||
|
|
eeda7c17c5 | ||
|
|
b89deb46db | ||
|
|
951a89c6c9 | ||
|
|
d4e6c84279 | ||
|
|
9731d85ca3 | ||
|
|
98f7ba8931 | ||
|
|
24c775608e | ||
|
|
f4eab12c3f | ||
|
|
8eeddeb4bc | ||
|
|
58f1594d9e | ||
|
|
c126ac0a84 | ||
|
|
6768be710e | ||
|
|
b63aa090dc | ||
|
|
05ef8be5bc | ||
|
|
1a09249bc3 | ||
|
|
803820dfca | ||
|
|
deb111bf62 | ||
|
|
7d64c7e308 | ||
|
|
ffb7183f51 | ||
|
|
20c6737aba | ||
|
|
959fb20b61 | ||
|
|
5d14c923fa | ||
|
|
5ffa7c4465 | ||
|
|
461744da5b | ||
|
|
6df33bc58b | ||
|
|
b5ae8f56cf | ||
|
|
2e0de52520 | ||
|
|
7b1ff0efc6 | ||
|
|
4002392b7f | ||
|
|
ee4b84fc11 | ||
|
|
90a0ce4a4d | ||
|
|
80faba8234 | ||
|
|
ac0574a473 | ||
|
|
792e3430f1 | ||
|
|
9e33be0ab1 | ||
|
|
c343c008ed | ||
|
|
6405be5144 | ||
|
|
db743daae5 | ||
|
|
290a25c393 | ||
|
|
6589e134b3 | ||
|
|
a2aa5e4271 | ||
|
|
205e2eb551 | ||
|
|
53209a9da3 | ||
|
|
2a23611c5f | ||
|
|
cb71913cbe | ||
|
|
836d7c6664 | ||
|
|
4cef9f2911 | ||
|
|
0913988d53 | ||
|
|
6468f82a7f | ||
|
|
fd0fc769b1 | ||
|
|
e5b648dfc6 | ||
|
|
7701c5f097 | ||
|
|
e5eddce868 | ||
|
|
72f917a744 | ||
|
|
0b82c83d44 | ||
|
|
602654cc1d | ||
|
|
350bed53a3 | ||
|
|
840754fb59 | ||
|
|
ed46583226 | ||
|
|
93252d430e | ||
|
|
bfd09a058e | ||
|
|
b1a7543d37 | ||
|
|
39195fe8c4 | ||
|
|
ea1c66b3fc | ||
|
|
f35eb73d50 | ||
|
|
c99af4718e | ||
|
|
dbef07e334 | ||
|
|
969ad64c47 | ||
|
|
5dd5e8661b | ||
|
|
5c9265fc99 | ||
|
|
1deab08d38 | ||
|
|
3d6da99e8e | ||
|
|
2005ee4d0b | ||
|
|
c99da2ccaa | ||
|
|
4bffb6634c | ||
|
|
504aca747d | ||
|
|
2085312c34 | ||
|
|
744d38259e | ||
|
|
868739445a | ||
|
|
98d8a798ce | ||
|
|
fe31cfaa3e | ||
|
|
17d5490448 | ||
|
|
d4c415d585 | ||
|
|
cb8f7f53c2 | ||
|
|
8d93641ebe | ||
|
|
4faf30e0a1 | ||
|
|
ed1b27ede0 | ||
|
|
252443a529 | ||
|
|
86f5a1a54e | ||
|
|
0b1872fa28 | ||
|
|
9eeb8133af | ||
|
|
1eb5504029 | ||
|
|
dfe8c3eb6b | ||
|
|
2b8c1fe709 | ||
|
|
905cc527d0 | ||
|
|
95019453fd | ||
|
|
e46a7f42df | ||
|
|
15a59f445b | ||
|
|
dea951fc42 | ||
|
|
347eb2c7f0 | ||
|
|
4221883eb4 | ||
|
|
27f729250f | ||
|
|
452bbb60c4 | ||
|
|
9d4aeaedd3 | ||
|
|
558b6c1648 | ||
|
|
98e263e69a | ||
|
|
18b126e0d2 | ||
|
|
82dff65572 | ||
|
|
d60d298b8f | ||
|
|
ffbd8184b5 | ||
|
|
11cf4aeecd | ||
|
|
b0b8b56816 | ||
|
|
967e5ecb9c | ||
|
|
f0315f7f77 | ||
|
|
facb07cbb1 | ||
|
|
41910aea9c | ||
|
|
8fcfa3b97a | ||
|
|
8ebb2349dd | ||
|
|
1e487a3c2a | ||
|
|
291ea94a10 | ||
|
|
ade6c1e4a0 | ||
|
|
bde5dc14c3 | ||
|
|
0a005eb054 | ||
|
|
735851486e | ||
|
|
56ba5c705a | ||
|
|
cdab2dbc65 | ||
|
|
4284eb8f75 | ||
|
|
2707fc9053 | ||
|
|
1d4d4319e4 | ||
|
|
c5cc0ce09d | ||
|
|
fdd7dab76f | ||
|
|
7624f2da98 | ||
|
|
ef51f827dc | ||
|
|
011ab13c83 | ||
|
|
017d61815f | ||
|
|
3d4d1c8650 | ||
|
|
1479369db1 | ||
|
|
31ef283e7b | ||
|
|
6b70554e63 | ||
|
|
9a1c329434 | ||
|
|
4aaf6dee05 | ||
|
|
86f08ee891 | ||
|
|
0b85ba9f32 | ||
|
|
812ce8724d | ||
|
|
06f81ff759 | ||
|
|
2693075f9f | ||
|
|
c1713810b9 | ||
|
|
e08e5d14a2 | ||
|
|
a3d685e132 | ||
|
|
5471760278 | ||
|
|
969c784df4 | ||
|
|
85e49dea6d | ||
|
|
a497afe5cf | ||
|
|
2333171de7 | ||
|
|
04318d7580 | ||
|
|
5e6e5fce1e | ||
|
|
af2ad46958 | ||
|
|
432d7d4a56 | ||
|
|
f93685811a | ||
|
|
914d07df03 | ||
|
|
9c60f104c8 | ||
|
|
ee7e630177 | ||
|
|
ae168ae885 | ||
|
|
ad0fcaed46 | ||
|
|
304b81908d | ||
|
|
b10f8c5bed | ||
|
|
f6b9dbbbc4 | ||
|
|
59cc912378 | ||
|
|
33663bef3e | ||
|
|
e75cd45ec0 | ||
|
|
c98f3cd040 | ||
|
|
4c4caba002 | ||
|
|
45f6cc5247 | ||
|
|
69460db294 | ||
|
|
f8095fcdbf | ||
|
|
1a0a2b3658 | ||
|
|
f9141dd39c | ||
|
|
8c2d49f029 | ||
|
|
da1e120de9 | ||
|
|
457aca25ee | ||
|
|
ae73ae29c4 | ||
|
|
5abf421f11 | ||
|
|
e792532051 | ||
|
|
5c39665b6a | ||
|
|
d1c4579398 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,4 +1,4 @@
|
|||||||
**What version of WebTorrent Desktop?**
|
**What version of WebTorrent Desktop?** (See the 'About WebTorrent' menu)
|
||||||
|
|
||||||
**What operating system and version?**
|
**What operating system and version?**
|
||||||
|
|
||||||
|
|||||||
@@ -11,5 +11,12 @@
|
|||||||
- Dan Flettre <fletd01@yahoo.com>
|
- Dan Flettre <fletd01@yahoo.com>
|
||||||
- Liam Gray <liam.r.gray@gmail.com>
|
- Liam Gray <liam.r.gray@gmail.com>
|
||||||
- grunjol <grunjol@argenteam.net>
|
- grunjol <grunjol@argenteam.net>
|
||||||
|
- Rémi Jouannet <remijouannet@users.noreply.github.com>
|
||||||
|
- Evan Miller <miller.evan815@gmail.com>
|
||||||
|
- Alex <alxmorais8@msn.com>
|
||||||
|
- Diego Rodríguez Baquero <diegorbaquero@gmail.com>
|
||||||
|
- Karlo Luis Martinez Martos <karlo.luis.m@gmail.com>
|
||||||
|
- gabriel <furstenheim@gmail.com>
|
||||||
|
- Rolando Guedes <rolando.guedes@3gnt.net>
|
||||||
|
|
||||||
#### Generated by bin/update-authors.sh.
|
#### Generated by bin/update-authors.sh.
|
||||||
|
|||||||
85
CHANGELOG.md
85
CHANGELOG.md
@@ -1,5 +1,89 @@
|
|||||||
# WebTorrent Desktop Version History
|
# WebTorrent Desktop Version History
|
||||||
|
|
||||||
|
## v0.5.0 - 2016-05-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Select/deselect individual files to torrent.
|
||||||
|
- Automatically include subtitle files (.srt, .vtt) from torrent in the subtitles menu.
|
||||||
|
- "Add Subtitle File..." menu item.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- When manually adding subtitle track(s), always switch to the new track.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Magnet links throw exception on app launch. (OS X)
|
||||||
|
- Multi-file torrents would not seed in-place, were copied to Downloads folder.
|
||||||
|
- Missing 'About WebTorrent' menu item. (Windows)
|
||||||
|
- Rare exception. ("Cannot create BrowserWindow before app is ready")
|
||||||
|
|
||||||
|
## v0.4.0 - 2016-05-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Better Windows support!
|
||||||
|
- Windows 32-bit build.
|
||||||
|
- Windows Portable App build.
|
||||||
|
- Windows app signing, for fewer install warnings.
|
||||||
|
- Better Linux support!
|
||||||
|
- Linux 32-bit build.
|
||||||
|
- Subtitles support!
|
||||||
|
- .srt and .vtt file support.
|
||||||
|
- Drag-and-drop files on video, or choose from file selector.
|
||||||
|
- Multiple subtitle files support.
|
||||||
|
- Stream to VLC when the audio codec is unplayable (e.g. AC3, EAC3).
|
||||||
|
- "Show in Folder" item in context menu.
|
||||||
|
- Volume slider, with mute/unmute button.
|
||||||
|
- New "Create torrent" page to modify:
|
||||||
|
- Torrent comment.
|
||||||
|
- Trackers.
|
||||||
|
- Private torrent flag.
|
||||||
|
- Use mouse wheel to increase/decrease volume.
|
||||||
|
- Bounce the Downloads stack when download completes. (OS X)
|
||||||
|
- New default torrent on first launch: The WIRED CD.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improve app startup time by 40%.
|
||||||
|
- UI tweaks: Reduce font size, reduce torrent list item height.
|
||||||
|
- Add Playback menu for playback-related functionality.
|
||||||
|
- Fix installing when the app is already installed. (Windows)
|
||||||
|
- Don't kill unrelated processes on uninstall. (Windows)
|
||||||
|
- Set "sheet offset" correctly for create torrent dialog. (OS X)
|
||||||
|
- Remove OS X-style Window menu. (Linux, Windows)
|
||||||
|
- Remove "Add Fake Airplay/Chromecast" menu items.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Disable WebRTC to fix 100% CPU usage/crashes caused by Chromium issue. This is
|
||||||
|
temporary. (OS X)
|
||||||
|
- When fullscreen, make controls use the full window. (OS X)
|
||||||
|
- Support creating torrents that contain .torrent files.
|
||||||
|
- Block power save while casting to a remote device.
|
||||||
|
- Do not block power save when the space key is pressed from the torrent list.
|
||||||
|
- Support playing .mpg and .ogv extensions in the app.
|
||||||
|
- Fix video centering for multi-screen setups.
|
||||||
|
- Show an error when adding a duplicate torrent.
|
||||||
|
- Show an error when adding an invalid magnet link.
|
||||||
|
- Do not stop music when tabbing to another program (OS X)
|
||||||
|
- Properly size the Windows volume mixer icon.
|
||||||
|
- Default to the user's OS-defined, localized "Downloads" folder.
|
||||||
|
- Enforce minimimum window size when resizing player to prevent window disappearing.
|
||||||
|
- Fix rare race condition error on app quit.
|
||||||
|
- Don't use zero-byte torrent "poster" images.
|
||||||
|
|
||||||
|
Thanks to @grunjol, @rguedes, @furstenheim, @karloluis, @DiegoRBaquero, @alxhotel,
|
||||||
|
@AgentEpsilon, @remijouannet, Rolando Guedes, @dcposch, and @feross for contributing
|
||||||
|
to this release!
|
||||||
|
|
||||||
|
## v0.3.3 - 2016-04-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- App icon was incorrect (OS X)
|
||||||
|
|
||||||
## v0.3.2 - 2016-04-07
|
## v0.3.2 - 2016-04-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -17,6 +101,7 @@
|
|||||||
|
|
||||||
- Fix installation bugs with .deb file (Linux)
|
- Fix installation bugs with .deb file (Linux)
|
||||||
- Pause audio reliably when closing the window
|
- Pause audio reliably when closing the window
|
||||||
|
- Enforce minimimum window size when resizing player (for audio-only .mov files, which are 0x0)
|
||||||
|
|
||||||
## v0.3.1 - 2016-04-06
|
## v0.3.1 - 2016-04-06
|
||||||
|
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -22,7 +22,7 @@
|
|||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./static/screenshot.png" width="562" height="630" alt="screenshot" align="center">
|
<img src="https://webtorrent.io/img/screenshot-main.png" width="562" height="630" alt="screenshot" align="center">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
@@ -50,12 +50,23 @@ $ npm run package
|
|||||||
To build for one platform:
|
To build for one platform:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ npm run package -- [platform] [package-type]
|
$ npm run package -- [platform]
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `[platform]` is `darwin`, `linux`, or `win32`
|
Where `[platform]` is `darwin`, `linux`, `win32`, or `all` (default).
|
||||||
|
|
||||||
and `[package-type]` is `all` (default), `deb` or `zip` (`linux` platform only)
|
The following optional arguments are available:
|
||||||
|
|
||||||
|
- `--sign` - Sign the application (OS X, Windows)
|
||||||
|
- `--package=[type]` - Package single output type.
|
||||||
|
- `deb` - Debian package
|
||||||
|
- `zip` - Linux zip file
|
||||||
|
- `dmg` - OS X disk image
|
||||||
|
- `exe` - Windows installer
|
||||||
|
- `portable` - Windows portable app
|
||||||
|
- `all` - All platforms (default)
|
||||||
|
|
||||||
|
Note: Even with the `--package` option, the auto-update files (.nupkg for Windows, *-darwin.zip for OS X) will always be produced.
|
||||||
|
|
||||||
#### Windows build notes
|
#### Windows build notes
|
||||||
|
|
||||||
|
|||||||
51
bin/check-deps.js
Executable file
51
bin/check-deps.js
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var fs = require('fs')
|
||||||
|
var cp = require('child_process')
|
||||||
|
|
||||||
|
var BUILT_IN_DEPS = ['child_process', 'electron', 'fs', 'os', 'path', 'screen']
|
||||||
|
var EXECUTABLE_DEPS = ['gh-release', 'standard']
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
// Scans our codebase and package.json for missing or unused dependencies
|
||||||
|
// Process returns 0 on success, prints a message and returns 1 on failure
|
||||||
|
function main () {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
console.log('Sorry, check-deps only works on Mac and Linux')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsDeps = findJSDeps()
|
||||||
|
var packageDeps = findPackageDeps()
|
||||||
|
|
||||||
|
var missingDeps = jsDeps.filter((dep) =>
|
||||||
|
packageDeps.indexOf(dep) < 0 &&
|
||||||
|
BUILT_IN_DEPS.indexOf(dep) < 0)
|
||||||
|
var unusedDeps = packageDeps.filter((dep) =>
|
||||||
|
jsDeps.indexOf(dep) < 0 &&
|
||||||
|
EXECUTABLE_DEPS.indexOf(dep) < 0)
|
||||||
|
|
||||||
|
if (missingDeps.length > 0) console.log('Missing package dependencies: ' + missingDeps)
|
||||||
|
if (unusedDeps.length > 0) console.log('Unused package dependencies: ' + unusedDeps)
|
||||||
|
|
||||||
|
if (missingDeps.length + unusedDeps.length > 0) process.exit(1)
|
||||||
|
|
||||||
|
console.log('Lookin good!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds all dependencies, required, optional, or dev, in package.json
|
||||||
|
function findPackageDeps () {
|
||||||
|
var pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
||||||
|
var requiredDeps = Object.keys(pkg.dependencies)
|
||||||
|
var devDeps = Object.keys(pkg.devDependencies)
|
||||||
|
var optionalDeps = Object.keys(pkg.optionalDependencies)
|
||||||
|
|
||||||
|
return [].concat(requiredDeps, devDeps, optionalDeps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds all dependencies required() in the code
|
||||||
|
function findJSDeps () {
|
||||||
|
var stdout = cp.execSync('./bin/list-deps.sh')
|
||||||
|
return stdout.toString().trim().split('\n')
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
* Useful for developers.
|
* Useful for developers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var fs = require('fs')
|
||||||
var os = require('os')
|
var os = require('os')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var pathExists = require('path-exists')
|
|
||||||
var rimraf = require('rimraf')
|
var rimraf = require('rimraf')
|
||||||
|
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
@@ -15,7 +15,12 @@ var handlers = require('../main/handlers')
|
|||||||
|
|
||||||
rimraf.sync(config.CONFIG_PATH)
|
rimraf.sync(config.CONFIG_PATH)
|
||||||
|
|
||||||
var tmpPath = path.join(pathExists.sync('/tmp') ? '/tmp' : os.tmpDir(), 'webtorrent')
|
var tmpPath
|
||||||
|
try {
|
||||||
|
tmpPath = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent')
|
||||||
|
} catch (err) {
|
||||||
|
tmpPath = path.join(os.tmpDir(), 'webtorrent')
|
||||||
|
}
|
||||||
rimraf.sync(tmpPath)
|
rimraf.sync(tmpPath)
|
||||||
|
|
||||||
// Uninstall .torrent file and magnet link handlers
|
// Uninstall .torrent file and magnet link handlers
|
||||||
|
|||||||
10
bin/list-deps.sh
Executable file
10
bin/list-deps.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# This is a truly heinous hack, but it works pretty nicely.
|
||||||
|
# Find all modules we're requiring---even conditional requires.
|
||||||
|
|
||||||
|
grep "require('" *.js bin/ main/ renderer/ -R |
|
||||||
|
grep '.js:' |
|
||||||
|
sed "s/.*require('\([^'\/]*\).*/\1/" |
|
||||||
|
grep -v '^\.' |
|
||||||
|
sort |
|
||||||
|
uniq
|
||||||
8
bin/open-config.js
Executable file
8
bin/open-config.js
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var config = require('../config')
|
||||||
|
var open = require('open')
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
var configPath = path.join(config.CONFIG_PATH, 'config.json')
|
||||||
|
open(configPath)
|
||||||
389
bin/package.js
389
bin/package.js
@@ -4,41 +4,59 @@
|
|||||||
* Builds app binaries for OS X, Linux, and Windows.
|
* Builds app binaries for OS X, Linux, and Windows.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var config = require('../config')
|
|
||||||
var cp = require('child_process')
|
var cp = require('child_process')
|
||||||
var electronPackager = require('electron-packager')
|
var electronPackager = require('electron-packager')
|
||||||
var fs = require('fs')
|
var fs = require('fs')
|
||||||
|
var minimist = require('minimist')
|
||||||
|
var mkdirp = require('mkdirp')
|
||||||
|
var os = require('os')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var pkg = require('../package.json')
|
|
||||||
var rimraf = require('rimraf')
|
var rimraf = require('rimraf')
|
||||||
|
var series = require('run-series')
|
||||||
|
var zip = require('cross-zip')
|
||||||
|
|
||||||
|
var config = require('../config')
|
||||||
|
var pkg = require('../package.json')
|
||||||
|
|
||||||
var BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
|
var BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
|
||||||
|
var DIST_PATH = path.join(config.ROOT_PATH, 'dist')
|
||||||
|
|
||||||
|
var argv = minimist(process.argv.slice(2), {
|
||||||
|
boolean: [
|
||||||
|
'sign'
|
||||||
|
],
|
||||||
|
default: {
|
||||||
|
package: 'all',
|
||||||
|
sign: false
|
||||||
|
},
|
||||||
|
string: [
|
||||||
|
'package'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
function build () {
|
function build () {
|
||||||
var platform = process.argv[2]
|
rimraf.sync(DIST_PATH)
|
||||||
var packageType = process.argv.length > 3 ? process.argv[3] : 'all'
|
var platform = argv._[0]
|
||||||
if (platform === 'darwin') {
|
if (platform === 'darwin') {
|
||||||
buildDarwin(printDone)
|
buildDarwin(printDone)
|
||||||
} else if (platform === 'win32') {
|
} else if (platform === 'win32') {
|
||||||
buildWin32(printDone)
|
buildWin32(printDone)
|
||||||
} else if (platform === 'linux') {
|
} else if (platform === 'linux') {
|
||||||
buildLinux(packageType, printDone)
|
buildLinux(printDone)
|
||||||
} else {
|
} else {
|
||||||
buildDarwin(function (err, buildPath) {
|
buildDarwin(function (err) {
|
||||||
printDone(err, buildPath)
|
printDone(err)
|
||||||
buildWin32(function (err, buildPath) {
|
buildWin32(function (err) {
|
||||||
printDone(err, buildPath)
|
printDone(err)
|
||||||
buildLinux(packageType, printDone)
|
buildLinux(printDone)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var all = {
|
var all = {
|
||||||
// Build 64 bit binaries only.
|
// The human-readable copyright line for the app. Maps to the `LegalCopyright` metadata
|
||||||
arch: 'x64',
|
// property on Windows, and `NSHumanReadableCopyright` on OS X.
|
||||||
|
|
||||||
// The human-readable copyright line for the app.
|
|
||||||
'app-copyright': config.APP_COPYRIGHT,
|
'app-copyright': config.APP_COPYRIGHT,
|
||||||
|
|
||||||
// The release version of the application. Maps to the `ProductVersion` metadata
|
// The release version of the application. Maps to the `ProductVersion` metadata
|
||||||
@@ -55,9 +73,9 @@ var all = {
|
|||||||
'asar-unpack': 'WebTorrent*',
|
'asar-unpack': 'WebTorrent*',
|
||||||
|
|
||||||
// The build version of the application. Maps to the FileVersion metadata property on
|
// The build version of the application. Maps to the FileVersion metadata property on
|
||||||
// Windows, and CFBundleVersion on OS X. We're using the short git hash (e.g. 'e7d837e')
|
// Windows, and CFBundleVersion on OS X. Note: Windows requires the build version to
|
||||||
// Windows requires the build version to start with a number :/ so we stick on a prefix
|
// start with a number. We're using the version of the underlying WebTorrent library.
|
||||||
'build-version': '0-' + cp.execSync('git rev-parse --short HEAD').toString().replace('\n', ''),
|
'build-version': require('webtorrent/package.json').version,
|
||||||
|
|
||||||
// The application source directory.
|
// The application source directory.
|
||||||
dir: config.ROOT_PATH,
|
dir: config.ROOT_PATH,
|
||||||
@@ -70,7 +88,7 @@ var all = {
|
|||||||
name: config.APP_NAME,
|
name: config.APP_NAME,
|
||||||
|
|
||||||
// The base directory where the finished package(s) are created.
|
// The base directory where the finished package(s) are created.
|
||||||
out: path.join(config.ROOT_PATH, 'dist'),
|
out: DIST_PATH,
|
||||||
|
|
||||||
// Replace an already existing output directory.
|
// Replace an already existing output directory.
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
@@ -80,12 +98,16 @@ var all = {
|
|||||||
prune: true,
|
prune: true,
|
||||||
|
|
||||||
// The Electron version with which the app is built (without the leading 'v')
|
// The Electron version with which the app is built (without the leading 'v')
|
||||||
version: pkg.dependencies['electron-prebuilt']
|
version: require('electron-prebuilt/package.json').version
|
||||||
}
|
}
|
||||||
|
|
||||||
var darwin = {
|
var darwin = {
|
||||||
|
// Build for OS X
|
||||||
platform: 'darwin',
|
platform: 'darwin',
|
||||||
|
|
||||||
|
// Build 64 bit binaries only.
|
||||||
|
arch: 'x64',
|
||||||
|
|
||||||
// The bundle identifier to use in the application's plist (OS X only).
|
// The bundle identifier to use in the application's plist (OS X only).
|
||||||
'app-bundle-id': 'io.webtorrent.webtorrent',
|
'app-bundle-id': 'io.webtorrent.webtorrent',
|
||||||
|
|
||||||
@@ -101,8 +123,12 @@ var darwin = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var win32 = {
|
var win32 = {
|
||||||
|
// Build for Windows.
|
||||||
platform: 'win32',
|
platform: 'win32',
|
||||||
|
|
||||||
|
// Build 32 bit binaries only.
|
||||||
|
arch: 'ia32',
|
||||||
|
|
||||||
// Object hash of application metadata to embed into the executable (Windows only)
|
// Object hash of application metadata to embed into the executable (Windows only)
|
||||||
'version-string': {
|
'version-string': {
|
||||||
|
|
||||||
@@ -131,7 +157,11 @@ var win32 = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var linux = {
|
var linux = {
|
||||||
platform: 'linux'
|
// Build for Linux.
|
||||||
|
platform: 'linux',
|
||||||
|
|
||||||
|
// Build 32 and 64 bit binaries.
|
||||||
|
arch: 'all'
|
||||||
|
|
||||||
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
|
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
|
||||||
}
|
}
|
||||||
@@ -141,8 +171,10 @@ build()
|
|||||||
function buildDarwin (cb) {
|
function buildDarwin (cb) {
|
||||||
var plist = require('plist')
|
var plist = require('plist')
|
||||||
|
|
||||||
|
console.log('OS X: Packaging electron...')
|
||||||
electronPackager(Object.assign({}, all, darwin), function (err, buildPath) {
|
electronPackager(Object.assign({}, all, darwin), function (err, buildPath) {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
|
console.log('OS X: Packaged electron. ' + buildPath)
|
||||||
|
|
||||||
var appPath = path.join(buildPath[0], config.APP_NAME + '.app')
|
var appPath = path.join(buildPath[0], config.APP_NAME + '.app')
|
||||||
var contentsPath = path.join(appPath, 'Contents')
|
var contentsPath = path.join(appPath, 'Contents')
|
||||||
@@ -185,11 +217,24 @@ function buildDarwin (cb) {
|
|||||||
cp.execSync(`cp ${config.APP_FILE_ICON + '.icns'} ${resourcesPath}`)
|
cp.execSync(`cp ${config.APP_FILE_ICON + '.icns'} ${resourcesPath}`)
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
var appDmg = require('appdmg')
|
if (argv.sign) {
|
||||||
|
signApp(function (err) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
pack(cb)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
printWarning()
|
||||||
|
pack(cb)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
function signApp (cb) {
|
||||||
var sign = require('electron-osx-sign')
|
var sign = require('electron-osx-sign')
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sign the app with Apple Developer ID certificate. We sign the app for 2 reasons:
|
* Sign the app with Apple Developer ID certificates. We sign the app for 2 reasons:
|
||||||
* - So the auto-updater (Squirrrel.Mac) can check that app updates are signed by
|
* - So the auto-updater (Squirrrel.Mac) can check that app updates are signed by
|
||||||
* the same author as the current version.
|
* the same author as the current version.
|
||||||
* - So users will not a see a warning about the app coming from an "Unidentified
|
* - So users will not a see a warning about the app coming from an "Unidentified
|
||||||
@@ -207,48 +252,71 @@ function buildDarwin (cb) {
|
|||||||
verbose: true
|
verbose: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('OS X: Signing app...')
|
||||||
sign(signOpts, function (err) {
|
sign(signOpts, function (err) {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
|
console.log('OS X: Signed app.')
|
||||||
|
cb(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Create .zip file (used by the auto-updater)
|
function pack (cb) {
|
||||||
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-darwin.zip')
|
packageZip() // always produce .zip file, used for automatic updates
|
||||||
cp.execSync(`cd ${buildPath[0]} && zip -r -y ${zipPath} ${config.APP_NAME + '.app'}`)
|
|
||||||
console.log('Created OS X .zip file.')
|
|
||||||
|
|
||||||
var targetPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.dmg')
|
if (argv.package === 'dmg' || argv.package === 'all') {
|
||||||
rimraf.sync(targetPath)
|
packageDmg(cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a .dmg (OS X disk image) file, for easy user installation.
|
function packageZip () {
|
||||||
var dmgOpts = {
|
// Create .zip file (used by the auto-updater)
|
||||||
basepath: config.ROOT_PATH,
|
console.log('OS X: Creating zip...')
|
||||||
target: targetPath,
|
|
||||||
specification: {
|
var inPath = path.join(buildPath[0], config.APP_NAME + '.app')
|
||||||
title: config.APP_NAME,
|
var outPath = path.join(DIST_PATH, BUILD_NAME + '-darwin.zip')
|
||||||
icon: config.APP_ICON + '.icns',
|
zip.zipSync(inPath, outPath)
|
||||||
background: path.join(config.STATIC_PATH, 'appdmg.png'),
|
|
||||||
'icon-size': 128,
|
console.log('OS X: Created zip.')
|
||||||
contents: [
|
}
|
||||||
{ x: 122, y: 240, type: 'file', path: appPath },
|
|
||||||
{ x: 380, y: 240, type: 'link', path: '/Applications' },
|
function packageDmg (cb) {
|
||||||
// Hide hidden icons out of view, for users who have hidden files shown.
|
console.log('OS X: Creating dmg...')
|
||||||
// https://github.com/LinusU/node-appdmg/issues/45#issuecomment-153924954
|
|
||||||
{ x: 50, y: 500, type: 'position', path: '.background' },
|
var appDmg = require('appdmg')
|
||||||
{ x: 100, y: 500, type: 'position', path: '.DS_Store' },
|
|
||||||
{ x: 150, y: 500, type: 'position', path: '.Trashes' },
|
var targetPath = path.join(DIST_PATH, BUILD_NAME + '.dmg')
|
||||||
{ x: 200, y: 500, type: 'position', path: '.VolumeIcon.icns' }
|
rimraf.sync(targetPath)
|
||||||
]
|
|
||||||
}
|
// Create a .dmg (OS X disk image) file, for easy user installation.
|
||||||
|
var dmgOpts = {
|
||||||
|
basepath: config.ROOT_PATH,
|
||||||
|
target: targetPath,
|
||||||
|
specification: {
|
||||||
|
title: config.APP_NAME,
|
||||||
|
icon: config.APP_ICON + '.icns',
|
||||||
|
background: path.join(config.STATIC_PATH, 'appdmg.png'),
|
||||||
|
'icon-size': 128,
|
||||||
|
contents: [
|
||||||
|
{ x: 122, y: 240, type: 'file', path: appPath },
|
||||||
|
{ x: 380, y: 240, type: 'link', path: '/Applications' },
|
||||||
|
// Hide hidden icons out of view, for users who have hidden files shown.
|
||||||
|
// https://github.com/LinusU/node-appdmg/issues/45#issuecomment-153924954
|
||||||
|
{ x: 50, y: 500, type: 'position', path: '.background' },
|
||||||
|
{ x: 100, y: 500, type: 'position', path: '.DS_Store' },
|
||||||
|
{ x: 150, y: 500, type: 'position', path: '.Trashes' },
|
||||||
|
{ x: 200, y: 500, type: 'position', path: '.VolumeIcon.icns' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var dmg = appDmg(dmgOpts)
|
var dmg = appDmg(dmgOpts)
|
||||||
dmg.on('error', cb)
|
dmg.on('error', cb)
|
||||||
dmg.on('progress', function (info) {
|
dmg.on('progress', function (info) {
|
||||||
if (info.type === 'step-begin') console.log(info.title + '...')
|
if (info.type === 'step-begin') console.log(info.title + '...')
|
||||||
})
|
})
|
||||||
dmg.on('finish', function (info) {
|
dmg.on('finish', function (info) {
|
||||||
console.log('Created OS X disk image (.dmg) file.')
|
console.log('OS X: Created dmg.')
|
||||||
cb(null, buildPath)
|
cb(null)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -256,81 +324,164 @@ function buildDarwin (cb) {
|
|||||||
|
|
||||||
function buildWin32 (cb) {
|
function buildWin32 (cb) {
|
||||||
var installer = require('electron-winstaller')
|
var installer = require('electron-winstaller')
|
||||||
|
console.log('Windows: Packaging electron...')
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Path to folder with the following files:
|
||||||
|
* - Windows Authenticode private key and cert (authenticode.p12)
|
||||||
|
* - Windows Authenticode password file (authenticode.txt)
|
||||||
|
*/
|
||||||
|
var CERT_PATH
|
||||||
|
try {
|
||||||
|
fs.accessSync('D:')
|
||||||
|
CERT_PATH = 'D:'
|
||||||
|
} catch (err) {
|
||||||
|
CERT_PATH = path.join(os.homedir(), 'Desktop')
|
||||||
|
}
|
||||||
|
|
||||||
electronPackager(Object.assign({}, all, win32), function (err, buildPath) {
|
electronPackager(Object.assign({}, all, win32), function (err, buildPath) {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
|
console.log('Windows: Packaged electron. ' + buildPath)
|
||||||
|
|
||||||
console.log('Creating Windows installer...')
|
var signWithParams
|
||||||
installer.createWindowsInstaller({
|
if (process.platform === 'win32') {
|
||||||
appDirectory: buildPath[0],
|
if (argv.sign) {
|
||||||
authors: config.APP_TEAM,
|
var certificateFile = path.join(CERT_PATH, 'authenticode.p12')
|
||||||
// certificateFile: '', // TODO
|
var certificatePassword = fs.readFileSync(path.join(CERT_PATH, 'authenticode.txt'), 'utf8')
|
||||||
description: config.APP_NAME,
|
var timestampServer = 'http://timestamp.comodoca.com'
|
||||||
exe: config.APP_NAME + '.exe',
|
signWithParams = `/a /f "${certificateFile}" /p "${certificatePassword}" /tr "${timestampServer}" /td sha256`
|
||||||
iconUrl: config.GITHUB_URL_RAW + '/static/' + config.APP_NAME + '.ico',
|
} else {
|
||||||
loadingGif: path.join(config.STATIC_PATH, 'loading.gif'),
|
printWarning()
|
||||||
remoteReleases: config.GITHUB_URL,
|
}
|
||||||
name: config.APP_NAME,
|
} else {
|
||||||
noMsi: true,
|
printWarning()
|
||||||
outputDirectory: path.join(config.ROOT_PATH, 'dist'),
|
}
|
||||||
productName: config.APP_NAME,
|
|
||||||
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
|
var tasks = []
|
||||||
setupIcon: config.APP_ICON + '.ico',
|
if (argv.package === 'exe' || argv.package === 'all') {
|
||||||
title: config.APP_NAME,
|
tasks.push((cb) => packageInstaller(cb))
|
||||||
usePackageJson: false,
|
}
|
||||||
version: pkg.version
|
if (argv.package === 'portable' || argv.package === 'all') {
|
||||||
}).then(function () {
|
tasks.push((cb) => packagePortable(cb))
|
||||||
console.log('Created Windows installer.')
|
}
|
||||||
cb(null, buildPath)
|
series(tasks, cb)
|
||||||
}).catch(cb)
|
|
||||||
|
function packageInstaller (cb) {
|
||||||
|
console.log('Windows: Creating installer...')
|
||||||
|
|
||||||
|
installer.createWindowsInstaller({
|
||||||
|
appDirectory: buildPath[0],
|
||||||
|
authors: config.APP_TEAM,
|
||||||
|
description: config.APP_NAME,
|
||||||
|
exe: config.APP_NAME + '.exe',
|
||||||
|
iconUrl: config.GITHUB_URL_RAW + '/static/' + config.APP_NAME + '.ico',
|
||||||
|
loadingGif: path.join(config.STATIC_PATH, 'loading.gif'),
|
||||||
|
name: config.APP_NAME,
|
||||||
|
noMsi: true,
|
||||||
|
outputDirectory: DIST_PATH,
|
||||||
|
productName: config.APP_NAME,
|
||||||
|
remoteReleases: config.GITHUB_URL,
|
||||||
|
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
|
||||||
|
setupIcon: config.APP_ICON + '.ico',
|
||||||
|
signWithParams: signWithParams,
|
||||||
|
title: config.APP_NAME,
|
||||||
|
usePackageJson: false,
|
||||||
|
version: pkg.version
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
console.log('Windows: Created installer.')
|
||||||
|
cb(null)
|
||||||
|
})
|
||||||
|
.catch(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
function packagePortable (cb) {
|
||||||
|
console.log('Windows: Creating portable app...')
|
||||||
|
|
||||||
|
var portablePath = path.join(buildPath[0], 'Portable Settings')
|
||||||
|
mkdirp.sync(portablePath)
|
||||||
|
|
||||||
|
var inPath = path.join(DIST_PATH, path.basename(buildPath[0]))
|
||||||
|
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
|
||||||
|
zip.zipSync(inPath, outPath)
|
||||||
|
|
||||||
|
console.log('Windows: Created portable app.')
|
||||||
|
cb(null)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLinux (packageType, cb) {
|
function buildLinux (cb) {
|
||||||
|
console.log('Linux: Packaging electron...')
|
||||||
electronPackager(Object.assign({}, all, linux), function (err, buildPath) {
|
electronPackager(Object.assign({}, all, linux), function (err, buildPath) {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
|
console.log('Linux: Packaged electron. ' + buildPath)
|
||||||
|
|
||||||
var distPath = path.join(config.ROOT_PATH, 'dist')
|
var tasks = []
|
||||||
var filesPath = buildPath[0]
|
buildPath.forEach(function (filesPath) {
|
||||||
|
var destArch = filesPath.split('-').pop()
|
||||||
|
|
||||||
if (packageType === 'deb' || packageType === 'all') {
|
if (argv.package === 'deb' || argv.package === 'all') {
|
||||||
// Create .deb file for debian based platforms
|
tasks.push((cb) => packageDeb(filesPath, destArch, cb))
|
||||||
var deb = require('nobin-debian-installer')()
|
}
|
||||||
var destPath = path.join('/opt', pkg.name)
|
if (argv.package === 'zip' || argv.package === 'all') {
|
||||||
|
tasks.push((cb) => packageZip(filesPath, destArch, cb))
|
||||||
deb.pack({
|
}
|
||||||
package: pkg,
|
})
|
||||||
info: {
|
series(tasks, cb)
|
||||||
arch: 'amd64',
|
|
||||||
targetDir: distPath,
|
|
||||||
depends: 'libc6 (>= 2.4)',
|
|
||||||
scripts: {
|
|
||||||
postinst: path.join(config.STATIC_PATH, 'linux', 'postinst'),
|
|
||||||
prerm: path.join(config.STATIC_PATH, 'linux', 'prerm')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [{
|
|
||||||
src: ['./**'],
|
|
||||||
dest: destPath,
|
|
||||||
expand: true,
|
|
||||||
cwd: filesPath
|
|
||||||
}], function (err, done) {
|
|
||||||
if (err) return console.error(err.message || err)
|
|
||||||
console.log('Created Linux .deb file.')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packageType === 'zip' || packageType === 'all') {
|
|
||||||
// Create .zip file for Linux
|
|
||||||
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-linux.zip')
|
|
||||||
var appFolderName = path.basename(filesPath)
|
|
||||||
cp.execSync(`cd ${distPath} && zip -r -y ${zipPath} ${appFolderName}`)
|
|
||||||
console.log('Created Linux .zip file.')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function packageDeb (filesPath, destArch, cb) {
|
||||||
|
// Create .deb file for Debian-based platforms
|
||||||
|
console.log(`Linux: Creating ${destArch} deb...`)
|
||||||
|
|
||||||
|
var deb = require('nobin-debian-installer')()
|
||||||
|
var destPath = path.join('/opt', pkg.name)
|
||||||
|
|
||||||
|
deb.pack({
|
||||||
|
package: pkg,
|
||||||
|
info: {
|
||||||
|
arch: destArch === 'x64' ? 'amd64' : 'i386',
|
||||||
|
targetDir: DIST_PATH,
|
||||||
|
depends: 'libc6 (>= 2.4)',
|
||||||
|
scripts: {
|
||||||
|
postinst: path.join(config.STATIC_PATH, 'linux', 'postinst'),
|
||||||
|
prerm: path.join(config.STATIC_PATH, 'linux', 'prerm')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [{
|
||||||
|
src: ['./**'],
|
||||||
|
dest: destPath,
|
||||||
|
expand: true,
|
||||||
|
cwd: filesPath
|
||||||
|
}], function (err) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
console.log(`Linux: Created ${destArch} deb.`)
|
||||||
|
cb(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function packageZip (filesPath, destArch, cb) {
|
||||||
|
// Create .zip file for Linux
|
||||||
|
console.log(`Linux: Creating ${destArch} zip...`)
|
||||||
|
|
||||||
|
var inPath = path.join(DIST_PATH, path.basename(filesPath))
|
||||||
|
var outPath = path.join(DIST_PATH, BUILD_NAME + '-linux-' + destArch + '.zip')
|
||||||
|
zip.zipSync(inPath, outPath)
|
||||||
|
|
||||||
|
console.log(`Linux: Created ${destArch} zip.`)
|
||||||
|
cb(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function printDone (err, buildPath) {
|
function printDone (err) {
|
||||||
if (err) console.error(err.message || err)
|
if (err) console.error(err.message || err)
|
||||||
else console.log('Built ' + buildPath[0])
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print a large warning when signing is disabled so we are less likely to accidentally
|
||||||
|
* ship unsigned binaries to users.
|
||||||
|
*/
|
||||||
|
function printWarning () {
|
||||||
|
console.log(fs.readFileSync(path.join(__dirname, 'warning.txt'), 'utf8'))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
npm run package
|
npm run package -- --sign
|
||||||
git push
|
git push
|
||||||
git push --tags
|
git push --tags
|
||||||
npm publish
|
npm publish
|
||||||
gh-release
|
./node_modules/.bin/gh-release
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ npm run update-authors
|
|||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
rm -rf node_modules/
|
rm -rf node_modules/
|
||||||
npm install
|
npm install
|
||||||
|
npm prune
|
||||||
|
npm dedupe
|
||||||
npm test
|
npm test
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ while (<>) {
|
|||||||
next if /<support\@greenkeeper.io>/;
|
next if /<support\@greenkeeper.io>/;
|
||||||
next if /<ungoldman\@gmail.com>/;
|
next if /<ungoldman\@gmail.com>/;
|
||||||
next if /<grunjol\@users.noreply.github.com>/;
|
next if /<grunjol\@users.noreply.github.com>/;
|
||||||
|
next if /<dc\@DCs-MacBook.local>/;
|
||||||
|
next if /<rolandoguedes\@gmail.com>/;
|
||||||
$seen{$_} = push @authors, "- ", $_;
|
$seen{$_} = push @authors, "- ", $_;
|
||||||
}
|
}
|
||||||
END {
|
END {
|
||||||
|
|||||||
12
bin/warning.txt
Normal file
12
bin/warning.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
*********************************************************
|
||||||
|
_ _ ___ ______ _ _ _____ _ _ _____
|
||||||
|
| | | |/ _ \ | ___ \ \ | |_ _| \ | | __ \
|
||||||
|
| | | / /_\ \| |_/ / \| | | | | \| | | \/
|
||||||
|
| |/\| | _ || /| . ` | | | | . ` | | __
|
||||||
|
\ /\ / | | || |\ \| |\ |_| |_| |\ | |_\ \
|
||||||
|
\/ \/\_| |_/\_| \_\_| \_/\___/\_| \_/\____/
|
||||||
|
|
||||||
|
Application is NOT signed. Do not ship this to users!
|
||||||
|
|
||||||
|
*********************************************************
|
||||||
77
config.js
77
config.js
@@ -1,10 +1,13 @@
|
|||||||
var applicationConfigPath = require('application-config-path')
|
var appConfig = require('application-config')('WebTorrent')
|
||||||
|
var fs = require('fs')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
var APP_NAME = 'WebTorrent'
|
var APP_NAME = 'WebTorrent'
|
||||||
var APP_TEAM = 'The WebTorrent Project'
|
var APP_TEAM = 'The WebTorrent Project'
|
||||||
var APP_VERSION = require('./package.json').version
|
var APP_VERSION = require('./package.json').version
|
||||||
|
|
||||||
|
var PORTABLE_PATH = path.join(path.dirname(process.execPath), 'Portable Settings')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
|
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
|
||||||
APP_FILE_ICON: path.join(__dirname, 'static', 'WebTorrentFile'),
|
APP_FILE_ICON: path.join(__dirname, 'static', 'WebTorrentFile'),
|
||||||
@@ -14,59 +17,51 @@ module.exports = {
|
|||||||
APP_VERSION: APP_VERSION,
|
APP_VERSION: APP_VERSION,
|
||||||
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
|
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
|
||||||
|
|
||||||
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update?version=' + APP_VERSION,
|
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update' +
|
||||||
AUTO_UPDATE_CHECK_STARTUP_DELAY: 5 * 1000 /* 5 seconds */,
|
'?version=' + APP_VERSION + '&platform=' + process.platform,
|
||||||
|
|
||||||
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
||||||
|
|
||||||
CONFIG_PATH: applicationConfigPath(APP_NAME),
|
CONFIG_PATH: getConfigPath(),
|
||||||
CONFIG_POSTER_PATH: path.join(applicationConfigPath(APP_NAME), 'Posters'),
|
CONFIG_POSTER_PATH: path.join(getConfigPath(), 'Posters'),
|
||||||
CONFIG_TORRENT_PATH: path.join(applicationConfigPath(APP_NAME), 'Torrents'),
|
CONFIG_TORRENT_PATH: path.join(getConfigPath(), 'Torrents'),
|
||||||
|
|
||||||
|
DELAYED_INIT: 3000 /* 3 seconds */,
|
||||||
|
|
||||||
GITHUB_URL: 'https://github.com/feross/webtorrent-desktop',
|
GITHUB_URL: 'https://github.com/feross/webtorrent-desktop',
|
||||||
|
GITHUB_URL_ISSUES: 'https://github.com/feross/webtorrent-desktop/issues',
|
||||||
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/feross/webtorrent-desktop/master',
|
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/feross/webtorrent-desktop/master',
|
||||||
|
|
||||||
|
HOME_PAGE_URL: 'https://webtorrent.io',
|
||||||
|
|
||||||
|
IS_PORTABLE: isPortable(),
|
||||||
IS_PRODUCTION: isProduction(),
|
IS_PRODUCTION: isProduction(),
|
||||||
|
|
||||||
ROOT_PATH: __dirname,
|
ROOT_PATH: __dirname,
|
||||||
STATIC_PATH: path.join(__dirname, 'static'),
|
STATIC_PATH: path.join(__dirname, 'static'),
|
||||||
|
|
||||||
SOUND_ADD: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'add.wav'),
|
|
||||||
volume: 0.2
|
|
||||||
},
|
|
||||||
SOUND_DELETE: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'delete.wav'),
|
|
||||||
volume: 0.1
|
|
||||||
},
|
|
||||||
SOUND_DISABLE: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'disable.wav'),
|
|
||||||
volume: 0.2
|
|
||||||
},
|
|
||||||
SOUND_DONE: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'done.wav'),
|
|
||||||
volume: 0.2
|
|
||||||
},
|
|
||||||
SOUND_ENABLE: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'enable.wav'),
|
|
||||||
volume: 0.2
|
|
||||||
},
|
|
||||||
SOUND_ERROR: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'error.wav'),
|
|
||||||
volume: 0.2
|
|
||||||
},
|
|
||||||
SOUND_PLAY: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'play.wav'),
|
|
||||||
volume: 0.2
|
|
||||||
},
|
|
||||||
SOUND_STARTUP: {
|
|
||||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'startup.wav'),
|
|
||||||
volume: 0.4
|
|
||||||
},
|
|
||||||
|
|
||||||
WINDOW_ABOUT: 'file://' + path.join(__dirname, 'renderer', 'about.html'),
|
WINDOW_ABOUT: 'file://' + path.join(__dirname, 'renderer', 'about.html'),
|
||||||
|
WINDOW_MAIN: 'file://' + path.join(__dirname, 'renderer', 'main.html'),
|
||||||
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, 'renderer', 'webtorrent.html'),
|
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, 'renderer', 'webtorrent.html'),
|
||||||
WINDOW_MAIN: 'file://' + path.join(__dirname, 'renderer', 'main.html')
|
|
||||||
|
WINDOW_MIN_HEIGHT: 38 + (120 * 2), // header height + 2 torrents
|
||||||
|
WINDOW_MIN_WIDTH: 425
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigPath () {
|
||||||
|
if (isPortable()) {
|
||||||
|
return PORTABLE_PATH
|
||||||
|
} else {
|
||||||
|
return path.dirname(appConfig.filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPortable () {
|
||||||
|
try {
|
||||||
|
return process.platform === 'win32' && isProduction() && !!fs.statSync(PORTABLE_PATH)
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProduction () {
|
function isProduction () {
|
||||||
@@ -74,7 +69,7 @@ function isProduction () {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
return !/\/Electron\.app\/Contents\/MacOS\/Electron$/.test(process.execPath)
|
return !/\/Electron\.app\//.test(process.execPath)
|
||||||
}
|
}
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
return !/\\electron\.exe$/.test(process.execPath)
|
return !/\\electron\.exe$/.test(process.execPath)
|
||||||
|
|||||||
@@ -11,5 +11,4 @@ function init () {
|
|||||||
productName: config.APP_NAME,
|
productName: config.APP_NAME,
|
||||||
submitURL: config.CRASH_REPORT_URL
|
submitURL: config.CRASH_REPORT_URL
|
||||||
})
|
})
|
||||||
console.log('crash reporter started')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
init
|
|
||||||
}
|
|
||||||
|
|
||||||
var electron = require('electron')
|
|
||||||
var get = require('simple-get')
|
|
||||||
|
|
||||||
var config = require('../config')
|
|
||||||
var log = require('./log')
|
|
||||||
var windows = require('./windows')
|
|
||||||
|
|
||||||
var autoUpdater = electron.autoUpdater
|
|
||||||
|
|
||||||
function init () {
|
|
||||||
autoUpdater.on('error', function (err) {
|
|
||||||
log.error('App update error: ' + err.message || err)
|
|
||||||
})
|
|
||||||
|
|
||||||
autoUpdater.setFeedURL(config.AUTO_UPDATE_URL)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We always check for updates on app startup. To keep app startup fast, we delay this
|
|
||||||
* first check so it happens when there is less going on.
|
|
||||||
*/
|
|
||||||
setTimeout(checkForUpdates, config.AUTO_UPDATE_CHECK_STARTUP_DELAY)
|
|
||||||
|
|
||||||
autoUpdater.on('checking-for-update', () => log('Checking for app update'))
|
|
||||||
autoUpdater.on('update-available', () => log('App update available'))
|
|
||||||
autoUpdater.on('update-not-available', () => log('App update not available'))
|
|
||||||
autoUpdater.on('update-downloaded', function (e, releaseNotes, releaseName, releaseDate, updateURL) {
|
|
||||||
log('App update downloaded: ', releaseName, updateURL)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkForUpdates () {
|
|
||||||
// Electron's built-in auto updater only supports Mac and Windows, for now
|
|
||||||
if (process.platform !== 'linux') {
|
|
||||||
return autoUpdater.checkForUpdates()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're on Linux, we have to do it ourselves
|
|
||||||
get.concat(config.AUTO_UPDATE_URL, function (err, res, data) {
|
|
||||||
if (err) return log('Error checking for app update: ' + err.message)
|
|
||||||
if (![200, 204].includes(res.statusCode)) return log('Error checking for app update, got HTTP ' + res.statusCode)
|
|
||||||
if (res.statusCode !== 200) return
|
|
||||||
|
|
||||||
var obj = JSON.parse(data)
|
|
||||||
windows.main.send('dispatch', 'updateAvailable', obj.version)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,8 @@ module.exports = {
|
|||||||
|
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
|
var config = require('../config')
|
||||||
|
|
||||||
function install () {
|
function install () {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
installDarwin()
|
installDarwin()
|
||||||
@@ -42,6 +44,12 @@ function installDarwin () {
|
|||||||
|
|
||||||
function uninstallDarwin () {}
|
function uninstallDarwin () {}
|
||||||
|
|
||||||
|
var EXEC_COMMAND = [ process.execPath ]
|
||||||
|
|
||||||
|
if (!config.IS_PRODUCTION) {
|
||||||
|
EXEC_COMMAND.push(config.ROOT_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
function installWin32 () {
|
function installWin32 () {
|
||||||
var Registry = require('winreg')
|
var Registry = require('winreg')
|
||||||
|
|
||||||
@@ -49,8 +57,8 @@ function installWin32 () {
|
|||||||
|
|
||||||
var iconPath = path.join(process.resourcesPath, 'app.asar.unpacked', 'static', 'WebTorrentFile.ico')
|
var iconPath = path.join(process.resourcesPath, 'app.asar.unpacked', 'static', 'WebTorrentFile.ico')
|
||||||
|
|
||||||
registerProtocolHandlerWin32('magnet', 'URL:BitTorrent Magnet URL', iconPath, process.execPath)
|
registerProtocolHandlerWin32('magnet', 'URL:BitTorrent Magnet URL', iconPath, EXEC_COMMAND)
|
||||||
registerFileHandlerWin32('.torrent', 'io.webtorrent.torrent', 'BitTorrent Document', iconPath, process.execPath)
|
registerFileHandlerWin32('.torrent', 'io.webtorrent.torrent', 'BitTorrent Document', iconPath, EXEC_COMMAND)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To add a protocol handler, the following keys must be added to the Windows registry:
|
* To add a protocol handler, the following keys must be added to the Windows registry:
|
||||||
@@ -108,7 +116,7 @@ function installWin32 () {
|
|||||||
hive: Registry.HKCU,
|
hive: Registry.HKCU,
|
||||||
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
|
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
|
||||||
})
|
})
|
||||||
commandKey.set('', Registry.REG_SZ, '"' + command + '" "%1"', done)
|
commandKey.set('', Registry.REG_SZ, `${commandToArgs(command)} "%1"`, done)
|
||||||
}
|
}
|
||||||
|
|
||||||
function done (err) {
|
function done (err) {
|
||||||
@@ -169,7 +177,7 @@ function installWin32 () {
|
|||||||
hive: Registry.HKCU,
|
hive: Registry.HKCU,
|
||||||
key: '\\Software\\Classes\\' + id + '\\shell\\open\\command'
|
key: '\\Software\\Classes\\' + id + '\\shell\\open\\command'
|
||||||
})
|
})
|
||||||
commandKey.set('', Registry.REG_SZ, '"' + command + '" "%1"', done)
|
commandKey.set('', Registry.REG_SZ, `${commandToArgs(command)} "%1"`, done)
|
||||||
}
|
}
|
||||||
|
|
||||||
function done (err) {
|
function done (err) {
|
||||||
@@ -181,8 +189,8 @@ function installWin32 () {
|
|||||||
function uninstallWin32 () {
|
function uninstallWin32 () {
|
||||||
var Registry = require('winreg')
|
var Registry = require('winreg')
|
||||||
|
|
||||||
unregisterProtocolHandlerWin32('magnet', process.execPath)
|
unregisterProtocolHandlerWin32('magnet', EXEC_COMMAND)
|
||||||
unregisterFileHandlerWin32('.torrent', 'io.webtorrent.torrent', process.execPath)
|
unregisterFileHandlerWin32('.torrent', 'io.webtorrent.torrent', EXEC_COMMAND)
|
||||||
|
|
||||||
function unregisterProtocolHandlerWin32 (protocol, command) {
|
function unregisterProtocolHandlerWin32 (protocol, command) {
|
||||||
getCommand()
|
getCommand()
|
||||||
@@ -193,18 +201,18 @@ function uninstallWin32 () {
|
|||||||
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
|
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
|
||||||
})
|
})
|
||||||
commandKey.get('', function (err, item) {
|
commandKey.get('', function (err, item) {
|
||||||
if (!err && item.value.indexOf(command) >= 0) {
|
if (!err && item.value.indexOf(commandToArgs(command)) >= 0) {
|
||||||
eraseProtocol()
|
destroyProtocol()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraseProtocol () {
|
function destroyProtocol () {
|
||||||
var protocolKey = new Registry({
|
var protocolKey = new Registry({
|
||||||
hive: Registry.HKCU,
|
hive: Registry.HKCU,
|
||||||
key: '\\Software\\Classes\\' + protocol
|
key: '\\Software\\Classes\\' + protocol
|
||||||
})
|
})
|
||||||
protocolKey.erase(function () {})
|
protocolKey.destroy(function () {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +224,7 @@ function uninstallWin32 () {
|
|||||||
hive: Registry.HKCU, // HKEY_CURRENT_USER
|
hive: Registry.HKCU, // HKEY_CURRENT_USER
|
||||||
key: '\\Software\\Classes\\' + id
|
key: '\\Software\\Classes\\' + id
|
||||||
})
|
})
|
||||||
idKey.erase(getExt)
|
idKey.destroy(getExt)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExt () {
|
function getExt () {
|
||||||
@@ -226,24 +234,27 @@ function uninstallWin32 () {
|
|||||||
})
|
})
|
||||||
extKey.get('', function (err, item) {
|
extKey.get('', function (err, item) {
|
||||||
if (!err && item.value === id) {
|
if (!err && item.value === id) {
|
||||||
eraseExt()
|
destroyExt()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraseExt () {
|
function destroyExt () {
|
||||||
var extKey = new Registry({
|
var extKey = new Registry({
|
||||||
hive: Registry.HKCU, // HKEY_CURRENT_USER
|
hive: Registry.HKCU, // HKEY_CURRENT_USER
|
||||||
key: '\\Software\\Classes\\' + ext
|
key: '\\Software\\Classes\\' + ext
|
||||||
})
|
})
|
||||||
extKey.erase(function () {})
|
extKey.destroy(function () {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function commandToArgs (command) {
|
||||||
|
return command.map((arg) => `"${arg}"`).join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
function installLinux () {
|
function installLinux () {
|
||||||
var fs = require('fs')
|
var fs = require('fs-extra')
|
||||||
var mkdirp = require('mkdirp')
|
|
||||||
var os = require('os')
|
var os = require('os')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
@@ -261,14 +272,14 @@ function installLinux () {
|
|||||||
function writeDesktopFile (err, desktopFile) {
|
function writeDesktopFile (err, desktopFile) {
|
||||||
if (err) return log.error(err.message)
|
if (err) return log.error(err.message)
|
||||||
|
|
||||||
var appPath = config.IS_PRODUCTION ? path.dirname(process.execPath) : config.ROOT_PATH
|
var appPath = config.IS_PRODUCTION
|
||||||
var execPath = process.execPath + (config.IS_PRODUCTION ? '' : ' \.')
|
? path.dirname(process.execPath)
|
||||||
var tryExecPath = process.execPath
|
: config.ROOT_PATH
|
||||||
|
|
||||||
desktopFile = desktopFile.replace(/\$APP_NAME/g, config.APP_NAME)
|
desktopFile = desktopFile.replace(/\$APP_NAME/g, config.APP_NAME)
|
||||||
desktopFile = desktopFile.replace(/\$APP_PATH/g, appPath)
|
desktopFile = desktopFile.replace(/\$APP_PATH/g, appPath)
|
||||||
desktopFile = desktopFile.replace(/\$EXEC_PATH/g, execPath)
|
desktopFile = desktopFile.replace(/\$EXEC_PATH/g, EXEC_COMMAND.join(' '))
|
||||||
desktopFile = desktopFile.replace(/\$TRY_EXEC_PATH/g, tryExecPath)
|
desktopFile = desktopFile.replace(/\$TRY_EXEC_PATH/g, process.execPath)
|
||||||
|
|
||||||
var desktopFilePath = path.join(
|
var desktopFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
@@ -277,7 +288,7 @@ function installLinux () {
|
|||||||
'applications',
|
'applications',
|
||||||
'webtorrent-desktop.desktop'
|
'webtorrent-desktop.desktop'
|
||||||
)
|
)
|
||||||
mkdirp(path.dirname(desktopFilePath))
|
fs.mkdirp(path.dirname(desktopFilePath))
|
||||||
fs.writeFile(desktopFilePath, desktopFile, function (err) {
|
fs.writeFile(desktopFilePath, desktopFile, function (err) {
|
||||||
if (err) return log.error(err.message)
|
if (err) return log.error(err.message)
|
||||||
})
|
})
|
||||||
@@ -298,7 +309,7 @@ function installLinux () {
|
|||||||
'icons',
|
'icons',
|
||||||
'webtorrent-desktop.png'
|
'webtorrent-desktop.png'
|
||||||
)
|
)
|
||||||
mkdirp(path.dirname(iconFilePath))
|
fs.mkdirp(path.dirname(iconFilePath))
|
||||||
fs.writeFile(iconFilePath, iconFile, function (err) {
|
fs.writeFile(iconFilePath, iconFile, function (err) {
|
||||||
if (err) return log.error(err.message)
|
if (err) return log.error(err.message)
|
||||||
})
|
})
|
||||||
@@ -308,7 +319,7 @@ function installLinux () {
|
|||||||
function uninstallLinux () {
|
function uninstallLinux () {
|
||||||
var os = require('os')
|
var os = require('os')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var rimraf = require('rimraf')
|
var fs = require('fs-extra')
|
||||||
|
|
||||||
var desktopFilePath = path.join(
|
var desktopFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
@@ -317,7 +328,7 @@ function uninstallLinux () {
|
|||||||
'applications',
|
'applications',
|
||||||
'webtorrent-desktop.desktop'
|
'webtorrent-desktop.desktop'
|
||||||
)
|
)
|
||||||
rimraf.sync(desktopFilePath)
|
fs.removeSync(desktopFilePath)
|
||||||
|
|
||||||
var iconFilePath = path.join(
|
var iconFilePath = path.join(
|
||||||
os.homedir(),
|
os.homedir(),
|
||||||
@@ -326,5 +337,5 @@ function uninstallLinux () {
|
|||||||
'icons',
|
'icons',
|
||||||
'webtorrent-desktop.png'
|
'webtorrent-desktop.png'
|
||||||
)
|
)
|
||||||
rimraf.sync(iconFilePath)
|
fs.removeSync(iconFilePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
console.time('init')
|
||||||
|
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
|
|
||||||
var app = electron.app
|
var app = electron.app
|
||||||
var ipcMain = electron.ipcMain
|
var ipcMain = electron.ipcMain
|
||||||
|
|
||||||
var autoUpdater = require('./auto-updater')
|
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var crashReporter = require('../crash-reporter')
|
var crashReporter = require('../crash-reporter')
|
||||||
var handlers = require('./handlers')
|
var handlers = require('./handlers')
|
||||||
@@ -12,8 +13,9 @@ var log = require('./log')
|
|||||||
var menu = require('./menu')
|
var menu = require('./menu')
|
||||||
var shortcuts = require('./shortcuts')
|
var shortcuts = require('./shortcuts')
|
||||||
var squirrelWin32 = require('./squirrel-win32')
|
var squirrelWin32 = require('./squirrel-win32')
|
||||||
var windows = require('./windows')
|
|
||||||
var tray = require('./tray')
|
var tray = require('./tray')
|
||||||
|
var updater = require('./updater')
|
||||||
|
var windows = require('./windows')
|
||||||
|
|
||||||
var shouldQuit = false
|
var shouldQuit = false
|
||||||
var argv = sliceArgv(process.argv)
|
var argv = sliceArgv(process.argv)
|
||||||
@@ -37,6 +39,11 @@ if (!shouldQuit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
|
if (config.IS_PORTABLE) {
|
||||||
|
app.setPath('userData', config.CONFIG_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isReady = false // app ready, windows can be created
|
||||||
app.ipcReady = false // main window has finished loading and IPC is ready
|
app.ipcReady = false // main window has finished loading and IPC is ready
|
||||||
app.isQuitting = false
|
app.isQuitting = false
|
||||||
|
|
||||||
@@ -48,21 +55,24 @@ function init () {
|
|||||||
|
|
||||||
app.on('will-finish-launching', function () {
|
app.on('will-finish-launching', function () {
|
||||||
crashReporter.init()
|
crashReporter.init()
|
||||||
autoUpdater.init()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('ready', function () {
|
app.on('ready', function () {
|
||||||
menu.init()
|
isReady = true
|
||||||
|
|
||||||
windows.createMainWindow()
|
windows.createMainWindow()
|
||||||
windows.createWebTorrentHiddenWindow()
|
windows.createWebTorrentHiddenWindow()
|
||||||
|
menu.init()
|
||||||
shortcuts.init()
|
shortcuts.init()
|
||||||
tray.init()
|
|
||||||
handlers.install()
|
// To keep app startup fast, some code is delayed.
|
||||||
|
setTimeout(delayedInit, config.DELAYED_INIT)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('ipcReady', function () {
|
app.on('ipcReady', function () {
|
||||||
log('Command line args:', argv)
|
log('Command line args:', argv)
|
||||||
processArgv(argv)
|
processArgv(argv)
|
||||||
|
console.timeEnd('init')
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('before-quit', function (e) {
|
app.on('before-quit', function (e) {
|
||||||
@@ -76,10 +86,16 @@ function init () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
windows.createMainWindow()
|
if (isReady) windows.createMainWindow()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function delayedInit () {
|
||||||
|
tray.init()
|
||||||
|
handlers.install()
|
||||||
|
updater.init()
|
||||||
|
}
|
||||||
|
|
||||||
function onOpen (e, torrentId) {
|
function onOpen (e, torrentId) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
@@ -116,11 +132,11 @@ function sliceArgv (argv) {
|
|||||||
function processArgv (argv) {
|
function processArgv (argv) {
|
||||||
argv.forEach(function (arg) {
|
argv.forEach(function (arg) {
|
||||||
if (arg === '-n') {
|
if (arg === '-n') {
|
||||||
windows.main.send('dispatch', 'showCreateTorrent')
|
menu.showOpenSeedFiles()
|
||||||
} else if (arg === '-o') {
|
} else if (arg === '-o') {
|
||||||
windows.main.send('dispatch', 'showOpenTorrentFile')
|
menu.showOpenTorrentFile()
|
||||||
} else if (arg === '-u') {
|
} else if (arg === '-u') {
|
||||||
windows.main.send('showOpenTorrentAddress')
|
menu.showOpenTorrentAddress()
|
||||||
} else if (arg.startsWith('-psn')) {
|
} else if (arg.startsWith('-psn')) {
|
||||||
// Ignore OS X launchd "process serial number" argument
|
// Ignore OS X launchd "process serial number" argument
|
||||||
// More: https://github.com/feross/webtorrent-desktop/issues/214
|
// More: https://github.com/feross/webtorrent-desktop/issues/214
|
||||||
|
|||||||
111
main/ipc.js
111
main/ipc.js
@@ -6,25 +6,29 @@ var electron = require('electron')
|
|||||||
|
|
||||||
var app = electron.app
|
var app = electron.app
|
||||||
var ipcMain = electron.ipcMain
|
var ipcMain = electron.ipcMain
|
||||||
var powerSaveBlocker = electron.powerSaveBlocker
|
|
||||||
|
|
||||||
var log = require('./log')
|
var log = require('./log')
|
||||||
var menu = require('./menu')
|
var menu = require('./menu')
|
||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
var shortcuts = require('./shortcuts')
|
var shortcuts = require('./shortcuts')
|
||||||
|
var vlc = require('./vlc')
|
||||||
|
|
||||||
// has to be a number, not a boolean, and undefined throws an error
|
// has to be a number, not a boolean, and undefined throws an error
|
||||||
var powerSaveBlockID = 0
|
var powerSaveBlockerId = 0
|
||||||
|
|
||||||
|
// messages from the main process, to be sent once the WebTorrent process starts
|
||||||
|
var messageQueueMainToWebTorrent = []
|
||||||
|
|
||||||
|
// holds a ChildProcess while we're playing a video in VLC, null otherwise
|
||||||
|
var vlcProcess
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
ipcMain.on('ipcReady', function (e) {
|
ipcMain.on('ipcReady', function (e) {
|
||||||
|
windows.main.show()
|
||||||
app.ipcReady = true
|
app.ipcReady = true
|
||||||
app.emit('ipcReady')
|
app.emit('ipcReady')
|
||||||
windows.main.show()
|
|
||||||
console.timeEnd('init')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
var messageQueueMainToWebTorrent = []
|
|
||||||
ipcMain.on('ipcReadyWebTorrent', function (e) {
|
ipcMain.on('ipcReadyWebTorrent', function (e) {
|
||||||
app.ipcReadyWebTorrent = true
|
app.ipcReadyWebTorrent = true
|
||||||
log('sending %d queued messages from the main win to the webtorrent window',
|
log('sending %d queued messages from the main win to the webtorrent window',
|
||||||
@@ -36,14 +40,13 @@ function init () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('showOpenTorrentFile', menu.showOpenTorrentFile)
|
ipcMain.on('showOpenTorrentFile', menu.showOpenTorrentFile)
|
||||||
ipcMain.on('showCreateTorrent', menu.showCreateTorrent)
|
|
||||||
|
|
||||||
ipcMain.on('setBounds', function (e, bounds, maximize) {
|
ipcMain.on('setBounds', function (e, bounds, maximize) {
|
||||||
setBounds(bounds, maximize)
|
setBounds(bounds, maximize)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('setAspectRatio', function (e, aspectRatio, extraSize) {
|
ipcMain.on('setAspectRatio', function (e, aspectRatio) {
|
||||||
setAspectRatio(aspectRatio, extraSize)
|
setAspectRatio(aspectRatio)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('setBadge', function (e, text) {
|
ipcMain.on('setBadge', function (e, text) {
|
||||||
@@ -63,31 +66,88 @@ function init () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('openItem', function (e, path) {
|
ipcMain.on('openItem', function (e, path) {
|
||||||
log('opening file or folder: ' + path)
|
log('open item: ' + path)
|
||||||
electron.shell.openItem(path)
|
electron.shell.openItem(path)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('showItemInFolder', function (e, path) {
|
||||||
|
log('show item in folder: ' + path)
|
||||||
|
electron.shell.showItemInFolder(path)
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.on('blockPowerSave', blockPowerSave)
|
ipcMain.on('blockPowerSave', blockPowerSave)
|
||||||
|
|
||||||
ipcMain.on('unblockPowerSave', unblockPowerSave)
|
ipcMain.on('unblockPowerSave', unblockPowerSave)
|
||||||
|
|
||||||
ipcMain.on('onPlayerOpen', function () {
|
ipcMain.on('onPlayerOpen', function () {
|
||||||
menu.onPlayerOpen()
|
menu.onPlayerOpen()
|
||||||
shortcuts.registerPlayerShortcuts()
|
shortcuts.onPlayerOpen()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('onPlayerClose', function () {
|
ipcMain.on('onPlayerClose', function () {
|
||||||
menu.onPlayerClose()
|
menu.onPlayerClose()
|
||||||
shortcuts.unregisterPlayerShortcuts()
|
shortcuts.onPlayerOpen()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('focusWindow', function (e, windowName) {
|
ipcMain.on('focusWindow', function (e, windowName) {
|
||||||
windows.focusWindow(windows[windowName])
|
windows.focusWindow(windows[windowName])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('downloadFinished', function (e, filePath) {
|
||||||
|
if (app.dock) {
|
||||||
|
// Bounces the Downloads stack if the filePath is inside the Downloads folder.
|
||||||
|
app.dock.downloadFinished(filePath)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('checkForVLC', function (e) {
|
||||||
|
vlc.checkForVLC(function (isInstalled) {
|
||||||
|
windows.main.send('checkForVLC', isInstalled)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('vlcPlay', function (e, url) {
|
||||||
|
var args = ['--play-and-exit', '--quiet', url]
|
||||||
|
console.log('Running vlc ' + args.join(' '))
|
||||||
|
|
||||||
|
vlc.spawn(args, function (err, proc) {
|
||||||
|
if (err) windows.main.send('dispatch', 'vlcNotFound')
|
||||||
|
vlcProcess = proc
|
||||||
|
|
||||||
|
// If it works, close the modal after a second
|
||||||
|
var closeModalTimeout = setTimeout(() =>
|
||||||
|
windows.main.send('dispatch', 'exitModal'), 1000)
|
||||||
|
|
||||||
|
vlcProcess.on('close', function (code) {
|
||||||
|
clearTimeout(closeModalTimeout)
|
||||||
|
if (!vlcProcess) return // Killed
|
||||||
|
console.log('VLC exited with code ', code)
|
||||||
|
if (code === 0) {
|
||||||
|
windows.main.send('dispatch', 'backToList')
|
||||||
|
} else {
|
||||||
|
windows.main.send('dispatch', 'vlcNotFound')
|
||||||
|
}
|
||||||
|
vlcProcess = null
|
||||||
|
})
|
||||||
|
|
||||||
|
vlcProcess.on('error', function (e) {
|
||||||
|
console.log('VLC error', e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('vlcQuit', function () {
|
||||||
|
if (!vlcProcess) return
|
||||||
|
console.log('Killing VLC, pid ' + vlcProcess.pid)
|
||||||
|
vlcProcess.kill('SIGKILL') // kill -9
|
||||||
|
vlcProcess = null
|
||||||
|
})
|
||||||
|
|
||||||
// Capture all events
|
// Capture all events
|
||||||
var oldEmit = ipcMain.emit
|
var oldEmit = ipcMain.emit
|
||||||
ipcMain.emit = function (name, e, ...args) {
|
ipcMain.emit = function (name, e, ...args) {
|
||||||
// Relay messages between the main window and the WebTorrent hidden window
|
// Relay messages between the main window and the WebTorrent hidden window
|
||||||
if (name.startsWith('wt-')) {
|
if (name.startsWith('wt-') && !app.isQuitting) {
|
||||||
if (e.sender.browserWindowOptions.title === 'webtorrent-hidden-window') {
|
if (e.sender.browserWindowOptions.title === 'webtorrent-hidden-window') {
|
||||||
// Send message to main window
|
// Send message to main window
|
||||||
windows.main.send(name, ...args)
|
windows.main.send(name, ...args)
|
||||||
@@ -140,23 +200,32 @@ function setBounds (bounds, maximize) {
|
|||||||
// Assuming we're not maximized or maximizing, set the window size
|
// Assuming we're not maximized or maximizing, set the window size
|
||||||
if (!willBeMaximized) {
|
if (!willBeMaximized) {
|
||||||
log('setBounds: setting bounds to ' + JSON.stringify(bounds))
|
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
|
||||||
|
var scr = electron.screen.getDisplayMatching(windows.main.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))
|
||||||
|
}
|
||||||
windows.main.setBounds(bounds, true)
|
windows.main.setBounds(bounds, true)
|
||||||
} else {
|
} else {
|
||||||
log('setBounds: not setting bounds because of window maximization')
|
log('setBounds: not setting bounds because of window maximization')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAspectRatio (aspectRatio, extraSize) {
|
function setAspectRatio (aspectRatio) {
|
||||||
log('setAspectRatio %o %o', aspectRatio, extraSize)
|
log('setAspectRatio %o', aspectRatio)
|
||||||
if (windows.main) {
|
if (windows.main) {
|
||||||
windows.main.setAspectRatio(aspectRatio, extraSize)
|
windows.main.setAspectRatio(aspectRatio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display string in dock badging area (OS X)
|
// Display string in dock badging area (OS X)
|
||||||
function setBadge (text) {
|
function setBadge (text) {
|
||||||
log('setBadge %s', text)
|
log('setBadge %s', text)
|
||||||
if (app.dock) app.dock.setBadge(String(text))
|
if (app.dock) {
|
||||||
|
app.dock.setBadge(String(text))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show progress bar. Valid range is [0, 1]. Remove when < 0; indeterminate when > 1.
|
// Show progress bar. Valid range is [0, 1]. Remove when < 0; indeterminate when > 1.
|
||||||
@@ -168,13 +237,13 @@ function setProgress (progress) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function blockPowerSave () {
|
function blockPowerSave () {
|
||||||
powerSaveBlockID = powerSaveBlocker.start('prevent-display-sleep')
|
powerSaveBlockerId = electron.powerSaveBlocker.start('prevent-display-sleep')
|
||||||
log('blockPowerSave %d', powerSaveBlockID)
|
log('blockPowerSave %d', powerSaveBlockerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function unblockPowerSave () {
|
function unblockPowerSave () {
|
||||||
if (powerSaveBlocker.isStarted(powerSaveBlockID)) {
|
if (electron.powerSaveBlocker.isStarted(powerSaveBlockerId)) {
|
||||||
powerSaveBlocker.stop(powerSaveBlockID)
|
electron.powerSaveBlocker.stop(powerSaveBlockerId)
|
||||||
log('unblockPowerSave %d', powerSaveBlockID)
|
log('unblockPowerSave %d', powerSaveBlockerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
279
main/menu.js
279
main/menu.js
@@ -1,11 +1,14 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
init,
|
init,
|
||||||
|
onPlayerClose,
|
||||||
|
onPlayerOpen,
|
||||||
onToggleFullScreen,
|
onToggleFullScreen,
|
||||||
onWindowHide,
|
onWindowHide,
|
||||||
onWindowShow,
|
onWindowShow,
|
||||||
onPlayerOpen,
|
|
||||||
onPlayerClose,
|
// TODO: move these out of menu.js -- they don't belong here
|
||||||
showCreateTorrent,
|
showOpenSeedFiles,
|
||||||
|
showOpenTorrentAddress,
|
||||||
showOpenTorrentFile,
|
showOpenTorrentFile,
|
||||||
toggleFullScreen
|
toggleFullScreen
|
||||||
}
|
}
|
||||||
@@ -18,20 +21,26 @@ var config = require('../config')
|
|||||||
var log = require('./log')
|
var log = require('./log')
|
||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
|
|
||||||
var appMenu, dockMenu
|
var appMenu
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
appMenu = electron.Menu.buildFromTemplate(getAppMenuTemplate())
|
appMenu = electron.Menu.buildFromTemplate(getAppMenuTemplate())
|
||||||
electron.Menu.setApplicationMenu(appMenu)
|
electron.Menu.setApplicationMenu(appMenu)
|
||||||
|
|
||||||
dockMenu = electron.Menu.buildFromTemplate(getDockMenuTemplate())
|
if (app.dock) {
|
||||||
if (app.dock) app.dock.setMenu(dockMenu)
|
var dockMenu = electron.Menu.buildFromTemplate(getDockMenuTemplate())
|
||||||
|
app.dock.setMenu(dockMenu)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFullScreen (flag) {
|
function toggleFullScreen (flag) {
|
||||||
log('toggleFullScreen %s', flag)
|
log('toggleFullScreen %s', flag)
|
||||||
if (windows.main && windows.main.isVisible()) {
|
if (windows.main && windows.main.isVisible()) {
|
||||||
flag = flag != null ? flag : !windows.main.isFullScreen()
|
flag = flag != null ? flag : !windows.main.isFullScreen()
|
||||||
|
if (flag) {
|
||||||
|
// Allows the window to use the full screen in fullscreen mode (OS X).
|
||||||
|
windows.main.setAspectRatio(0)
|
||||||
|
}
|
||||||
windows.main.setFullScreen(flag)
|
windows.main.setFullScreen(flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +55,25 @@ function toggleFloatOnTop (flag) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleDevTools () {
|
||||||
|
log('toggleDevTools')
|
||||||
|
if (windows.main) {
|
||||||
|
windows.main.toggleDevTools()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWebTorrentWindow () {
|
||||||
|
log('showWebTorrentWindow')
|
||||||
|
windows.webtorrent.show()
|
||||||
|
windows.webtorrent.webContents.openDevTools({ detach: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
function playPause () {
|
||||||
|
if (windows.main) {
|
||||||
|
windows.main.send('dispatch', 'playPause')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function increaseVolume () {
|
function increaseVolume () {
|
||||||
if (windows.main) {
|
if (windows.main) {
|
||||||
windows.main.send('dispatch', 'changeVolume', 0.1)
|
windows.main.send('dispatch', 'changeVolume', 0.1)
|
||||||
@@ -58,23 +86,12 @@ function decreaseVolume () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDevTools () {
|
function openSubtitles () {
|
||||||
log('toggleDevTools')
|
|
||||||
if (windows.main) {
|
if (windows.main) {
|
||||||
windows.main.toggleDevTools()
|
windows.main.send('dispatch', 'openSubtitles')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWebTorrentWindow () {
|
|
||||||
windows.webtorrent.show()
|
|
||||||
windows.webtorrent.webContents.openDevTools({ detach: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFakeDevice (device) {
|
|
||||||
log('addFakeDevice %s', device)
|
|
||||||
windows.main.send('addFakeDevice', device)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowShow () {
|
function onWindowShow () {
|
||||||
log('onWindowShow')
|
log('onWindowShow')
|
||||||
getMenuItem('Full Screen').enabled = true
|
getMenuItem('Full Screen').enabled = true
|
||||||
@@ -88,13 +105,19 @@ function onWindowHide () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onPlayerOpen () {
|
function onPlayerOpen () {
|
||||||
|
log('onPlayerOpen')
|
||||||
|
getMenuItem('Play/Pause').enabled = true
|
||||||
getMenuItem('Increase Volume').enabled = true
|
getMenuItem('Increase Volume').enabled = true
|
||||||
getMenuItem('Decrease Volume').enabled = true
|
getMenuItem('Decrease Volume').enabled = true
|
||||||
|
getMenuItem('Add Subtitles File...').enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPlayerClose () {
|
function onPlayerClose () {
|
||||||
|
log('onPlayerClose')
|
||||||
|
getMenuItem('Play/Pause').enabled = false
|
||||||
getMenuItem('Increase Volume').enabled = false
|
getMenuItem('Increase Volume').enabled = false
|
||||||
getMenuItem('Decrease Volume').enabled = false
|
getMenuItem('Decrease Volume').enabled = false
|
||||||
|
getMenuItem('Add Subtitles File...').enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function onToggleFullScreen (isFullScreen) {
|
function onToggleFullScreen (isFullScreen) {
|
||||||
@@ -113,19 +136,29 @@ function getMenuItem (label) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompts the user for a file or folder, then makes a torrent out of the data
|
// Prompts the user for a file, then creates a torrent. Only allows a single file
|
||||||
function showCreateTorrent () {
|
// selection.
|
||||||
// Allow only a single selection
|
function showOpenSeedFile () {
|
||||||
// To create a multi-file torrent, the user must select a folder
|
electron.dialog.showOpenDialog({
|
||||||
|
title: 'Select a file for the torrent file.',
|
||||||
|
properties: [ 'openFile' ]
|
||||||
|
}, function (selectedPaths) {
|
||||||
|
if (!Array.isArray(selectedPaths)) return
|
||||||
|
var selectedPath = selectedPaths[0]
|
||||||
|
windows.main.send('dispatch', 'showCreateTorrent', selectedPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompts the user for a file or directory, then creates a torrent. Only allows a single
|
||||||
|
// selection. To create a multi-file torrent, the user must select a directory.
|
||||||
|
function showOpenSeedFiles () {
|
||||||
electron.dialog.showOpenDialog({
|
electron.dialog.showOpenDialog({
|
||||||
title: 'Select a file or folder for the torrent file.',
|
title: 'Select a file or folder for the torrent file.',
|
||||||
properties: [ 'openFile', 'openDirectory' ]
|
properties: [ 'openFile', 'openDirectory' ]
|
||||||
}, function (filenames) {
|
}, function (selectedPaths) {
|
||||||
if (!Array.isArray(filenames)) return
|
if (!Array.isArray(selectedPaths)) return
|
||||||
var options = {
|
var selectedPath = selectedPaths[0]
|
||||||
files: filenames[0]
|
windows.main.send('dispatch', 'showCreateTorrent', selectedPath)
|
||||||
}
|
|
||||||
windows.main.send('dispatch', 'createTorrent', options)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,10 +168,10 @@ function showOpenTorrentFile () {
|
|||||||
title: 'Select a .torrent file to open.',
|
title: 'Select a .torrent file to open.',
|
||||||
filters: [{ name: 'Torrent Files', extensions: ['torrent'] }],
|
filters: [{ name: 'Torrent Files', extensions: ['torrent'] }],
|
||||||
properties: [ 'openFile', 'multiSelections' ]
|
properties: [ 'openFile', 'multiSelections' ]
|
||||||
}, function (filenames) {
|
}, function (selectedPaths) {
|
||||||
if (!Array.isArray(filenames)) return
|
if (!Array.isArray(selectedPaths)) return
|
||||||
filenames.forEach(function (filename) {
|
selectedPaths.forEach(function (selectedPath) {
|
||||||
windows.main.send('dispatch', 'addTorrent', filename)
|
windows.main.send('dispatch', 'addTorrent', selectedPath)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -149,44 +182,38 @@ function showOpenTorrentAddress () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAppMenuTemplate () {
|
function getAppMenuTemplate () {
|
||||||
var fileMenu = [
|
|
||||||
{
|
|
||||||
label: 'Create New Torrent...',
|
|
||||||
accelerator: 'CmdOrCtrl+N',
|
|
||||||
click: showCreateTorrent
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Open Torrent File...',
|
|
||||||
accelerator: 'CmdOrCtrl+O',
|
|
||||||
click: showOpenTorrentFile
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Open Torrent Address...',
|
|
||||||
accelerator: 'CmdOrCtrl+U',
|
|
||||||
click: showOpenTorrentAddress
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: process.platform === 'windows' ? 'Close' : 'Close Window',
|
|
||||||
accelerator: 'CmdOrCtrl+W',
|
|
||||||
role: 'close'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// File > Quit for Linux users with distros where the system tray is broken
|
|
||||||
if (process.platform === 'linux') {
|
|
||||||
fileMenu.push({
|
|
||||||
label: 'Quit',
|
|
||||||
click: () => app.quit()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var template = [
|
var template = [
|
||||||
{
|
{
|
||||||
label: 'File',
|
label: 'File',
|
||||||
submenu: fileMenu
|
submenu: [
|
||||||
|
{
|
||||||
|
label: process.platform === 'darwin'
|
||||||
|
? 'Create New Torrent...'
|
||||||
|
: 'Create New Torrent from Folder...',
|
||||||
|
accelerator: 'CmdOrCtrl+N',
|
||||||
|
click: showOpenSeedFiles
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Open Torrent File...',
|
||||||
|
accelerator: 'CmdOrCtrl+O',
|
||||||
|
click: showOpenTorrentFile
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Open Torrent Address...',
|
||||||
|
accelerator: 'CmdOrCtrl+U',
|
||||||
|
click: showOpenTorrentAddress
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: process.platform === 'win32'
|
||||||
|
? 'Close'
|
||||||
|
: 'Close Window',
|
||||||
|
accelerator: 'CmdOrCtrl+W',
|
||||||
|
role: 'close'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
@@ -232,21 +259,6 @@ function getAppMenuTemplate () {
|
|||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Increase Volume',
|
|
||||||
accelerator: 'CmdOrCtrl+Up',
|
|
||||||
click: increaseVolume,
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Decrease Volume',
|
|
||||||
accelerator: 'CmdOrCtrl+Down',
|
|
||||||
click: decreaseVolume,
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Developer',
|
label: 'Developer',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -263,30 +275,42 @@ function getAppMenuTemplate () {
|
|||||||
? 'Alt+Command+P'
|
? 'Alt+Command+P'
|
||||||
: 'Ctrl+Shift+P',
|
: 'Ctrl+Shift+P',
|
||||||
click: showWebTorrentWindow
|
click: showWebTorrentWindow
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Add Fake Airplay',
|
|
||||||
click: () => addFakeDevice('airplay')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Add Fake Chromecast',
|
|
||||||
click: () => addFakeDevice('chromecast')
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Window',
|
label: 'Playback',
|
||||||
role: 'window',
|
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Minimize',
|
label: 'Play/Pause',
|
||||||
accelerator: 'CmdOrCtrl+M',
|
accelerator: 'CmdOrCtrl+P',
|
||||||
role: 'minimize'
|
click: playPause,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Increase Volume',
|
||||||
|
accelerator: 'CmdOrCtrl+Up',
|
||||||
|
click: increaseVolume,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Decrease Volume',
|
||||||
|
accelerator: 'CmdOrCtrl+Down',
|
||||||
|
click: decreaseVolume,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Add Subtitles File...',
|
||||||
|
click: openSubtitles,
|
||||||
|
enabled: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -296,7 +320,7 @@ function getAppMenuTemplate () {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Learn more about ' + config.APP_NAME,
|
label: 'Learn more about ' + config.APP_NAME,
|
||||||
click: () => electron.shell.openExternal('https://webtorrent.io')
|
click: () => electron.shell.openExternal(config.HOME_PAGE_URL)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Contribute on GitHub',
|
label: 'Contribute on GitHub',
|
||||||
@@ -307,14 +331,14 @@ function getAppMenuTemplate () {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Report an Issue...',
|
label: 'Report an Issue...',
|
||||||
click: () => electron.shell.openExternal(config.GITHUB_URL + '/issues')
|
click: () => electron.shell.openExternal(config.GITHUB_URL_ISSUES)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
// WebTorrent menu (OS X)
|
// Add WebTorrent app menu (OS X)
|
||||||
template.unshift({
|
template.unshift({
|
||||||
label: config.APP_NAME,
|
label: config.APP_NAME,
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -358,17 +382,35 @@ function getAppMenuTemplate () {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Window menu (OS X)
|
// Add Window menu (OS X)
|
||||||
template[4].submenu.push(
|
template.splice(5, 0, {
|
||||||
{
|
label: 'Window',
|
||||||
type: 'separator'
|
role: 'window',
|
||||||
},
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Bring All to Front',
|
label: 'Minimize',
|
||||||
role: 'front'
|
accelerator: 'CmdOrCtrl+M',
|
||||||
}
|
role: 'minimize'
|
||||||
)
|
},
|
||||||
} else {
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bring All to Front',
|
||||||
|
role: 'front'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Linux and Windows it is not possible to open both folders and files
|
||||||
|
if (process.platform === 'linux' || process.platform === 'win32') {
|
||||||
|
// File menu (Windows, Linux)
|
||||||
|
template[0].submenu.unshift({
|
||||||
|
label: 'Create New Torrent from File...',
|
||||||
|
click: showOpenSeedFile
|
||||||
|
})
|
||||||
|
|
||||||
// Help menu (Windows, Linux)
|
// Help menu (Windows, Linux)
|
||||||
template[4].submenu.push(
|
template[4].submenu.push(
|
||||||
{
|
{
|
||||||
@@ -380,6 +422,15 @@ function getAppMenuTemplate () {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// Add "File > Quit" menu item so Linux distros where the system tray icon is missing
|
||||||
|
// will have a way to quit the app.
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
// File menu (Linux)
|
||||||
|
template[0].submenu.push({
|
||||||
|
label: 'Quit',
|
||||||
|
click: () => app.quit()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
@@ -389,7 +440,7 @@ function getDockMenuTemplate () {
|
|||||||
{
|
{
|
||||||
label: 'Create New Torrent...',
|
label: 'Create New Torrent...',
|
||||||
accelerator: 'CmdOrCtrl+N',
|
accelerator: 'CmdOrCtrl+N',
|
||||||
click: showCreateTorrent
|
click: showOpenSeedFiles
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Open Torrent File...',
|
label: 'Open Torrent File...',
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
init,
|
init,
|
||||||
registerPlayerShortcuts,
|
onPlayerClose,
|
||||||
unregisterPlayerShortcuts
|
onPlayerOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
var localShortcut = require('electron-localshortcut')
|
|
||||||
|
|
||||||
var globalShortcut = electron.globalShortcut
|
|
||||||
|
|
||||||
var menu = require('./menu')
|
var menu = require('./menu')
|
||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
// ⌘+Shift+F is an alternative fullscreen shortcut to the ones defined in menu.js.
|
var localShortcut = require('electron-localshortcut')
|
||||||
// Electron does not support multiple accelerators for a single menu item, so this
|
|
||||||
// is registered separately here.
|
// Alternate shortcuts. Most shortcuts are registered in menu,js, but Electron
|
||||||
|
// does not support multiple shortcuts for a single menu item.
|
||||||
localShortcut.register('CmdOrCtrl+Shift+F', menu.toggleFullScreen)
|
localShortcut.register('CmdOrCtrl+Shift+F', menu.toggleFullScreen)
|
||||||
|
localShortcut.register('Space', () => windows.main.send('dispatch', 'playPause'))
|
||||||
|
|
||||||
|
// Hidden shortcuts, i.e. not shown in the menu
|
||||||
|
localShortcut.register('Esc', () => windows.main.send('dispatch', 'escapeBack'))
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerPlayerShortcuts () {
|
function onPlayerOpen () {
|
||||||
// Special "media key" for play/pause, available on some keyboards
|
// Register special "media key" for play/pause, available on some keyboards
|
||||||
globalShortcut.register('MediaPlayPause', () => windows.main.send('dispatch', 'playPause'))
|
electron.globalShortcut.register(
|
||||||
|
'MediaPlayPause',
|
||||||
|
() => windows.main.send('dispatch', 'playPause')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregisterPlayerShortcuts () {
|
function onPlayerClose () {
|
||||||
globalShortcut.unregister('MediaPlayPause')
|
electron.globalShortcut.unregister('MediaPlayPause')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ var electron = require('electron')
|
|||||||
var fs = require('fs')
|
var fs = require('fs')
|
||||||
var os = require('os')
|
var os = require('os')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var pathExists = require('path-exists')
|
|
||||||
|
|
||||||
var app = electron.app
|
var app = electron.app
|
||||||
|
|
||||||
@@ -118,7 +117,8 @@ function updateShortcuts (cb) {
|
|||||||
var desktopShortcutPath = path.join(homeDir, 'Desktop', 'WebTorrent.lnk')
|
var desktopShortcutPath = path.join(homeDir, 'Desktop', 'WebTorrent.lnk')
|
||||||
// Check if the desktop shortcut has been previously deleted and and keep it deleted
|
// Check if the desktop shortcut has been previously deleted and and keep it deleted
|
||||||
// if it was
|
// if it was
|
||||||
pathExists(desktopShortcutPath).then(function (desktopShortcutExists) {
|
fs.access(desktopShortcutPath, function (err) {
|
||||||
|
var desktopShortcutExists = !err
|
||||||
createShortcuts(function () {
|
createShortcuts(function () {
|
||||||
if (desktopShortcutExists) {
|
if (desktopShortcutExists) {
|
||||||
cb()
|
cb()
|
||||||
|
|||||||
39
main/tray.js
39
main/tray.js
@@ -1,13 +1,13 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
init
|
init,
|
||||||
|
hasTray
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cp = require('child_process')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
|
|
||||||
var app = electron.app
|
var app = electron.app
|
||||||
var Menu = electron.Menu
|
|
||||||
var Tray = electron.Tray
|
|
||||||
|
|
||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
|
|
||||||
@@ -17,7 +17,23 @@ function init () {
|
|||||||
// OS X has no tray icon
|
// OS X has no tray icon
|
||||||
if (process.platform === 'darwin') return
|
if (process.platform === 'darwin') return
|
||||||
|
|
||||||
trayIcon = new Tray(path.join(__dirname, '..', 'static', 'WebTorrentSmall.png'))
|
// On Linux, asynchronously check for libappindicator1
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
checkLinuxTraySupport(function (supportsTray) {
|
||||||
|
if (supportsTray) createTrayIcon()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows always supports minimize-to-tray
|
||||||
|
if (process.platform === 'win32') createTrayIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTray () {
|
||||||
|
return !!trayIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTrayIcon () {
|
||||||
|
trayIcon = new electron.Tray(path.join(__dirname, '..', 'static', 'WebTorrentSmall.png'))
|
||||||
|
|
||||||
// On Windows, left click to open the app, right click for context menu
|
// On Windows, left click to open the app, right click for context menu
|
||||||
// On Linux, any click (right or left) opens the context menu
|
// On Linux, any click (right or left) opens the context menu
|
||||||
@@ -29,6 +45,18 @@ function init () {
|
|||||||
windows.main.on('hide', updateTrayMenu)
|
windows.main.on('hide', updateTrayMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkLinuxTraySupport (cb) {
|
||||||
|
// 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(false)
|
||||||
|
// Unfortunately there's no cleaner way, as far as I can tell, to check
|
||||||
|
// whether a debian package is installed:
|
||||||
|
cb(stdout.endsWith('\tinstall\n'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function updateTrayMenu () {
|
function updateTrayMenu () {
|
||||||
var showHideMenuItem
|
var showHideMenuItem
|
||||||
if (windows.main.isVisible()) {
|
if (windows.main.isVisible()) {
|
||||||
@@ -36,7 +64,7 @@ function updateTrayMenu () {
|
|||||||
} else {
|
} else {
|
||||||
showHideMenuItem = { label: 'Show', click: showApp }
|
showHideMenuItem = { label: 'Show', click: showApp }
|
||||||
}
|
}
|
||||||
var contextMenu = Menu.buildFromTemplate([
|
var contextMenu = electron.Menu.buildFromTemplate([
|
||||||
showHideMenuItem,
|
showHideMenuItem,
|
||||||
{ label: 'Quit', click: () => app.quit() }
|
{ label: 'Quit', click: () => app.quit() }
|
||||||
])
|
])
|
||||||
@@ -49,4 +77,5 @@ function showApp () {
|
|||||||
|
|
||||||
function hideApp () {
|
function hideApp () {
|
||||||
windows.main.hide()
|
windows.main.hide()
|
||||||
|
windows.main.send('dispatch', 'backToList')
|
||||||
}
|
}
|
||||||
|
|||||||
71
main/updater.js
Normal file
71
main/updater.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
module.exports = {
|
||||||
|
init
|
||||||
|
}
|
||||||
|
|
||||||
|
var electron = require('electron')
|
||||||
|
var get = require('simple-get')
|
||||||
|
|
||||||
|
var config = require('../config')
|
||||||
|
var log = require('./log')
|
||||||
|
var windows = require('./windows')
|
||||||
|
|
||||||
|
function init () {
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
initLinux()
|
||||||
|
} else {
|
||||||
|
initDarwinWin32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Electron auto-updater does not support Linux yet, so manually check for updates and
|
||||||
|
// `show the user a modal notification.
|
||||||
|
function initLinux () {
|
||||||
|
get.concat(config.AUTO_UPDATE_URL, onResponse)
|
||||||
|
|
||||||
|
function onResponse (err, res, data) {
|
||||||
|
if (err) return log(`Update error: ${err.message}`)
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
// Update available
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data)
|
||||||
|
} catch (err) {
|
||||||
|
return log(`Update error: Invalid JSON response: ${err.message}`)
|
||||||
|
}
|
||||||
|
windows.main.send('dispatch', 'updateAvailable', data.version)
|
||||||
|
} else if (res.statusCode === 204) {
|
||||||
|
// No update available
|
||||||
|
} else {
|
||||||
|
// Unexpected status code
|
||||||
|
log(`Update error: Unexpected status code: ${res.statusCode}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDarwinWin32 () {
|
||||||
|
electron.autoUpdater.on(
|
||||||
|
'error',
|
||||||
|
(err) => log.error(`Update error: ${err.message}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
electron.autoUpdater.on(
|
||||||
|
'checking-for-update',
|
||||||
|
() => log('Checking for update')
|
||||||
|
)
|
||||||
|
|
||||||
|
electron.autoUpdater.on(
|
||||||
|
'update-available',
|
||||||
|
() => log('Update available')
|
||||||
|
)
|
||||||
|
|
||||||
|
electron.autoUpdater.on(
|
||||||
|
'update-not-available',
|
||||||
|
() => log('Update not available')
|
||||||
|
)
|
||||||
|
|
||||||
|
electron.autoUpdater.on(
|
||||||
|
'update-downloaded',
|
||||||
|
(e, notes, name, date, url) => log(`Update downloaded: ${name}: ${url}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
electron.autoUpdater.setFeedURL(config.AUTO_UPDATE_URL)
|
||||||
|
}
|
||||||
22
main/vlc.js
Normal file
22
main/vlc.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
module.exports = {
|
||||||
|
checkForVLC,
|
||||||
|
spawn
|
||||||
|
}
|
||||||
|
|
||||||
|
var cp = require('child_process')
|
||||||
|
var vlcCommand = require('vlc-command')
|
||||||
|
|
||||||
|
// Finds if VLC is installed on Mac, Windows, or Linux.
|
||||||
|
// Calls back with true or false: whether VLC was detected
|
||||||
|
function checkForVLC (cb) {
|
||||||
|
vlcCommand((err) => cb(!err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawns VLC with child_process.spawn() to return a ChildProcess object
|
||||||
|
// Calls back with (err, childProcess)
|
||||||
|
function spawn (args, cb) {
|
||||||
|
vlcCommand(function (err, vlcPath) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
cb(null, cp.spawn(vlcPath, args))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -9,8 +9,11 @@ var windows = module.exports = {
|
|||||||
|
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
|
|
||||||
|
var app = electron.app
|
||||||
|
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var menu = require('./menu')
|
var menu = require('./menu')
|
||||||
|
var tray = require('./tray')
|
||||||
|
|
||||||
function createAboutWindow () {
|
function createAboutWindow () {
|
||||||
if (windows.about) {
|
if (windows.about) {
|
||||||
@@ -67,13 +70,20 @@ function createWebTorrentHiddenWindow () {
|
|||||||
|
|
||||||
// Prevent killing the WebTorrent process
|
// Prevent killing the WebTorrent process
|
||||||
win.on('close', function (e) {
|
win.on('close', function (e) {
|
||||||
if (!electron.app.isQuitting) {
|
if (!app.isQuitting) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
win.hide()
|
win.hide()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
win.once('closed', function () {
|
||||||
|
windows.webtorrent = null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var HEADER_HEIGHT = 37
|
||||||
|
var TORRENT_HEIGHT = 100
|
||||||
|
|
||||||
function createMainWindow () {
|
function createMainWindow () {
|
||||||
if (windows.main) {
|
if (windows.main) {
|
||||||
return focusWindow(windows.main)
|
return focusWindow(windows.main)
|
||||||
@@ -81,17 +91,20 @@ function createMainWindow () {
|
|||||||
var win = windows.main = new electron.BrowserWindow({
|
var win = windows.main = new electron.BrowserWindow({
|
||||||
backgroundColor: '#1E1E1E',
|
backgroundColor: '#1E1E1E',
|
||||||
darkTheme: true, // Forces dark theme (GTK+3)
|
darkTheme: true, // Forces dark theme (GTK+3)
|
||||||
icon: config.APP_ICON + '.png',
|
icon: config.APP_ICON + 'Smaller.png', // Window and Volume Mixer icon.
|
||||||
minWidth: 425,
|
minWidth: config.WINDOW_MIN_WIDTH,
|
||||||
minHeight: 38 + (120 * 2), // header height + 2 torrents
|
minHeight: config.WINDOW_MIN_HEIGHT,
|
||||||
show: false, // Hide window until DOM finishes loading
|
show: false, // Hide window until renderer sends 'ipcReady' event
|
||||||
title: config.APP_WINDOW_TITLE,
|
title: config.APP_WINDOW_TITLE,
|
||||||
titleBarStyle: 'hidden-inset', // Hide OS chrome, except traffic light buttons (OS X)
|
titleBarStyle: 'hidden-inset', // Hide OS chrome, except traffic light buttons (OS X)
|
||||||
useContentSize: true, // Specify web page size without OS chrome
|
useContentSize: true, // Specify web page size without OS chrome
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 38 + (120 * 5) // header height + 4 torrents
|
height: HEADER_HEIGHT + (TORRENT_HEIGHT * 6) // header height + 5 torrents
|
||||||
})
|
})
|
||||||
win.loadURL(config.WINDOW_MAIN)
|
win.loadURL(config.WINDOW_MAIN)
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
win.setSheetOffset(HEADER_HEIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
win.webContents.on('dom-ready', function () {
|
win.webContents.on('dom-ready', function () {
|
||||||
menu.onToggleFullScreen()
|
menu.onToggleFullScreen()
|
||||||
@@ -104,10 +117,12 @@ function createMainWindow () {
|
|||||||
win.on('leave-full-screen', () => menu.onToggleFullScreen(false))
|
win.on('leave-full-screen', () => menu.onToggleFullScreen(false))
|
||||||
|
|
||||||
win.on('close', function (e) {
|
win.on('close', function (e) {
|
||||||
if (!electron.app.isQuitting) {
|
if (process.platform !== 'darwin' && !tray.hasTray()) {
|
||||||
|
app.quit()
|
||||||
|
} else if (!app.isQuitting) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
win.send('dispatch', 'pause')
|
|
||||||
win.hide()
|
win.hide()
|
||||||
|
win.send('dispatch', 'backToList')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
50
package.json
50
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "webtorrent-desktop",
|
"name": "webtorrent-desktop",
|
||||||
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
|
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
|
||||||
"version": "0.3.2",
|
"version": "0.5.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Feross Aboukhadijeh",
|
"name": "Feross Aboukhadijeh",
|
||||||
"email": "feross@feross.org",
|
"email": "feross@feross.org",
|
||||||
@@ -15,44 +15,56 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"airplay-js": "guerrerocarlos/node-airplay-js",
|
"airplay-js": "guerrerocarlos/node-airplay-js",
|
||||||
"application-config": "^0.2.0",
|
"application-config": "^0.2.1",
|
||||||
"application-config-path": "^0.1.0",
|
"async": "^2.0.0-rc.5",
|
||||||
"bitfield": "^1.0.2",
|
"bitfield": "^1.0.2",
|
||||||
"chromecasts": "^1.8.0",
|
"chromecasts": "^1.8.0",
|
||||||
"create-torrent": "^3.22.1",
|
"concat-stream": "^1.5.1",
|
||||||
|
"create-torrent": "^3.24.5",
|
||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"dlnacasts": "^0.0.3",
|
"dlnacasts": "^0.1.0",
|
||||||
"drag-drop": "^2.11.0",
|
"drag-drop": "^2.11.0",
|
||||||
"electron-localshortcut": "^0.6.0",
|
"electron-localshortcut": "^0.6.0",
|
||||||
"electron-prebuilt": "0.37.5",
|
"electron-prebuilt": "1.0.2",
|
||||||
|
"fs-extra": "^0.27.0",
|
||||||
"hyperx": "^2.0.2",
|
"hyperx": "^2.0.2",
|
||||||
|
"iso-639-1": "^1.2.1",
|
||||||
|
"languagedetect": "^1.1.1",
|
||||||
"main-loop": "^3.2.0",
|
"main-loop": "^3.2.0",
|
||||||
"mkdirp": "^0.5.1",
|
|
||||||
"musicmetadata": "^2.0.2",
|
"musicmetadata": "^2.0.2",
|
||||||
"network-address": "^1.1.0",
|
"network-address": "^1.1.0",
|
||||||
"path-exists": "^2.1.0",
|
|
||||||
"prettier-bytes": "^1.0.1",
|
"prettier-bytes": "^1.0.1",
|
||||||
"rimraf": "^2.5.2",
|
|
||||||
"simple-get": "^2.0.0",
|
"simple-get": "^2.0.0",
|
||||||
"upload-element": "^1.0.1",
|
"srt-to-vtt": "^1.1.1",
|
||||||
"virtual-dom": "^2.1.1",
|
"virtual-dom": "^2.1.1",
|
||||||
"webtorrent": "^0.90.0",
|
"vlc-command": "^1.0.1",
|
||||||
"winreg": "feross/node-winreg"
|
"webtorrent": "0.x",
|
||||||
|
"winreg": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"cross-zip": "^2.0.1",
|
||||||
"electron-osx-sign": "^0.3.0",
|
"electron-osx-sign": "^0.3.0",
|
||||||
"electron-packager": "^6.0.0",
|
"electron-packager": "^7.0.0",
|
||||||
"electron-winstaller": "feross/windows-installer#build",
|
"electron-winstaller": "^2.3.0",
|
||||||
"gh-release": "^2.0.3",
|
"gh-release": "^2.0.3",
|
||||||
"nobin-debian-installer": "^0.0.8",
|
"minimist": "^1.2.0",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"nobin-debian-installer": "^0.0.9",
|
||||||
|
"open": "0.0.5",
|
||||||
"plist": "^1.2.0",
|
"plist": "^1.2.0",
|
||||||
"standard": "^6.0.5"
|
"rimraf": "^2.5.2",
|
||||||
|
"run-series": "^1.1.4",
|
||||||
|
"standard": "^7.0.0"
|
||||||
},
|
},
|
||||||
"homepage": "https://webtorrent.io",
|
"homepage": "https://webtorrent.io",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"desktop",
|
"desktop",
|
||||||
"electron",
|
"electron",
|
||||||
"electron-app",
|
"electron-app",
|
||||||
|
"hybrid webtorrent client",
|
||||||
|
"mad science",
|
||||||
|
"torrent client",
|
||||||
|
"torrent",
|
||||||
"webtorrent"
|
"webtorrent"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -67,10 +79,10 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "node ./bin/clean.js",
|
"clean": "node ./bin/clean.js",
|
||||||
"package": "npm install && npm prune && npm dedupe && node ./bin/package.js",
|
"open-config": "node ./bin/open-config.js",
|
||||||
"size": "npm run package -- darwin && du -ch dist/WebTorrent-darwin-x64 | grep total",
|
"package": "node ./bin/package.js",
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"test": "standard",
|
"test": "standard && node ./bin/check-deps.js",
|
||||||
"update-authors": "./bin/update-authors.sh"
|
"update-authors": "./bin/update-authors.sh"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@
|
|||||||
<body>
|
<body>
|
||||||
<img src="../static/WebTorrent.png">
|
<img src="../static/WebTorrent.png">
|
||||||
<h1>WebTorrent</h1>
|
<h1>WebTorrent</h1>
|
||||||
<p>Version <script>document.write(require('../package.json').version)</script></p>
|
<p>
|
||||||
|
Version <script>document.write(require('../package.json').version)</script>
|
||||||
|
(<script>document.write(require('webtorrent/package.json').version)</script>)
|
||||||
|
</p>
|
||||||
<p><script>document.write(require('../config').APP_COPYRIGHT)</script></p>
|
<p><script>document.write(require('../config').APP_COPYRIGHT)</script></p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -113,28 +113,28 @@ table {
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
.float-right {
|
||||||
* BUTTONS
|
float: right;
|
||||||
*/
|
|
||||||
|
|
||||||
a,
|
|
||||||
i {
|
|
||||||
cursor: default;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a:not(.disabled):hover,
|
.expand-collapse.expanded::before {
|
||||||
i:not(.disabled):hover {
|
content: '▲'
|
||||||
-webkit-filter: brightness(1.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.expand-collapse.collapsed::before {
|
||||||
width: 40px;
|
content: '▼'
|
||||||
height: 40px;
|
}
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 22px;
|
.expand-collapse::before {
|
||||||
transition: all 0.1s ease-out;
|
margin-right: 5px;
|
||||||
text-align: center;
|
}
|
||||||
|
|
||||||
|
.expand-collapse.collapsed {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsed {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -266,64 +266,137 @@ i:not(.disabled):hover {
|
|||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.4);
|
box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.4);
|
||||||
background-color: white;
|
background-color: #eee;
|
||||||
color: #222;
|
color: #222;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent-modal input,
|
.modal label {
|
||||||
.open-torrent-address-modal input {
|
font-weight: bold;
|
||||||
width: calc(100% - 100px)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent-modal .torrent-attribute {
|
.open-torrent-address-modal input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-torrent-page {
|
||||||
|
padding: 10px 25px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-torrent-page .torrent-attribute {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent-modal .torrent-attribute>* {
|
.create-torrent-page .torrent-attribute>* {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent-modal .torrent-attribute label {
|
.create-torrent-page .torrent-attribute label {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-torrent-modal .torrent-attribute div {
|
.create-torrent-page .torrent-attribute>div {
|
||||||
font-family: Consolas, monospace;
|
width: calc(100% - 90px);
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.create-torrent-page .torrent-attribute div {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-torrent-page .torrent-attribute textarea {
|
||||||
|
width: calc(100% - 80px);
|
||||||
|
height: 80px;
|
||||||
|
color: #eee;
|
||||||
|
background-color: transparent;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-torrent-page textarea.torrent-trackers {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-torrent-page input.torrent-is-private {
|
||||||
|
width: initial;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BUTTONS
|
* BUTTONS
|
||||||
|
* See https://www.google.com/design/spec/components/buttons.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button {
|
a,
|
||||||
|
i { /* Links and icons */
|
||||||
|
cursor: default;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not(.disabled):hover,
|
||||||
|
i:not(.disabled):hover { /* Show they're clickable without pointer: cursor */
|
||||||
|
-webkit-filter: brightness(1.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-round { /* Circular icon buttons, used on <i> tags */
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 22px;
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button { /* Rectangular text buttons */
|
||||||
background: transparent;
|
background: transparent;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
cursor: pointer;
|
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary {
|
button.button-flat {
|
||||||
color: #0cf;
|
color: #222;
|
||||||
|
padding: 7px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button.button-flat.light {
|
||||||
-webkit-filter: brightness(1.1);
|
color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
button.button-flat:hover,
|
||||||
-webkit-filter: brightness(1.1);
|
button.button-flat:focus { /* Material design: focused */
|
||||||
text-shadow: none;
|
background-color: rgba(153, 153, 153, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.button-flat:active { /* Material design: pressed */
|
||||||
|
background-color: rgba(153, 153, 153, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.button-raised {
|
||||||
|
background-color: #2196f3;
|
||||||
|
color: #eee;
|
||||||
|
padding: 7px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.button-raised:hover,
|
||||||
|
button.button-raised:focus {
|
||||||
|
background-color: #38a0f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.button-raised:active {
|
||||||
|
background-color: #51abf6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -336,7 +409,7 @@ input {
|
|||||||
padding: 6px;
|
padding: 6px;
|
||||||
border: 1px solid #bbb;
|
border: 1px solid #bbb;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: 1px 1px 1px 0px rgba(0,0,0,0.1);
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -355,7 +428,7 @@ input {
|
|||||||
|
|
||||||
.torrent,
|
.torrent,
|
||||||
.torrent-placeholder {
|
.torrent-placeholder {
|
||||||
height: 120px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent:not(:last-child) {
|
.torrent:not(:last-child) {
|
||||||
@@ -368,9 +441,9 @@ input {
|
|||||||
|
|
||||||
.torrent .metadata {
|
.torrent .metadata {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 25px;
|
||||||
left: 20px;
|
left: 15px;
|
||||||
right: 20px;
|
right: 15px;
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px);
|
||||||
text-shadow: rgba(0, 0, 0, 0.5) 0 0 4px;
|
text-shadow: rgba(0, 0, 0, 0.5) 0 0 4px;
|
||||||
}
|
}
|
||||||
@@ -380,12 +453,15 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.torrent .metadata span:not(:last-child)::after {
|
.torrent .metadata span:not(:last-child)::after {
|
||||||
content: ' — ';
|
content: ' • ';
|
||||||
|
opacity: 0.7;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent .buttons {
|
.torrent .buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 25px;
|
top: 29px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: none;
|
display: none;
|
||||||
@@ -468,17 +544,11 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.torrent .name {
|
.torrent .name {
|
||||||
font-size: 1.5em;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent .status,
|
|
||||||
.torrent .status2 {
|
|
||||||
font-size: 1em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TORRENT LIST: DRAG-DROP TARGET
|
* TORRENT LIST: DRAG-DROP TARGET
|
||||||
*/
|
*/
|
||||||
@@ -522,11 +592,7 @@ body.drag .app::after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.torrent-details {
|
.torrent-details {
|
||||||
padding: 8em 20px 20px 20px;
|
padding: 8em 12px 20px 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.torrent-details .open-folder {
|
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-details table {
|
.torrent-details table {
|
||||||
@@ -540,8 +606,11 @@ body.drag .app::after {
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-details tr:hover,
|
.torrent-details td {
|
||||||
.torrent-details .open-folder:hover {
|
vertical-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.torrent-details tr:hover {
|
||||||
background-color: rgba(200, 200, 200, 0.3);
|
background-color: rgba(200, 200, 200, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,16 +620,16 @@ body.drag .app::after {
|
|||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent-details td.col-icon {
|
.torrent-details td .icon {
|
||||||
width: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.torrent-details td.col-icon .icon {
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.torrent-details td.col-icon {
|
||||||
|
width: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
.torrent-details td.col-name {
|
.torrent-details td.col-name {
|
||||||
width: auto;
|
width: auto;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -576,6 +645,11 @@ body.drag .app::after {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.torrent-details td.col-select {
|
||||||
|
width: 2em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PLAYER
|
* PLAYER
|
||||||
*/
|
*/
|
||||||
@@ -677,18 +751,28 @@ body.drag .app::after {
|
|||||||
|
|
||||||
.player-controls .device,
|
.player-controls .device,
|
||||||
.player-controls .fullscreen,
|
.player-controls .fullscreen,
|
||||||
|
.player-controls .closed-captions,
|
||||||
|
.player-controls .volume-icon,
|
||||||
.player-controls .back {
|
.player-controls .back {
|
||||||
display: block;
|
display: block;
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fix for overflowing captions icon
|
||||||
|
* https://github.com/feross/webtorrent-desktop/issues/467
|
||||||
|
*/
|
||||||
|
max-width: 22px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-controls .volume,
|
||||||
.player-controls .back {
|
.player-controls .back {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-controls .device,
|
.player-controls .device,
|
||||||
|
.player-controls .closed-captions,
|
||||||
.player-controls .fullscreen {
|
.player-controls .fullscreen {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
@@ -697,15 +781,52 @@ body.drag .app::after {
|
|||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-controls .volume-icon,
|
||||||
.player-controls .device {
|
.player-controls .device {
|
||||||
font-size: 18px; /* make the cast icons less huge */
|
font-size: 18px; /* make the cast icons less huge */
|
||||||
margin-top: 8px !important;
|
margin-top: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-controls .closed-captions.active,
|
||||||
.player-controls .device.active {
|
.player-controls .device.active {
|
||||||
color: #9af;
|
color: #9af;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-controls .volume {
|
||||||
|
display: block;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-controls .volume-icon {
|
||||||
|
float: left;
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-controls .volume-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 50px;
|
||||||
|
height: 3px;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: sub;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-controls .volume-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-color: #fff;
|
||||||
|
opacity: 1.0;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border: 1px solid #303233;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-controls .volume-slider:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.player .playback-bar:hover .loading-bar {
|
.player .playback-bar:hover .loading-bar {
|
||||||
height: 5px;
|
height: 5px;
|
||||||
}
|
}
|
||||||
@@ -716,6 +837,15 @@ body.drag .app::after {
|
|||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::cue {
|
||||||
|
background: none;
|
||||||
|
color: #FFF;
|
||||||
|
font: 24px;
|
||||||
|
line-height: 1.3em;
|
||||||
|
text-shadow: #000 -1px 0 1px, #000 1px 0 1px, #000 0 -1px 1px, #000 0 1px 1px, rgba(50, 50, 50, 0.5) 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CHROMECAST / AIRPLAY CONTROLS
|
* CHROMECAST / AIRPLAY CONTROLS
|
||||||
*/
|
*/
|
||||||
@@ -742,6 +872,29 @@ body.drag .app::after {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Subtitles list
|
||||||
|
*/
|
||||||
|
|
||||||
|
.subtitles-list {
|
||||||
|
position: fixed;
|
||||||
|
background: rgba(40, 40, 40, 0.8);
|
||||||
|
min-width: 100px;
|
||||||
|
bottom: 45px;
|
||||||
|
right: 3px;
|
||||||
|
transition: opacity 0.15s ease-out;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitles-list i {
|
||||||
|
font-size: 11px; /* make the cast icons less huge */
|
||||||
|
margin-right: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MEDIA OVERLAY / AUDIO DETAILS
|
* MEDIA OVERLAY / AUDIO DETAILS
|
||||||
*/
|
*/
|
||||||
@@ -837,3 +990,7 @@ body.drag .app::after {
|
|||||||
.error-popover .error:last-child {
|
.error-popover .error:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
color: #c44;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,8 @@ module.exports = {
|
|||||||
init,
|
init,
|
||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
playPause,
|
play,
|
||||||
|
pause,
|
||||||
seek,
|
seek,
|
||||||
setVolume
|
setVolume
|
||||||
}
|
}
|
||||||
@@ -81,9 +82,12 @@ function chromecastPlayer (player) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function playPause (callback) {
|
function play (callback) {
|
||||||
if (!state.playing.isPaused) player.pause(callback)
|
player.play(null, null, callback)
|
||||||
else player.play(null, null, callback)
|
}
|
||||||
|
|
||||||
|
function pause (callback) {
|
||||||
|
player.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
@@ -113,7 +117,8 @@ function chromecastPlayer (player) {
|
|||||||
return {
|
return {
|
||||||
player: player,
|
player: player,
|
||||||
open: open,
|
open: open,
|
||||||
playPause: playPause,
|
play: play,
|
||||||
|
pause: pause,
|
||||||
stop: stop,
|
stop: stop,
|
||||||
status: status,
|
status: status,
|
||||||
seek: seek,
|
seek: seek,
|
||||||
@@ -138,9 +143,12 @@ function airplayPlayer (player) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function playPause (callback) {
|
function play (callback) {
|
||||||
if (!state.playing.isPaused) player.rate(0, callback)
|
player.rate(1, callback)
|
||||||
else player.rate(1, callback)
|
}
|
||||||
|
|
||||||
|
function pause (callback) {
|
||||||
|
player.rate(0, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
@@ -172,7 +180,8 @@ function airplayPlayer (player) {
|
|||||||
return {
|
return {
|
||||||
player: player,
|
player: player,
|
||||||
open: open,
|
open: open,
|
||||||
playPause: playPause,
|
play: play,
|
||||||
|
pause: pause,
|
||||||
stop: stop,
|
stop: stop,
|
||||||
status: status,
|
status: status,
|
||||||
seek: seek,
|
seek: seek,
|
||||||
@@ -217,9 +226,12 @@ function dlnaPlayer (player) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function playPause (callback) {
|
function play (callback) {
|
||||||
if (!state.playing.isPaused) player.pause(callback)
|
player.play(null, null, callback)
|
||||||
else player.play(null, null, callback)
|
}
|
||||||
|
|
||||||
|
function pause (callback) {
|
||||||
|
player.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
@@ -253,7 +265,8 @@ function dlnaPlayer (player) {
|
|||||||
return {
|
return {
|
||||||
player: player,
|
player: player,
|
||||||
open: open,
|
open: open,
|
||||||
playPause: playPause,
|
play: play,
|
||||||
|
pause: pause,
|
||||||
stop: stop,
|
stop: stop,
|
||||||
status: status,
|
status: status,
|
||||||
seek: seek,
|
seek: seek,
|
||||||
@@ -317,10 +330,17 @@ function getDevice (location) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function playPause () {
|
function play () {
|
||||||
var device = getDevice()
|
var device = getDevice()
|
||||||
if (device) {
|
if (device) {
|
||||||
device.playPause(castCallback)
|
device.play(castCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause () {
|
||||||
|
var device = getDevice()
|
||||||
|
if (device) {
|
||||||
|
device.pause(castCallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function dispatcher (...args) {
|
|||||||
var json = JSON.stringify(args)
|
var json = JSON.stringify(args)
|
||||||
var handler = _dispatchers[json]
|
var handler = _dispatchers[json]
|
||||||
if (!handler) {
|
if (!handler) {
|
||||||
_dispatchers[json] = (e) => {
|
handler = _dispatchers[json] = (e) => {
|
||||||
// Don't click on whatever is below the button
|
// Don't click on whatever is below the button
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
// Don't regisiter clicks on disabled buttons
|
// Don't regisiter clicks on disabled buttons
|
||||||
|
|||||||
@@ -7,46 +7,56 @@ function LocationHistory () {
|
|||||||
this._pending = null
|
this._pending = null
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.go = function (page) {
|
LocationHistory.prototype.go = function (page, cb) {
|
||||||
console.log('go', page)
|
console.log('go', page)
|
||||||
this.clearForward()
|
this.clearForward()
|
||||||
this._go(page)
|
this._go(page, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype._go = function (page) {
|
LocationHistory.prototype._go = function (page, cb) {
|
||||||
if (this._pending) return
|
if (this._pending) return
|
||||||
if (page.onbeforeload) {
|
if (page.onbeforeload) {
|
||||||
this._pending = page
|
this._pending = page
|
||||||
page.onbeforeload((err) => {
|
page.onbeforeload((err) => {
|
||||||
if (this._pending !== page) return /* navigation was cancelled */
|
if (this._pending !== page) return /* navigation was cancelled */
|
||||||
this._pending = null
|
this._pending = null
|
||||||
if (err) return
|
if (err) {
|
||||||
|
if (cb) cb(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this._history.push(page)
|
this._history.push(page)
|
||||||
|
if (cb) cb()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this._history.push(page)
|
this._history.push(page)
|
||||||
|
if (cb) cb()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.back = function () {
|
LocationHistory.prototype.back = function (cb) {
|
||||||
if (this._history.length <= 1) return
|
if (this._history.length <= 1) return
|
||||||
|
|
||||||
var page = this._history.pop()
|
var page = this._history.pop()
|
||||||
|
|
||||||
if (page.onbeforeunload) {
|
if (page.onbeforeunload) {
|
||||||
|
// TODO: this is buggy. If the user clicks back twice, then those pages
|
||||||
|
// may end up in _forward in the wrong order depending on which onbeforeunload
|
||||||
|
// call finishes first.
|
||||||
page.onbeforeunload(() => {
|
page.onbeforeunload(() => {
|
||||||
this._forward.push(page)
|
this._forward.push(page)
|
||||||
|
if (cb) cb()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this._forward.push(page)
|
this._forward.push(page)
|
||||||
|
if (cb) cb()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.forward = function () {
|
LocationHistory.prototype.forward = function (cb) {
|
||||||
if (this._forward.length === 0) return
|
if (this._forward.length === 0) return
|
||||||
|
|
||||||
var page = this._forward.pop()
|
var page = this._forward.pop()
|
||||||
this._go(page)
|
this._go(page, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.clearForward = function () {
|
LocationHistory.prototype.clearForward = function () {
|
||||||
|
|||||||
73
renderer/lib/sound.js
Normal file
73
renderer/lib/sound.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
module.exports = {
|
||||||
|
preload,
|
||||||
|
play
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = require('../../config')
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
var VOLUME = 0.15
|
||||||
|
|
||||||
|
/* Cache of Audio elements, for instant playback */
|
||||||
|
var cache = {}
|
||||||
|
|
||||||
|
var sounds = {
|
||||||
|
ADD: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'add.wav'),
|
||||||
|
volume: VOLUME
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'delete.wav'),
|
||||||
|
volume: VOLUME
|
||||||
|
},
|
||||||
|
DISABLE: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'disable.wav'),
|
||||||
|
volume: VOLUME
|
||||||
|
},
|
||||||
|
DONE: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'done.wav'),
|
||||||
|
volume: VOLUME
|
||||||
|
},
|
||||||
|
ENABLE: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'enable.wav'),
|
||||||
|
volume: VOLUME
|
||||||
|
},
|
||||||
|
ERROR: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'error.wav'),
|
||||||
|
volume: VOLUME
|
||||||
|
},
|
||||||
|
PLAY: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'play.wav'),
|
||||||
|
volume: VOLUME
|
||||||
|
},
|
||||||
|
STARTUP: {
|
||||||
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'startup.wav'),
|
||||||
|
volume: VOLUME * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function preload () {
|
||||||
|
for (var name in sounds) {
|
||||||
|
if (!cache[name]) {
|
||||||
|
var sound = sounds[name]
|
||||||
|
var audio = cache[name] = new window.Audio()
|
||||||
|
audio.volume = sound.volume
|
||||||
|
audio.src = sound.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function play (name) {
|
||||||
|
var audio = cache[name]
|
||||||
|
if (!audio) {
|
||||||
|
var sound = sounds[name]
|
||||||
|
if (!sound) {
|
||||||
|
throw new Error('Invalid sound name')
|
||||||
|
}
|
||||||
|
audio = cache[name] = new window.Audio()
|
||||||
|
audio.volume = sound.volume
|
||||||
|
audio.src = sound.url
|
||||||
|
}
|
||||||
|
audio.currentTime = 0
|
||||||
|
audio.play()
|
||||||
|
}
|
||||||
@@ -15,13 +15,28 @@ function isPlayable (file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isVideo (file) {
|
function isVideo (file) {
|
||||||
var ext = path.extname(file.name)
|
var ext = path.extname(file.name).toLowerCase()
|
||||||
return ['.mp4', '.m4v', '.webm', '.mov', '.mkv'].indexOf(ext) !== -1
|
return [
|
||||||
|
'.avi',
|
||||||
|
'.m4v',
|
||||||
|
'.mkv',
|
||||||
|
'.mov',
|
||||||
|
'.mp4',
|
||||||
|
'.mpg',
|
||||||
|
'.ogv',
|
||||||
|
'.webm'
|
||||||
|
].includes(ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAudio (file) {
|
function isAudio (file) {
|
||||||
var ext = path.extname(file.name)
|
var ext = path.extname(file.name).toLowerCase()
|
||||||
return ['.mp3', '.aac', '.ogg', '.wav'].indexOf(ext) !== -1
|
return [
|
||||||
|
'.aac',
|
||||||
|
'.ac3',
|
||||||
|
'.mp3',
|
||||||
|
'.ogg',
|
||||||
|
'.wav'
|
||||||
|
].includes(ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlayableTorrent (torrentSummary) {
|
function isPlayableTorrent (torrentSummary) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function torrentPoster (torrent, cb) {
|
|||||||
|
|
||||||
function getLargestFileByExtension (torrent, extensions) {
|
function getLargestFileByExtension (torrent, extensions) {
|
||||||
var files = torrent.files.filter(function (file) {
|
var files = torrent.files.filter(function (file) {
|
||||||
var extname = path.extname(file.name)
|
var extname = path.extname(file.name).toLowerCase()
|
||||||
return extensions.indexOf(extname) !== -1
|
return extensions.indexOf(extname) !== -1
|
||||||
})
|
})
|
||||||
if (files.length === 0) return undefined
|
if (files.length === 0) return undefined
|
||||||
@@ -64,6 +64,8 @@ function torrentPosterFromVideo (file, torrent, cb) {
|
|||||||
|
|
||||||
server.destroy()
|
server.destroy()
|
||||||
|
|
||||||
|
if (buf.length === 0) return cb(new Error('Generated poster contains no data'))
|
||||||
|
|
||||||
cb(null, buf, '.jpg')
|
cb(null, buf, '.jpg')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
renderer/lib/torrent-summary.js
Normal file
24
renderer/lib/torrent-summary.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
getPosterPath,
|
||||||
|
getTorrentPath
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = require('path')
|
||||||
|
var config = require('../../config')
|
||||||
|
|
||||||
|
// Expects a torrentSummary
|
||||||
|
// Returns an absolute path to the torrent file, or null if unavailable
|
||||||
|
function getTorrentPath (torrentSummary) {
|
||||||
|
if (!torrentSummary || !torrentSummary.torrentFileName) return null
|
||||||
|
return path.join(config.CONFIG_TORRENT_PATH, torrentSummary.torrentFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expects a torrentSummary
|
||||||
|
// Returns an absolute path to the poster image, or null if unavailable
|
||||||
|
function getPosterPath (torrentSummary) {
|
||||||
|
if (!torrentSummary || !torrentSummary.posterFileName) return null
|
||||||
|
var posterPath = path.join(config.CONFIG_POSTER_PATH, torrentSummary.posterFileName)
|
||||||
|
// Work around a Chrome bug (reproduced in vanilla Chrome, not just Electron):
|
||||||
|
// Backslashes in URLS in CSS cause bizarre string encoding issues
|
||||||
|
return posterPath.replace(/\\/g, '/')
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
var os = require('os')
|
var electron = require('electron')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
|
var remote = electron.remote
|
||||||
|
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var LocationHistory = require('./lib/location-history')
|
var LocationHistory = require('./lib/location-history')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getInitialState,
|
getInitialState,
|
||||||
getDefaultPlayState,
|
getDefaultPlayState,
|
||||||
getDefaultSavedState
|
getDefaultSavedState,
|
||||||
|
getPlayingTorrentSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialState () {
|
function getInitialState () {
|
||||||
@@ -43,6 +46,7 @@ function getInitialState () {
|
|||||||
/*
|
/*
|
||||||
* Saved state is read from and written to a file every time the app runs.
|
* Saved state is read from and written to a file every time the app runs.
|
||||||
* It should be simple and minimal and must be JSON.
|
* It should be simple and minimal and must be JSON.
|
||||||
|
* It must never contain absolute paths since we have a portable app.
|
||||||
*
|
*
|
||||||
* Config path:
|
* Config path:
|
||||||
*
|
*
|
||||||
@@ -54,7 +58,12 @@ function getInitialState () {
|
|||||||
*
|
*
|
||||||
* Also accessible via `require('application-config')('WebTorrent').filePath`
|
* Also accessible via `require('application-config')('WebTorrent').filePath`
|
||||||
*/
|
*/
|
||||||
saved: {}
|
saved: {},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Getters, for convenience
|
||||||
|
*/
|
||||||
|
getPlayingTorrentSummary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +79,13 @@ function getDefaultPlayState () {
|
|||||||
isPaused: true,
|
isPaused: true,
|
||||||
isStalled: false,
|
isStalled: false,
|
||||||
lastTimeUpdate: 0, /* Unix time in ms */
|
lastTimeUpdate: 0, /* Unix time in ms */
|
||||||
mouseStationarySince: 0 /* Unix time in ms */
|
mouseStationarySince: 0, /* Unix time in ms */
|
||||||
|
subtitles: {
|
||||||
|
tracks: [], /* subtitle tracks, each {label, language, ...} */
|
||||||
|
selectedIndex: -1, /* current subtitle track */
|
||||||
|
showMenu: false /* popover menu, above the video */
|
||||||
|
},
|
||||||
|
aspectRatio: 0 /* aspect ratio of the video */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,10 +103,8 @@ function getDefaultSavedState () {
|
|||||||
torrentPath: 'bigBuckBunny.torrent',
|
torrentPath: 'bigBuckBunny.torrent',
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
'name': 'bbb_sunflower_1080p_30fps_normal.mp4',
|
length: 276134947,
|
||||||
'length': 276134947,
|
name: 'bbb_sunflower_1080p_30fps_normal.mp4'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 527
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -104,10 +117,8 @@ function getDefaultSavedState () {
|
|||||||
torrentPath: 'sintel.torrent',
|
torrentPath: 'sintel.torrent',
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
'name': 'sintel.mp4',
|
length: 129241752,
|
||||||
'length': 129241752,
|
name: 'sintel.mp4'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 987
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -120,10 +131,8 @@ function getDefaultSavedState () {
|
|||||||
torrentPath: 'tearsOfSteel.torrent',
|
torrentPath: 'tearsOfSteel.torrent',
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
'name': 'tears_of_steel_1080p.webm',
|
length: 571346576,
|
||||||
'length': 571346576,
|
name: 'tears_of_steel_1080p.webm'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 2180
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -136,62 +145,133 @@ function getDefaultSavedState () {
|
|||||||
torrentPath: 'cosmosLaundromat.torrent',
|
torrentPath: 'cosmosLaundromat.torrent',
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
'name': 'Cosmos Laundromat - First Cycle (1080p).gif',
|
length: 223580,
|
||||||
'length': 223580,
|
name: 'Cosmos Laundromat - First Cycle (1080p).gif'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Cosmos Laundromat - First Cycle (1080p).mp4',
|
length: 220087570,
|
||||||
'length': 220087570,
|
name: 'Cosmos Laundromat - First Cycle (1080p).mp4'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 421
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Cosmos Laundromat - First Cycle (1080p).ogv',
|
length: 56832560,
|
||||||
'length': 56832560,
|
name: 'Cosmos Laundromat - First Cycle (1080p).ogv'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 109
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CosmosLaundromat-FirstCycle1080p.en.srt',
|
length: 3949,
|
||||||
'length': 3949,
|
name: 'CosmosLaundromat-FirstCycle1080p.en.srt'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CosmosLaundromat-FirstCycle1080p.es.srt',
|
length: 3907,
|
||||||
'length': 3907,
|
name: 'CosmosLaundromat-FirstCycle1080p.es.srt'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CosmosLaundromat-FirstCycle1080p.fr.srt',
|
length: 4119,
|
||||||
'length': 4119,
|
name: 'CosmosLaundromat-FirstCycle1080p.fr.srt'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CosmosLaundromat-FirstCycle1080p.it.srt',
|
length: 3941,
|
||||||
'length': 3941,
|
name: 'CosmosLaundromat-FirstCycle1080p.it.srt'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CosmosLaundromatFirstCycle_meta.sqlite',
|
length: 11264,
|
||||||
'length': 11264,
|
name: 'CosmosLaundromatFirstCycle_meta.sqlite'
|
||||||
'numPiecesPresent': 0,
|
|
||||||
'numPieces': 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'CosmosLaundromatFirstCycle_meta.xml',
|
length: 1204,
|
||||||
'length': 1204,
|
name: 'CosmosLaundromatFirstCycle_meta.xml'
|
||||||
'numPiecesPresent': 0,
|
}
|
||||||
'numPieces': 1
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 'paused',
|
||||||
|
infoHash: '3ba219a8634bf7bae3d848192b2da75ae995589d',
|
||||||
|
magnetURI: 'magnet:?xt=urn:btih:3ba219a8634bf7bae3d848192b2da75ae995589d&dn=The+WIRED+CD+-+Rip.+Sample.+Mash.+Share.&tr=udp%3A%2F%2Fexodus.desync.com%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.webtorrent.io&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F',
|
||||||
|
displayName: 'The WIRED CD - Rip. Sample. Mash. Share.',
|
||||||
|
posterURL: 'wired-cd.jpg',
|
||||||
|
torrentPath: 'wired-cd.torrent',
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
length: 1964275,
|
||||||
|
name: '01 - Beastie Boys - Now Get Busy.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3610523,
|
||||||
|
name: '02 - David Byrne - My Fair Lady.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 2759377,
|
||||||
|
name: '03 - Zap Mama - Wadidyusay.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 5816537,
|
||||||
|
name: '04 - My Morning Jacket - One Big Holiday.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 2106421,
|
||||||
|
name: '05 - Spoon - Revenge!.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3347550,
|
||||||
|
name: '06 - Gilberto Gil - Oslodum.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 2107577,
|
||||||
|
name: '07 - Dan The Automator - Relaxation Spa Treatment.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3108130,
|
||||||
|
name: '08 - Thievery Corporation - Dc 3000.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3051528,
|
||||||
|
name: '09 - Le Tigre - Fake French.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3270259,
|
||||||
|
name: '10 - Paul Westerberg - Looking Up In Heaven.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3263528,
|
||||||
|
name: '11 - Chuck D - No Meaning No (feat. Fine Arts Militia).mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 6380952,
|
||||||
|
name: '12 - The Rapture - Sister Saviour (Blackstrobe Remix).mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 6550396,
|
||||||
|
name: '13 - Cornelius - Wataridori 2.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3034692,
|
||||||
|
name: '14 - DJ Danger Mouse - What U Sittin\' On (feat. Jemini, Cee Lo And Tha Alkaholiks).mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 3854611,
|
||||||
|
name: '15 - DJ Dolores - Oslodum 2004.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 1762120,
|
||||||
|
name: '16 - Matmos - Action At A Distance.mp3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 4071,
|
||||||
|
name: 'README.md'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 78163,
|
||||||
|
name: 'poster.jpg'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
downloadPath: path.join(os.homedir(), 'Downloads')
|
downloadPath: config.IS_PORTABLE
|
||||||
|
? path.join(config.CONFIG_PATH, 'Downloads')
|
||||||
|
: remote.app.getPath('downloads')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPlayingTorrentSummary () {
|
||||||
|
var infoHash = this.playing.infoHash
|
||||||
|
return this.saved.torrents.find((x) => x.infoHash === infoHash)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
var path = require('path')
|
|
||||||
|
|
||||||
var config = require('../config')
|
|
||||||
|
|
||||||
exports.getAbsoluteStaticPath = function (filePath) {
|
|
||||||
return path.isAbsolute(filePath)
|
|
||||||
? filePath
|
|
||||||
: path.join(config.STATIC_PATH, filePath)
|
|
||||||
}
|
|
||||||
@@ -5,15 +5,18 @@ var hyperx = require('hyperx')
|
|||||||
var hx = hyperx(h)
|
var hx = hyperx(h)
|
||||||
|
|
||||||
var Header = require('./header')
|
var Header = require('./header')
|
||||||
var Player = require('./player')
|
var Views = {
|
||||||
var TorrentList = require('./torrent-list')
|
'home': require('./torrent-list'),
|
||||||
|
'player': require('./player'),
|
||||||
|
'create-torrent': require('./create-torrent-page')
|
||||||
|
}
|
||||||
var Modals = {
|
var Modals = {
|
||||||
'open-torrent-address-modal': require('./open-torrent-address-modal'),
|
'open-torrent-address-modal': require('./open-torrent-address-modal'),
|
||||||
'update-available-modal': require('./update-available-modal'),
|
'update-available-modal': require('./update-available-modal'),
|
||||||
'create-torrent-modal': require('./create-torrent-modal')
|
'unsupported-media-modal': require('./unsupported-media-modal')
|
||||||
}
|
}
|
||||||
|
|
||||||
function App (state, dispatch) {
|
function App (state) {
|
||||||
// Hide player controls while playing video, if the mouse stays still for a while
|
// Hide player controls while playing video, if the mouse stays still for a while
|
||||||
// Never hide the controls when:
|
// Never hide the controls when:
|
||||||
// * The mouse is over the controls or we're scrubbing (see CSS)
|
// * The mouse is over the controls or we're scrubbing (see CSS)
|
||||||
@@ -40,47 +43,43 @@ function App (state, dispatch) {
|
|||||||
|
|
||||||
return hx`
|
return hx`
|
||||||
<div class='app ${cls.join(' ')}'>
|
<div class='app ${cls.join(' ')}'>
|
||||||
${Header(state, dispatch)}
|
${Header(state)}
|
||||||
${getErrorPopover()}
|
${getErrorPopover(state)}
|
||||||
<div class='content'>${getView()}</div>
|
<div class='content'>${getView(state)}</div>
|
||||||
${getModal()}
|
${getModal(state)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
}
|
||||||
function getErrorPopover () {
|
|
||||||
var now = new Date().getTime()
|
function getErrorPopover (state) {
|
||||||
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
|
var now = new Date().getTime()
|
||||||
|
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
|
||||||
var errorElems = recentErrors.map(function (error) {
|
|
||||||
return hx`<div class='error'>${error.message}</div>`
|
var errorElems = recentErrors.map(function (error) {
|
||||||
})
|
return hx`<div class='error'>${error.message}</div>`
|
||||||
return hx`
|
})
|
||||||
<div class='error-popover ${recentErrors.length > 0 ? 'visible' : 'hidden'}'>
|
return hx`
|
||||||
<div class='title'>Error</div>
|
<div class='error-popover ${recentErrors.length > 0 ? 'visible' : 'hidden'}'>
|
||||||
${errorElems}
|
<div class='title'>Error</div>
|
||||||
</div>
|
${errorElems}
|
||||||
`
|
</div>
|
||||||
}
|
`
|
||||||
|
}
|
||||||
function getModal () {
|
|
||||||
if (state.modal) {
|
function getModal (state) {
|
||||||
var contents = Modals[state.modal.id](state, dispatch)
|
if (!state.modal) return
|
||||||
return hx`
|
var contents = Modals[state.modal.id](state)
|
||||||
<div class='modal'>
|
return hx`
|
||||||
<div class='modal-background'></div>
|
<div class='modal'>
|
||||||
<div class='modal-content'>
|
<div class='modal-background'></div>
|
||||||
${contents}
|
<div class='modal-content'>
|
||||||
</div>
|
${contents}
|
||||||
</div>
|
</div>
|
||||||
`
|
</div>
|
||||||
}
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getView () {
|
function getView (state) {
|
||||||
if (state.location.current().url === 'home') {
|
var url = state.location.current().url
|
||||||
return TorrentList(state, dispatch)
|
return Views[url](state)
|
||||||
} else if (state.location.current().url === 'player') {
|
|
||||||
return Player(state, dispatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
module.exports = UpdateAvailableModal
|
|
||||||
|
|
||||||
var h = require('virtual-dom/h')
|
|
||||||
var hyperx = require('hyperx')
|
|
||||||
var hx = hyperx(h)
|
|
||||||
|
|
||||||
var path = require('path')
|
|
||||||
|
|
||||||
var {dispatch} = require('../lib/dispatcher')
|
|
||||||
|
|
||||||
function UpdateAvailableModal (state) {
|
|
||||||
// First, extract the base folder that the files are all in
|
|
||||||
var files = state.modal.files
|
|
||||||
var pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
|
|
||||||
if (files.length > 0 && !pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
|
|
||||||
pathPrefix = path.dirname(pathPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then, use the name of the base folder (or sole file, for a single file torrent)
|
|
||||||
// as the default name. Show all files relative to the base folder.
|
|
||||||
var defaultName = path.basename(pathPrefix)
|
|
||||||
var basePath = path.dirname(pathPrefix)
|
|
||||||
var fileElems = files.map(function (file) {
|
|
||||||
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
|
|
||||||
return hx`<div>${relativePath}</div>`
|
|
||||||
})
|
|
||||||
|
|
||||||
return hx`
|
|
||||||
<div class='create-torrent-modal'>
|
|
||||||
<p><strong>Create New Torrent</strong></p>
|
|
||||||
<p class='torrent-attribute'>
|
|
||||||
<label>Name:</label>
|
|
||||||
<div class='torrent-attribute'>${defaultName}</div>
|
|
||||||
</p>
|
|
||||||
<p class='torrent-attribute'>
|
|
||||||
<label>Path:</label>
|
|
||||||
<div class='torrent-attribute'>${pathPrefix}</div>
|
|
||||||
</p>
|
|
||||||
<p class='torrent-attribute'>
|
|
||||||
<label>Files:</label>
|
|
||||||
<div>${fileElems}</div>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button class='primary' onclick=${handleOK}>Create Torrent</button>
|
|
||||||
<button class='cancel' onclick=${handleCancel}>Cancel</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
|
|
||||||
function handleOK () {
|
|
||||||
var options = {
|
|
||||||
// TODO: we can't let the user choose their own name if we want WebTorrent
|
|
||||||
// to use the files in place rather than creating a new folder.
|
|
||||||
// name: document.querySelector('.torrent-name').value
|
|
||||||
name: defaultName,
|
|
||||||
path: basePath,
|
|
||||||
files: files
|
|
||||||
}
|
|
||||||
dispatch('createTorrent', options)
|
|
||||||
dispatch('exitModal')
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCancel () {
|
|
||||||
dispatch('exitModal')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finds the longest common prefix
|
|
||||||
function findCommonPrefix (a, b) {
|
|
||||||
for (var i = 0; i < a.length && i < b.length; i++) {
|
|
||||||
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
|
|
||||||
}
|
|
||||||
if (i === a.length) return a
|
|
||||||
if (i === b.length) return b
|
|
||||||
return a.substring(0, i)
|
|
||||||
}
|
|
||||||
145
renderer/views/create-torrent-page.js
Normal file
145
renderer/views/create-torrent-page.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
module.exports = CreateTorrentPage
|
||||||
|
|
||||||
|
var h = require('virtual-dom/h')
|
||||||
|
var hyperx = require('hyperx')
|
||||||
|
var hx = hyperx(h)
|
||||||
|
|
||||||
|
var createTorrent = require('create-torrent')
|
||||||
|
var path = require('path')
|
||||||
|
var prettyBytes = require('prettier-bytes')
|
||||||
|
|
||||||
|
var {dispatch} = require('../lib/dispatcher')
|
||||||
|
|
||||||
|
function CreateTorrentPage (state) {
|
||||||
|
var info = state.location.current()
|
||||||
|
|
||||||
|
// Preprocess: exclude .DS_Store and other dotfiles
|
||||||
|
var files = info.files
|
||||||
|
.filter((f) => !f.name.startsWith('.'))
|
||||||
|
.map((f) => ({name: f.name, path: f.path, size: f.size}))
|
||||||
|
|
||||||
|
// First, extract the base folder that the files are all in
|
||||||
|
var pathPrefix = info.folderPath
|
||||||
|
if (!pathPrefix) {
|
||||||
|
if (files.length > 0) {
|
||||||
|
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
|
||||||
|
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
|
||||||
|
pathPrefix = path.dirname(pathPrefix)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pathPrefix = files[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check: show the number of files and total size
|
||||||
|
var numFiles = files.length
|
||||||
|
var totalBytes = files
|
||||||
|
.map((f) => f.size)
|
||||||
|
.reduce((a, b) => a + b, 0)
|
||||||
|
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
|
||||||
|
|
||||||
|
// Then, use the name of the base folder (or sole file, for a single file torrent)
|
||||||
|
// as the default name. Show all files relative to the base folder.
|
||||||
|
var defaultName, basePath
|
||||||
|
if (files.length === 1) {
|
||||||
|
// Single file torrent: /a/b/foo.jpg -> torrent name "foo.jpg", path "/a/b"
|
||||||
|
defaultName = files[0].name
|
||||||
|
basePath = pathPrefix
|
||||||
|
} else {
|
||||||
|
// Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name "b", path "/a"
|
||||||
|
defaultName = path.basename(pathPrefix)
|
||||||
|
basePath = path.dirname(pathPrefix)
|
||||||
|
}
|
||||||
|
var maxFileElems = 100
|
||||||
|
var fileElems = files.slice(0, maxFileElems).map(function (file) {
|
||||||
|
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
|
||||||
|
return hx`<div>${relativePath}</div>`
|
||||||
|
})
|
||||||
|
if (files.length > maxFileElems) {
|
||||||
|
fileElems.push(hx`<div>+ ${maxFileElems - files.length} more</div>`)
|
||||||
|
}
|
||||||
|
var trackers = createTorrent.announceList.join('\n')
|
||||||
|
var collapsedClass = info.showAdvanced ? 'expanded' : 'collapsed'
|
||||||
|
|
||||||
|
return hx`
|
||||||
|
<div class='create-torrent-page'>
|
||||||
|
<h2>Create torrent ${defaultName}</h2>
|
||||||
|
<p class="torrent-info">
|
||||||
|
${torrentInfo}
|
||||||
|
</p>
|
||||||
|
<p class='torrent-attribute'>
|
||||||
|
<label>Path:</label>
|
||||||
|
<div class='torrent-attribute'>${pathPrefix}</div>
|
||||||
|
</p>
|
||||||
|
<div class='expand-collapse ${collapsedClass}' onclick=${handleToggleShowAdvanced}>
|
||||||
|
${info.showAdvanced ? 'Basic' : 'Advanced'}
|
||||||
|
</div>
|
||||||
|
<div class="create-torrent-advanced ${collapsedClass}">
|
||||||
|
<p class='torrent-attribute'>
|
||||||
|
<label>Comment:</label>
|
||||||
|
<textarea class='torrent-attribute torrent-comment'></textarea>
|
||||||
|
</p>
|
||||||
|
<p class='torrent-attribute'>
|
||||||
|
<label>Trackers:</label>
|
||||||
|
<textarea class='torrent-attribute torrent-trackers'>${trackers}</textarea>
|
||||||
|
</p>
|
||||||
|
<p class='torrent-attribute'>
|
||||||
|
<label>Private:</label>
|
||||||
|
<input type='checkbox' class='torrent-is-private' value='torrent-is-private'>
|
||||||
|
</p>
|
||||||
|
<p class='torrent-attribute'>
|
||||||
|
<label>Files:</label>
|
||||||
|
<div>${fileElems}</div>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p class="float-right">
|
||||||
|
<button class='button-flat light' onclick=${handleCancel}>Cancel</button>
|
||||||
|
<button class='button-raised' onclick=${handleOK}>Create Torrent</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
function handleOK () {
|
||||||
|
var announceList = document.querySelector('.torrent-trackers').value
|
||||||
|
.split('\n')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s !== '')
|
||||||
|
var isPrivate = document.querySelector('.torrent-is-private').checked
|
||||||
|
var comment = document.querySelector('.torrent-comment').value.trim()
|
||||||
|
var options = {
|
||||||
|
// We can't let the user choose their own name if we want WebTorrent
|
||||||
|
// to use the files in place rather than creating a new folder.
|
||||||
|
// If we ever want to add support for that:
|
||||||
|
// name: document.querySelector('.torrent-name').value
|
||||||
|
name: defaultName,
|
||||||
|
path: basePath,
|
||||||
|
files: files,
|
||||||
|
announce: announceList,
|
||||||
|
private: isPrivate,
|
||||||
|
comment: comment
|
||||||
|
}
|
||||||
|
dispatch('createTorrent', options)
|
||||||
|
dispatch('backToList')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel () {
|
||||||
|
dispatch('backToList')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToggleShowAdvanced () {
|
||||||
|
// TODO: what's the clean way to handle this?
|
||||||
|
// Should every button on every screen have its own dispatch()?
|
||||||
|
info.showAdvanced = !info.showAdvanced
|
||||||
|
dispatch('update')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds the longest common prefix
|
||||||
|
function findCommonPrefix (a, b) {
|
||||||
|
for (var i = 0; i < a.length && i < b.length; i++) {
|
||||||
|
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
|
||||||
|
}
|
||||||
|
if (i === a.length) return a
|
||||||
|
if (i === b.length) return b
|
||||||
|
return a.substring(0, i)
|
||||||
|
}
|
||||||
@@ -9,12 +9,15 @@ var {dispatch} = require('../lib/dispatcher')
|
|||||||
function OpenTorrentAddressModal (state) {
|
function OpenTorrentAddressModal (state) {
|
||||||
return hx`
|
return hx`
|
||||||
<div class='open-torrent-address-modal'>
|
<div class='open-torrent-address-modal'>
|
||||||
<p><strong>Enter torrent address or magnet link</strong></p>
|
<p><label>Enter torrent address or magnet link</label></p>
|
||||||
<p>
|
<p>
|
||||||
<input id='add-torrent-url' type='text' autofocus onkeypress=${handleKeyPress} />
|
<input id='add-torrent-url' type='text' onkeypress=${handleKeyPress} />
|
||||||
<button class='primary' onclick=${handleOK}>OK</button>
|
|
||||||
<button class='cancel' onclick=${handleCancel}>Cancel</button>
|
|
||||||
</p>
|
</p>
|
||||||
|
<p class='float-right'>
|
||||||
|
<button class='button button-flat' onclick=${handleCancel}>CANCEL</button>
|
||||||
|
<button class='button button-raised' onclick=${handleOK}>OK</button>
|
||||||
|
</p>
|
||||||
|
<script>document.querySelector('#add-torrent-url').focus()</script>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ var hx = hyperx(h)
|
|||||||
var prettyBytes = require('prettier-bytes')
|
var prettyBytes = require('prettier-bytes')
|
||||||
var Bitfield = require('bitfield')
|
var Bitfield = require('bitfield')
|
||||||
|
|
||||||
var util = require('../util')
|
var TorrentSummary = require('../lib/torrent-summary')
|
||||||
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||||
|
|
||||||
// Shows a streaming video player. Standard features + Chromecast + Airplay
|
// Shows a streaming video player. Standard features + Chromecast + Airplay
|
||||||
@@ -18,6 +18,7 @@ function Player (state) {
|
|||||||
return hx`
|
return hx`
|
||||||
<div
|
<div
|
||||||
class='player'
|
class='player'
|
||||||
|
onwheel=${handleVolumeWheel}
|
||||||
onmousemove=${dispatcher('mediaMouseMoved')}>
|
onmousemove=${dispatcher('mediaMouseMoved')}>
|
||||||
${showVideo ? renderMedia(state) : renderCastScreen(state)}
|
${showVideo ? renderMedia(state) : renderCastScreen(state)}
|
||||||
${renderPlayerControls(state)}
|
${renderPlayerControls(state)}
|
||||||
@@ -25,13 +26,17 @@ function Player (state) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles volume change by wheel
|
||||||
|
function handleVolumeWheel (e) {
|
||||||
|
dispatch('changeVolume', (-e.deltaY | e.deltaX) / 500)
|
||||||
|
}
|
||||||
|
|
||||||
function renderMedia (state) {
|
function renderMedia (state) {
|
||||||
if (!state.server) return
|
if (!state.server) return
|
||||||
|
|
||||||
// Unfortunately, play/pause can't be done just by modifying HTML.
|
// Unfortunately, play/pause can't be done just by modifying HTML.
|
||||||
// Instead, grab the DOM node and play/pause it if necessary
|
// Instead, grab the DOM node and play/pause it if necessary
|
||||||
var mediaType = state.playing.type /* 'audio' or 'video' */
|
var mediaElement = document.querySelector(state.playing.type) /* get the <video> or <audio> tag */
|
||||||
var mediaElement = document.querySelector(mediaType) /* get the <video> or <audio> tag */
|
|
||||||
if (mediaElement !== null) {
|
if (mediaElement !== null) {
|
||||||
if (state.playing.isPaused && !mediaElement.paused) {
|
if (state.playing.isPaused && !mediaElement.paused) {
|
||||||
mediaElement.pause()
|
mediaElement.pause()
|
||||||
@@ -49,11 +54,33 @@ function renderMedia (state) {
|
|||||||
state.playing.setVolume = null
|
state.playing.setVolume = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Switch to the newly added subtitle track, if available
|
||||||
|
var tracks = mediaElement.textTracks
|
||||||
|
for (var j = 0; j < tracks.length; j++) {
|
||||||
|
tracks[j].mode = (j === state.playing.subtitles.selectedIndex) ? 'showing' : 'hidden'
|
||||||
|
}
|
||||||
|
|
||||||
state.playing.currentTime = mediaElement.currentTime
|
state.playing.currentTime = mediaElement.currentTime
|
||||||
state.playing.duration = mediaElement.duration
|
state.playing.duration = mediaElement.duration
|
||||||
state.playing.volume = mediaElement.volume
|
state.playing.volume = mediaElement.volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add subtitles to the <video> tag
|
||||||
|
var trackTags = []
|
||||||
|
if (state.playing.subtitles.selectedIndex >= 0) {
|
||||||
|
for (var i = 0; i < state.playing.subtitles.tracks.length; i++) {
|
||||||
|
var track = state.playing.subtitles.tracks[i]
|
||||||
|
var isSelected = state.playing.subtitles.selectedIndex === i
|
||||||
|
trackTags.push(hx`
|
||||||
|
<track
|
||||||
|
${isSelected ? 'default' : ''}
|
||||||
|
label=${track.label}
|
||||||
|
type='subtitles'
|
||||||
|
src=${track.buffer}>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the <audio> or <video> tag
|
// Create the <audio> or <video> tag
|
||||||
var mediaTag = hx`
|
var mediaTag = hx`
|
||||||
<div
|
<div
|
||||||
@@ -61,14 +88,15 @@ function renderMedia (state) {
|
|||||||
ondblclick=${dispatcher('toggleFullScreen')}
|
ondblclick=${dispatcher('toggleFullScreen')}
|
||||||
onloadedmetadata=${onLoadedMetadata}
|
onloadedmetadata=${onLoadedMetadata}
|
||||||
onended=${onEnded}
|
onended=${onEnded}
|
||||||
onplay=${dispatcher('mediaPlaying')}
|
|
||||||
onpause=${dispatcher('mediaPaused')}
|
|
||||||
onstalling=${dispatcher('mediaStalled')}
|
onstalling=${dispatcher('mediaStalled')}
|
||||||
|
onerror=${dispatcher('mediaError')}
|
||||||
ontimeupdate=${dispatcher('mediaTimeUpdate')}
|
ontimeupdate=${dispatcher('mediaTimeUpdate')}
|
||||||
autoplay>
|
onencrypted=${dispatcher('mediaEncrypted')}
|
||||||
|
oncanplay=${onCanPlay}>
|
||||||
|
${trackTags}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
mediaTag.tagName = mediaType
|
mediaTag.tagName = state.playing.type // conditional tag name
|
||||||
|
|
||||||
// Show the media.
|
// Show the media.
|
||||||
return hx`
|
return hx`
|
||||||
@@ -82,7 +110,7 @@ function renderMedia (state) {
|
|||||||
|
|
||||||
// As soon as the video loads enough to know the video dimensions, resize the window
|
// As soon as the video loads enough to know the video dimensions, resize the window
|
||||||
function onLoadedMetadata (e) {
|
function onLoadedMetadata (e) {
|
||||||
if (mediaType !== 'video') return
|
if (state.playing.type !== 'video') return
|
||||||
var video = e.target
|
var video = e.target
|
||||||
var dimensions = {
|
var dimensions = {
|
||||||
width: video.videoWidth,
|
width: video.videoWidth,
|
||||||
@@ -95,6 +123,16 @@ function renderMedia (state) {
|
|||||||
function onEnded (e) {
|
function onEnded (e) {
|
||||||
state.playing.isPaused = true
|
state.playing.isPaused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onCanPlay (e) {
|
||||||
|
var video = e.target
|
||||||
|
if (video.webkitVideoDecodedByteCount > 0 &&
|
||||||
|
video.webkitAudioDecodedByteCount === 0) {
|
||||||
|
dispatch('mediaError', 'Audio codec unsupported')
|
||||||
|
} else {
|
||||||
|
video.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOverlay (state) {
|
function renderOverlay (state) {
|
||||||
@@ -123,7 +161,7 @@ function renderOverlay (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderAudioMetadata (state) {
|
function renderAudioMetadata (state) {
|
||||||
var torrentSummary = getPlayingTorrentSummary(state)
|
var torrentSummary = state.getPlayingTorrentSummary()
|
||||||
var fileSummary = torrentSummary.files[state.playing.fileIndex]
|
var fileSummary = torrentSummary.files[state.playing.fileIndex]
|
||||||
if (!fileSummary.audioInfo) return
|
if (!fileSummary.audioInfo) return
|
||||||
var info = fileSummary.audioInfo
|
var info = fileSummary.audioInfo
|
||||||
@@ -162,7 +200,7 @@ function renderLoadingSpinner (state) {
|
|||||||
(new Date().getTime() - state.playing.lastTimeUpdate > 2000)
|
(new Date().getTime() - state.playing.lastTimeUpdate > 2000)
|
||||||
if (!isProbablyStalled) return
|
if (!isProbablyStalled) return
|
||||||
|
|
||||||
var prog = getPlayingTorrentSummary(state).progress || {}
|
var prog = state.getPlayingTorrentSummary().progress || {}
|
||||||
var fileProgress = 0
|
var fileProgress = 0
|
||||||
if (prog.files) {
|
if (prog.files) {
|
||||||
var file = prog.files[state.playing.fileIndex]
|
var file = prog.files[state.playing.fileIndex]
|
||||||
@@ -182,20 +220,33 @@ function renderLoadingSpinner (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderCastScreen (state) {
|
function renderCastScreen (state) {
|
||||||
var castIcon, castType
|
var castIcon, castType, isCast
|
||||||
if (state.playing.location.startsWith('chromecast')) {
|
if (state.playing.location.startsWith('chromecast')) {
|
||||||
castIcon = 'cast_connected'
|
castIcon = 'cast_connected'
|
||||||
castType = 'Chromecast'
|
castType = 'Chromecast'
|
||||||
|
isCast = true
|
||||||
} else if (state.playing.location.startsWith('airplay')) {
|
} else if (state.playing.location.startsWith('airplay')) {
|
||||||
castIcon = 'airplay'
|
castIcon = 'airplay'
|
||||||
castType = 'AirPlay'
|
castType = 'AirPlay'
|
||||||
|
isCast = true
|
||||||
} else if (state.playing.location.startsWith('dlna')) {
|
} else if (state.playing.location.startsWith('dlna')) {
|
||||||
castIcon = 'tv'
|
castIcon = 'tv'
|
||||||
castType = 'DLNA'
|
castType = 'DLNA'
|
||||||
|
isCast = true
|
||||||
|
} else if (state.playing.location === 'vlc') {
|
||||||
|
castIcon = 'tv'
|
||||||
|
castType = 'VLC'
|
||||||
|
isCast = false
|
||||||
|
} else if (state.playing.location === 'error') {
|
||||||
|
castIcon = 'error_outline'
|
||||||
|
castType = 'Error'
|
||||||
|
isCast = false
|
||||||
}
|
}
|
||||||
|
|
||||||
var isStarting = state.playing.location.endsWith('-pending')
|
var isStarting = state.playing.location.endsWith('-pending')
|
||||||
var castStatus = isStarting ? 'Connecting...' : 'Connected'
|
var castStatus
|
||||||
|
if (isCast) castStatus = isStarting ? 'Connecting...' : 'Connected'
|
||||||
|
else castStatus = ''
|
||||||
|
|
||||||
// Show a nice title image, if possible
|
// Show a nice title image, if possible
|
||||||
var style = {
|
var style = {
|
||||||
@@ -213,9 +264,40 @@ function renderCastScreen (state) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSubtitlesOptions (state) {
|
||||||
|
var subtitles = state.playing.subtitles
|
||||||
|
if (!subtitles.tracks.length || !subtitles.showMenu) return
|
||||||
|
|
||||||
|
var items = subtitles.tracks.map(function (track, ix) {
|
||||||
|
var isSelected = state.playing.subtitles.selectedIndex === ix
|
||||||
|
return hx`
|
||||||
|
<li onclick=${dispatcher('selectSubtitle', ix)}>
|
||||||
|
<i.icon>${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||||
|
${track.label}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
var noneSelected = state.playing.subtitles.selectedIndex === -1
|
||||||
|
return hx`
|
||||||
|
<ul.subtitles-list>
|
||||||
|
${items}
|
||||||
|
<li onclick=${dispatcher('selectSubtitle', -1)}>
|
||||||
|
<i.icon>${noneSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||||
|
None
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
function renderPlayerControls (state) {
|
function renderPlayerControls (state) {
|
||||||
var positionPercent = 100 * state.playing.currentTime / state.playing.duration
|
var positionPercent = 100 * state.playing.currentTime / state.playing.duration
|
||||||
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
|
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
|
||||||
|
var captionsClass = state.playing.subtitles.tracks.length === 0
|
||||||
|
? 'disabled'
|
||||||
|
: state.playing.subtitles.selectedIndex >= 0
|
||||||
|
? 'active'
|
||||||
|
: ''
|
||||||
|
|
||||||
var elements = [
|
var elements = [
|
||||||
hx`
|
hx`
|
||||||
@@ -236,6 +318,17 @@ function renderPlayerControls (state) {
|
|||||||
`
|
`
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (state.playing.type === 'video') {
|
||||||
|
// show closed captions icon
|
||||||
|
elements.push(hx`
|
||||||
|
<i.icon.closed-captions
|
||||||
|
class=${captionsClass}
|
||||||
|
onclick=${handleSubtitles}>
|
||||||
|
closed_captions
|
||||||
|
</i>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
// If we've detected a Chromecast or AppleTV, the user can play video there
|
// If we've detected a Chromecast or AppleTV, the user can play video there
|
||||||
var isOnChromecast = state.playing.location.startsWith('chromecast')
|
var isOnChromecast = state.playing.location.startsWith('chromecast')
|
||||||
var isOnAirplay = state.playing.location.startsWith('airplay')
|
var isOnAirplay = state.playing.location.startsWith('airplay')
|
||||||
@@ -310,6 +403,29 @@ function renderPlayerControls (state) {
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// render volume
|
||||||
|
var volume = state.playing.volume
|
||||||
|
var volumeIcon = 'volume_' + (volume === 0 ? 'off' : volume < 0.3 ? 'mute' : volume < 0.6 ? 'down' : 'up')
|
||||||
|
var volumeStyle = { background: '-webkit-gradient(linear, left top, right top, ' +
|
||||||
|
'color-stop(' + (volume * 100) + '%, #eee), ' +
|
||||||
|
'color-stop(' + (volume * 100) + '%, #727272))'
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.push(hx`
|
||||||
|
<div.volume>
|
||||||
|
<i.icon.volume-icon onmousedown=${handleVolumeMute}>
|
||||||
|
${volumeIcon}
|
||||||
|
</i>
|
||||||
|
<input.volume-slider
|
||||||
|
type='range' min='0' max='1' step='0.05' value=${volumeChanging !== false ? volumeChanging : volume}
|
||||||
|
onmousedown=${handleVolumeScrub}
|
||||||
|
onmouseup=${handleVolumeScrub}
|
||||||
|
onmousemove=${handleVolumeScrub}
|
||||||
|
style=${volumeStyle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
|
||||||
// Finally, the big button in the center plays or pauses the video
|
// Finally, the big button in the center plays or pauses the video
|
||||||
elements.push(hx`
|
elements.push(hx`
|
||||||
<i class='icon play-pause' onclick=${dispatcher('playPause')}>
|
<i class='icon play-pause' onclick=${dispatcher('playPause')}>
|
||||||
@@ -317,7 +433,12 @@ function renderPlayerControls (state) {
|
|||||||
</i>
|
</i>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
return hx`<div class='player-controls'>${elements}</div>`
|
return hx`
|
||||||
|
<div class='player-controls'>
|
||||||
|
${elements}
|
||||||
|
${renderSubtitlesOptions(state)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
// Handles a click or drag to scrub (jump to another position in the video)
|
// Handles a click or drag to scrub (jump to another position in the video)
|
||||||
function handleScrub (e) {
|
function handleScrub (e) {
|
||||||
@@ -327,12 +448,52 @@ function renderPlayerControls (state) {
|
|||||||
var position = fraction * state.playing.duration /* seconds */
|
var position = fraction * state.playing.duration /* seconds */
|
||||||
dispatch('playbackJump', position)
|
dispatch('playbackJump', position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles volume muting and Unmuting
|
||||||
|
function handleVolumeMute (e) {
|
||||||
|
if (state.playing.volume === 0.0) {
|
||||||
|
dispatch('setVolume', 1.0)
|
||||||
|
} else {
|
||||||
|
dispatch('setVolume', 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles volume slider scrub
|
||||||
|
function handleVolumeScrub (e) {
|
||||||
|
switch (e.type) {
|
||||||
|
case 'mouseup':
|
||||||
|
volumeChanging = false
|
||||||
|
dispatch('setVolume', e.offsetX / 50)
|
||||||
|
break
|
||||||
|
case 'mousedown':
|
||||||
|
volumeChanging = this.value
|
||||||
|
break
|
||||||
|
case 'mousemove':
|
||||||
|
// only change if move was started by click
|
||||||
|
if (volumeChanging !== false) {
|
||||||
|
volumeChanging = this.value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubtitles (e) {
|
||||||
|
if (!state.playing.subtitles.tracks.length || e.ctrlKey || e.metaKey) {
|
||||||
|
// if no subtitles available select it
|
||||||
|
dispatch('openSubtitles')
|
||||||
|
} else {
|
||||||
|
dispatch('toggleSubtitlesMenu')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lets scrub without sending to volume backend
|
||||||
|
var volumeChanging = false
|
||||||
|
|
||||||
// Renders the loading bar. Shows which parts of the torrent are loaded, which
|
// Renders the loading bar. Shows which parts of the torrent are loaded, which
|
||||||
// can be "spongey" / non-contiguous
|
// can be "spongey" / non-contiguous
|
||||||
function renderLoadingBar (state) {
|
function renderLoadingBar (state) {
|
||||||
var torrentSummary = getPlayingTorrentSummary(state)
|
var torrentSummary = state.getPlayingTorrentSummary()
|
||||||
if (!torrentSummary.progress) {
|
if (!torrentSummary.progress) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -369,19 +530,13 @@ function renderLoadingBar (state) {
|
|||||||
|
|
||||||
// Returns the CSS background-image string for a poster image + dark vignette
|
// Returns the CSS background-image string for a poster image + dark vignette
|
||||||
function cssBackgroundImagePoster (state) {
|
function cssBackgroundImagePoster (state) {
|
||||||
var torrentSummary = getPlayingTorrentSummary(state)
|
var torrentSummary = state.getPlayingTorrentSummary()
|
||||||
if (!torrentSummary || !torrentSummary.posterURL) return ''
|
var posterPath = TorrentSummary.getPosterPath(torrentSummary)
|
||||||
var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL)
|
if (!posterPath) return ''
|
||||||
var cleanURL = posterURL.replace(/\\/g, '/')
|
return cssBackgroundImageDarkGradient() + `, url(${posterPath})`
|
||||||
return cssBackgroundImageDarkGradient() + `, url(${cleanURL})`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cssBackgroundImageDarkGradient () {
|
function cssBackgroundImageDarkGradient () {
|
||||||
return 'radial-gradient(circle at center, ' +
|
return 'radial-gradient(circle at center, ' +
|
||||||
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)'
|
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlayingTorrentSummary (state) {
|
|
||||||
var infoHash = state.playing.infoHash
|
|
||||||
return state.saved.torrents.find((x) => x.infoHash === infoHash)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ var hyperx = require('hyperx')
|
|||||||
var hx = hyperx(h)
|
var hx = hyperx(h)
|
||||||
var prettyBytes = require('prettier-bytes')
|
var prettyBytes = require('prettier-bytes')
|
||||||
|
|
||||||
var util = require('../util')
|
var TorrentSummary = require('../lib/torrent-summary')
|
||||||
|
|
||||||
var TorrentPlayer = require('../lib/torrent-player')
|
var TorrentPlayer = require('../lib/torrent-player')
|
||||||
var {dispatcher} = require('../lib/dispatcher')
|
var {dispatcher} = require('../lib/dispatcher')
|
||||||
|
|
||||||
@@ -31,15 +30,12 @@ function TorrentList (state) {
|
|||||||
|
|
||||||
// Background image: show some nice visuals, like a frame from the movie, if possible
|
// Background image: show some nice visuals, like a frame from the movie, if possible
|
||||||
var style = {}
|
var style = {}
|
||||||
if (torrentSummary.posterURL) {
|
if (torrentSummary.posterFileName) {
|
||||||
var gradient = isSelected
|
var gradient = isSelected
|
||||||
? 'linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 100%)'
|
? 'linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 100%)'
|
||||||
: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%)'
|
: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%)'
|
||||||
var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL)
|
var posterPath = TorrentSummary.getPosterPath(torrentSummary)
|
||||||
// Work around a Chrome bug (reproduced in vanilla Chrome, not just Electron):
|
style.backgroundImage = gradient + `, url('${posterPath}')`
|
||||||
// Backslashes in URLS in CSS cause bizarre string encoding issues
|
|
||||||
var cleanURL = posterURL.replace(/\\/g, '/')
|
|
||||||
style.backgroundImage = gradient + `, url('${cleanURL}')`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Foreground: name of the torrent, basic info like size, play button,
|
// Foreground: name of the torrent, basic info like size, play button,
|
||||||
@@ -71,39 +67,49 @@ function TorrentList (state) {
|
|||||||
// If it's downloading/seeding then show progress info
|
// If it's downloading/seeding then show progress info
|
||||||
var prog = torrentSummary.progress
|
var prog = torrentSummary.progress
|
||||||
if (torrentSummary.status !== 'paused' && prog) {
|
if (torrentSummary.status !== 'paused' && prog) {
|
||||||
var progress = Math.floor(100 * prog.progress)
|
|
||||||
var downloaded = prettyBytes(prog.downloaded)
|
|
||||||
var total = prettyBytes(prog.length || 0)
|
|
||||||
if (downloaded !== total) downloaded += ` / ${total}`
|
|
||||||
|
|
||||||
elements.push(hx`
|
elements.push(hx`
|
||||||
<div class='status ellipsis'>
|
<div class='ellipsis'>
|
||||||
${getFilesLength()}
|
${renderPercentProgress()}
|
||||||
<span>${getPeers()}</span>
|
${renderTotalProgress()}
|
||||||
<span>↓ ${prettyBytes(prog.downloadSpeed || 0)}/s</span>
|
${renderPeers()}
|
||||||
<span>↑ ${prettyBytes(prog.uploadSpeed || 0)}/s</span>
|
${renderDownloadSpeed()}
|
||||||
</div>
|
${renderUploadSpeed()}
|
||||||
`)
|
|
||||||
elements.push(hx`
|
|
||||||
<div class='status2 ellipsis'>
|
|
||||||
<span class='progress'>${progress}%</span>
|
|
||||||
<span>${downloaded}</span>
|
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return hx`<div class='metadata'>${elements}</div>`
|
return hx`<div class='metadata'>${elements}</div>`
|
||||||
|
|
||||||
function getPeers () {
|
function renderPercentProgress () {
|
||||||
var count = prog.numPeers === 1 ? 'peer' : 'peers'
|
var progress = Math.floor(100 * prog.progress)
|
||||||
return `${prog.numPeers} ${count}`
|
return hx`<span>${progress}%</span>`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilesLength () {
|
function renderTotalProgress () {
|
||||||
if (torrentSummary.files && torrentSummary.files.length > 1) {
|
var downloaded = prettyBytes(prog.downloaded)
|
||||||
return hx`<span class='files'>${torrentSummary.files.length} files</span>`
|
var total = prettyBytes(prog.length || 0)
|
||||||
|
if (downloaded === total) {
|
||||||
|
return hx`<span>${downloaded}</span>`
|
||||||
|
} else {
|
||||||
|
return hx`<span>${downloaded} / ${total}</span>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPeers () {
|
||||||
|
if (prog.numPeers === 0) return
|
||||||
|
var count = prog.numPeers === 1 ? 'peer' : 'peers'
|
||||||
|
return hx`<span>${prog.numPeers} ${count}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDownloadSpeed () {
|
||||||
|
if (prog.downloadSpeed === 0) return
|
||||||
|
return hx`<span>↓ ${prettyBytes(prog.downloadSpeed)}/s</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUploadSpeed () {
|
||||||
|
if (prog.uploadSpeed === 0) return
|
||||||
|
return hx`<span>↑ ${prettyBytes(prog.uploadSpeed)}/s</span>`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download button toggles between torrenting (DL/seed) and paused
|
// Download button toggles between torrenting (DL/seed) and paused
|
||||||
@@ -141,7 +147,7 @@ function TorrentList (state) {
|
|||||||
var playButton
|
var playButton
|
||||||
if (TorrentPlayer.isPlayableTorrent(torrentSummary)) {
|
if (TorrentPlayer.isPlayableTorrent(torrentSummary)) {
|
||||||
playButton = hx`
|
playButton = hx`
|
||||||
<i.btn.icon.play
|
<i.button-round.icon.play
|
||||||
title=${playTooltip}
|
title=${playTooltip}
|
||||||
class=${playClass}
|
class=${playClass}
|
||||||
onclick=${dispatcher('play', infoHash)}>
|
onclick=${dispatcher('play', infoHash)}>
|
||||||
@@ -153,7 +159,7 @@ function TorrentList (state) {
|
|||||||
return hx`
|
return hx`
|
||||||
<div class='buttons'>
|
<div class='buttons'>
|
||||||
${playButton}
|
${playButton}
|
||||||
<i.btn.icon.download
|
<i.button-round.icon.download
|
||||||
class=${torrentSummary.status}
|
class=${torrentSummary.status}
|
||||||
title=${downloadTooltip}
|
title=${downloadTooltip}
|
||||||
onclick=${dispatcher('toggleTorrent', infoHash)}>
|
onclick=${dispatcher('toggleTorrent', infoHash)}>
|
||||||
@@ -171,7 +177,6 @@ function TorrentList (state) {
|
|||||||
|
|
||||||
// Show files, per-file download status and play buttons, and so on
|
// Show files, per-file download status and play buttons, and so on
|
||||||
function renderTorrentDetails (torrentSummary) {
|
function renderTorrentDetails (torrentSummary) {
|
||||||
var infoHash = torrentSummary.infoHash
|
|
||||||
var filesElement
|
var filesElement
|
||||||
if (!torrentSummary.files) {
|
if (!torrentSummary.files) {
|
||||||
// We don't know what files this torrent contains
|
// We don't know what files this torrent contains
|
||||||
@@ -186,10 +191,6 @@ function TorrentList (state) {
|
|||||||
filesElement = hx`
|
filesElement = hx`
|
||||||
<div class='files'>
|
<div class='files'>
|
||||||
<strong>Files</strong>
|
<strong>Files</strong>
|
||||||
<span class='open-folder'
|
|
||||||
onclick=${dispatcher('openFolder', infoHash)}>
|
|
||||||
Open folder
|
|
||||||
</span>
|
|
||||||
<table>
|
<table>
|
||||||
${fileRows}
|
${fileRows}
|
||||||
</table>
|
</table>
|
||||||
@@ -207,7 +208,8 @@ function TorrentList (state) {
|
|||||||
// Show a single torrentSummary file in the details view for a single torrent
|
// Show a single torrentSummary file in the details view for a single torrent
|
||||||
function renderFileRow (torrentSummary, file, index) {
|
function renderFileRow (torrentSummary, file, index) {
|
||||||
// First, find out how much of the file we've downloaded
|
// First, find out how much of the file we've downloaded
|
||||||
var isDone = false
|
var isSelected = torrentSummary.selections[index] // Are we even torrenting it?
|
||||||
|
var isDone = false // Are we finished torrenting it?
|
||||||
var progress = ''
|
var progress = ''
|
||||||
if (torrentSummary.progress && torrentSummary.progress.files) {
|
if (torrentSummary.progress && torrentSummary.progress.files) {
|
||||||
var fileProg = torrentSummary.progress.files[index]
|
var fileProg = torrentSummary.progress.files[index]
|
||||||
@@ -216,26 +218,38 @@ function TorrentList (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Second, render the file as a table row
|
// Second, render the file as a table row
|
||||||
|
var isPlayable = TorrentPlayer.isPlayable(file)
|
||||||
var infoHash = torrentSummary.infoHash
|
var infoHash = torrentSummary.infoHash
|
||||||
var icon
|
var icon
|
||||||
var rowClass = ''
|
|
||||||
var handleClick
|
var handleClick
|
||||||
if (TorrentPlayer.isPlayable(file)) {
|
if (isPlayable) {
|
||||||
icon = 'play_arrow' /* playable? add option to play */
|
icon = 'play_arrow' /* playable? add option to play */
|
||||||
handleClick = dispatcher('play', infoHash, index)
|
handleClick = dispatcher('play', infoHash, index)
|
||||||
} else {
|
} else {
|
||||||
icon = 'description' /* file icon, opens in OS default app */
|
icon = 'description' /* file icon, opens in OS default app */
|
||||||
rowClass = isDone ? '' : 'disabled'
|
|
||||||
handleClick = dispatcher('openFile', infoHash, index)
|
handleClick = dispatcher('openFile', infoHash, index)
|
||||||
}
|
}
|
||||||
|
var rowClass = ''
|
||||||
|
if (!isSelected) rowClass = 'disabled' // File deselected, not being torrented
|
||||||
|
if (!isDone && !isPlayable) rowClass = 'disabled' // Can't open yet, can't stream
|
||||||
return hx`
|
return hx`
|
||||||
<tr onclick=${handleClick} class='${rowClass}'>
|
<tr>
|
||||||
<td class='col-icon'>
|
<td class='col-icon ${rowClass}' onclick=${handleClick}>
|
||||||
<i class='icon'>${icon}</i>
|
<i class='icon'>${icon}</i>
|
||||||
</td>
|
</td>
|
||||||
<td class='col-name'>${file.name}</td>
|
<td class='col-name ${rowClass}' onclick=${handleClick}>
|
||||||
<td class='col-progress'>${progress}</td>
|
${file.name}
|
||||||
<td class='col-size'>${prettyBytes(file.length)}</td>
|
</td>
|
||||||
|
<td class='col-progress ${rowClass}' onclick=${handleClick}>
|
||||||
|
${isSelected ? progress : ''}
|
||||||
|
</td>
|
||||||
|
<td class='col-size ${rowClass}' onclick=${handleClick}>
|
||||||
|
${prettyBytes(file.length)}
|
||||||
|
</td>
|
||||||
|
<td class='col-select'
|
||||||
|
onclick=${dispatcher('toggleTorrentFile', infoHash, index)}>
|
||||||
|
<i class='icon'>${isSelected ? 'close' : 'add'}</i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|||||||
42
renderer/views/unsupported-media-modal.js
Normal file
42
renderer/views/unsupported-media-modal.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module.exports = UnsupportedMediaModal
|
||||||
|
|
||||||
|
var h = require('virtual-dom/h')
|
||||||
|
var hyperx = require('hyperx')
|
||||||
|
var hx = hyperx(h)
|
||||||
|
|
||||||
|
var electron = require('electron')
|
||||||
|
|
||||||
|
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||||
|
|
||||||
|
function UnsupportedMediaModal (state) {
|
||||||
|
var err = state.modal.error
|
||||||
|
var message = (err && err.getMessage)
|
||||||
|
? err.getMessage()
|
||||||
|
: err
|
||||||
|
var actionButton = state.modal.vlcInstalled
|
||||||
|
? hx`<button class="button-raised" onclick=${onPlay}>Play in VLC</button>`
|
||||||
|
: hx`<button class="button-raised" onclick=${onInstall}>Install VLC</button>`
|
||||||
|
var vlcMessage = state.modal.vlcNotFound
|
||||||
|
? 'Couldn\'t run VLC. Please make sure it\'s installed.'
|
||||||
|
: ''
|
||||||
|
return hx`
|
||||||
|
<div>
|
||||||
|
<p><strong>Sorry, we can't play that file.</strong></p>
|
||||||
|
<p>${message}</p>
|
||||||
|
<p class='float-right'>
|
||||||
|
<button class="button-flat" onclick=${dispatcher('backToList')}>Cancel</button>
|
||||||
|
${actionButton}
|
||||||
|
</p>
|
||||||
|
<p class='error-text'>${vlcMessage}</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
function onInstall () {
|
||||||
|
electron.shell.openExternal('http://www.videolan.org/vlc/')
|
||||||
|
state.modal.vlcInstalled = true // Assume they'll install it successfully
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPlay () {
|
||||||
|
dispatch('vlcPlay')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,7 @@ var WebTorrent = require('webtorrent')
|
|||||||
var defaultAnnounceList = require('create-torrent').announceList
|
var defaultAnnounceList = require('create-torrent').announceList
|
||||||
var deepEqual = require('deep-equal')
|
var deepEqual = require('deep-equal')
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
var fs = require('fs')
|
var fs = require('fs-extra')
|
||||||
var mkdirp = require('mkdirp')
|
|
||||||
var musicmetadata = require('musicmetadata')
|
var musicmetadata = require('musicmetadata')
|
||||||
var networkAddress = require('network-address')
|
var networkAddress = require('network-address')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
@@ -29,7 +28,14 @@ global.WEBTORRENT_ANNOUNCE = defaultAnnounceList
|
|||||||
|
|
||||||
// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
|
// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
|
||||||
// client, as explained here: https://webtorrent.io/faq
|
// client, as explained here: https://webtorrent.io/faq
|
||||||
var client = window.client = new WebTorrent()
|
var client = window.client = new WebTorrent({
|
||||||
|
tracker: {
|
||||||
|
// HACK: OS X: Disable WebRTC peers to fix 100% CPU issue caused by Chrome bug.
|
||||||
|
// Fixed in Chrome 51, so we can remove this hack once Electron updates Chrome.
|
||||||
|
// Issue: https://github.com/feross/webtorrent-desktop/issues/353
|
||||||
|
wrtc: process.platform !== 'darwin'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// WebTorrent-to-HTTP streaming sever
|
// WebTorrent-to-HTTP streaming sever
|
||||||
var server = window.server = null
|
var server = window.server = null
|
||||||
@@ -43,8 +49,8 @@ function init () {
|
|||||||
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
|
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
|
||||||
client.on('error', (err) => ipc.send('wt-error', null, err.message))
|
client.on('error', (err) => ipc.send('wt-error', null, err.message))
|
||||||
|
|
||||||
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes) =>
|
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
|
||||||
startTorrenting(torrentKey, torrentID, path, fileModtimes))
|
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
|
||||||
ipc.on('wt-stop-torrenting', (e, infoHash) =>
|
ipc.on('wt-stop-torrenting', (e, infoHash) =>
|
||||||
stopTorrenting(infoHash))
|
stopTorrenting(infoHash))
|
||||||
ipc.on('wt-create-torrent', (e, torrentKey, options) =>
|
ipc.on('wt-create-torrent', (e, torrentKey, options) =>
|
||||||
@@ -59,6 +65,8 @@ function init () {
|
|||||||
startServer(infoHash, index))
|
startServer(infoHash, index))
|
||||||
ipc.on('wt-stop-server', (e) =>
|
ipc.on('wt-stop-server', (e) =>
|
||||||
stopServer())
|
stopServer())
|
||||||
|
ipc.on('wt-select-files', (e, infoHash, selections) =>
|
||||||
|
selectFiles(infoHash, selections))
|
||||||
|
|
||||||
ipc.send('ipcReadyWebTorrent')
|
ipc.send('ipcReadyWebTorrent')
|
||||||
|
|
||||||
@@ -67,26 +75,34 @@ function init () {
|
|||||||
|
|
||||||
// Starts a given TorrentID, which can be an infohash, magnet URI, etc. Returns WebTorrent object
|
// Starts a given TorrentID, which can be an infohash, magnet URI, etc. Returns WebTorrent object
|
||||||
// See https://github.com/feross/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-
|
// See https://github.com/feross/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-
|
||||||
function startTorrenting (torrentKey, torrentID, path, fileModtimes) {
|
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
|
||||||
console.log('starting torrent %s: %s', torrentKey, torrentID)
|
console.log('starting torrent %s: %s', torrentKey, torrentID)
|
||||||
|
|
||||||
var torrent = client.add(torrentID, {
|
var torrent = client.add(torrentID, {
|
||||||
path: path,
|
path: path,
|
||||||
fileModtimes: fileModtimes
|
fileModtimes: fileModtimes
|
||||||
})
|
})
|
||||||
torrent.key = torrentKey
|
torrent.key = torrentKey
|
||||||
|
|
||||||
|
// Listen for ready event, progress notifications, etc
|
||||||
addTorrentEvents(torrent)
|
addTorrentEvents(torrent)
|
||||||
|
|
||||||
|
// Only download the files the user wants, not necessarily all files
|
||||||
|
torrent.once('ready', () => selectFiles(torrent, selections))
|
||||||
|
|
||||||
return torrent
|
return torrent
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopTorrenting (infoHash) {
|
function stopTorrenting (infoHash) {
|
||||||
var torrent = client.get(infoHash)
|
var torrent = client.get(infoHash)
|
||||||
torrent.destroy()
|
if (torrent) torrent.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new torrent, start seeding
|
// Create a new torrent, start seeding
|
||||||
function createTorrent (torrentKey, options) {
|
function createTorrent (torrentKey, options) {
|
||||||
console.log('creating torrent %s', torrentKey, options)
|
console.log('creating torrent', torrentKey, options)
|
||||||
var torrent = client.seed(options.files, options)
|
var paths = options.files.map((f) => f.path)
|
||||||
|
var torrent = client.seed(paths, options)
|
||||||
torrent.key = torrentKey
|
torrent.key = torrentKey
|
||||||
addTorrentEvents(torrent)
|
addTorrentEvents(torrent)
|
||||||
ipc.send('wt-new-torrent')
|
ipc.send('wt-new-torrent')
|
||||||
@@ -148,9 +164,7 @@ function getTorrentFileInfo (file) {
|
|||||||
return {
|
return {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
length: file.length,
|
length: file.length,
|
||||||
path: file.path,
|
path: file.path
|
||||||
numPiecesPresent: 0,
|
|
||||||
numPieces: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,9 +173,10 @@ function getTorrentFileInfo (file) {
|
|||||||
function saveTorrentFile (torrentKey) {
|
function saveTorrentFile (torrentKey) {
|
||||||
var torrent = getTorrent(torrentKey)
|
var torrent = getTorrent(torrentKey)
|
||||||
checkIfTorrentFileExists(torrent.infoHash, function (torrentPath, exists) {
|
checkIfTorrentFileExists(torrent.infoHash, function (torrentPath, exists) {
|
||||||
|
var fileName = torrent.infoHash + '.torrent'
|
||||||
if (exists) {
|
if (exists) {
|
||||||
// We've already saved the file
|
// We've already saved the file
|
||||||
return ipc.send('wt-file-saved', torrentKey, torrentPath)
|
return ipc.send('wt-file-saved', torrentKey, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, save the .torrent file, under the app config folder
|
// Otherwise, save the .torrent file, under the app config folder
|
||||||
@@ -169,7 +184,7 @@ function saveTorrentFile (torrentKey) {
|
|||||||
fs.writeFile(torrentPath, torrent.torrentFile, function (err) {
|
fs.writeFile(torrentPath, torrent.torrentFile, function (err) {
|
||||||
if (err) return console.log('error saving torrent file %s: %o', torrentPath, err)
|
if (err) return console.log('error saving torrent file %s: %o', torrentPath, err)
|
||||||
console.log('saved torrent file %s', torrentPath)
|
console.log('saved torrent file %s', torrentPath)
|
||||||
return ipc.send('wt-file-saved', torrentKey, torrentPath)
|
return ipc.send('wt-file-saved', torrentKey, fileName)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -191,13 +206,14 @@ function generateTorrentPoster (torrentKey) {
|
|||||||
torrentPoster(torrent, function (err, buf, extension) {
|
torrentPoster(torrent, function (err, buf, extension) {
|
||||||
if (err) return console.log('error generating poster: %o', err)
|
if (err) return console.log('error generating poster: %o', err)
|
||||||
// save it for next time
|
// save it for next time
|
||||||
mkdirp(config.CONFIG_POSTER_PATH, function (err) {
|
fs.mkdirp(config.CONFIG_POSTER_PATH, function (err) {
|
||||||
if (err) return console.log('error creating poster dir: %o', err)
|
if (err) return console.log('error creating poster dir: %o', err)
|
||||||
var posterFilePath = path.join(config.CONFIG_POSTER_PATH, torrent.infoHash + extension)
|
var posterFileName = torrent.infoHash + extension
|
||||||
|
var posterFilePath = path.join(config.CONFIG_POSTER_PATH, posterFileName)
|
||||||
fs.writeFile(posterFilePath, buf, function (err) {
|
fs.writeFile(posterFilePath, buf, function (err) {
|
||||||
if (err) return console.log('error saving poster: %o', err)
|
if (err) return console.log('error saving poster: %o', err)
|
||||||
// show the poster
|
// show the poster
|
||||||
ipc.send('wt-poster', torrentKey, posterFilePath)
|
ipc.send('wt-poster', torrentKey, posterFileName)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -299,6 +315,48 @@ function getAudioMetadata (infoHash, index) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectFiles (torrentOrInfoHash, selections) {
|
||||||
|
// Get the torrent object
|
||||||
|
var torrent
|
||||||
|
if (typeof torrentOrInfoHash === 'string') {
|
||||||
|
torrent = client.get(torrentOrInfoHash)
|
||||||
|
} else {
|
||||||
|
torrent = torrentOrInfoHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selections not specified?
|
||||||
|
// Load all files. We still need to replace the default whole-torrent
|
||||||
|
// selection with individual selections for each file, so we can
|
||||||
|
// select/deselect files later on
|
||||||
|
if (!selections) {
|
||||||
|
selections = torrent.files.map((x) => true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selections specified incorrectly?
|
||||||
|
if (selections.length !== torrent.files.length) {
|
||||||
|
throw new Error('got ' + selections.length + ' file selections, ' +
|
||||||
|
'but the torrent contains ' + torrent.files.length + ' files')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove default selection (whole torrent)
|
||||||
|
torrent.deselect(0, torrent.pieces.length - 1, false)
|
||||||
|
|
||||||
|
// Add selections (individual files)
|
||||||
|
for (var i = 0; i < selections.length; i++) {
|
||||||
|
var file = torrent.files[i]
|
||||||
|
if (selections[i]) {
|
||||||
|
file.select()
|
||||||
|
} else {
|
||||||
|
console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
|
||||||
|
file.deselect()
|
||||||
|
|
||||||
|
// If we deselected a file, try to nuke it to save disk space
|
||||||
|
var filePath = path.join(torrent.path, file.path)
|
||||||
|
fs.unlink(filePath) // Ignore errors for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Gets a WebTorrent handle by torrentKey
|
// Gets a WebTorrent handle by torrentKey
|
||||||
// Throws an Error if we're not currently torrenting anything w/ that key
|
// Throws an Error if we're not currently torrenting anything w/ that key
|
||||||
function getTorrent (torrentKey) {
|
function getTorrent (torrentKey) {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
BIN
static/WebTorrentSmaller.png
Normal file
BIN
static/WebTorrentSmaller.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 726 KiB |
BIN
static/wired-cd.jpg
Normal file
BIN
static/wired-cd.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
static/wired-cd.torrent
Normal file
BIN
static/wired-cd.torrent
Normal file
Binary file not shown.
Reference in New Issue
Block a user