Compare commits

...

52 Commits

Author SHA1 Message Date
Alberto Miranda
e2c3c182d0 Plugins divided into main and renderer processes. 2017-04-23 13:46:52 -03:00
Alberto Miranda
687038560c Passing state and params to plugins.
Dispatcher now sends params to plugins; notifying about plugin load
errors; allowing local plugin paths not to end with a slash.
2017-04-19 09:00:11 -03:00
Alberto Miranda
09d00c6383 Passing dispatcher to plugins.
This will enable plugins to dispatch events and interact with the
renderer process; removed unused extension keys.
2017-04-16 16:31:39 -03:00
Alberto Miranda
957c5b64e9 Merge branch 'master' into plugins 2017-04-16 13:29:51 -03:00
Feross Aboukhadijeh
a82498ba16 gitter 2017-04-14 14:40:18 -07:00
Feross Aboukhadijeh
3b05b52e57 feross -> webtorrent 2017-04-14 13:48:39 -07:00
Feross Aboukhadijeh
cdfb907c75 standard 2017-03-20 19:41:44 -07:00
Alberto Miranda
c2abb50e9e Merge pull request #840 from feross/playback-priority
Playback priority
2017-03-15 14:39:26 -03:00
Alberto Miranda
99f4fc96bf Fixed resuming when downloading finishes. 2017-03-14 19:52:29 -03:00
Alberto Miranda
043f81996e fixed styling issues; returning early in pauseActiveTorrents. 2017-03-14 19:50:49 -03:00
Alberto Miranda
213f2f124d Merge branch 'playback-priority' of https://github.com/feross/webtorrent-desktop into playback-priority 2017-03-14 19:44:45 -03:00
Alberto Miranda
96e7e83e20 Merge branch 'master' into playback-priority
# Conflicts:
#	src/renderer/controllers/playback-controller.js
#	src/renderer/controllers/torrent-list-controller.js
#	src/renderer/main.js
2017-03-14 19:43:57 -03:00
Feross Aboukhadijeh
e5fccd93a8 Tweak pinch-to-zoom timing
Toggle fullscreen immediately, then prevent future toggles for 1s.
2017-03-08 15:00:42 -08:00
Feross Aboukhadijeh
78e46f5576 Merge branch 'ebraminio-master' 2017-03-08 14:56:38 -08:00
Ebrahim Byagowi
69ce07fbf7 Toggle fullscreen on pinch to zoom 2017-03-08 23:13:05 +03:30
Feross Aboukhadijeh
827131e136 standard 2017-03-02 00:33:36 -08:00
Feross Aboukhadijeh
c764bf4884 fixes for standard v10 2017-03-01 23:08:29 -08:00
Feross Aboukhadijeh
972203d675 material-ui@0.17
Tested and everything seems to work alright with the new version.
2017-02-20 03:09:56 -08:00
Feross Aboukhadijeh
2c382e14b5 electron@1.6.0
This version of Electron is 3 versions of Chromium newer, so there are
lots of performance improvements.

There is one known issue with this update:
https://github.com/electron/electron/issues/8694

But I think we should merge this anyway so that collaborators can try
out the new Chromium engine before we do a release with it. We might
find other bugs.
2017-02-15 15:31:35 -08:00
Feross Aboukhadijeh
c298950d34 Add 'Start Speaking' and 'Stop Speaking' menu item (Mac)
Probably improves the accessibility situation a little bit: #439
2017-02-15 13:31:28 -08:00
Feross Aboukhadijeh
00f2e5ccd6 Treat .FLAC as playable audio
Fixes: https://github.com/feross/webtorrent-desktop/issues/1124

