Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5d0692a56 | ||
|
|
2f4fe3e521 | ||
|
|
4937f61199 | ||
|
|
e74b993ecd | ||
|
|
36be5b2556 | ||
|
|
6a3aaba01f | ||
|
|
c50783d058 | ||
|
|
32b251fa58 | ||
|
|
fcf087810e | ||
|
|
0124412d3d | ||
|
|
dd570d9250 | ||
|
|
64add5d68b | ||
|
|
36a43b645e |
@@ -34,8 +34,5 @@
|
|||||||
- PurgingPanda (t3ch0wn3r@gmail.com)
|
- PurgingPanda (t3ch0wn3r@gmail.com)
|
||||||
- Kai Curtis (morecode@kcurtis.com)
|
- Kai Curtis (morecode@kcurtis.com)
|
||||||
- Omri Litov (omrilitov@gmail.com)
|
- Omri Litov (omrilitov@gmail.com)
|
||||||
- Alexey Romanov (romanalexey@gmail.com)
|
|
||||||
- Karan Thakkar (karanjthakkar@gmail.com)
|
|
||||||
- Nuno Campos (nuno.campos@me.com)
|
|
||||||
|
|
||||||
#### Generated by bin/update-authors.sh.
|
#### Generated by bin/update-authors.sh.
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
## v0.16.0 - 2016-09-18
|
## v0.16.0 - 2016-09-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **Windows 64-bit support!** ([#931](https://github.com/webtorrent/webtorrent-desktop/pull/931))
|
- **Windows 64-bit support!** ([#931](https://github.com/feross/webtorrent-desktop/pull/931))
|
||||||
- Existing 32-bit users will update to 64-bit automatically in next release
|
- Existing 32-bit users will update to 64-bit automatically in next release
|
||||||
- 64-bit reduces likelihood of out-of-memory errors by increasing the address space
|
- 64-bit reduces likelihood of out-of-memory errors by increasing the address space
|
||||||
|
|
||||||
@@ -516,7 +516,7 @@ Windows, and Linux. For now, we're only releasing binaries for OS X.
|
|||||||
|
|
||||||
WebTorrent Desktop is in ALPHA and under very active development – expect lots more polish in
|
WebTorrent Desktop is in ALPHA and under very active development – expect lots more polish in
|
||||||
the coming weeks! If you know JavaScript and want to help us out, there's
|
the coming weeks! If you know JavaScript and want to help us out, there's
|
||||||
[lots to do](https://github.com/webtorrent/webtorrent-desktop/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)!
|
[lots to do](https://github.com/feross/webtorrent-desktop/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)!
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ and to avoid style arguments. `npm test` runs `standard` automatically, so you d
|
|||||||
to!
|
to!
|
||||||
|
|
||||||
[standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg
|
[standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg
|
||||||
[standard-url]: https://standardjs.com
|
[standard-url]: https://github.com/feross/standard
|
||||||
|
|
||||||
## Project Governance
|
## Project Governance
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ standard guarded open source project.
|
|||||||
|
|
||||||
There are a few basic ground-rules for contributors:
|
There are a few basic ground-rules for contributors:
|
||||||
|
|
||||||
1. **No `--force` pushes to master** or modifying history in any way. Rebasing and force pushing your own PR branch is fine.
|
1. **No `--force` pushes** or modifying the Git history in any way.
|
||||||
2. **Non-master branches** should be used for ongoing work.
|
2. **Non-master branches** should be used for ongoing work.
|
||||||
3. **Significant modifications** like API changes should be subject to a **pull request**
|
3. **Significant modifications** like API changes should be subject to a **pull request**
|
||||||
to solicit feedback from other contributors.
|
to solicit feedback from other contributors.
|
||||||
@@ -74,99 +74,6 @@ By making a contribution to this project, I certify that:
|
|||||||
including my sign-off) is maintained indefinitely and may be redistributed consistent
|
including my sign-off) is maintained indefinitely and may be redistributed consistent
|
||||||
with this project or the open source license(s) involved.
|
with this project or the open source license(s) involved.
|
||||||
|
|
||||||
## Release Procedure
|
|
||||||
|
|
||||||
### 1. Create a new version
|
|
||||||
|
|
||||||
- Update `AUTHORS`
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run update-authors
|
|
||||||
```
|
|
||||||
|
|
||||||
Commit if necessary. The commit message should be "authors".
|
|
||||||
|
|
||||||
- Write the changelog
|
|
||||||
|
|
||||||
You can use `git log --oneline <last version tag>..HEAD` to get a list of changes.
|
|
||||||
|
|
||||||
Summarize them concisely in `CHANGELOG.md`. The commit message should be "changelog".
|
|
||||||
|
|
||||||
- Update the version
|
|
||||||
|
|
||||||
```
|
|
||||||
npm version [major|minor|patch]
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates both a commit and a git tag.
|
|
||||||
|
|
||||||
- Make a PR
|
|
||||||
|
|
||||||
Once the PR is reviewed, merge it:
|
|
||||||
|
|
||||||
```
|
|
||||||
git push origin <branch-name>:master
|
|
||||||
```
|
|
||||||
|
|
||||||
This makes it so that the commit hash on master matches the commit hash of the version tag.
|
|
||||||
|
|
||||||
Finally, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
git push --tags
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Create the release binaries
|
|
||||||
|
|
||||||
- On a Mac:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run package -- darwin --sign
|
|
||||||
npm run package -- linux --sign
|
|
||||||
```
|
|
||||||
|
|
||||||
- On Windows, or in a Windows VM:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run package -- win32 --sign
|
|
||||||
```
|
|
||||||
|
|
||||||
- Then, upload the release binaries to Github:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run gh-release
|
|
||||||
```
|
|
||||||
|
|
||||||
Follow the URL to a newly created Github release page. Manually upload the binaries from
|
|
||||||
`webtorrent-desktop/dist/`. Open the previous release in another tab, and make sure that you
|
|
||||||
are uploading the same set of files, no more, no less.
|
|
||||||
|
|
||||||
### 3. Test it
|
|
||||||
|
|
||||||
**This is the most important part.**
|
|
||||||
|
|
||||||
- Manually download the binaries for each platform from Github.
|
|
||||||
|
|
||||||
**Do not use your locally built binaries.** Modern OSs treat executables differently if they've
|
|
||||||
been downloaded, even though the files are byte for byte identical. This ensures that the
|
|
||||||
codesigning worked and is valid.
|
|
||||||
|
|
||||||
- Smoke test WebTorrent Desktop on each platform.
|
|
||||||
|
|
||||||
See Smoke Tests below for details. Open DevTools
|
|
||||||
on Windows and Mac, and ensure that the auto updater is running. If the auto updater does not
|
|
||||||
run, users will successfully auto update to this new version, and then be stuck there forever.
|
|
||||||
|
|
||||||
### 4. Ship it
|
|
||||||
|
|
||||||
- Update the website
|
|
||||||
|
|
||||||
Create a pull request in [webtorrent.io](https://github.com/webtorrent/webtorrent.io). Update
|
|
||||||
`config.js`, updating the desktop app version.
|
|
||||||
|
|
||||||
As soon as this PR is merged, Jenkins will automatically redeploy the WebTorrent website, and
|
|
||||||
hundreds of thousands of users around the world will start auto updating. **Merge with care.**
|
|
||||||
|
|
||||||
## Smoke Tests
|
## Smoke Tests
|
||||||
|
|
||||||
Before a release, check that the following basic use cases work correctly:
|
Before a release, check that the following basic use cases work correctly:
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -1,8 +1,6 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<br>
|
<br>
|
||||||
<a href="https://webtorrent.io">
|
<a href="https://webtorrent.io"><img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200"></a>
|
||||||
<img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200">
|
|
||||||
</a>
|
|
||||||
<br>
|
<br>
|
||||||
WebTorrent Desktop
|
WebTorrent Desktop
|
||||||
<br>
|
<br>
|
||||||
@@ -12,17 +10,16 @@
|
|||||||
<h4 align="center">The streaming torrent app. For Mac, Windows, and Linux.</h4>
|
<h4 align="center">The streaming torrent app. For Mac, Windows, and Linux.</h4>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://gitter.im/webtorrent/webtorrent"><img src="https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg" alt="gitter"></a>
|
<a href="https://gitter.im/feross/webtorrent"><img src="https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg" alt="Gitter"></a>
|
||||||
<a href="https://github.com/webtorrent/webtorrent-desktop/releases"><img src="https://img.shields.io/github/release/webtorrent/webtorrent-desktop.svg" alt="github release"></a>
|
<a href="https://travis-ci.org/feross/webtorrent-desktop"><img src="https://img.shields.io/travis/feross/webtorrent-desktop/master.svg" alt="Travis"></a>
|
||||||
<a href="https://travis-ci.org/webtorrent/webtorrent-desktop"><img src="https://img.shields.io/travis/webtorrent/webtorrent-desktop/master.svg" alt="travis"></a>
|
<a href="https://github.com/feross/webtorrent-desktop/releases"><img src="https://img.shields.io/github/release/feross/webtorrent-desktop.svg" alt="Release"></a>
|
||||||
<a href="https://standardjs.com"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="Standard - JavaScript Style Guide"></a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Download the latest version of WebTorrent Desktop from
|
Download the latest version of WebTorrent Desktop from
|
||||||
[the official website](https://webtorrent.io/desktop/) or the
|
[the official website](https://webtorrent.io/desktop/) or the
|
||||||
[GitHub releases](https://github.com/webtorrent/webtorrent-desktop/releases) page.
|
[GitHub releases](https://github.com/feross/webtorrent-desktop/releases) page.
|
||||||
|
|
||||||
**WebTorrent Desktop** is under very active development. You can try out the
|
**WebTorrent Desktop** is under very active development. You can try out the
|
||||||
current (unstable) development version by cloning the Git repo. See the
|
current (unstable) development version by cloning the Git repo. See the
|
||||||
@@ -40,7 +37,7 @@ instructions below in the ["How to Contribute"](#how-to-contribute) section.
|
|||||||
### Get the code
|
### Get the code
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://github.com/webtorrent/webtorrent-desktop.git
|
$ git clone https://github.com/feross/webtorrent-desktop.git
|
||||||
$ cd webtorrent-desktop
|
$ cd webtorrent-desktop
|
||||||
$ npm install
|
$ npm install
|
||||||
```
|
```
|
||||||
@@ -71,25 +68,19 @@ $ npm test
|
|||||||
$ npm run test-integration
|
$ npm run test-integration
|
||||||
```
|
```
|
||||||
|
|
||||||
The integration tests use Spectron and Tape. They click through the app, taking screenshots and
|
The integration tests use Spectron and Tape. They click through the app, taking screenshots and comparing each one to a reference. Why screenshots?
|
||||||
comparing each one to a reference. Why screenshots?
|
|
||||||
|
|
||||||
* Ad-hoc checking makes the tests a lot more work to write
|
* Ad-hoc checking makes the tests a lot more work to write
|
||||||
* Even diffing the whole HTML is not as thorough as screenshot diffing. For example, it wouldn't
|
* Even diffing the whole HTML is not as thorough as screenshot diffing. For example, it wouldn't catch an bug where hitting ESC from a video doesn't correctly restore window size.
|
||||||
catch an bug where hitting ESC from a video doesn't correctly restore window size.
|
|
||||||
* Chrome's own integration tests use screenshot diffing iirc
|
* Chrome's own integration tests use screenshot diffing iirc
|
||||||
* Small UI changes will break a few tests, but the fix is as easy as deleting the offending
|
* Small UI changes will break a few tests, but the fix is as easy as deleting the offending screenshots and running the tests, which will recreate them with the new look.
|
||||||
screenshots and running the tests, which will recreate them with the new look.
|
* The resulting Github PR will then show, pixel by pixel, the exact UI changes that were made! Ses https://github.com/blog/817-behold-image-view-modes
|
||||||
* The resulting Github PR will then show, pixel by pixel, the exact UI changes that were made! See
|
|
||||||
https://github.com/blog/817-behold-image-view-modes
|
|
||||||
|
|
||||||
For MacOS, you'll need a Retina screen for the integration tests to pass. Your screen should have
|
For MacOS, you'll need a Retina screen for the integration tests to pass. Your screen should have the same resolution as a 2016 12" Macbook.
|
||||||
the same resolution as a 2016 12" Macbook.
|
|
||||||
|
|
||||||
For Windows, you'll need Windows 10 with a 1366x768 screen.
|
For Windows, you'll need Windows 10 with a 1366x768 screen.
|
||||||
|
|
||||||
When running integration tests, keep the mouse on the edge of the screen and don't touch the mouse
|
When running integration tests, keep the mouse on the edge of the screen and don't touch the mouse or keyboard while the tests are running.
|
||||||
or keyboard while the tests are running.
|
|
||||||
|
|
||||||
### Package the app
|
### Package the app
|
||||||
|
|
||||||
@@ -119,7 +110,7 @@ The following optional arguments are available:
|
|||||||
- `all` - All platforms (default)
|
- `all` - All platforms (default)
|
||||||
|
|
||||||
Note: Even with the `--package` option, the auto-update files (.nupkg for Windows,
|
Note: Even with the `--package` option, the auto-update files (.nupkg for Windows,
|
||||||
-darwin.zip for Mac) will always be produced.
|
*-darwin.zip for Mac) will always be produced.
|
||||||
|
|
||||||
#### Windows build notes
|
#### Windows build notes
|
||||||
|
|
||||||
@@ -154,6 +145,10 @@ Time out? Show a missing codec error?
|
|||||||
The app never sends any personally identifying information, nor does it track which
|
The app never sends any personally identifying information, nor does it track which
|
||||||
torrents you add.
|
torrents you add.
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
[](https://github.com/feross/standard)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT. Copyright (c) [WebTorrent, LLC](https://webtorrent.io).
|
MIT. Copyright (c) [WebTorrent, LLC](https://webtorrent.io).
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ let tmpPath
|
|||||||
try {
|
try {
|
||||||
tmpPath = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent')
|
tmpPath = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
tmpPath = path.join(os.tmpdir(), 'webtorrent')
|
tmpPath = path.join(os.tmpDir(), 'webtorrent')
|
||||||
}
|
}
|
||||||
rimraf.sync(tmpPath)
|
rimraf.sync(tmpPath)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const pkg = require('../package.json')
|
|||||||
const BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
|
const BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
|
||||||
const BUILD_PATH = path.join(config.ROOT_PATH, 'build')
|
const BUILD_PATH = path.join(config.ROOT_PATH, 'build')
|
||||||
const DIST_PATH = path.join(config.ROOT_PATH, 'dist')
|
const DIST_PATH = path.join(config.ROOT_PATH, 'dist')
|
||||||
const NODE_MODULES_PATH = path.join(config.ROOT_PATH, 'node_modules')
|
|
||||||
|
|
||||||
const argv = minimist(process.argv.slice(2), {
|
const argv = minimist(process.argv.slice(2), {
|
||||||
boolean: [
|
boolean: [
|
||||||
@@ -37,12 +36,6 @@ const argv = minimist(process.argv.slice(2), {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function build () {
|
function build () {
|
||||||
console.log('Reinstalling node_modules...')
|
|
||||||
rimraf.sync(NODE_MODULES_PATH)
|
|
||||||
cp.execSync('npm install', { stdio: 'inherit' })
|
|
||||||
cp.execSync('npm dedupe', { stdio: 'inherit' })
|
|
||||||
|
|
||||||
console.log('Nuking dist/ and build/...')
|
|
||||||
rimraf.sync(DIST_PATH)
|
rimraf.sync(DIST_PATH)
|
||||||
rimraf.sync(BUILD_PATH)
|
rimraf.sync(BUILD_PATH)
|
||||||
|
|
||||||
|
|||||||
9
bin/release-_post.sh
Executable file
9
bin/release-_post.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
npm run update-authors
|
||||||
|
git diff --exit-code
|
||||||
|
npm run package -- --sign
|
||||||
|
git push
|
||||||
|
git push --tags
|
||||||
|
npm run gh-release
|
||||||
8
bin/release-_pre.sh
Executable file
8
bin/release-_pre.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
git pull
|
||||||
|
rm -rf node_modules/
|
||||||
|
npm install
|
||||||
|
npm dedupe
|
||||||
|
npm test
|
||||||
7
bin/release-major.sh
Executable file
7
bin/release-major.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
BIN=`dirname $0`
|
||||||
|
|
||||||
|
$BIN/release-_pre.sh
|
||||||
|
npm version major
|
||||||
|
$BIN/release-_post.sh
|
||||||
7
bin/release-minor.sh
Executable file
7
bin/release-minor.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
BIN=`dirname $0`
|
||||||
|
|
||||||
|
$BIN/release-_pre.sh
|
||||||
|
npm version minor
|
||||||
|
$BIN/release-_post.sh
|
||||||
7
bin/release-patch.sh
Executable file
7
bin/release-patch.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
BIN=`dirname $0`
|
||||||
|
|
||||||
|
$BIN/release-_pre.sh
|
||||||
|
npm version patch
|
||||||
|
$BIN/release-_post.sh
|
||||||
16
package.json
16
package.json
@@ -8,7 +8,7 @@
|
|||||||
"url": "https://webtorrent.io"
|
"url": "https://webtorrent.io"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/webtorrent/webtorrent-desktop/issues"
|
"url": "https://github.com/feross/webtorrent-desktop/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"airplayer": "^2.0.0",
|
"airplayer": "^2.0.0",
|
||||||
@@ -26,25 +26,21 @@
|
|||||||
"drag-drop": "^2.12.1",
|
"drag-drop": "^2.12.1",
|
||||||
"es6-error": "^4.0.0",
|
"es6-error": "^4.0.0",
|
||||||
"fn-getter": "^1.0.0",
|
"fn-getter": "^1.0.0",
|
||||||
"gaze": "^1.1.2",
|
|
||||||
"iso-639-1": "^1.2.1",
|
"iso-639-1": "^1.2.1",
|
||||||
"languagedetect": "^1.1.1",
|
"languagedetect": "^1.1.1",
|
||||||
"location-history": "^1.0.0",
|
"location-history": "^1.0.0",
|
||||||
"material-ui": "^0.17.0",
|
"material-ui": "^0.16.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"ms": "^0.7.2",
|
|
||||||
"musicmetadata": "^2.0.2",
|
"musicmetadata": "^2.0.2",
|
||||||
"network-address": "^1.1.0",
|
"network-address": "^1.1.0",
|
||||||
"node-notifier": "^5.0.2",
|
|
||||||
"parse-torrent": "^5.7.3",
|
"parse-torrent": "^5.7.3",
|
||||||
"prettier-bytes": "^1.0.1",
|
"prettier-bytes": "^1.0.1",
|
||||||
"react": "^15.4.2",
|
"react": "^15.2.1",
|
||||||
"react-dom": "^15.4.2",
|
"react-dom": "^15.2.1",
|
||||||
"react-tap-event-plugin": "^2.0.1",
|
"react-tap-event-plugin": "^2.0.1",
|
||||||
"rimraf": "^2.5.2",
|
"rimraf": "^2.5.2",
|
||||||
"run-parallel": "^1.1.6",
|
"run-parallel": "^1.1.6",
|
||||||
"semver": "^5.1.0",
|
"semver": "^5.1.0",
|
||||||
"shell-env": "^0.3.0",
|
|
||||||
"simple-concat": "^1.0.0",
|
"simple-concat": "^1.0.0",
|
||||||
"simple-get": "^2.0.0",
|
"simple-get": "^2.0.0",
|
||||||
"srt-to-vtt": "^1.1.1",
|
"srt-to-vtt": "^1.1.1",
|
||||||
@@ -57,7 +53,7 @@
|
|||||||
"buble": "^0.15.2",
|
"buble": "^0.15.2",
|
||||||
"cross-zip": "^2.0.1",
|
"cross-zip": "^2.0.1",
|
||||||
"depcheck": "^0.6.4",
|
"depcheck": "^0.6.4",
|
||||||
"electron": "1.6.0",
|
"electron": "1.4.15",
|
||||||
"electron-osx-sign": "0.4.3",
|
"electron-osx-sign": "0.4.3",
|
||||||
"electron-packager": "~8.5.1",
|
"electron-packager": "~8.5.1",
|
||||||
"electron-winstaller": "~2.5.2",
|
"electron-winstaller": "~2.5.2",
|
||||||
@@ -97,7 +93,7 @@
|
|||||||
"productName": "WebTorrent",
|
"productName": "WebTorrent",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/webtorrent/webtorrent-desktop.git"
|
"url": "git://github.com/feross/webtorrent-desktop.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "buble src --output build",
|
"build": "buble src --output build",
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
const appConfig = require('application-config')('WebTorrent')
|
const appConfig = require('application-config')('WebTorrent')
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const arch = require('arch')
|
const arch = require('arch')
|
||||||
const gaze = require('gaze')
|
|
||||||
|
|
||||||
const APP_NAME = 'WebTorrent'
|
const APP_NAME = 'WebTorrent'
|
||||||
const APP_TEAM = 'WebTorrent, LLC'
|
const APP_TEAM = 'WebTorrent, LLC'
|
||||||
@@ -19,7 +17,7 @@ const IS_PORTABLE = isPortable()
|
|||||||
const UI_HEADER_HEIGHT = 38
|
const UI_HEADER_HEIGHT = 38
|
||||||
const UI_TORRENT_HEIGHT = 100
|
const UI_TORRENT_HEIGHT = 100
|
||||||
|
|
||||||
const exports = module.exports = {
|
module.exports = {
|
||||||
ANNOUNCEMENT_URL: 'https://webtorrent.io/desktop/announcement',
|
ANNOUNCEMENT_URL: 'https://webtorrent.io/desktop/announcement',
|
||||||
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update',
|
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update',
|
||||||
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
||||||
@@ -72,9 +70,9 @@ const exports = module.exports = {
|
|||||||
|
|
||||||
DEFAULT_DOWNLOAD_PATH: getDefaultDownloadPath(),
|
DEFAULT_DOWNLOAD_PATH: getDefaultDownloadPath(),
|
||||||
|
|
||||||
GITHUB_URL: 'https://github.com/webtorrent/webtorrent-desktop',
|
GITHUB_URL: 'https://github.com/feross/webtorrent-desktop',
|
||||||
GITHUB_URL_ISSUES: 'https://github.com/webtorrent/webtorrent-desktop/issues',
|
GITHUB_URL_ISSUES: 'https://github.com/feross/webtorrent-desktop/issues',
|
||||||
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/webtorrent/webtorrent-desktop/master',
|
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/feross/webtorrent-desktop/master',
|
||||||
|
|
||||||
HOME_PAGE_URL: 'https://webtorrent.io',
|
HOME_PAGE_URL: 'https://webtorrent.io',
|
||||||
|
|
||||||
@@ -104,62 +102,6 @@ const exports = module.exports = {
|
|||||||
UI_TORRENT_HEIGHT: UI_TORRENT_HEIGHT
|
UI_TORRENT_HEIGHT: UI_TORRENT_HEIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
const configFile = appConfig.filePath
|
|
||||||
let config = getConfig()
|
|
||||||
const watchers = []
|
|
||||||
|
|
||||||
function updateConfig () {
|
|
||||||
config = JSON.parse(fs.readFileSync(configFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
function watch () {
|
|
||||||
gaze(configFile, function (err) {
|
|
||||||
if (err) {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
this.on('changed', () => {
|
|
||||||
try {
|
|
||||||
updateConfig()
|
|
||||||
console.log('WebTorrent configuration reloaded!')
|
|
||||||
watchers.forEach(fn => fn())
|
|
||||||
} catch (err) {
|
|
||||||
// TODO: display notification
|
|
||||||
console.log(`An error occurred loading your configuration (${configFile}): ${err.message}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.on('error', () => {
|
|
||||||
// Ignore file watching errors
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// start watching for config changes
|
|
||||||
watch()
|
|
||||||
|
|
||||||
exports.subscribe = function (fn) {
|
|
||||||
watchers.push(fn)
|
|
||||||
return () => {
|
|
||||||
watchers.splice(watchers.indexOf(fn), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPlugins () {
|
|
||||||
return config.plugins || {}
|
|
||||||
}
|
|
||||||
exports.getPlugins = getPlugins
|
|
||||||
|
|
||||||
exports.getConfigPath = getConfigPath
|
|
||||||
exports.getConfig = getConfig
|
|
||||||
|
|
||||||
function getConfig () {
|
|
||||||
const config = {}
|
|
||||||
try {
|
|
||||||
return require(configFile)
|
|
||||||
} catch (e) {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigPath () {
|
function getConfigPath () {
|
||||||
if (IS_PORTABLE) {
|
if (IS_PORTABLE) {
|
||||||
return PORTABLE_PATH
|
return PORTABLE_PATH
|
||||||
@@ -203,6 +145,8 @@ function isPortable () {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// This line throws if the "Portable Settings" folder does not exist, and does
|
// This line throws if the "Portable Settings" folder does not exist, and does
|
||||||
// nothing otherwise.
|
// nothing otherwise.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function downloadFinished (path) {
|
|||||||
*/
|
*/
|
||||||
function setBadge (count) {
|
function setBadge (count) {
|
||||||
if (process.platform === 'darwin' ||
|
if (process.platform === 'darwin' ||
|
||||||
(process.platform === 'linux' && app.isUnityRunning())) {
|
process.platform === 'linux' && app.isUnityRunning()) {
|
||||||
log(`setBadge: ${count}`)
|
log(`setBadge: ${count}`)
|
||||||
app.setBadgeCount(Number(count))
|
app.setBadgeCount(Number(count))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ let proc = null
|
|||||||
function checkInstall (playerPath, cb) {
|
function checkInstall (playerPath, cb) {
|
||||||
// check for VLC if external player has not been specified by the user
|
// check for VLC if external player has not been specified by the user
|
||||||
// otherwise assume the player is installed
|
// otherwise assume the player is installed
|
||||||
if (playerPath == null) return vlcCommand(cb)
|
if (playerPath == null) return vlcCommand((err) => cb(!err))
|
||||||
process.nextTick(() => cb(null))
|
process.nextTick(() => cb(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawn (playerPath, url, title) {
|
function spawn (playerPath, url, title) {
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ const log = require('./log')
|
|||||||
const menu = require('./menu')
|
const menu = require('./menu')
|
||||||
const State = require('../renderer/lib/state')
|
const State = require('../renderer/lib/state')
|
||||||
const windows = require('./windows')
|
const windows = require('./windows')
|
||||||
const Plugins = require('./plugins')
|
|
||||||
const plugins = new Plugins()
|
|
||||||
|
|
||||||
let shouldQuit = false
|
let shouldQuit = false
|
||||||
let argv = sliceArgv(process.argv)
|
let argv = sliceArgv(process.argv)
|
||||||
@@ -74,17 +72,10 @@ function init () {
|
|||||||
if (err) throw err
|
if (err) throw err
|
||||||
|
|
||||||
isReady = true
|
isReady = true
|
||||||
const state = results.state
|
|
||||||
|
|
||||||
// init new plugins then notify user
|
windows.main.init(results.state, {hidden: hidden})
|
||||||
plugins.subscribe(() => {
|
windows.webtorrent.init()
|
||||||
// update menu and windows
|
menu.init()
|
||||||
// passing thru new plugin decorators
|
|
||||||
initApp(state)
|
|
||||||
})
|
|
||||||
|
|
||||||
plugins.init(state)
|
|
||||||
initApp(state)
|
|
||||||
|
|
||||||
// To keep app startup fast, some code is delayed.
|
// To keep app startup fast, some code is delayed.
|
||||||
setTimeout(delayedInit, config.DELAYED_INIT)
|
setTimeout(delayedInit, config.DELAYED_INIT)
|
||||||
@@ -97,26 +88,6 @@ function init () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function initApp (state) {
|
|
||||||
// decorate app
|
|
||||||
plugins.onApp(app)
|
|
||||||
|
|
||||||
// init decorate menu
|
|
||||||
menu.init((tpl) => plugins.decorateMenu(tpl))
|
|
||||||
|
|
||||||
// init and decorate window
|
|
||||||
windows.main.init(
|
|
||||||
state,
|
|
||||||
{hidden: hidden},
|
|
||||||
(options) => plugins.decorateWindow(options)
|
|
||||||
)
|
|
||||||
windows.webtorrent.init((options) => plugins.decorateWindow(options))
|
|
||||||
plugins.onWindow([
|
|
||||||
windows.main.win,
|
|
||||||
windows.webtorrent.win
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('open-file', onOpen)
|
app.on('open-file', onOpen)
|
||||||
app.on('open-url', onOpen)
|
app.on('open-url', onOpen)
|
||||||
|
|
||||||
@@ -227,7 +198,7 @@ function processArgv (argv) {
|
|||||||
// Ignore hidden argument, already being handled
|
// Ignore hidden argument, already being handled
|
||||||
} else if (arg.startsWith('-psn')) {
|
} else if (arg.startsWith('-psn')) {
|
||||||
// Ignore Mac launchd "process serial number" argument
|
// Ignore Mac launchd "process serial number" argument
|
||||||
// Issue: https://github.com/webtorrent/webtorrent-desktop/issues/214
|
// Issue: https://github.com/feross/webtorrent-desktop/issues/214
|
||||||
} else if (arg.startsWith('--')) {
|
} else if (arg.startsWith('--')) {
|
||||||
// Ignore Spectron flags
|
// Ignore Spectron flags
|
||||||
} else if (arg === 'data:,') {
|
} else if (arg === 'data:,') {
|
||||||
|
|||||||
@@ -166,8 +166,8 @@ function init () {
|
|||||||
ipc.on('checkForExternalPlayer', function (e, path) {
|
ipc.on('checkForExternalPlayer', function (e, path) {
|
||||||
const externalPlayer = require('./external-player')
|
const externalPlayer = require('./external-player')
|
||||||
|
|
||||||
externalPlayer.checkInstall(path, function (err) {
|
externalPlayer.checkInstall(path, function (isInstalled) {
|
||||||
windows.main.send('checkForExternalPlayer', !err)
|
windows.main.send('checkForExternalPlayer', isInstalled)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,8 @@ const windows = require('./windows')
|
|||||||
|
|
||||||
let menu = null
|
let menu = null
|
||||||
|
|
||||||
function init (decorate) {
|
function init () {
|
||||||
let template = getMenuTemplate()
|
menu = electron.Menu.buildFromTemplate(getMenuTemplate())
|
||||||
if (decorate) template = decorate(template)
|
|
||||||
|
|
||||||
menu = electron.Menu.buildFromTemplate(template)
|
|
||||||
electron.Menu.setApplicationMenu(menu)
|
electron.Menu.setApplicationMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +324,7 @@ function getMenuTemplate () {
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
// WebTorrent menu (Mac)
|
// Add WebTorrent app menu (Mac)
|
||||||
template.unshift({
|
template.unshift({
|
||||||
label: config.APP_NAME,
|
label: config.APP_NAME,
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -370,25 +367,7 @@ function getMenuTemplate () {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// Edit menu (Mac)
|
// Add Window menu (Mac)
|
||||||
template[2].submenu.push(
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Speech',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
role: 'startspeaking'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'stopspeaking'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Window menu (Mac)
|
|
||||||
template.splice(6, 0, {
|
template.splice(6, 0, {
|
||||||
role: 'window',
|
role: 'window',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -429,7 +408,7 @@ function getMenuTemplate () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Help menu (Windows, Linux)
|
// Help menu (Windows, Linux)
|
||||||
template[5].submenu.push(
|
template[4].submenu.push(
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,424 +0,0 @@
|
|||||||
const {exec} = require('child_process')
|
|
||||||
const {resolve, basename} = require('path')
|
|
||||||
const {writeFileSync} = require('fs')
|
|
||||||
const State = require('../renderer/lib/state')
|
|
||||||
const notifier = require('node-notifier')
|
|
||||||
const {app} = require('electron')
|
|
||||||
|
|
||||||
const {sync: mkdirpSync} = require('mkdirp')
|
|
||||||
const ms = require('ms')
|
|
||||||
const shellEnv = require('shell-env')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
|
|
||||||
const config = require('../config')
|
|
||||||
|
|
||||||
module.exports = class Plugins {
|
|
||||||
constructor () {
|
|
||||||
// modules path
|
|
||||||
this.path = resolve(config.getConfigPath(), 'plugins')
|
|
||||||
log('Path: ', this.path)
|
|
||||||
this.availableExtensions = new Set([
|
|
||||||
'onApp', 'onWindow', 'decorateMenu', 'decorateWindow', 'decorateConfig'
|
|
||||||
])
|
|
||||||
|
|
||||||
this.forceUpdate = false
|
|
||||||
this.updating = false
|
|
||||||
this.watchers = []
|
|
||||||
}
|
|
||||||
|
|
||||||
init (state) {
|
|
||||||
this.state = state
|
|
||||||
|
|
||||||
// initialize state
|
|
||||||
this.state.saved = Object.assign(this.state.saved || {})
|
|
||||||
|
|
||||||
// init plugin directories if not present
|
|
||||||
mkdirpSync(this.path)
|
|
||||||
|
|
||||||
// caches
|
|
||||||
this.plugins = config.getPlugins()
|
|
||||||
this.paths = this.getPaths(this.plugins)
|
|
||||||
this.id = this.getId(this.plugins)
|
|
||||||
this.modules = this.requirePlugins()
|
|
||||||
|
|
||||||
// we listen on configuration updates to trigger
|
|
||||||
// plugin installation
|
|
||||||
config.subscribe(() => {
|
|
||||||
const plugins = config.getPlugins()
|
|
||||||
if (plugins !== this.plugins) {
|
|
||||||
const id = this.getId(plugins)
|
|
||||||
if (this.id !== id) {
|
|
||||||
this.alert('Installing plugins...')
|
|
||||||
log('UPDATING...')
|
|
||||||
this.id = id
|
|
||||||
this.plugins = plugins
|
|
||||||
this.paths = this.getPaths(this.plugins)
|
|
||||||
this.updatePlugins()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// schedule the initial plugins update
|
|
||||||
// a bit after the user launches the app
|
|
||||||
// to prevent slowness
|
|
||||||
if (this.needsUpdate()) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.updatePlugins()
|
|
||||||
}, 5000)
|
|
||||||
log('Installation scheduled')
|
|
||||||
}
|
|
||||||
|
|
||||||
// update plugins every 5 hours
|
|
||||||
setInterval(() => {
|
|
||||||
this.updatePlugins()
|
|
||||||
}, ms('5h'))
|
|
||||||
}
|
|
||||||
|
|
||||||
on (action, params) {
|
|
||||||
log(`ON ${action}:`, params)
|
|
||||||
this.modules.forEach(plugin => {
|
|
||||||
const actionName = this.capitalizeFirstLetter(action)
|
|
||||||
const methodName = `on${actionName}`
|
|
||||||
if (typeof plugin[methodName] === 'function') {
|
|
||||||
plugin[methodName](params)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
capitalizeFirstLetter (string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
didPluginsChange () {
|
|
||||||
return this.state.saved.installedPlugins !== this.id
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPlugins () {
|
|
||||||
return !this.isEmptyObject(this.plugins)
|
|
||||||
}
|
|
||||||
|
|
||||||
isFirstInstall () {
|
|
||||||
return (!this.state.saved.installedPlugins && this.hasPlugins())
|
|
||||||
}
|
|
||||||
|
|
||||||
needsUpdate () {
|
|
||||||
return (this.didPluginsChange() || this.isFirstInstall())
|
|
||||||
}
|
|
||||||
|
|
||||||
getId (plugins) {
|
|
||||||
const hash = crypto.createHash('sha256')
|
|
||||||
hash.update(JSON.stringify(plugins))
|
|
||||||
return hash.digest('hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePlugins (forceUpdate = false) {
|
|
||||||
this.forceUpdate = forceUpdate
|
|
||||||
if (this.updating) {
|
|
||||||
// TODO
|
|
||||||
// return notify('Plugin update in progress')
|
|
||||||
}
|
|
||||||
this.updating = true
|
|
||||||
this.syncPackageJSON()
|
|
||||||
this.installPackages((err) => this.loadPlugins(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
loadPlugins (err, localOnly = false) {
|
|
||||||
this.updating = false
|
|
||||||
|
|
||||||
// handle errors first
|
|
||||||
if (err) {
|
|
||||||
console.error(err.stack)
|
|
||||||
if (/not a recognized/.test(err.message) || /command not found/.test(err.message)) {
|
|
||||||
this.alert(
|
|
||||||
'Error updating plugins: We could not find the "npm" command. Make sure it\'s in $PATH'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.alert(`Error updating plugins: Check '${this.path}/npm-debug.log' for more information.`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// update state with latest plugins
|
|
||||||
this.state.saved.plugins = this.plugins
|
|
||||||
|
|
||||||
// cache modules
|
|
||||||
this.modules = this.requirePlugins()
|
|
||||||
|
|
||||||
// clear require cache
|
|
||||||
this.clearCache()
|
|
||||||
|
|
||||||
// we're done with local plugins
|
|
||||||
if (localOnly) return
|
|
||||||
|
|
||||||
// OK, no errors
|
|
||||||
// flag successful plugin update
|
|
||||||
this.state.saved.installedPlugins = this.id
|
|
||||||
|
|
||||||
// check if package based plugins were updated
|
|
||||||
const loaded = this.modules.length
|
|
||||||
const total = this.paths.plugins.length
|
|
||||||
const pluginVersions = JSON.stringify(this.getPluginVersions())
|
|
||||||
const changed = this.state.saved.installedPluginVersions !== pluginVersions && loaded === total
|
|
||||||
this.state.saved.installedPluginVersions = pluginVersions
|
|
||||||
|
|
||||||
// notify watchers
|
|
||||||
if (this.forceUpdate || changed) {
|
|
||||||
this.watchers.forEach(fn => fn(err, {forceUpdate: this.forceUpdate}))
|
|
||||||
this.alert('Installation completed')
|
|
||||||
log('Installation completed')
|
|
||||||
}
|
|
||||||
|
|
||||||
// save state
|
|
||||||
State.save(this.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginVersions () {
|
|
||||||
const paths_ = this.paths.plugins
|
|
||||||
return paths_.map(path => {
|
|
||||||
let version = null
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line import/no-dynamic-require
|
|
||||||
version = require(resolve(path, 'package.json')).version
|
|
||||||
} catch (err) { }
|
|
||||||
return [
|
|
||||||
basename(path),
|
|
||||||
version
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
clearCache () {
|
|
||||||
// trigger unload hooks
|
|
||||||
this.modules.forEach(mod => {
|
|
||||||
if (mod.onUnload) {
|
|
||||||
mod.onUnload(app)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// clear require cache
|
|
||||||
for (const entry in require.cache) {
|
|
||||||
if (entry.indexOf(this.path) === 0) {
|
|
||||||
delete require.cache[entry]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isEmptyObject (obj) {
|
|
||||||
return (Object.keys(obj).length === 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
syncPackageJSON () {
|
|
||||||
const dependencies = this.toDependencies(this.plugins)
|
|
||||||
const pkg = {
|
|
||||||
name: 'webtorrent-plugins',
|
|
||||||
description: 'Auto-generated from WebTorrent config.',
|
|
||||||
private: true,
|
|
||||||
version: '0.0.1',
|
|
||||||
repository: 'feross/webtorrent-desktop',
|
|
||||||
license: 'MIT',
|
|
||||||
homepage: 'https://webtorrent.io',
|
|
||||||
dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = resolve(this.path, 'package.json')
|
|
||||||
try {
|
|
||||||
writeFileSync(file, JSON.stringify(pkg, null, 2))
|
|
||||||
return true
|
|
||||||
} catch (err) {
|
|
||||||
this.alert(`An error occurred writing to ${file}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alert (message) {
|
|
||||||
notifier.notify({
|
|
||||||
title: 'WebTorrent Plugins',
|
|
||||||
// icon: config.icon, // TODO: save icon in webtorrent local folder and set config.icon
|
|
||||||
message: message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
isLocalPath (string) {
|
|
||||||
// matches unix and windows local paths
|
|
||||||
return string.match(/^(\/|[a-z]:\/)/i)
|
|
||||||
}
|
|
||||||
|
|
||||||
toDependencies (plugins) {
|
|
||||||
const obj = {}
|
|
||||||
const pluginNames = Object.keys(plugins)
|
|
||||||
|
|
||||||
pluginNames.forEach(name => {
|
|
||||||
let url = plugins[name]
|
|
||||||
if (this.isLocalPath(url)) return
|
|
||||||
obj[name] = url
|
|
||||||
})
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
installPackages (fn) {
|
|
||||||
const {shell = '', npmRegistry} = config
|
|
||||||
|
|
||||||
shellEnv(shell).then(env => {
|
|
||||||
if (npmRegistry) {
|
|
||||||
env.NPM_CONFIG_REGISTRY = npmRegistry
|
|
||||||
}
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
env.npm_config_runtime = 'electron'
|
|
||||||
env.npm_config_target = process.versions.electron
|
|
||||||
env.npm_config_disturl = 'https://atom.io/download/atom-shell'
|
|
||||||
/* eslint-enable camelcase */
|
|
||||||
// Shell-specific installation commands
|
|
||||||
const installCommands = {
|
|
||||||
fish: 'npm prune; and npm install --production',
|
|
||||||
posix: 'npm prune && npm install --production'
|
|
||||||
}
|
|
||||||
// determine the shell we're running in
|
|
||||||
const whichShell = shell.match(/fish/) ? 'fish' : 'posix'
|
|
||||||
|
|
||||||
// Use the install command that is appropriate for our shell
|
|
||||||
exec(installCommands[whichShell], {
|
|
||||||
cwd: this.path
|
|
||||||
}, err => {
|
|
||||||
if (err) return fn(err)
|
|
||||||
fn(null)
|
|
||||||
})
|
|
||||||
}).catch(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe (fn) {
|
|
||||||
this.watchers.push(fn)
|
|
||||||
return () => {
|
|
||||||
this.watchers.splice(this.watchers.indexOf(fn), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPaths (plugins) {
|
|
||||||
const pluginNames = Object.keys(plugins)
|
|
||||||
|
|
||||||
return {
|
|
||||||
plugins: pluginNames.map(name => {
|
|
||||||
let url = plugins[name]
|
|
||||||
|
|
||||||
// plugin is already on a local folder
|
|
||||||
// directly load it from its current location
|
|
||||||
if (this.isLocalPath(url)) return url
|
|
||||||
|
|
||||||
// plugin will be installed with npm install from a remote url
|
|
||||||
return resolve(this.path, 'node_modules', name.split('#')[0])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requirePlugins () {
|
|
||||||
const {plugins} = this.paths
|
|
||||||
let installNeeded = false
|
|
||||||
|
|
||||||
const load = (path) => {
|
|
||||||
if (!path.match(/\/$/)) {
|
|
||||||
path += '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainPath = `${path}main.js`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const Plugin = require(mainPath) // eslint-disable import/no-dynamic-require
|
|
||||||
const plugin = new Plugin()
|
|
||||||
|
|
||||||
const exposed = plugin && Object.keys(plugin).some(key => this.availableExtensions.has(key))
|
|
||||||
if (!exposed) return
|
|
||||||
|
|
||||||
// populate the name for internal errors here
|
|
||||||
plugin._name = basename(mainPath)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
} catch (err) {
|
|
||||||
log('Require plugins ERROR:', err)
|
|
||||||
this.alert(`Error loading plugin: ${mainPath}`)
|
|
||||||
// plugin not installed
|
|
||||||
// node_modules removed? did a manual plugin uninstall?
|
|
||||||
// try installing and then loading if successfull
|
|
||||||
installNeeded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plugin installation happens on the MAIN process.
|
|
||||||
// If plugins haven't finished installing, wait for them.
|
|
||||||
if (installNeeded) {
|
|
||||||
log('Plugins install needed, wait...')
|
|
||||||
setTimeout(() => {
|
|
||||||
this.requirePlugins()
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
return plugins.map(load).filter(v => Boolean(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
onApp (app) {
|
|
||||||
this.modules.forEach(plugin => {
|
|
||||||
if (plugin.onApp) {
|
|
||||||
plugin.onApp(app)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onWindow (win) {
|
|
||||||
this.modules.forEach(plugin => {
|
|
||||||
if (plugin.onWindow) {
|
|
||||||
plugin.onWindow(win)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// decorates the base object by calling plugin[key]
|
|
||||||
// for all the available plugins
|
|
||||||
decorateObject (base, key) {
|
|
||||||
let decorated = base
|
|
||||||
this.modules.forEach(plugin => {
|
|
||||||
if (plugin[key]) {
|
|
||||||
const res = plugin[key](decorated)
|
|
||||||
if (res && typeof res === 'object') {
|
|
||||||
decorated = res
|
|
||||||
} else {
|
|
||||||
this.alert(`Plugin error: "${plugin._name}": invalid return type for \`${key}\``)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
}
|
|
||||||
|
|
||||||
decorateMenu (tpl) {
|
|
||||||
return this.decorateObject(tpl, 'decorateMenu')
|
|
||||||
}
|
|
||||||
|
|
||||||
decorateWindow (options) {
|
|
||||||
return this.decorateObject(options, 'decorateWindow')
|
|
||||||
}
|
|
||||||
|
|
||||||
getDecoratedEnv (baseEnv) {
|
|
||||||
return this.decorateObject(baseEnv, 'decorateEnv')
|
|
||||||
}
|
|
||||||
|
|
||||||
getDecoratedConfig () {
|
|
||||||
const baseConfig = config.getConfig()
|
|
||||||
return this.decorateObject(baseConfig, 'decorateConfig')
|
|
||||||
}
|
|
||||||
|
|
||||||
getDecoratedBrowserOptions (defaults) {
|
|
||||||
return this.decorateObject(defaults, 'decorateBrowserOptions')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs passed arguments to console using a prefix.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function log () {
|
|
||||||
const prefix = '[ PLUGINS.Main ]-->'
|
|
||||||
const args = [prefix]
|
|
||||||
|
|
||||||
for (var i = 0; i < arguments.length; ++i) {
|
|
||||||
args.push(arguments[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log.apply(console, args)
|
|
||||||
}
|
|
||||||
@@ -36,8 +36,8 @@ function setWindowFocus (flag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initLinux () {
|
function initLinux () {
|
||||||
checkLinuxTraySupport(function (err) {
|
checkLinuxTraySupport(function (supportsTray) {
|
||||||
if (!err) createTray()
|
if (supportsTray) createTray()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,14 +55,10 @@ function checkLinuxTraySupport (cb) {
|
|||||||
// libappindicator1. If WebTorrent was installed from the deb file, we should
|
// 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.
|
// always have it. If it was installed from the zip file, we might not.
|
||||||
cp.exec('dpkg --get-selections libappindicator1', function (err, stdout) {
|
cp.exec('dpkg --get-selections libappindicator1', function (err, stdout) {
|
||||||
if (err) return cb(err)
|
if (err) return cb(false)
|
||||||
// Unfortunately there's no cleaner way, as far as I can tell, to check
|
// Unfortunately there's no cleaner way, as far as I can tell, to check
|
||||||
// whether a debian package is installed:
|
// whether a debian package is installed:
|
||||||
if (stdout.endsWith('\tinstall\n')) {
|
cb(stdout.endsWith('\tinstall\n'))
|
||||||
cb(null)
|
|
||||||
} else {
|
|
||||||
cb(new Error('debian package not installed'))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ const config = require('../../config')
|
|||||||
const log = require('../log')
|
const log = require('../log')
|
||||||
const menu = require('../menu')
|
const menu = require('../menu')
|
||||||
|
|
||||||
function init (state, options, decorate) {
|
function init (state, options) {
|
||||||
if (main.win) {
|
if (main.win) {
|
||||||
return main.win.show()
|
return main.win.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialBounds = Object.assign(config.WINDOW_INITIAL_BOUNDS, state.saved.bounds)
|
const initialBounds = Object.assign(config.WINDOW_INITIAL_BOUNDS, state.saved.bounds)
|
||||||
|
|
||||||
let windowOptions = {
|
const win = main.win = new electron.BrowserWindow({
|
||||||
backgroundColor: '#282828',
|
backgroundColor: '#282828',
|
||||||
backgroundThrottling: false, // do not throttle animations/timers when page is background
|
backgroundThrottling: false, // do not throttle animations/timers when page is background
|
||||||
darkTheme: true, // Forces dark theme (GTK+3)
|
darkTheme: true, // Forces dark theme (GTK+3)
|
||||||
@@ -45,10 +45,7 @@ function init (state, options, decorate) {
|
|||||||
width: initialBounds.width,
|
width: initialBounds.width,
|
||||||
x: initialBounds.x,
|
x: initialBounds.x,
|
||||||
y: initialBounds.y
|
y: initialBounds.y
|
||||||
}
|
})
|
||||||
if (decorate) windowOptions = decorate(windowOptions)
|
|
||||||
|
|
||||||
const win = main.win = new electron.BrowserWindow(windowOptions)
|
|
||||||
|
|
||||||
win.loadURL(config.WINDOW_MAIN)
|
win.loadURL(config.WINDOW_MAIN)
|
||||||
|
|
||||||
@@ -169,8 +166,8 @@ function setBounds (bounds, maximize) {
|
|||||||
if (bounds.x === null && bounds.y === null) {
|
if (bounds.x === null && bounds.y === null) {
|
||||||
// X and Y not specified? By default, center on current screen
|
// X and Y not specified? By default, center on current screen
|
||||||
const scr = electron.screen.getDisplayMatching(main.win.getBounds())
|
const scr = electron.screen.getDisplayMatching(main.win.getBounds())
|
||||||
bounds.x = Math.round(scr.bounds.x + (scr.bounds.width / 2) - (bounds.width / 2))
|
bounds.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))
|
bounds.y = Math.round(scr.bounds.y + scr.bounds.height / 2 - bounds.height / 2)
|
||||||
log('setBounds: centered to ' + JSON.stringify(bounds))
|
log('setBounds: centered to ' + JSON.stringify(bounds))
|
||||||
}
|
}
|
||||||
// Resize the window's content area (so window border doesn't need to be taken
|
// Resize the window's content area (so window border doesn't need to be taken
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const electron = require('electron')
|
|||||||
|
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
|
|
||||||
function init (decorate) {
|
function init () {
|
||||||
let options = {
|
const win = webtorrent.win = new electron.BrowserWindow({
|
||||||
backgroundColor: '#1E1E1E',
|
backgroundColor: '#1E1E1E',
|
||||||
backgroundThrottling: false, // do not throttle animations/timers when page is background
|
backgroundThrottling: false, // do not throttle animations/timers when page is background
|
||||||
center: true,
|
center: true,
|
||||||
@@ -26,9 +26,7 @@ function init (decorate) {
|
|||||||
title: 'webtorrent-hidden-window',
|
title: 'webtorrent-hidden-window',
|
||||||
useContentSize: true,
|
useContentSize: true,
|
||||||
width: 150
|
width: 150
|
||||||
}
|
})
|
||||||
if (decorate) options = decorate(options)
|
|
||||||
const win = webtorrent.win = new electron.BrowserWindow(options)
|
|
||||||
|
|
||||||
win.loadURL(config.WINDOW_WEBTORRENT)
|
win.loadURL(config.WINDOW_WEBTORRENT)
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ module.exports = class UpdateAvailableModal extends React.Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
function handleShow () {
|
function handleShow () {
|
||||||
// TODO: use the GitHub urls from config.js
|
electron.shell.openExternal('https://github.com/feross/webtorrent-desktop/releases')
|
||||||
electron.shell.openExternal('https://github.com/webtorrent/webtorrent-desktop/releases')
|
|
||||||
dispatch('exitModal')
|
dispatch('exitModal')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ module.exports = class PlaybackController {
|
|||||||
// * Stream, if not already fully downloaded
|
// * Stream, if not already fully downloaded
|
||||||
// * If no file index is provided, restore the most recently viewed file or autoplay the first
|
// * If no file index is provided, restore the most recently viewed file or autoplay the first
|
||||||
playFile (infoHash, index /* optional */) {
|
playFile (infoHash, index /* optional */) {
|
||||||
this.pauseActiveTorrents(infoHash)
|
|
||||||
|
|
||||||
const state = this.state
|
const state = this.state
|
||||||
if (state.location.url() === 'player') {
|
if (state.location.url() === 'player') {
|
||||||
this.updatePlayer(infoHash, index, false, (err) => {
|
this.updatePlayer(infoHash, index, false, (err) => {
|
||||||
@@ -86,17 +84,6 @@ module.exports = class PlaybackController {
|
|||||||
else this.pause()
|
else this.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseActiveTorrents (infoHash) {
|
|
||||||
// Playback Priority: pause all active torrents if needed.
|
|
||||||
if (!this.state.saved.prefs.highestPlaybackPriority) return
|
|
||||||
|
|
||||||
// Do not pause active torrents if playing a fully downloaded torrent.
|
|
||||||
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
|
||||||
if (torrentSummary.status === 'seeding') return
|
|
||||||
|
|
||||||
dispatch('prioritizeTorrent', infoHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play next file in list (if any)
|
// Play next file in list (if any)
|
||||||
nextTrack () {
|
nextTrack () {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
@@ -354,11 +341,6 @@ module.exports = class PlaybackController {
|
|||||||
|
|
||||||
ipcRenderer.send('onPlayerClose')
|
ipcRenderer.send('onPlayerClose')
|
||||||
|
|
||||||
// Playback Priority: resume previously paused downloads.
|
|
||||||
if (this.state.saved.prefs.highestPlaybackPriority) {
|
|
||||||
dispatch('resumePausedTorrents')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update()
|
this.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,10 +121,11 @@ module.exports = class TorrentListController {
|
|||||||
torrentSummary.status = 'new'
|
torrentSummary.status = 'new'
|
||||||
this.startTorrentingSummary(torrentSummary.torrentKey)
|
this.startTorrentingSummary(torrentSummary.torrentKey)
|
||||||
sound.play('ENABLE')
|
sound.play('ENABLE')
|
||||||
return
|
} else {
|
||||||
|
torrentSummary.status = 'paused'
|
||||||
|
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
|
||||||
|
sound.play('DISABLE')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pauseTorrent(torrentSummary, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseAllTorrents () {
|
pauseAllTorrents () {
|
||||||
@@ -148,40 +149,6 @@ module.exports = class TorrentListController {
|
|||||||
sound.play('ENABLE')
|
sound.play('ENABLE')
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseTorrent (torrentSummary, playSound) {
|
|
||||||
torrentSummary.status = 'paused'
|
|
||||||
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
|
|
||||||
|
|
||||||
if (playSound) sound.play('DISABLE')
|
|
||||||
}
|
|
||||||
|
|
||||||
prioritizeTorrent (infoHash) {
|
|
||||||
this.state.saved.torrents
|
|
||||||
.filter((torrent) => { // We're interested in active torrents only.
|
|
||||||
return (['downloading', 'seeding'].indexOf(torrent.status) !== -1)
|
|
||||||
})
|
|
||||||
.map((torrent) => { // Pause all active torrents except the one that started playing.
|
|
||||||
if (infoHash === torrent.infoHash) return
|
|
||||||
|
|
||||||
// Pause torrent without playing sounds.
|
|
||||||
this.pauseTorrent(torrent, false)
|
|
||||||
|
|
||||||
this.state.saved.torrentsToResume.push(torrent.infoHash)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('Playback Priority: paused torrents: ', this.state.saved.torrentsToResume)
|
|
||||||
}
|
|
||||||
|
|
||||||
resumePausedTorrents () {
|
|
||||||
console.log('Playback Priority: resuming paused torrents')
|
|
||||||
this.state.saved.torrentsToResume.map((infoHash) => {
|
|
||||||
this.toggleTorrent(infoHash)
|
|
||||||
})
|
|
||||||
|
|
||||||
// reset paused torrents
|
|
||||||
this.state.saved.torrentsToResume = []
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleTorrentFile (infoHash, index) {
|
toggleTorrentFile (infoHash, index) {
|
||||||
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
||||||
torrentSummary.selections[index] = !torrentSummary.selections[index]
|
torrentSummary.selections[index] = !torrentSummary.selections[index]
|
||||||
@@ -314,7 +281,7 @@ module.exports = class TorrentListController {
|
|||||||
|
|
||||||
// Recursively finds {name, path, size} for all files in a folder
|
// Recursively finds {name, path, size} for all files in a folder
|
||||||
// Calls `cb` on success, calls `onError` on failure
|
// Calls `cb` on success, calls `onError` on failure
|
||||||
function findFilesRecursive (paths, cb_) {
|
function findFilesRecursive (paths, cb) {
|
||||||
if (paths.length > 1) {
|
if (paths.length > 1) {
|
||||||
let numComplete = 0
|
let numComplete = 0
|
||||||
let ret = []
|
let ret = []
|
||||||
@@ -323,7 +290,7 @@ function findFilesRecursive (paths, cb_) {
|
|||||||
ret.push(...fileObjs)
|
ret.push(...fileObjs)
|
||||||
if (++numComplete === paths.length) {
|
if (++numComplete === paths.length) {
|
||||||
ret.sort((a, b) => a.path < b.path ? -1 : a.path > b.path)
|
ret.sort((a, b) => a.path < b.path ? -1 : a.path > b.path)
|
||||||
cb_(ret)
|
cb(ret)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -337,7 +304,7 @@ function findFilesRecursive (paths, cb_) {
|
|||||||
// Files: return name, path, and size
|
// Files: return name, path, and size
|
||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
const filePath = fileOrFolder
|
const filePath = fileOrFolder
|
||||||
return cb_([{
|
return cb([{
|
||||||
name: path.basename(filePath),
|
name: path.basename(filePath),
|
||||||
path: filePath,
|
path: filePath,
|
||||||
size: stat.size
|
size: stat.size
|
||||||
@@ -349,7 +316,7 @@ function findFilesRecursive (paths, cb_) {
|
|||||||
fs.readdir(folderPath, function (err, fileNames) {
|
fs.readdir(folderPath, function (err, fileNames) {
|
||||||
if (err) return dispatch('error', err)
|
if (err) return dispatch('error', err)
|
||||||
const paths = fileNames.map((fileName) => path.join(folderPath, fileName))
|
const paths = fileNames.map((fileName) => path.join(folderPath, fileName))
|
||||||
findFilesRecursive(paths, cb_)
|
findFilesRecursive(paths, cb)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ function migrate_0_7_0 (saved) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix exception caused by incorrect file ordering.
|
// Fix exception caused by incorrect file ordering.
|
||||||
// https://github.com/webtorrent/webtorrent-desktop/pull/604#issuecomment-222805214
|
// https://github.com/feross/webtorrent-desktop/pull/604#issuecomment-222805214
|
||||||
delete ts.defaultPlayFileIndex
|
delete ts.defaultPlayFileIndex
|
||||||
delete ts.files
|
delete ts.files
|
||||||
delete ts.selections
|
delete ts.selections
|
||||||
@@ -111,7 +111,7 @@ function migrate_0_12_0 (saved) {
|
|||||||
|
|
||||||
// Undo a terrible bug where clicking Play on a default torrent on a fresh
|
// Undo a terrible bug where clicking Play on a default torrent on a fresh
|
||||||
// install results in a "path missing" error
|
// install results in a "path missing" error
|
||||||
// See https://github.com/webtorrent/webtorrent-desktop/pull/806
|
// See https://github.com/feross/webtorrent-desktop/pull/806
|
||||||
const defaultTorrentFiles = [
|
const defaultTorrentFiles = [
|
||||||
'6a9759bffd5c0af65319979fb7832189f4f3c35d.torrent',
|
'6a9759bffd5c0af65319979fb7832189f4f3c35d.torrent',
|
||||||
'88594aaacbde40ef3e2510c47374ec0aa396c08e.torrent',
|
'88594aaacbde40ef3e2510c47374ec0aa396c08e.torrent',
|
||||||
@@ -153,7 +153,7 @@ function migrate_0_17_0 (saved) {
|
|||||||
function migrate_0_17_2 (saved) {
|
function migrate_0_17_2 (saved) {
|
||||||
// Remove the trailing dot (.) from the Wired CD torrent name, since
|
// Remove the trailing dot (.) from the Wired CD torrent name, since
|
||||||
// folders/files that end in a trailing dot (.) or space are not deletable from
|
// folders/files that end in a trailing dot (.) or space are not deletable from
|
||||||
// Windows Explorer. See: https://github.com/webtorrent/webtorrent-desktop/issues/905
|
// Windows Explorer. See: https://github.com/feross/webtorrent-desktop/issues/905
|
||||||
|
|
||||||
const cpFile = require('cp-file')
|
const cpFile = require('cp-file')
|
||||||
const rimraf = require('rimraf')
|
const rimraf = require('rimraf')
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ function setupStateSaved (cb) {
|
|||||||
startup: false
|
startup: false
|
||||||
},
|
},
|
||||||
torrents: config.DEFAULT_TORRENTS.map(createTorrentObject),
|
torrents: config.DEFAULT_TORRENTS.map(createTorrentObject),
|
||||||
torrentsToResume: [],
|
|
||||||
version: config.APP_VERSION /* make sure we can upgrade gracefully later */
|
version: config.APP_VERSION /* make sure we can upgrade gracefully later */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ function isAudio (file) {
|
|||||||
'.mp3',
|
'.mp3',
|
||||||
'.ogg',
|
'.ogg',
|
||||||
'.wav',
|
'.wav',
|
||||||
'.flac',
|
|
||||||
'.m4a'
|
'.m4a'
|
||||||
].includes(getFileExtension(file))
|
].includes(getFileExtension(file))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const State = require('./lib/state')
|
|||||||
State.load(onState)
|
State.load(onState)
|
||||||
|
|
||||||
const createGetter = require('fn-getter')
|
const createGetter = require('fn-getter')
|
||||||
const debounce = require('debounce')
|
|
||||||
const dragDrop = require('drag-drop')
|
const dragDrop = require('drag-drop')
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
@@ -36,9 +35,6 @@ const telemetry = require('./lib/telemetry')
|
|||||||
const sound = require('./lib/sound')
|
const sound = require('./lib/sound')
|
||||||
const TorrentPlayer = require('./lib/torrent-player')
|
const TorrentPlayer = require('./lib/torrent-player')
|
||||||
|
|
||||||
const Plugins = require('./plugins')
|
|
||||||
const plugins = new Plugins()
|
|
||||||
|
|
||||||
// Perf optimization: Needed immediately, so do not lazy load it below
|
// Perf optimization: Needed immediately, so do not lazy load it below
|
||||||
const TorrentListController = require('./controllers/torrent-list-controller')
|
const TorrentListController = require('./controllers/torrent-list-controller')
|
||||||
|
|
||||||
@@ -117,8 +113,6 @@ function onState (err, _state) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.init({dispatch, state})
|
|
||||||
|
|
||||||
// Add first page to location history
|
// Add first page to location history
|
||||||
state.location.go({
|
state.location.go({
|
||||||
url: 'home',
|
url: 'home',
|
||||||
@@ -151,18 +145,6 @@ function onState (err, _state) {
|
|||||||
// ...same thing if you paste a torrent
|
// ...same thing if you paste a torrent
|
||||||
document.addEventListener('paste', onPaste)
|
document.addEventListener('paste', onPaste)
|
||||||
|
|
||||||
const debouncedFullscreenToggle = debounce(function () {
|
|
||||||
dispatch('toggleFullScreen')
|
|
||||||
}, 1000, true)
|
|
||||||
|
|
||||||
document.addEventListener('wheel', function (event) {
|
|
||||||
// ctrlKey detects pinch to zoom, http://crbug.com/289887
|
|
||||||
if (event.ctrlKey) {
|
|
||||||
event.preventDefault()
|
|
||||||
debouncedFullscreenToggle()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// ...focus and blur. Needed to show correct dock icon text ('badge') in OSX
|
// ...focus and blur. Needed to show correct dock icon text ('badge') in OSX
|
||||||
window.addEventListener('focus', onFocus)
|
window.addEventListener('focus', onFocus)
|
||||||
window.addEventListener('blur', onBlur)
|
window.addEventListener('blur', onBlur)
|
||||||
@@ -259,8 +241,6 @@ const dispatchHandlers = {
|
|||||||
controllers.torrentList().startTorrentingSummary(torrentKey),
|
controllers.torrentList().startTorrentingSummary(torrentKey),
|
||||||
'saveTorrentFileAs': (torrentKey) =>
|
'saveTorrentFileAs': (torrentKey) =>
|
||||||
controllers.torrentList().saveTorrentFileAs(torrentKey),
|
controllers.torrentList().saveTorrentFileAs(torrentKey),
|
||||||
'prioritizeTorrent': (infoHash) => controllers.torrentList().prioritizeTorrent(infoHash),
|
|
||||||
'resumePausedTorrents': () => controllers.torrentList().resumePausedTorrents(),
|
|
||||||
|
|
||||||
// Playback
|
// Playback
|
||||||
'playFile': (infoHash, index) => controllers.playback().playFile(infoHash, index),
|
'playFile': (infoHash, index) => controllers.playback().playFile(infoHash, index),
|
||||||
@@ -337,7 +317,6 @@ function dispatch (action, ...args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handler = dispatchHandlers[action]
|
const handler = dispatchHandlers[action]
|
||||||
plugins.on(action, args)
|
|
||||||
if (handler) handler(...args)
|
if (handler) handler(...args)
|
||||||
else console.error('Missing dispatch handler: ' + action)
|
else console.error('Missing dispatch handler: ' + action)
|
||||||
|
|
||||||
@@ -362,7 +341,6 @@ function setupIpc () {
|
|||||||
ipcRenderer.on('wt-infohash', (e, ...args) => tc.torrentInfoHash(...args))
|
ipcRenderer.on('wt-infohash', (e, ...args) => tc.torrentInfoHash(...args))
|
||||||
ipcRenderer.on('wt-metadata', (e, ...args) => tc.torrentMetadata(...args))
|
ipcRenderer.on('wt-metadata', (e, ...args) => tc.torrentMetadata(...args))
|
||||||
ipcRenderer.on('wt-done', (e, ...args) => tc.torrentDone(...args))
|
ipcRenderer.on('wt-done', (e, ...args) => tc.torrentDone(...args))
|
||||||
ipcRenderer.on('wt-done', () => controllers.torrentList().resumePausedTorrents())
|
|
||||||
ipcRenderer.on('wt-warning', (e, ...args) => tc.torrentWarning(...args))
|
ipcRenderer.on('wt-warning', (e, ...args) => tc.torrentWarning(...args))
|
||||||
ipcRenderer.on('wt-error', (e, ...args) => tc.torrentError(...args))
|
ipcRenderer.on('wt-error', (e, ...args) => tc.torrentError(...args))
|
||||||
|
|
||||||
|
|||||||
@@ -62,24 +62,6 @@ class PreferencesPage extends React.Component {
|
|||||||
dispatch('updatePreferences', 'openExternalPlayer', !isChecked)
|
dispatch('updatePreferences', 'openExternalPlayer', !isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
highestPlaybackPriorityCheckbox () {
|
|
||||||
return (
|
|
||||||
<Preference>
|
|
||||||
<Checkbox
|
|
||||||
className='control'
|
|
||||||
checked={this.props.state.unsaved.prefs.highestPlaybackPriority}
|
|
||||||
label={'Highest Playback Priority'}
|
|
||||||
onCheck={this.handleHighestPlaybackPriorityChange}
|
|
||||||
/>
|
|
||||||
<p>Pauses all active torrents to allow playback to use all of the available bandwidth.</p>
|
|
||||||
</Preference>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleHighestPlaybackPriorityChange (e, isChecked) {
|
|
||||||
dispatch('updatePreferences', 'highestPlaybackPriority', isChecked)
|
|
||||||
}
|
|
||||||
|
|
||||||
externalPlayerPathSelector () {
|
externalPlayerPathSelector () {
|
||||||
const playerPath = this.props.state.unsaved.prefs.externalPlayerPath
|
const playerPath = this.props.state.unsaved.prefs.externalPlayerPath
|
||||||
const playerName = this.props.state.getExternalPlayerName()
|
const playerName = this.props.state.getExternalPlayerName()
|
||||||
@@ -169,7 +151,6 @@ class PreferencesPage extends React.Component {
|
|||||||
<PreferencesSection title='Playback'>
|
<PreferencesSection title='Playback'>
|
||||||
{this.openExternalPlayerCheckbox()}
|
{this.openExternalPlayerCheckbox()}
|
||||||
{this.externalPlayerPathSelector()}
|
{this.externalPlayerPathSelector()}
|
||||||
{this.highestPlaybackPriorityCheckbox()}
|
|
||||||
</PreferencesSection>
|
</PreferencesSection>
|
||||||
<PreferencesSection title='Default torrent app'>
|
<PreferencesSection title='Default torrent app'>
|
||||||
{this.setDefaultAppButton()}
|
{this.setDefaultAppButton()}
|
||||||
|
|||||||
@@ -1,287 +0,0 @@
|
|||||||
const {resolve, basename} = require('path')
|
|
||||||
const notifier = require('node-notifier')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
|
|
||||||
const config = require('../config')
|
|
||||||
|
|
||||||
module.exports = class Plugins {
|
|
||||||
constructor () {
|
|
||||||
// modules path
|
|
||||||
this.path = resolve(config.getConfigPath(), 'plugins')
|
|
||||||
log('Path: ', this.path)
|
|
||||||
this.availableExtensions = [
|
|
||||||
'onCheckForSubtitles'
|
|
||||||
]
|
|
||||||
|
|
||||||
this.forceUpdate = false
|
|
||||||
this.updating = false
|
|
||||||
this.watchers = []
|
|
||||||
this.state = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
init (params) {
|
|
||||||
this.state = params.state
|
|
||||||
|
|
||||||
// initialize state
|
|
||||||
this.state.saved = Object.assign(this.state.saved || {})
|
|
||||||
|
|
||||||
// caches
|
|
||||||
this.plugins = config.getPlugins()
|
|
||||||
this.paths = this.getPaths(this.plugins)
|
|
||||||
this.id = this.getId(this.plugins)
|
|
||||||
this.modules = this.requirePlugins()
|
|
||||||
|
|
||||||
// TODO: fire an event when plugins finish updating and listen to that.
|
|
||||||
// Listen to plugin changes on config.
|
|
||||||
// New plugins added, plugins removed or updated.
|
|
||||||
// The actual plugin update action will take place in the MAIN process.
|
|
||||||
config.subscribe(() => {
|
|
||||||
const plugins = config.getPlugins()
|
|
||||||
if (plugins !== this.plugins) {
|
|
||||||
const id = this.getId(plugins)
|
|
||||||
if (this.id !== id) {
|
|
||||||
log('UPDATING...')
|
|
||||||
this.id = id
|
|
||||||
this.plugins = plugins
|
|
||||||
this.paths = this.getPaths(this.plugins)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Plugins will be updated on the MAIN process after 5s.
|
|
||||||
if (this.needsUpdate()) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.init(params)
|
|
||||||
}, 6000)
|
|
||||||
log('Plugins need update, init scheduled')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadPlugins()
|
|
||||||
this.initPlugins(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
initPlugins (params) {
|
|
||||||
this.modules.forEach(plugin => {
|
|
||||||
if (plugin.init) {
|
|
||||||
plugin.init(params)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
on (action, params) {
|
|
||||||
log(`ON ${action}:`, params)
|
|
||||||
this.modules.forEach(plugin => {
|
|
||||||
const actionName = this.capitalizeFirstLetter(action)
|
|
||||||
const methodName = `on${actionName}`
|
|
||||||
if (typeof plugin[methodName] === 'function') {
|
|
||||||
plugin[methodName](params)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
capitalizeFirstLetter (string) {
|
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
didPluginsChange () {
|
|
||||||
return this.state.saved.installedPlugins !== this.id
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPlugins () {
|
|
||||||
return !this.isEmptyObject(this.plugins)
|
|
||||||
}
|
|
||||||
|
|
||||||
isFirstInstall () {
|
|
||||||
return (!this.state.saved.installedPlugins && this.hasPlugins())
|
|
||||||
}
|
|
||||||
|
|
||||||
needsUpdate () {
|
|
||||||
return (this.didPluginsChange() || this.isFirstInstall())
|
|
||||||
}
|
|
||||||
|
|
||||||
getId (plugins) {
|
|
||||||
const hash = crypto.createHash('sha256')
|
|
||||||
hash.update(JSON.stringify(plugins))
|
|
||||||
return hash.digest('hex')
|
|
||||||
}
|
|
||||||
|
|
||||||
loadPlugins (err, localOnly = false) {
|
|
||||||
this.updating = false
|
|
||||||
|
|
||||||
// handle errors first
|
|
||||||
if (err) {
|
|
||||||
console.error(err.stack)
|
|
||||||
if (/not a recognized/.test(err.message) || /command not found/.test(err.message)) {
|
|
||||||
this.alert(
|
|
||||||
'Error updating plugins: We could not find the "npm" command. Make sure it\'s in $PATH'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.alert(`Error updating plugins: Check '${this.path}/npm-debug.log' for more information.`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// update state with latest plugins
|
|
||||||
this.state.saved.plugins = this.plugins
|
|
||||||
|
|
||||||
// cache modules
|
|
||||||
this.modules = this.requirePlugins()
|
|
||||||
|
|
||||||
// clear require cache
|
|
||||||
this.clearCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginVersions () {
|
|
||||||
const paths_ = this.paths.plugins
|
|
||||||
return paths_.map(path => {
|
|
||||||
let version = null
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line import/no-dynamic-require
|
|
||||||
version = require(resolve(path, 'package.json')).version
|
|
||||||
} catch (err) { }
|
|
||||||
return [
|
|
||||||
basename(path),
|
|
||||||
version
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
clearCache () {
|
|
||||||
// clear require cache
|
|
||||||
for (const entry in require.cache) {
|
|
||||||
if (entry.indexOf(this.path) === 0) {
|
|
||||||
delete require.cache[entry]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isEmptyObject (obj) {
|
|
||||||
return (Object.keys(obj).length === 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
alert (message) {
|
|
||||||
notifier.notify({
|
|
||||||
title: 'WebTorrent Plugins',
|
|
||||||
// icon: config.icon, // TODO: save icon in webtorrent local folder and set config.icon
|
|
||||||
message: message
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe (fn) {
|
|
||||||
this.watchers.push(fn)
|
|
||||||
return () => {
|
|
||||||
this.watchers.splice(this.watchers.indexOf(fn), 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isLocalPath (string) {
|
|
||||||
// matches unix and windows local paths
|
|
||||||
return string.match(/^(\/|[a-z]:\/)/i)
|
|
||||||
}
|
|
||||||
|
|
||||||
getPaths (plugins) {
|
|
||||||
const pluginNames = Object.keys(plugins)
|
|
||||||
|
|
||||||
return {
|
|
||||||
plugins: pluginNames.map(name => {
|
|
||||||
let url = plugins[name]
|
|
||||||
|
|
||||||
// plugin is already on a local folder
|
|
||||||
// directly load it from its current location
|
|
||||||
if (this.isLocalPath(url)) return url
|
|
||||||
|
|
||||||
// plugin will be installed with npm install from a remote url
|
|
||||||
return resolve(this.path, 'node_modules', name.split('#')[0])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exposesSupportedApi (plugin) {
|
|
||||||
if (!plugin) return false
|
|
||||||
|
|
||||||
return this.availableExtensions.some((methodName) => {
|
|
||||||
return (typeof plugin[methodName] === 'function')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
requirePlugins () {
|
|
||||||
const {plugins} = this.paths
|
|
||||||
let installNeeded = false
|
|
||||||
|
|
||||||
const load = (path) => {
|
|
||||||
if (!path.match(/\/$/)) {
|
|
||||||
path += '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
const rendererPath = `${path}renderer.js`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const Plugin = require(rendererPath) // eslint-disable import/no-dynamic-require
|
|
||||||
const plugin = new Plugin()
|
|
||||||
|
|
||||||
const exposed = this.exposesSupportedApi(plugin)
|
|
||||||
if (!exposed) {
|
|
||||||
log('Plugin not exposing any available extensions.', rendererPath, Object.keys(plugin))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate the name for internal errors here
|
|
||||||
plugin._name = basename(rendererPath)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
} catch (err) {
|
|
||||||
log('Require plugins ERROR:', err)
|
|
||||||
this.alert(`Error loading plugin: ${rendererPath}`)
|
|
||||||
// plugin not installed
|
|
||||||
// node_modules removed? did a manual plugin uninstall?
|
|
||||||
// try installing and then loading if successfull
|
|
||||||
installNeeded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plugin installation happens on the MAIN process.
|
|
||||||
// If plugins haven't finished installing, wait for them.
|
|
||||||
if (installNeeded) {
|
|
||||||
log('Plugins install needed, wait...')
|
|
||||||
setTimeout(() => {
|
|
||||||
this.requirePlugins()
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
return plugins.map(load).filter(v => Boolean(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// decorates the base object by calling plugin[key]
|
|
||||||
// for all the available plugins
|
|
||||||
decorateObject (base, key) {
|
|
||||||
let decorated = base
|
|
||||||
this.modules.forEach(plugin => {
|
|
||||||
if (plugin[key]) {
|
|
||||||
const res = plugin[key](decorated)
|
|
||||||
if (res && typeof res === 'object') {
|
|
||||||
decorated = res
|
|
||||||
} else {
|
|
||||||
this.alert(`Plugin error: "${plugin._name}": invalid return type for \`${key}\``)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs passed arguments to console using a prefix.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function log () {
|
|
||||||
const prefix = '[ PLUGINS.Renderer ]-->'
|
|
||||||
const args = [prefix]
|
|
||||||
|
|
||||||
for (var i = 0; i < arguments.length; ++i) {
|
|
||||||
args.push(arguments[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log.apply(console, args)
|
|
||||||
}
|
|
||||||
@@ -128,7 +128,6 @@ function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function stopTorrenting (infoHash) {
|
function stopTorrenting (infoHash) {
|
||||||
console.log('--- STOP TORRENTING: ', infoHash)
|
|
||||||
const torrent = client.get(infoHash)
|
const torrent = client.get(infoHash)
|
||||||
if (torrent) torrent.destroy()
|
if (torrent) torrent.destroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ function compareIgnoringTransparency (bufActual, bufExpected) {
|
|||||||
const de = pngE.data
|
const de = pngE.data
|
||||||
for (let y = 0; y < h; y++) {
|
for (let y = 0; y < h; y++) {
|
||||||
for (let x = 0; x < w; x++) {
|
for (let x = 0; x < w; x++) {
|
||||||
const i = ((y * w) + x) * 4
|
const i = (y * w + x) * 4
|
||||||
if (de[i + 3] === 0) continue // Skip transparent pixels
|
if (de[i + 3] === 0) continue // Skip transparent pixels
|
||||||
const ca = (da[i] << 16) | (da[i + 1] << 8) | da[i + 2]
|
const ca = (da[i] << 16) | (da[i + 1] << 8) | da[i + 2]
|
||||||
const ce = (de[i] << 16) | (de[i + 1] << 8) | de[i + 2]
|
const ce = (de[i] << 16) | (de[i + 1] << 8) | de[i + 2]
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ test('torrent-list: expand torrent, unselect file', function (t) {
|
|||||||
// Make sure that it creates all files EXCEPT the deslected one
|
// Make sure that it creates all files EXCEPT the deslected one
|
||||||
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', [
|
.then(() => setup.compareDownloadFolder(t, 'CosmosLaundromatFirstCycle', [
|
||||||
// TODO: the .gif should NOT be here, since we just deselected it.
|
// TODO: the .gif should NOT be here, since we just deselected it.
|
||||||
// This is a bug. See https://github.com/webtorrent/webtorrent-desktop/issues/719
|
// This is a bug. See https://github.com/feross/webtorrent-desktop/issues/719
|
||||||
'Cosmos Laundromat - First Cycle (1080p).gif',
|
'Cosmos Laundromat - First Cycle (1080p).gif',
|
||||||
'Cosmos Laundromat - First Cycle (1080p).mp4',
|
'Cosmos Laundromat - First Cycle (1080p).mp4',
|
||||||
'Cosmos Laundromat - First Cycle (1080p).ogv',
|
'Cosmos Laundromat - First Cycle (1080p).ogv',
|
||||||
|
|||||||
Reference in New Issue
Block a user