Note FLAC is not actually playable in Electron, by default. But this
will at least offer to open it in VLC or the user's preferred player.
2017-02-15 13:31:06 -08:00
Feross Aboukhadijeh
8d86cffabd standard 2017-02-08 16:07:25 -08:00
Alberto Miranda
568fede67e fixed periodic plugins update 2017-02-06 08:06:02 -03:00
Alberto Miranda
944eb8b8b0 styling fixes; tests passing; added missing ms dependency. 2017-02-06 00:54:16 -03:00
Alberto Miranda
c3cdda354e added node-notifier; showing plugin notifications. 2017-02-06 00:38:27 -03:00
Alberto Miranda
25ed12ba3c removed extra logging; cleanup 2017-02-06 00:00:26 -03:00
Alberto Miranda
830312842b hot reloading config; hot loading plugins when config changes. 2017-02-05 13:15:20 -03:00
Feross Aboukhadijeh
95d6ec5fdd move release instructions to contributing.md 2017-02-04 18:00:50 -08:00
DC
9fbee6cfeb Replace release scripts with instructions
The release scripts don't really work, and there are a lot of manual steps that weren't documented. They are now.
2017-02-05 02:55:13 +01:00
Feross Aboukhadijeh
9892f88530 Fix 'About WebTorrent' menu location on Windows 2017-02-03 03:27:35 -08:00
Feross Aboukhadijeh
10ad990f97 update AUTHORS 2017-02-03 02:17:04 -08:00
Feross Aboukhadijeh
0ee7e80fd2 0.18.0 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
7d1520f858 disable background throttling in all windows 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
77ba258fbf move Transfers menu 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
06366529cc updated copyright year 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
d1c263f1b0 changelog for 0.18.0 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
d7a031a457 test: update test for new default trackers 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
b7201f424d test: update screenshot for new default trackers 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
89f2785244 remove unneeded package from depcheck 2017-02-03 02:08:59 -08:00
Feross Aboukhadijeh
cffe416f50 package: fix deprecation warning 2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
d0eea34e9b lock down dev dependencies
Let's be very explicit about updating these. They are very critical to
the build process, so we should always read the changelogs / commit log
before upgrading.
2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
4e82718788 electron-osx-sign@0.4 2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
293ca60e72 electron@1.4.15 2017-02-03 02:01:54 -08:00
Feross Aboukhadijeh
af4a6a5960 buble@0.15 2017-02-03 02:01:54 -08:00
Alberto Miranda
086d8bf00a fixed initialization on first run 2017-02-01 14:35:28 -03:00
Alberto Miranda
50c100130a removed unused packages; hashing installed plugins; saving plugins config; detecting newly installed plugins. 2017-02-01 00:34:28 -03:00
Alberto Miranda
edcea2661a working on plugins features: lots of todos, but current version loads remote and local plugins. 2017-01-30 00:08:22 -03:00
Alberto Miranda
44a0f760de Resuming paused torrents when streaming torrent finishes downloading (wt-done). 2016-08-30 12:09:23 -03:00
Alberto Miranda
1e5ac1df9c removed logging 2016-08-27 03:56:08 -03:00
Alberto Miranda
4cdc3085ff not pausing active torrents when playing an already downloaded torrent. 2016-08-27 03:55:23 -03:00
Alberto Miranda
e483263d70 removed logging 2016-08-27 03:36:28 -03:00
Alberto Miranda
bd41bd4db8 added highest playback priority feature; added highest playback priority checkbox on preferences. 2016-08-27 03:35:53 -03:00
36 changed files with 1143 additions and 179 deletions

View File

@@ -34,5 +34,8 @@
- 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.

View File

@@ -1,10 +1,24 @@
# WebTorrent Desktop Version History # WebTorrent Desktop Version History
## UNRELEASED ## v0.18.0
### Added
- Add a new "Transfers" menu for pausing or resuming all torrents (#1027)
### Changed ### Changed
- Update Electron to 1.4.3 - Update Electron to 1.4.15
- Windows 32-bit: App can use 4GB of memory instead of just 2GB - Windows 32-bit: App can use 4GB of memory instead of just 2GB
- Fix "Portable App" writing crash reports to "%APPDATA%\Temp" (Windows)
- Updated WebTorrent engine to 0.98.5
- Fix issue where http web seeds would sometimes stall
- Don't send 'completed' event to tracker again if torrent is already complete
- Add more peer ID entropy
- Set user-agent header for tracker http requests
### Fixed
- Fix paste shortcut in tracker list on Create Torrent page (#1112)
- Auto-focus the 'OK' button in modal dialogs (#1058)
- Fix formatting issue in the speed stats on the Player page (#1039)
## v0.17.2 - 2016-10-10 ## v0.17.2 - 2016-10-10
@@ -53,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/feross/webtorrent-desktop/pull/931)) - **Windows 64-bit support!** ([#931](https://github.com/webtorrent/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
@@ -113,16 +127,13 @@
## v0.12.0 - 2016-08-23 ## v0.12.0 - 2016-08-23
### Added ### Added
- Custom external media player - Custom external media player
- Linux: add system-wide launcher and icons for Debian, including Ubuntu - Linux: add system-wide launcher and icons for Debian, including Ubuntu
### Changed ### Changed
- Telemetry improvements: redact stacktraces, log app version - Telemetry improvements: redact stacktraces, log app version
### Fixed ### Fixed
- Fix playback and download of default torrents ("missing path" error) (#804) - Fix playback and download of default torrents ("missing path" error) (#804)
- Fix Delete Torrent + Data for newly added magnet links - Fix Delete Torrent + Data for newly added magnet links
- Fix jumpToTime error (#804) - Fix jumpToTime error (#804)
@@ -135,13 +146,11 @@
- Check for missing default download path and torrent folders on start up (#776) - Check for missing default download path and torrent folders on start up (#776)
### Changed ### Changed
- Do not automatically set WebTorrent as the default handler for torrents (#771) - Do not automatically set WebTorrent as the default handler for torrents (#771)
- Torrents can only be created from the home screen (#770) - Torrents can only be created from the home screen (#770)
- Update Electron to 1.3.3 (#772) - Update Electron to 1.3.3 (#772)
### Fixed ### Fixed
- Allow modifying the default tracker list on the Create Torrent page (#775) - Allow modifying the default tracker list on the Create Torrent page (#775)
- Prevent opening multiple stacked Preference windows or Create Torrent windows (#770) - Prevent opening multiple stacked Preference windows or Create Torrent windows (#770)
- Windows: Player window auto-resize does not match video aspect ratio (#565) - Windows: Player window auto-resize does not match video aspect ratio (#565)
@@ -150,13 +159,11 @@
## v0.10.0 - 2016-08-05 ## v0.10.0 - 2016-08-05
### Added ### Added
- Drag-and-drop magnet links (selected text) is now supported (#284) - Drag-and-drop magnet links (selected text) is now supported (#284)
- Windows: Add "User Tasks" shortcuts to app icon in Start Menu (#114) - Windows: Add "User Tasks" shortcuts to app icon in Start Menu (#114)
- Linux: Show badge count for completed torrent downloads - Linux: Show badge count for completed torrent downloads
### Changed ### Changed
- Change WebTorrent Desktop peer ID prefix to 'WD' to distinguish from WebTorrent in the browser, 'WW' (#688) - Change WebTorrent Desktop peer ID prefix to 'WD' to distinguish from WebTorrent in the browser, 'WW' (#688)
- Switch UI to React to improve UI rendering speed (#729) - Switch UI to React to improve UI rendering speed (#729)
- The primary bottleneck was actually `hyperx`, not `virtual-dom`. - The primary bottleneck was actually `hyperx`, not `virtual-dom`.
@@ -173,7 +180,6 @@
- Location history abstraction released independently as [`location-history`](https://www.npmjs.com/package/location-history) - Location history abstraction released independently as [`location-history`](https://www.npmjs.com/package/location-history)
### Fixed ### Fixed
- When streaming to VLC, set VLC window title to torrent file name (#746) - When streaming to VLC, set VLC window title to torrent file name (#746)
- Fix "Cannot read property 'numPiecesPresent' of undefined" exception (#695) - Fix "Cannot read property 'numPiecesPresent' of undefined" exception (#695)
- Fix rare case where config file could not be completely written (#733) - Fix rare case where config file could not be completely written (#733)
@@ -181,35 +187,29 @@
## v0.9.0 - 2016-07-20 ## v0.9.0 - 2016-07-20
### Added ### Added
- Save selected subtitles - Save selected subtitles
- Ask for confirmation before deleting torrents - Ask for confirmation before deleting torrents
- Support Debian Jessie - Support Debian Jessie
### Changed ### Changed
- Only send telemetry in production - Only send telemetry in production
- Clean up the code. Split main.js, refactor lots of things - Clean up the code. Split main.js, refactor lots of things
### Fixed ### Fixed
- Fix state.playing.jumpToTime behavior - Fix state.playing.jumpToTime behavior
- Remove torrent file and poster image when deleting a torrent - Remove torrent file and poster image when deleting a torrent
## v0.8.1 - 2016-06-24 ## v0.8.1 - 2016-06-24
### Added ### Added
- New URI handler: stream-magnet - New URI handler: stream-magnet
### Fixed ### Fixed
- DLNA crashing bug - DLNA crashing bug
## v0.8.0 - 2016-06-23 ## v0.8.0 - 2016-06-23
### Added ### Added
- Cast menu: choose which Chromecast, Airplay, or DLNA device you want to use - Cast menu: choose which Chromecast, Airplay, or DLNA device you want to use
- Telemetry: send basic data, plus stats on how often the play button works - Telemetry: send basic data, plus stats on how often the play button works
- Make posters from jpeg files, not just jpg - Make posters from jpeg files, not just jpg
@@ -217,17 +217,14 @@
- Windows thumbnail bar with a play/pause button - Windows thumbnail bar with a play/pause button
### Changed ### Changed
- Nicer modal styles - Nicer modal styles
### Fixed ### Fixed
- Windows tray icon now stays in the right state - Windows tray icon now stays in the right state
## v0.7.2 - 2016-06-02 ## v0.7.2 - 2016-06-02
### Fixed ### Fixed
- Fix exception that affects users upgrading from v0.5.1 or older - Fix exception that affects users upgrading from v0.5.1 or older
- Ensure `state.saved.prefs` configuration exists - Ensure `state.saved.prefs` configuration exists
- Fix window title on "About WebTorrent" window - Fix window title on "About WebTorrent" window
@@ -235,23 +232,19 @@
## v0.7.1 - 2016-06-02 ## v0.7.1 - 2016-06-02
### Changed ### Changed
- Change "Step Forward" keyboard shortcut to `Alt+Left` (Windows) - Change "Step Forward" keyboard shortcut to `Alt+Left` (Windows)
- Change "Step Backward" keyboard shortcut to to `Alt+Right` (Windows) - Change "Step Backward" keyboard shortcut to to `Alt+Right` (Windows)
### Fixed ### Fixed
- First time startup bug -- invalid torrent/poster paths - First time startup bug -- invalid torrent/poster paths
## v0.7.0 - 2016-06-02 ## v0.7.0 - 2016-06-02
### Added ### Added
- Improved AirPlay support -- using the new [`airplayer`](https://www.npmjs.com/package/airplayer) package - Improved AirPlay support -- using the new [`airplayer`](https://www.npmjs.com/package/airplayer) package
- Remember volume setting in player, for as long as the app is open - Remember volume setting in player, for as long as the app is open
### Changed ### Changed
- Add (+) button now also accepts non .torrent files and creates a torrent from - Add (+) button now also accepts non .torrent files and creates a torrent from
those files those files
- Show prompt text in title bar for open dialogs (OS X) - Show prompt text in title bar for open dialogs (OS X)
@@ -261,7 +254,6 @@
- Fix crash reporter not working (Windows) - Fix crash reporter not working (Windows)
### Fixed ### Fixed
- Re-enable WebRTC (web peers)! (OS X, Windows) - Re-enable WebRTC (web peers)! (OS X, Windows)
- Windows support was disabled in v0.6.1 to work around a bug in Electron - Windows support was disabled in v0.6.1 to work around a bug in Electron
- OS X support was disabled in v0.4.0 to work around a 100% CPU bug - OS X support was disabled in v0.4.0 to work around a 100% CPU bug
@@ -272,7 +264,6 @@
- Fix torrent loading message UI misalignment - Fix torrent loading message UI misalignment
### Known issues ### Known issues
- When upgrading to WebTorrent Desktop v0.7.0, some torrent metadata (file list, - When upgrading to WebTorrent Desktop v0.7.0, some torrent metadata (file list,
selected files, whether torrent is streamable) will be cleared. Just start the selected files, whether torrent is streamable) will be cleared. Just start the
torrent to re-populate the metadata. torrent to re-populate the metadata.
@@ -280,7 +271,6 @@
## v0.6.1 - 2016-05-26 ## v0.6.1 - 2016-05-26
### Fixed ### Fixed
- Disable WebRTC to work around Electron crash (Windows) - Disable WebRTC to work around Electron crash (Windows)
- Will be re-enabled in the next version of WebTorrent, which will be based on - Will be re-enabled in the next version of WebTorrent, which will be based on
the next version of Electron, where the bug is fixed. the next version of Electron, where the bug is fixed.
@@ -292,7 +282,6 @@
## v0.6.0 - 2016-05-24 ## v0.6.0 - 2016-05-24
### Added ### Added
- Added Preferences page to set Download folder - Added Preferences page to set Download folder
- Save video position, resume playback from saved position - Save video position, resume playback from saved position
- Add additional video player keyboard shortcuts (#275) - Add additional video player keyboard shortcuts (#275)
@@ -302,7 +291,6 @@
- Add announcement feature - Add announcement feature
### Changed ### Changed
- Nicer player UI - Nicer player UI
- Reduce startup jank, improve startup time (#568) - Reduce startup jank, improve startup time (#568)
- Cleanup unsupported codec detection (#569, #570) - Cleanup unsupported codec detection (#569, #570)
@@ -310,7 +298,6 @@
- Improve subtitle positioning (#551) - Improve subtitle positioning (#551)
### Fixed ### Fixed
- Fix Uncaught TypeError: Cannot read property 'update' of undefined (#567) - Fix Uncaught TypeError: Cannot read property 'update' of undefined (#567)
- Fix bugs in LocationHistory - Fix bugs in LocationHistory
- When player is active, and magnet link is pasted, go back to list - When player is active, and magnet link is pasted, go back to list
@@ -321,23 +308,19 @@
## v0.5.1 - 2016-05-18 ## v0.5.1 - 2016-05-18
### Fixed ### Fixed
- Fix auto-updater (OS X, Windows). - Fix auto-updater (OS X, Windows).
## v0.5.0 - 2016-05-17 ## v0.5.0 - 2016-05-17
### Added ### Added
- Select/deselect individual files to torrent. - Select/deselect individual files to torrent.
- Automatically include subtitle files (.srt, .vtt) from torrent in the subtitles menu. - Automatically include subtitle files (.srt, .vtt) from torrent in the subtitles menu.
- "Add Subtitle File..." menu item. - "Add Subtitle File..." menu item.
### Changed ### Changed
- When manually adding subtitle track(s), always switch to the new track. - When manually adding subtitle track(s), always switch to the new track.
### Fixed ### Fixed
- Magnet links throw exception on app launch. (OS X) - Magnet links throw exception on app launch. (OS X)
- Multi-file torrents would not seed in-place, were copied to Downloads folder. - Multi-file torrents would not seed in-place, were copied to Downloads folder.
- Missing 'About WebTorrent' menu item. (Windows) - Missing 'About WebTorrent' menu item. (Windows)
@@ -346,7 +329,6 @@
## v0.4.0 - 2016-05-13 ## v0.4.0 - 2016-05-13
### Added ### Added
- Better Windows support! - Better Windows support!
- Windows 32-bit build. - Windows 32-bit build.
- Windows Portable App build. - Windows Portable App build.
@@ -369,7 +351,6 @@
- New default torrent on first launch: The WIRED CD. - New default torrent on first launch: The WIRED CD.
### Changed ### Changed
- Improve app startup time by 40%. - Improve app startup time by 40%.
- UI tweaks: Reduce font size, reduce torrent list item height. - UI tweaks: Reduce font size, reduce torrent list item height.
- Add Playback menu for playback-related functionality. - Add Playback menu for playback-related functionality.
@@ -380,7 +361,6 @@
- Remove "Add Fake Airplay/Chromecast" menu items. - Remove "Add Fake Airplay/Chromecast" menu items.
### Fixed ### Fixed
- Disable WebRTC to fix 100% CPU usage/crashes caused by Chromium issue. This is - Disable WebRTC to fix 100% CPU usage/crashes caused by Chromium issue. This is
temporary. (OS X) temporary. (OS X)
- When fullscreen, make controls use the full window. (OS X) - When fullscreen, make controls use the full window. (OS X)
@@ -405,24 +385,20 @@ to this release!
## v0.3.3 - 2016-04-07 ## v0.3.3 - 2016-04-07
### Fixed ### Fixed
- App icon was incorrect (OS X) - App icon was incorrect (OS X)
## v0.3.2 - 2016-04-07 ## v0.3.2 - 2016-04-07
### Added ### Added
- Register WebTorrent as default handler for magnet links (OS X) - Register WebTorrent as default handler for magnet links (OS X)
### Changed ### Changed
- Faster startup time (50ms) - Faster startup time (50ms)
- Update Electron to 0.37.5 - Update Electron to 0.37.5
- Remove the white flash when loading pages and resizing the window - Remove the white flash when loading pages and resizing the window
- Fix crash when sending IPC messages - Fix crash when sending IPC messages
### Fixed ### Fixed
- 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) - Enforce minimimum window size when resizing player (for audio-only .mov files, which are 0x0)
@@ -430,17 +406,14 @@ to this release!
## v0.3.1 - 2016-04-06 ## v0.3.1 - 2016-04-06
### Added ### Added
- Add crash reporter to torrent engine process - Add crash reporter to torrent engine process
### Fixed ### Fixed
- Fix cast screen background: cover, don't tile - Fix cast screen background: cover, don't tile
## v0.3.0 - 2016-04-06 ## v0.3.0 - 2016-04-06
### Added ### Added
- **Ubuntu/Debian support!** (.deb installer) - **Ubuntu/Debian support!** (.deb installer)
- **DLNA streaming support** - **DLNA streaming support**
- Add "File > Quit" menu item (Linux) - Add "File > Quit" menu item (Linux)
@@ -448,14 +421,12 @@ to this release!
- Crash reporting - Crash reporting
### Changed ### Changed
- On startup, do not re-verify files when timestamps are unchanged - On startup, do not re-verify files when timestamps are unchanged
- Moved torrent engine to an independent process, for better UI performance - Moved torrent engine to an independent process, for better UI performance
- Removed media queries (UI resizing based on window width) - Removed media queries (UI resizing based on window width)
- Improved Chromecast icon, when connected - Improved Chromecast icon, when connected
### Fixed ### Fixed
- "Download Complete" notification shows consistently - "Download Complete" notification shows consistently
- Create new torrents and seed them without copying to temporary folder - Create new torrents and seed them without copying to temporary folder
- Clicking the "Download Complete" notification will always activate app - Clicking the "Download Complete" notification will always activate app
@@ -474,7 +445,6 @@ Thanks to @dcposch, @grunjol, and @feross for contributing to this release.
## v0.2.0 - 2016-03-29 ## v0.2.0 - 2016-03-29
### Added ### Added
- Minimise to tray (Windows, Linux) - Minimise to tray (Windows, Linux)
- Show spinner and download speed when player is stalled waiting for data - Show spinner and download speed when player is stalled waiting for data
- Highlight window on drag-and-drop - Highlight window on drag-and-drop
@@ -483,12 +453,10 @@ Thanks to @dcposch, @grunjol, and @feross for contributing to this release.
Linux users need to download new versions manually. Linux users need to download new versions manually.
### Changed ### Changed
- Renamed WebTorrent.app to WebTorrent Desktop - Renamed WebTorrent.app to WebTorrent Desktop
- Add Cosmos Laundromat as a default torrent - Add Cosmos Laundromat as a default torrent
### Fixed ### Fixed
- Only capture media keys when player is active - Only capture media keys when player is active
- Update WebTorrent to 0.88.1 for performance improvements - Update WebTorrent to 0.88.1 for performance improvements
- When seeding, do not proactively connect to new peers - When seeding, do not proactively connect to new peers
@@ -548,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/feross/webtorrent-desktop/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)! [lots to do](https://github.com/webtorrent/webtorrent-desktop/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)!
### Features ### Features

View File

@@ -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://github.com/feross/standard [standard-url]: https://standardjs.com
## 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** or modifying the Git history in any way. 1. **No `--force` pushes to master** or modifying history in any way. Rebasing and force pushing your own PR branch is fine.
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,6 +74,99 @@ 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:

View File

@@ -1,6 +1,8 @@
<h1 align="center"> <h1 align="center">
<br> <br>
<a href="https://webtorrent.io"><img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200"></a> <a href="https://webtorrent.io">
<img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200">
</a>
<br> <br>
WebTorrent Desktop WebTorrent Desktop
<br> <br>
@@ -10,16 +12,17 @@
<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/feross/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/webtorrent/webtorrent"><img src="https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg" alt="gitter"></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://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://github.com/feross/webtorrent-desktop/releases"><img src="https://img.shields.io/github/release/feross/webtorrent-desktop.svg" alt="Release"></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://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/feross/webtorrent-desktop/releases) page. [GitHub releases](https://github.com/webtorrent/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
@@ -37,7 +40,7 @@ instructions below in the ["How to Contribute"](#how-to-contribute) section.
### Get the code ### Get the code
``` ```
$ git clone https://github.com/feross/webtorrent-desktop.git $ git clone https://github.com/webtorrent/webtorrent-desktop.git
$ cd webtorrent-desktop $ cd webtorrent-desktop
$ npm install $ npm install
``` ```
@@ -68,19 +71,25 @@ $ 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 comparing each one to a reference. Why screenshots? The integration tests use Spectron and Tape. They click through the app, taking screenshots and
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 catch an bug where hitting ESC from a video doesn't correctly restore window size. * 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.
* 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 screenshots and running the tests, which will recreate them with the new look. * Small UI changes will break a few tests, but the fix is as easy as deleting the offending
* 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 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! 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 the same resolution as a 2016 12" Macbook. 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.
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 or keyboard while the tests are running. 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.
### Package the app ### Package the app
@@ -110,7 +119,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
@@ -145,10 +154,6 @@ 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
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
## License ## License
MIT. Copyright (c) [WebTorrent, LLC](https://webtorrent.io). MIT. Copyright (c) [WebTorrent, LLC](https://webtorrent.io).

View File

@@ -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)

View File

@@ -21,6 +21,7 @@ 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: [
@@ -36,6 +37,12 @@ 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)
@@ -104,8 +111,8 @@ const all = {
// "devDependencies" before starting to package the app. // "devDependencies" before starting to package the app.
prune: true, prune: true,
// The Electron version with which the app is built (without the leading 'v') // The Electron version that the app is built with (without the leading 'v')
version: require('electron/package.json').version electronVersion: require('electron/package.json').version
} }
const darwin = { const darwin = {

View File

@@ -1,9 +0,0 @@
#!/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

View File

@@ -1,8 +0,0 @@
#!/bin/sh
set -e
git pull
rm -rf node_modules/
npm install
npm dedupe
npm test

View File

@@ -1,7 +0,0 @@
#!/bin/sh
set -e
BIN=`dirname $0`
$BIN/release-_pre.sh
npm version major
$BIN/release-_post.sh

View File

@@ -1,7 +0,0 @@
#!/bin/sh
set -e
BIN=`dirname $0`
$BIN/release-_pre.sh
npm version minor
$BIN/release-_post.sh

View File

@@ -1,7 +0,0 @@
#!/bin/sh
set -e
BIN=`dirname $0`
$BIN/release-_pre.sh
npm version patch
$BIN/release-_post.sh

View File

@@ -1,14 +1,14 @@
{ {
"name": "webtorrent-desktop", "name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.", "description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
"version": "0.17.2", "version": "0.18.0",
"author": { "author": {
"name": "WebTorrent, LLC", "name": "WebTorrent, LLC",
"email": "feross@webtorrent.io", "email": "feross@webtorrent.io",
"url": "https://webtorrent.io" "url": "https://webtorrent.io"
}, },
"bugs": { "bugs": {
"url": "https://github.com/feross/webtorrent-desktop/issues" "url": "https://github.com/webtorrent/webtorrent-desktop/issues"
}, },
"dependencies": { "dependencies": {
"airplayer": "^2.0.0", "airplayer": "^2.0.0",
@@ -26,21 +26,25 @@
"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.16.0", "material-ui": "^0.17.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.2.1", "react": "^15.4.2",
"react-dom": "^15.2.1", "react-dom": "^15.4.2",
"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",
@@ -50,16 +54,16 @@
"zero-fill": "^2.2.3" "zero-fill": "^2.2.3"
}, },
"devDependencies": { "devDependencies": {
"buble": "^0.14.0", "buble": "^0.15.2",
"cross-zip": "^2.0.1", "cross-zip": "^2.0.1",
"depcheck": "^0.6.4", "depcheck": "^0.6.4",
"electron": "1.4.3", "electron": "1.6.0",
"electron-osx-sign": "^0.3.0", "electron-osx-sign": "0.4.3",
"electron-packager": "^8.0.0", "electron-packager": "~8.5.1",
"electron-winstaller": "^2.3.0", "electron-winstaller": "~2.5.2",
"gh-release": "^2.0.3", "gh-release": "^2.0.3",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"nobin-debian-installer": "^0.0.10", "nobin-debian-installer": "0.0.10",
"nodemon": "^1.10.2", "nodemon": "^1.10.2",
"open": "0.0.5", "open": "0.0.5",
"plist": "^2.0.1", "plist": "^2.0.1",
@@ -93,7 +97,7 @@
"productName": "WebTorrent", "productName": "WebTorrent",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/feross/webtorrent-desktop.git" "url": "git://github.com/webtorrent/webtorrent-desktop.git"
}, },
"scripts": { "scripts": {
"build": "buble src --output build", "build": "buble src --output build",
@@ -103,7 +107,7 @@
"package": "node ./bin/package.js", "package": "node ./bin/package.js",
"prepublish": "npm run build", "prepublish": "npm run build",
"start": "npm run build && electron .", "start": "npm run build && electron .",
"test": "standard && depcheck --ignores=buble,lodash.merge,nodemon,gh-release --ignore-dirs=build,dist && node ./bin/extra-lint.js", "test": "standard && depcheck --ignores=buble,nodemon,gh-release --ignore-dirs=build,dist && node ./bin/extra-lint.js",
"test-integration": "npm run build && node ./test", "test-integration": "npm run build && node ./test",
"update-authors": "./bin/update-authors.sh", "update-authors": "./bin/update-authors.sh",
"watch": "nodemon --exec \"npm run start\" --ext js,css --ignore build/ --ignore dist/" "watch": "nodemon --exec \"npm run start\" --ext js,css --ignore build/ --ignore dist/"

View File

@@ -1,7 +1,9 @@
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'
@@ -17,13 +19,13 @@ const IS_PORTABLE = isPortable()
const UI_HEADER_HEIGHT = 38 const UI_HEADER_HEIGHT = 38
const UI_TORRENT_HEIGHT = 100 const UI_TORRENT_HEIGHT = 100
module.exports = { const 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',
TELEMETRY_URL: 'https://webtorrent.io/desktop/telemetry', TELEMETRY_URL: 'https://webtorrent.io/desktop/telemetry',
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM, APP_COPYRIGHT: 'Copyright © 2014-2017 ' + APP_TEAM,
APP_FILE_ICON: path.join(__dirname, '..', 'static', 'WebTorrentFile'), APP_FILE_ICON: path.join(__dirname, '..', 'static', 'WebTorrentFile'),
APP_ICON: path.join(__dirname, '..', 'static', 'WebTorrent'), APP_ICON: path.join(__dirname, '..', 'static', 'WebTorrent'),
APP_NAME: APP_NAME, APP_NAME: APP_NAME,
@@ -70,9 +72,9 @@ module.exports = {
DEFAULT_DOWNLOAD_PATH: getDefaultDownloadPath(), DEFAULT_DOWNLOAD_PATH: getDefaultDownloadPath(),
GITHUB_URL: 'https://github.com/feross/webtorrent-desktop', GITHUB_URL: 'https://github.com/webtorrent/webtorrent-desktop',
GITHUB_URL_ISSUES: 'https://github.com/feross/webtorrent-desktop/issues', GITHUB_URL_ISSUES: 'https://github.com/webtorrent/webtorrent-desktop/issues',
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/feross/webtorrent-desktop/master', GITHUB_URL_RAW: 'https://raw.githubusercontent.com/webtorrent/webtorrent-desktop/master',
HOME_PAGE_URL: 'https://webtorrent.io', HOME_PAGE_URL: 'https://webtorrent.io',
@@ -102,6 +104,62 @@ 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
@@ -145,8 +203,6 @@ 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.

View File

@@ -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))
} }

View File

@@ -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((err) => cb(!err)) if (playerPath == null) return vlcCommand(cb)
process.nextTick(() => cb(true)) process.nextTick(() => cb(null))
} }
function spawn (playerPath, url, title) { function spawn (playerPath, url, title) {

View File

@@ -12,6 +12,8 @@ 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)
@@ -72,10 +74,17 @@ function init () {
if (err) throw err if (err) throw err
isReady = true isReady = true
const state = results.state
windows.main.init(results.state, {hidden: hidden}) // init new plugins then notify user
windows.webtorrent.init() plugins.subscribe(() => {
menu.init() // update menu and windows
// 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)
@@ -88,6 +97,26 @@ 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)
@@ -198,7 +227,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/feross/webtorrent-desktop/issues/214 // Issue: https://github.com/webtorrent/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:,') {

View File

@@ -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 (isInstalled) { externalPlayer.checkInstall(path, function (err) {
windows.main.send('checkForExternalPlayer', isInstalled) windows.main.send('checkForExternalPlayer', !err)
}) })
}) })

View File

@@ -17,8 +17,11 @@ const windows = require('./windows')
let menu = null let menu = null
function init () { function init (decorate) {
menu = electron.Menu.buildFromTemplate(getMenuTemplate()) let template = getMenuTemplate()
if (decorate) template = decorate(template)
menu = electron.Menu.buildFromTemplate(template)
electron.Menu.setApplicationMenu(menu) electron.Menu.setApplicationMenu(menu)
} }
@@ -285,9 +288,6 @@ function getMenuTemplate () {
label: 'Pause All', label: 'Pause All',
click: () => windows.main.dispatch('pauseAllTorrents') click: () => windows.main.dispatch('pauseAllTorrents')
}, },
{
type: 'separator'
},
{ {
label: 'Resume All', label: 'Resume All',
click: () => windows.main.dispatch('resumeAllTorrents') click: () => windows.main.dispatch('resumeAllTorrents')
@@ -327,7 +327,7 @@ function getMenuTemplate () {
] ]
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
// Add WebTorrent app menu (Mac) // WebTorrent menu (Mac)
template.unshift({ template.unshift({
label: config.APP_NAME, label: config.APP_NAME,
submenu: [ submenu: [
@@ -370,8 +370,26 @@ function getMenuTemplate () {
] ]
}) })
// Add Window menu (Mac) // Edit menu (Mac)
template.splice(5, 0, { template[2].submenu.push(
{
type: 'separator'
},
{
label: 'Speech',
submenu: [
{
role: 'startspeaking'
},
{
role: 'stopspeaking'
}
]
}
)
// Window menu (Mac)
template.splice(6, 0, {
role: 'window', role: 'window',
submenu: [ submenu: [
{ {
@@ -411,7 +429,7 @@ function getMenuTemplate () {
}) })
// Help menu (Windows, Linux) // Help menu (Windows, Linux)
template[4].submenu.push( template[5].submenu.push(
{ {
type: 'separator' type: 'separator'
}, },

424
src/main/plugins.js Normal file
View File

@@ -0,0 +1,424 @@
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)
}

View File

@@ -36,8 +36,8 @@ function setWindowFocus (flag) {
} }
function initLinux () { function initLinux () {
checkLinuxTraySupport(function (supportsTray) { checkLinuxTraySupport(function (err) {
if (supportsTray) createTray() if (!err) createTray()
}) })
} }
@@ -55,10 +55,14 @@ 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(false) if (err) return cb(err)
// 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:
cb(stdout.endsWith('\tinstall\n')) if (stdout.endsWith('\tinstall\n')) {
cb(null)
} else {
cb(new Error('debian package not installed'))
}
}) })
} }

View File

@@ -23,28 +23,32 @@ const config = require('../../config')
const log = require('../log') const log = require('../log')
const menu = require('../menu') const menu = require('../menu')
function init (state, options) { function init (state, options, decorate) {
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)
const win = main.win = new electron.BrowserWindow({ let windowOptions = {
backgroundColor: '#282828', backgroundColor: '#282828',
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)
height: initialBounds.height,
icon: getIconPath(), // Window icon (Windows, Linux) icon: getIconPath(), // Window icon (Windows, Linux)
minWidth: config.WINDOW_MIN_WIDTH,
minHeight: config.WINDOW_MIN_HEIGHT, minHeight: config.WINDOW_MIN_HEIGHT,
minWidth: config.WINDOW_MIN_WIDTH,
show: false,
title: config.APP_WINDOW_TITLE, title: config.APP_WINDOW_TITLE,
titleBarStyle: 'hidden-inset', // Hide title bar (Mac) titleBarStyle: 'hidden-inset', // Hide title bar (Mac)
useContentSize: true, // Specify web page size without OS chrome useContentSize: true, // Specify web page size without OS chrome
show: false,
width: initialBounds.width, width: initialBounds.width,
height: initialBounds.height,
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)
@@ -165,8 +169,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

View File

@@ -10,9 +10,10 @@ const electron = require('electron')
const config = require('../../config') const config = require('../../config')
function init () { function init (decorate) {
const win = webtorrent.win = new electron.BrowserWindow({ let options = {
backgroundColor: '#1E1E1E', backgroundColor: '#1E1E1E',
backgroundThrottling: false, // do not throttle animations/timers when page is background
center: true, center: true,
fullscreen: false, fullscreen: false,
fullscreenable: false, fullscreenable: false,
@@ -25,7 +26,9 @@ function init () {
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)

View File

@@ -23,7 +23,8 @@ module.exports = class UpdateAvailableModal extends React.Component {
) )
function handleShow () { function handleShow () {
electron.shell.openExternal('https://github.com/feross/webtorrent-desktop/releases') // TODO: use the GitHub urls from config.js
electron.shell.openExternal('https://github.com/webtorrent/webtorrent-desktop/releases')
dispatch('exitModal') dispatch('exitModal')
} }

View File

@@ -27,6 +27,8 @@ 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) => {
@@ -84,6 +86,17 @@ 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
@@ -341,6 +354,11 @@ 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()
} }
} }

View File

@@ -121,11 +121,10 @@ module.exports = class TorrentListController {
torrentSummary.status = 'new' torrentSummary.status = 'new'
this.startTorrentingSummary(torrentSummary.torrentKey) this.startTorrentingSummary(torrentSummary.torrentKey)
sound.play('ENABLE') sound.play('ENABLE')
} else { return
torrentSummary.status = 'paused'
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
sound.play('DISABLE')
} }
this.pauseTorrent(torrentSummary, true)
} }
pauseAllTorrents () { pauseAllTorrents () {
@@ -149,6 +148,40 @@ 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]
@@ -281,7 +314,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 = []
@@ -290,7 +323,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)
} }
}) })
}) })
@@ -304,7 +337,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
@@ -316,7 +349,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_)
}) })
}) })
} }

View File

@@ -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/feross/webtorrent-desktop/pull/604#issuecomment-222805214 // https://github.com/webtorrent/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/feross/webtorrent-desktop/pull/806 // See https://github.com/webtorrent/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/feross/webtorrent-desktop/issues/905 // Windows Explorer. See: https://github.com/webtorrent/webtorrent-desktop/issues/905
const cpFile = require('cp-file') const cpFile = require('cp-file')
const rimraf = require('rimraf') const rimraf = require('rimraf')

View File

@@ -124,6 +124,7 @@ 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 */
} }

View File

@@ -37,6 +37,7 @@ function isAudio (file) {
'.mp3', '.mp3',
'.ogg', '.ogg',
'.wav', '.wav',
'.flac',
'.m4a' '.m4a'
].includes(getFileExtension(file)) ].includes(getFileExtension(file))
} }

View File

@@ -24,6 +24,7 @@ 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')
@@ -35,6 +36,9 @@ 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')
@@ -113,6 +117,8 @@ 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',
@@ -145,6 +151,18 @@ 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)
@@ -241,6 +259,8 @@ 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),
@@ -317,6 +337,7 @@ 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)
@@ -341,6 +362,7 @@ 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))

View File

@@ -62,6 +62,24 @@ 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()
@@ -151,6 +169,7 @@ 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()}

287
src/renderer/plugins.js Normal file
View File

@@ -0,0 +1,287 @@
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)
}

View File

@@ -128,6 +128,7 @@ 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()
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

@@ -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]

View File

@@ -38,11 +38,12 @@ test('create-torrent', function (t) {
const expectedTorrent = { const expectedTorrent = {
announce: [ announce: [
'udp://exodus.desync.com:6969', 'udp://explodie.org:6969',
'udp://tracker.coppersurfer.tk:6969', 'udp://tracker.coppersurfer.tk:6969',
'udp://tracker.internetwarriors.net:1337',
'udp://tracker.leechers-paradise.org:6969', 'udp://tracker.leechers-paradise.org:6969',
'udp://tracker.openbittorrent.com:80', 'udp://tracker.openbittorrent.com:80',
'udp://tracker.opentrackr.org:1337',
'udp://zer0day.ch:1337',
'wss://tracker.btorrent.xyz', 'wss://tracker.btorrent.xyz',
'wss://tracker.fastcast.nz', 'wss://tracker.fastcast.nz',
'wss://tracker.openwebtorrent.com' 'wss://tracker.openwebtorrent.com'

View File

@@ -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/feross/webtorrent-desktop/issues/719 // This is a bug. See https://github.com/webtorrent/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',