Compare commits
32 Commits
v0.5.0
...
entitlemen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93a9ba11b6 | ||
|
|
b367c0b72e | ||
|
|
bf3b9ced74 | ||
|
|
9ecc12fb7f | ||
|
|
aafb1421c6 | ||
|
|
76c732bafb | ||
|
|
ab476c9a9c | ||
|
|
4470310814 | ||
|
|
b6ba4f45c8 | ||
|
|
84c860cfcb | ||
|
|
47c554a5ff | ||
|
|
4e46b16c13 | ||
|
|
22cdcdb468 | ||
|
|
f238b2d105 | ||
|
|
3a81799828 | ||
|
|
5dca89b61c | ||
|
|
264c035ef7 | ||
|
|
8f39f8a23e | ||
|
|
a29dbd7a71 | ||
|
|
60a8969abc | ||
|
|
9747d28514 | ||
|
|
17ccd217a9 | ||
|
|
0df6198549 | ||
|
|
74ada99f2b | ||
|
|
81d5a367da | ||
|
|
189e4bdc24 | ||
|
|
7bd30f8a16 | ||
|
|
7c6b7e4a6d | ||
|
|
fe50f76619 | ||
|
|
973a366b94 | ||
|
|
b0116deb35 | ||
|
|
511382d384 |
@@ -1,5 +1,11 @@
|
|||||||
# WebTorrent Desktop Version History
|
# WebTorrent Desktop Version History
|
||||||
|
|
||||||
|
## v0.5.1 - 2016-05-18
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix auto-updater (OS X, Windows).
|
||||||
|
|
||||||
## v0.5.0 - 2016-05-17
|
## v0.5.0 - 2016-05-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) Feross Aboukhadijeh
|
Copyright (c) WebTorrent, LLC
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -87,4 +87,4 @@ brew install wine
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org).
|
MIT. Copyright (c) [WebTorrent, LLC](https://webtorrent.io).
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ var all = {
|
|||||||
|
|
||||||
var darwin = {
|
var darwin = {
|
||||||
// Build for OS X
|
// Build for OS X
|
||||||
platform: 'darwin',
|
platform: 'mas',
|
||||||
|
|
||||||
// Build 64 bit binaries only.
|
// Build 64 bit binaries only.
|
||||||
arch: 'x64',
|
arch: 'x64',
|
||||||
@@ -211,6 +211,8 @@ function buildDarwin (cb) {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
infoPlist.ElectronTeamID = '5MAMC8G3L8'
|
||||||
|
|
||||||
fs.writeFileSync(infoPlistPath, plist.build(infoPlist))
|
fs.writeFileSync(infoPlistPath, plist.build(infoPlist))
|
||||||
|
|
||||||
// Copy torrent file icon into app bundle
|
// Copy torrent file icon into app bundle
|
||||||
@@ -248,8 +250,9 @@ function buildDarwin (cb) {
|
|||||||
*/
|
*/
|
||||||
var signOpts = {
|
var signOpts = {
|
||||||
app: appPath,
|
app: appPath,
|
||||||
platform: 'darwin',
|
entitlements: path.join(config.STATIC_PATH, 'parent.entitlements'),
|
||||||
verbose: true
|
'entitlements-inherit': path.join(config.STATIC_PATH, 'child.entitlements'),
|
||||||
|
platform: 'mas'
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('OS X: Signing app...')
|
console.log('OS X: Signing app...')
|
||||||
|
|||||||
@@ -6,6 +6,5 @@ npm run update-authors
|
|||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
rm -rf node_modules/
|
rm -rf node_modules/
|
||||||
npm install
|
npm install
|
||||||
npm prune
|
|
||||||
npm dedupe
|
npm dedupe
|
||||||
npm test
|
npm test
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ var fs = require('fs')
|
|||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
var APP_NAME = 'WebTorrent'
|
var APP_NAME = 'WebTorrent'
|
||||||
var APP_TEAM = 'The WebTorrent Project'
|
var APP_TEAM = 'WebTorrent, LLC'
|
||||||
var APP_VERSION = require('./package.json').version
|
var APP_VERSION = require('./package.json').version
|
||||||
|
|
||||||
var PORTABLE_PATH = path.join(path.dirname(process.execPath), 'Portable Settings')
|
var PORTABLE_PATH = path.join(path.dirname(process.execPath), 'Portable Settings')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
ANNOUNCEMENT_URL: 'https://webtorrent.io/desktop/announcement',
|
||||||
|
|
||||||
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
|
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
|
||||||
APP_FILE_ICON: path.join(__dirname, 'static', 'WebTorrentFile'),
|
APP_FILE_ICON: path.join(__dirname, 'static', 'WebTorrentFile'),
|
||||||
APP_ICON: path.join(__dirname, 'static', 'WebTorrent'),
|
APP_ICON: path.join(__dirname, 'static', 'WebTorrent'),
|
||||||
@@ -17,8 +19,7 @@ module.exports = {
|
|||||||
APP_VERSION: APP_VERSION,
|
APP_VERSION: APP_VERSION,
|
||||||
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
|
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
|
||||||
|
|
||||||
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update' +
|
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update',
|
||||||
'?version=' + APP_VERSION + '&platform=' + process.platform,
|
|
||||||
|
|
||||||
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
||||||
|
|
||||||
|
|||||||
38
main/announcement.js
Normal file
38
main/announcement.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
module.exports = {
|
||||||
|
init
|
||||||
|
}
|
||||||
|
|
||||||
|
var electron = require('electron')
|
||||||
|
var get = require('simple-get')
|
||||||
|
|
||||||
|
var config = require('../config')
|
||||||
|
var log = require('./log')
|
||||||
|
|
||||||
|
var ANNOUNCEMENT_URL = config.ANNOUNCEMENT_URL +
|
||||||
|
'?version=' + config.APP_VERSION +
|
||||||
|
'&platform=' + process.platform
|
||||||
|
|
||||||
|
function init () {
|
||||||
|
get.concat(ANNOUNCEMENT_URL, function (err, res, data) {
|
||||||
|
if (err) return log('failed to retrieve remote message')
|
||||||
|
if (res.statusCode !== 200) return log('no remote message')
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data.toString())
|
||||||
|
} catch (err) {
|
||||||
|
data = {
|
||||||
|
title: 'WebTorrent Desktop Announcement',
|
||||||
|
message: 'WebTorrent Desktop Announcement',
|
||||||
|
detail: data.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.dialog.showMessageBox({
|
||||||
|
type: 'info',
|
||||||
|
buttons: ['OK'],
|
||||||
|
title: data.title,
|
||||||
|
message: data.message,
|
||||||
|
detail: data.detail
|
||||||
|
}, function () {})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ var electron = require('electron')
|
|||||||
var app = electron.app
|
var app = electron.app
|
||||||
var ipcMain = electron.ipcMain
|
var ipcMain = electron.ipcMain
|
||||||
|
|
||||||
|
var announcement = require('./announcement')
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var crashReporter = require('../crash-reporter')
|
var crashReporter = require('../crash-reporter')
|
||||||
var handlers = require('./handlers')
|
var handlers = require('./handlers')
|
||||||
@@ -25,14 +26,14 @@ if (process.platform === 'win32') {
|
|||||||
argv = argv.filter((arg) => arg.indexOf('--squirrel') === -1)
|
argv = argv.filter((arg) => arg.indexOf('--squirrel') === -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldQuit) {
|
// if (!shouldQuit) {
|
||||||
// Prevent multiple instances of app from running at same time. New instances signal
|
// // Prevent multiple instances of app from running at same time. New instances signal
|
||||||
// this instance and quit.
|
// // this instance and quit.
|
||||||
shouldQuit = app.makeSingleInstance(onAppOpen)
|
// shouldQuit = app.makeSingleInstance(onAppOpen)
|
||||||
if (shouldQuit) {
|
// if (shouldQuit) {
|
||||||
app.quit()
|
// app.quit()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!shouldQuit) {
|
if (!shouldQuit) {
|
||||||
init()
|
init()
|
||||||
@@ -91,6 +92,7 @@ function init () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function delayedInit () {
|
function delayedInit () {
|
||||||
|
announcement.init()
|
||||||
tray.init()
|
tray.init()
|
||||||
handlers.install()
|
handlers.install()
|
||||||
updater.init()
|
updater.init()
|
||||||
|
|||||||
@@ -107,11 +107,11 @@ function init () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('vlcPlay', function (e, url) {
|
ipcMain.on('vlcPlay', function (e, url) {
|
||||||
var args = ['--play-and-exit', '--quiet', url]
|
var args = ['--play-and-exit', '--video-on-top', '--no-video-title-show', '--quiet', url]
|
||||||
console.log('Running vlc ' + args.join(' '))
|
console.log('Running vlc ' + args.join(' '))
|
||||||
|
|
||||||
vlc.spawn(args, function (err, proc) {
|
vlc.spawn(args, function (err, proc) {
|
||||||
if (err) windows.main.send('dispatch', 'vlcNotFound')
|
if (err) return windows.main.send('dispatch', 'vlcNotFound')
|
||||||
vlcProcess = proc
|
vlcProcess = proc
|
||||||
|
|
||||||
// If it works, close the modal after a second
|
// If it works, close the modal after a second
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ var config = require('../config')
|
|||||||
var log = require('./log')
|
var log = require('./log')
|
||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
|
|
||||||
|
var AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
|
||||||
|
'?version=' + config.APP_VERSION +
|
||||||
|
'&platform=' + process.platform
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
initLinux()
|
initLinux()
|
||||||
@@ -20,7 +24,7 @@ function init () {
|
|||||||
// The Electron auto-updater does not support Linux yet, so manually check for updates and
|
// The Electron auto-updater does not support Linux yet, so manually check for updates and
|
||||||
// `show the user a modal notification.
|
// `show the user a modal notification.
|
||||||
function initLinux () {
|
function initLinux () {
|
||||||
get.concat(config.AUTO_UPDATE_URL, onResponse)
|
get.concat(AUTO_UPDATE_URL, onResponse)
|
||||||
|
|
||||||
function onResponse (err, res, data) {
|
function onResponse (err, res, data) {
|
||||||
if (err) return log(`Update error: ${err.message}`)
|
if (err) return log(`Update error: ${err.message}`)
|
||||||
@@ -67,5 +71,6 @@ function initDarwinWin32 () {
|
|||||||
(e, notes, name, date, url) => log(`Update downloaded: ${name}: ${url}`)
|
(e, notes, name, date, url) => log(`Update downloaded: ${name}: ${url}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
electron.autoUpdater.setFeedURL(config.AUTO_UPDATE_URL)
|
electron.autoUpdater.setFeedURL(AUTO_UPDATE_URL)
|
||||||
|
electron.autoUpdater.checkForUpdates()
|
||||||
}
|
}
|
||||||
|
|||||||
16
package.json
16
package.json
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "webtorrent-desktop",
|
"name": "webtorrent-desktop",
|
||||||
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
|
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Feross Aboukhadijeh",
|
"name": "WebTorrent, LLC",
|
||||||
"email": "feross@feross.org",
|
"email": "feross@feross.org",
|
||||||
"url": "http://feross.org"
|
"url": "https://webtorrent.io"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"webtorrent-desktop": "./bin/cmd.js"
|
"webtorrent-desktop": "./bin/cmd.js"
|
||||||
@@ -16,16 +16,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"airplay-js": "guerrerocarlos/node-airplay-js",
|
"airplay-js": "guerrerocarlos/node-airplay-js",
|
||||||
"application-config": "^0.2.1",
|
"application-config": "^0.2.1",
|
||||||
"async": "^2.0.0-rc.5",
|
|
||||||
"bitfield": "^1.0.2",
|
"bitfield": "^1.0.2",
|
||||||
"chromecasts": "^1.8.0",
|
"chromecasts": "^1.8.0",
|
||||||
"concat-stream": "^1.5.1",
|
|
||||||
"create-torrent": "^3.24.5",
|
"create-torrent": "^3.24.5",
|
||||||
"deep-equal": "^1.0.1",
|
"deep-equal": "^1.0.1",
|
||||||
"dlnacasts": "^0.1.0",
|
"dlnacasts": "^0.1.0",
|
||||||
"drag-drop": "^2.11.0",
|
"drag-drop": "^2.11.0",
|
||||||
"electron-localshortcut": "^0.6.0",
|
"electron-localshortcut": "^0.6.0",
|
||||||
"electron-prebuilt": "1.0.2",
|
"electron-prebuilt": "1.1.1",
|
||||||
"fs-extra": "^0.27.0",
|
"fs-extra": "^0.27.0",
|
||||||
"hyperx": "^2.0.2",
|
"hyperx": "^2.0.2",
|
||||||
"iso-639-1": "^1.2.1",
|
"iso-639-1": "^1.2.1",
|
||||||
@@ -34,6 +32,8 @@
|
|||||||
"musicmetadata": "^2.0.2",
|
"musicmetadata": "^2.0.2",
|
||||||
"network-address": "^1.1.0",
|
"network-address": "^1.1.0",
|
||||||
"prettier-bytes": "^1.0.1",
|
"prettier-bytes": "^1.0.1",
|
||||||
|
"run-parallel": "^1.1.6",
|
||||||
|
"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",
|
||||||
"virtual-dom": "^2.1.1",
|
"virtual-dom": "^2.1.1",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"gh-release": "^2.0.3",
|
"gh-release": "^2.0.3",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"nobin-debian-installer": "^0.0.9",
|
"nobin-debian-installer": "^0.0.10",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
"plist": "^1.2.0",
|
"plist": "^1.2.0",
|
||||||
"rimraf": "^2.5.2",
|
"rimraf": "^2.5.2",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"appdmg": "^0.3.6"
|
"appdmg": "^0.4.3"
|
||||||
},
|
},
|
||||||
"productName": "WebTorrent",
|
"productName": "WebTorrent",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ var ipcRenderer = electron.ipcRenderer
|
|||||||
setupIpc()
|
setupIpc()
|
||||||
|
|
||||||
var appConfig = require('application-config')('WebTorrent')
|
var appConfig = require('application-config')('WebTorrent')
|
||||||
var Async = require('async')
|
var concat = require('simple-concat')
|
||||||
var concat = require('concat-stream')
|
|
||||||
var dragDrop = require('drag-drop')
|
var dragDrop = require('drag-drop')
|
||||||
var fs = require('fs-extra')
|
var fs = require('fs-extra')
|
||||||
var iso639 = require('iso-639-1')
|
var iso639 = require('iso-639-1')
|
||||||
var mainLoop = require('main-loop')
|
var mainLoop = require('main-loop')
|
||||||
|
var parallel = require('run-parallel')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
var createElement = require('virtual-dom/create-element')
|
var createElement = require('virtual-dom/create-element')
|
||||||
@@ -87,7 +87,7 @@ function init () {
|
|||||||
|
|
||||||
// OS integrations:
|
// OS integrations:
|
||||||
// ...drag and drop a torrent or video file to play or seed
|
// ...drag and drop a torrent or video file to play or seed
|
||||||
dragDrop('body', (files) => dispatch('onOpen', files))
|
dragDrop('body', onOpen)
|
||||||
|
|
||||||
// ...same thing if you paste a torrent
|
// ...same thing if you paste a torrent
|
||||||
document.addEventListener('paste', onPaste)
|
document.addEventListener('paste', onPaste)
|
||||||
@@ -252,16 +252,7 @@ function dispatch (action, ...args) {
|
|||||||
setDimensions(args[0] /* dimensions */)
|
setDimensions(args[0] /* dimensions */)
|
||||||
}
|
}
|
||||||
if (action === 'backToList') {
|
if (action === 'backToList') {
|
||||||
// Exit any modals and screens with a back button
|
backToList()
|
||||||
state.modal = null
|
|
||||||
while (state.location.hasBack()) state.location.back()
|
|
||||||
|
|
||||||
// Work around virtual-dom issue: it doesn't expose its redraw function,
|
|
||||||
// and only redraws on requestAnimationFrame(). That means when the user
|
|
||||||
// closes the window (hide window / minimize to tray) and we want to pause
|
|
||||||
// the video, we update the vdom but it keeps playing until you reopen!
|
|
||||||
var mediaTag = document.querySelector('video,audio')
|
|
||||||
if (mediaTag) mediaTag.pause()
|
|
||||||
}
|
}
|
||||||
if (action === 'escapeBack') {
|
if (action === 'escapeBack') {
|
||||||
if (state.modal) {
|
if (state.modal) {
|
||||||
@@ -282,15 +273,14 @@ function dispatch (action, ...args) {
|
|||||||
playPause()
|
playPause()
|
||||||
}
|
}
|
||||||
if (action === 'play') {
|
if (action === 'play') {
|
||||||
if (state.location.pending()) return
|
|
||||||
state.location.go({
|
state.location.go({
|
||||||
url: 'player',
|
url: 'player',
|
||||||
onbeforeload: function (cb) {
|
onbeforeload: function (cb) {
|
||||||
|
play()
|
||||||
openPlayer(args[0] /* infoHash */, args[1] /* index */, cb)
|
openPlayer(args[0] /* infoHash */, args[1] /* index */, cb)
|
||||||
},
|
},
|
||||||
onbeforeunload: closePlayer
|
onbeforeunload: closePlayer
|
||||||
})
|
})
|
||||||
play()
|
|
||||||
}
|
}
|
||||||
if (action === 'playbackJump') {
|
if (action === 'playbackJump') {
|
||||||
jumpToTime(args[0] /* seconds */)
|
jumpToTime(args[0] /* seconds */)
|
||||||
@@ -314,7 +304,7 @@ function dispatch (action, ...args) {
|
|||||||
state.playing.isStalled = true
|
state.playing.isStalled = true
|
||||||
}
|
}
|
||||||
if (action === 'mediaError') {
|
if (action === 'mediaError') {
|
||||||
if (state.location.current().url === 'player') {
|
if (state.location.url() === 'player') {
|
||||||
state.playing.location = 'error'
|
state.playing.location = 'error'
|
||||||
ipcRenderer.send('checkForVLC')
|
ipcRenderer.send('checkForVLC')
|
||||||
ipcRenderer.once('checkForVLC', function (e, isInstalled) {
|
ipcRenderer.once('checkForVLC', function (e, isInstalled) {
|
||||||
@@ -394,7 +384,7 @@ function pause () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function playPause () {
|
function playPause () {
|
||||||
if (state.location.current().url !== 'player') return
|
if (state.location.url() !== 'player') return
|
||||||
if (state.playing.isPaused) {
|
if (state.playing.isPaused) {
|
||||||
play()
|
play()
|
||||||
} else {
|
} else {
|
||||||
@@ -436,6 +426,24 @@ function openSubtitles () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quits any modal popovers and returns to the torrent list screen
|
||||||
|
function backToList () {
|
||||||
|
// Exit any modals and screens with a back button
|
||||||
|
state.modal = null
|
||||||
|
state.location.backToFirst(function () {
|
||||||
|
// If we were already on the torrent list, scroll to the top
|
||||||
|
var contentTag = document.querySelector('.content')
|
||||||
|
if (contentTag) contentTag.scrollTop = 0
|
||||||
|
|
||||||
|
// Work around virtual-dom issue: it doesn't expose its redraw function,
|
||||||
|
// and only redraws on requestAnimationFrame(). That means when the user
|
||||||
|
// closes the window (hide window / minimize to tray) and we want to pause
|
||||||
|
// the video, we update the vdom but it keeps playing until you reopen!
|
||||||
|
var mediaTag = document.querySelector('video,audio')
|
||||||
|
if (mediaTag) mediaTag.pause()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Checks whether we are connected and already casting
|
// Checks whether we are connected and already casting
|
||||||
// Returns false if we not casting (state.playing.location === 'local')
|
// Returns false if we not casting (state.playing.location === 'local')
|
||||||
// or if we're trying to connect but haven't yet ('chromecast-pending', etc)
|
// or if we're trying to connect but haven't yet ('chromecast-pending', etc)
|
||||||
@@ -546,26 +554,32 @@ function saveState () {
|
|||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the user drag-drops files onto the app
|
||||||
function onOpen (files) {
|
function onOpen (files) {
|
||||||
if (!Array.isArray(files)) files = [ files ]
|
if (!Array.isArray(files)) files = [ files ]
|
||||||
|
|
||||||
// In the player, the only drag-drop function is adding subtitles
|
if (state.modal) {
|
||||||
var isInPlayer = state.location.current().url === 'player'
|
state.modal = null
|
||||||
if (isInPlayer) {
|
|
||||||
return addSubtitles(files.filter(isSubtitle), true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, you can only drag-drop onto the home screen
|
var subtitles = files.filter(isSubtitle)
|
||||||
var isHome = state.location.current().url === 'home' && !state.modal
|
|
||||||
if (isHome) {
|
if (state.location.url() === 'home' || subtitles.length === 0) {
|
||||||
if (files.every(isTorrent)) {
|
if (files.every(isTorrent)) {
|
||||||
// All .torrent files? Start downloading
|
if (state.location.url() !== 'home') {
|
||||||
|
backToList()
|
||||||
|
}
|
||||||
|
// All .torrent files? Add them.
|
||||||
files.forEach(addTorrent)
|
files.forEach(addTorrent)
|
||||||
} else {
|
} else {
|
||||||
// Show the Create Torrent screen. Let's seed those files.
|
// Show the Create Torrent screen. Let's seed those files.
|
||||||
showCreateTorrent(files)
|
showCreateTorrent(files)
|
||||||
}
|
}
|
||||||
|
} else if (state.location.url() === 'player') {
|
||||||
|
addSubtitles(subtitles, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTorrent (file) {
|
function isTorrent (file) {
|
||||||
@@ -592,6 +606,7 @@ function getTorrentSummary (torrentKey) {
|
|||||||
// Adds a torrent to the list, starts downloading/seeding. TorrentID can be a
|
// Adds a torrent to the list, starts downloading/seeding. TorrentID can be a
|
||||||
// magnet URI, infohash, or torrent file: https://github.com/feross/webtorrent#clientaddtorrentid-opts-function-ontorrent-torrent-
|
// magnet URI, infohash, or torrent file: https://github.com/feross/webtorrent#clientaddtorrentid-opts-function-ontorrent-torrent-
|
||||||
function addTorrent (torrentId) {
|
function addTorrent (torrentId) {
|
||||||
|
backToList()
|
||||||
var torrentKey = state.nextTorrentKey++
|
var torrentKey = state.nextTorrentKey++
|
||||||
var path = state.saved.downloadPath
|
var path = state.saved.downloadPath
|
||||||
if (torrentId.path) {
|
if (torrentId.path) {
|
||||||
@@ -606,22 +621,23 @@ function addSubtitles (files, autoSelect) {
|
|||||||
if (state.playing.type !== 'video') return
|
if (state.playing.type !== 'video') return
|
||||||
|
|
||||||
// Read the files concurrently, then add all resulting subtitle tracks
|
// Read the files concurrently, then add all resulting subtitle tracks
|
||||||
console.log(files)
|
var jobs = files.map((file) => (cb) => loadSubtitle(file, cb))
|
||||||
var subs = state.playing.subtitles
|
parallel(jobs, function (err, tracks) {
|
||||||
Async.map(files, loadSubtitle, function (err, tracks) {
|
|
||||||
if (err) return onError(err)
|
if (err) return onError(err)
|
||||||
|
|
||||||
for (var i = 0; i < tracks.length; i++) {
|
for (var i = 0; i < tracks.length; i++) {
|
||||||
// No dupes allowed
|
// No dupes allowed
|
||||||
var track = tracks[i]
|
var track = tracks[i]
|
||||||
if (subs.tracks.some((t) => track.filePath === t.filePath)) continue
|
if (state.playing.subtitles.tracks.some(
|
||||||
|
(t) => track.filePath === t.filePath)) continue
|
||||||
|
|
||||||
// Add the track
|
// Add the track
|
||||||
subs.tracks.push(track)
|
state.playing.subtitles.tracks.push(track)
|
||||||
|
|
||||||
// If we're auto-selecting a track, try to find one in the user's language
|
// If we're auto-selecting a track, try to find one in the user's language
|
||||||
if (autoSelect && (i === 0 || isSystemLanguage(track.language))) {
|
if (autoSelect && (i === 0 || isSystemLanguage(track.language))) {
|
||||||
state.playing.subtitles.selectedIndex = subs.tracks.length - 1
|
state.playing.subtitles.selectedIndex =
|
||||||
|
state.playing.subtitles.tracks.length - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,7 +652,12 @@ function loadSubtitle (file, cb) {
|
|||||||
|
|
||||||
// Read the .SRT or .VTT file, parse it, add subtitle track
|
// Read the .SRT or .VTT file, parse it, add subtitle track
|
||||||
var filePath = file.path || file
|
var filePath = file.path || file
|
||||||
fs.createReadStream(filePath).pipe(srtToVtt()).pipe(concat(function (buf) {
|
|
||||||
|
var vttStream = fs.createReadStream(filePath).pipe(srtToVtt())
|
||||||
|
|
||||||
|
concat(vttStream, function (err, buf) {
|
||||||
|
if (err) return onError(new Error('Error parsing subtitles file.'))
|
||||||
|
|
||||||
// Detect what language the subtitles are in
|
// Detect what language the subtitles are in
|
||||||
var vttContents = buf.toString().replace(/(.*-->.*)/g, '')
|
var vttContents = buf.toString().replace(/(.*-->.*)/g, '')
|
||||||
var langDetected = (new LanguageDetect()).detect(vttContents, 2)
|
var langDetected = (new LanguageDetect()).detect(vttContents, 2)
|
||||||
@@ -655,7 +676,7 @@ function loadSubtitle (file, cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cb(null, track)
|
cb(null, track)
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectSubtitle (ix) {
|
function selectSubtitle (ix) {
|
||||||
@@ -728,7 +749,6 @@ function startTorrentingSummary (torrentSummary) {
|
|||||||
// Shows the Create Torrent page with options to seed a given file or folder
|
// Shows the Create Torrent page with options to seed a given file or folder
|
||||||
function showCreateTorrent (files) {
|
function showCreateTorrent (files) {
|
||||||
if (Array.isArray(files)) {
|
if (Array.isArray(files)) {
|
||||||
if (state.location.pending() || state.location.current().url !== 'home') return
|
|
||||||
state.location.go({
|
state.location.go({
|
||||||
url: 'create-torrent',
|
url: 'create-torrent',
|
||||||
files: files
|
files: files
|
||||||
@@ -778,6 +798,9 @@ function findFilesRecursive (fileOrFolder, cb) {
|
|||||||
function createTorrent (options) {
|
function createTorrent (options) {
|
||||||
var torrentKey = state.nextTorrentKey++
|
var torrentKey = state.nextTorrentKey++
|
||||||
ipcRenderer.send('wt-create-torrent', torrentKey, options)
|
ipcRenderer.send('wt-create-torrent', torrentKey, options)
|
||||||
|
state.location.backToFirst(function () {
|
||||||
|
state.location.clearForward('create-torrent')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function torrentInfoHash (torrentKey, infoHash) {
|
function torrentInfoHash (torrentKey, infoHash) {
|
||||||
@@ -790,7 +813,7 @@ function torrentInfoHash (torrentKey, infoHash) {
|
|||||||
torrentKey: torrentKey,
|
torrentKey: torrentKey,
|
||||||
status: 'new'
|
status: 'new'
|
||||||
}
|
}
|
||||||
state.saved.torrents.push(torrentSummary)
|
state.saved.torrents.unshift(torrentSummary)
|
||||||
sound.play('ADD')
|
sound.play('ADD')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1059,7 +1082,7 @@ function deleteTorrent (infoHash) {
|
|||||||
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
|
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
|
||||||
if (index > -1) state.saved.torrents.splice(index, 1)
|
if (index > -1) state.saved.torrents.splice(index, 1)
|
||||||
saveStateThrottled()
|
saveStateThrottled()
|
||||||
state.location.clearForward() // prevent user from going forward to a deleted torrent
|
state.location.clearForward('player') // prevent user from going forward to a deleted torrent
|
||||||
sound.play('DELETE')
|
sound.play('DELETE')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1207,7 +1230,7 @@ function showDoneNotification (torrent) {
|
|||||||
// * The video is paused
|
// * The video is paused
|
||||||
// * The video is playing remotely on Chromecast or Airplay
|
// * The video is playing remotely on Chromecast or Airplay
|
||||||
function showOrHidePlayerControls () {
|
function showOrHidePlayerControls () {
|
||||||
var hideControls = state.location.current().url === 'player' &&
|
var hideControls = state.location.url() === 'player' &&
|
||||||
state.playing.mouseStationarySince !== 0 &&
|
state.playing.mouseStationarySince !== 0 &&
|
||||||
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
|
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
|
||||||
!state.playing.isPaused &&
|
!state.playing.isPaused &&
|
||||||
@@ -1242,8 +1265,10 @@ function onPaste (e) {
|
|||||||
torrentIds.forEach(function (torrentId) {
|
torrentIds.forEach(function (torrentId) {
|
||||||
torrentId = torrentId.trim()
|
torrentId = torrentId.trim()
|
||||||
if (torrentId.length === 0) return
|
if (torrentId.length === 0) return
|
||||||
dispatch('addTorrent', torrentId)
|
addTorrent(torrentId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFocus (e) {
|
function onFocus (e) {
|
||||||
|
|||||||
@@ -4,81 +4,123 @@ function LocationHistory () {
|
|||||||
if (!new.target) return new LocationHistory()
|
if (!new.target) return new LocationHistory()
|
||||||
this._history = []
|
this._history = []
|
||||||
this._forward = []
|
this._forward = []
|
||||||
this._pending = null
|
this._pending = false
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.go = function (page, cb) {
|
LocationHistory.prototype.url = function () {
|
||||||
console.log('go', page)
|
return this.current() && this.current().url
|
||||||
this.clearForward()
|
|
||||||
this._go(page, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationHistory.prototype._go = function (page, cb) {
|
|
||||||
if (this._pending) return
|
|
||||||
if (page.onbeforeload) {
|
|
||||||
this._pending = page
|
|
||||||
page.onbeforeload((err) => {
|
|
||||||
if (this._pending !== page) return /* navigation was cancelled */
|
|
||||||
this._pending = null
|
|
||||||
if (err) {
|
|
||||||
if (cb) cb(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._history.push(page)
|
|
||||||
if (cb) cb()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this._history.push(page)
|
|
||||||
if (cb) cb()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationHistory.prototype.back = function (cb) {
|
|
||||||
if (this._history.length <= 1) return
|
|
||||||
|
|
||||||
var page = this._history.pop()
|
|
||||||
|
|
||||||
if (page.onbeforeunload) {
|
|
||||||
// TODO: this is buggy. If the user clicks back twice, then those pages
|
|
||||||
// may end up in _forward in the wrong order depending on which onbeforeunload
|
|
||||||
// call finishes first.
|
|
||||||
page.onbeforeunload(() => {
|
|
||||||
this._forward.push(page)
|
|
||||||
if (cb) cb()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this._forward.push(page)
|
|
||||||
if (cb) cb()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationHistory.prototype.forward = function (cb) {
|
|
||||||
if (this._forward.length === 0) return
|
|
||||||
|
|
||||||
var page = this._forward.pop()
|
|
||||||
this._go(page, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationHistory.prototype.clearForward = function () {
|
|
||||||
this._forward = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.current = function () {
|
LocationHistory.prototype.current = function () {
|
||||||
return this._history[this._history.length - 1]
|
return this._history[this._history.length - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocationHistory.prototype.go = function (page, cb) {
|
||||||
|
if (!cb) cb = noop
|
||||||
|
if (this._pending) return cb(null)
|
||||||
|
|
||||||
|
console.log('go', page)
|
||||||
|
|
||||||
|
this.clearForward()
|
||||||
|
this._go(page, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationHistory.prototype.back = function (cb) {
|
||||||
|
var self = this
|
||||||
|
if (!cb) cb = noop
|
||||||
|
if (self._history.length <= 1 || self._pending) return cb(null)
|
||||||
|
|
||||||
|
var page = self._history.pop()
|
||||||
|
self._unload(page, done)
|
||||||
|
|
||||||
|
function done (err) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
self._forward.push(page)
|
||||||
|
self._load(self.current(), cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.hasBack = function () {
|
LocationHistory.prototype.hasBack = function () {
|
||||||
return this._history.length > 1
|
return this._history.length > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocationHistory.prototype.forward = function (cb) {
|
||||||
|
if (!cb) cb = noop
|
||||||
|
if (this._forward.length === 0 || this._pending) return cb(null)
|
||||||
|
|
||||||
|
var page = this._forward.pop()
|
||||||
|
this._go(page, cb)
|
||||||
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.hasForward = function () {
|
LocationHistory.prototype.hasForward = function () {
|
||||||
return this._forward.length > 0
|
return this._forward.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.pending = function () {
|
LocationHistory.prototype.clearForward = function (url) {
|
||||||
return this._pending
|
if (url == null) {
|
||||||
|
this._forward = []
|
||||||
|
} else {
|
||||||
|
console.log(this._forward)
|
||||||
|
console.log(url)
|
||||||
|
this._forward = this._forward.filter(function (page) {
|
||||||
|
return page.url !== url
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.clearPending = function () {
|
LocationHistory.prototype.backToFirst = function (cb) {
|
||||||
this._pending = null
|
var self = this
|
||||||
|
if (!cb) cb = noop
|
||||||
|
if (self._history.length <= 1) return cb(null)
|
||||||
|
|
||||||
|
self.back(function (err) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
self.backToFirst(cb)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocationHistory.prototype._go = function (page, cb) {
|
||||||
|
var self = this
|
||||||
|
if (!cb) cb = noop
|
||||||
|
|
||||||
|
self._unload(self.current(), done1)
|
||||||
|
|
||||||
|
function done1 (err) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
self._load(page, done2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function done2 (err) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
self._history.push(page)
|
||||||
|
cb(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationHistory.prototype._load = function (page, cb) {
|
||||||
|
var self = this
|
||||||
|
self._pending = true
|
||||||
|
|
||||||
|
if (page && page.onbeforeload) page.onbeforeload(done)
|
||||||
|
else done(null)
|
||||||
|
|
||||||
|
function done (err) {
|
||||||
|
self._pending = false
|
||||||
|
cb(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationHistory.prototype._unload = function (page, cb) {
|
||||||
|
var self = this
|
||||||
|
self._pending = true
|
||||||
|
|
||||||
|
if (page && page.onbeforeunload) page.onbeforeunload(done)
|
||||||
|
else done(null)
|
||||||
|
|
||||||
|
function done (err) {
|
||||||
|
self._pending = false
|
||||||
|
cb(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop () {}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function App (state) {
|
|||||||
// * The mouse is over the controls or we're scrubbing (see CSS)
|
// * The mouse is over the controls or we're scrubbing (see CSS)
|
||||||
// * The video is paused
|
// * The video is paused
|
||||||
// * The video is playing remotely on Chromecast or Airplay
|
// * The video is playing remotely on Chromecast or Airplay
|
||||||
var hideControls = state.location.current().url === 'player' &&
|
var hideControls = state.location.url() === 'player' &&
|
||||||
state.playing.mouseStationarySince !== 0 &&
|
state.playing.mouseStationarySince !== 0 &&
|
||||||
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
|
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
|
||||||
!state.playing.isPaused &&
|
!state.playing.isPaused &&
|
||||||
@@ -30,10 +30,10 @@ function App (state) {
|
|||||||
|
|
||||||
// Hide the header on Windows/Linux when in the player
|
// Hide the header on Windows/Linux when in the player
|
||||||
// On OSX, the header appears as part of the title bar
|
// On OSX, the header appears as part of the title bar
|
||||||
var hideHeader = process.platform !== 'darwin' && state.location.current().url === 'player'
|
var hideHeader = process.platform !== 'darwin' && state.location.url() === 'player'
|
||||||
|
|
||||||
var cls = [
|
var cls = [
|
||||||
'view-' + state.location.current().url, /* e.g. view-home, view-player */
|
'view-' + state.location.url(), /* e.g. view-home, view-player */
|
||||||
'is-' + process.platform /* e.g. is-darwin, is-win32, is-linux */
|
'is-' + process.platform /* e.g. is-darwin, is-win32, is-linux */
|
||||||
]
|
]
|
||||||
if (state.window.isFullScreen) cls.push('is-fullscreen')
|
if (state.window.isFullScreen) cls.push('is-fullscreen')
|
||||||
@@ -54,12 +54,13 @@ function App (state) {
|
|||||||
function getErrorPopover (state) {
|
function getErrorPopover (state) {
|
||||||
var now = new Date().getTime()
|
var now = new Date().getTime()
|
||||||
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
|
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
|
||||||
|
var hasErrors = recentErrors.length > 0
|
||||||
|
|
||||||
var errorElems = recentErrors.map(function (error) {
|
var errorElems = recentErrors.map(function (error) {
|
||||||
return hx`<div class='error'>${error.message}</div>`
|
return hx`<div class='error'>${error.message}</div>`
|
||||||
})
|
})
|
||||||
return hx`
|
return hx`
|
||||||
<div class='error-popover ${recentErrors.length > 0 ? 'visible' : 'hidden'}'>
|
<div class='error-popover ${hasErrors ? 'visible' : 'hidden'}'>
|
||||||
<div class='title'>Error</div>
|
<div class='title'>Error</div>
|
||||||
${errorElems}
|
${errorElems}
|
||||||
</div>
|
</div>
|
||||||
@@ -80,6 +81,6 @@ function getModal (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getView (state) {
|
function getView (state) {
|
||||||
var url = state.location.current().url
|
var url = state.location.url()
|
||||||
return Views[url](state)
|
return Views[url](state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,11 +119,10 @@ function CreateTorrentPage (state) {
|
|||||||
comment: comment
|
comment: comment
|
||||||
}
|
}
|
||||||
dispatch('createTorrent', options)
|
dispatch('createTorrent', options)
|
||||||
dispatch('backToList')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancel () {
|
function handleCancel () {
|
||||||
dispatch('backToList')
|
dispatch('back')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleToggleShowAdvanced () {
|
function handleToggleShowAdvanced () {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function Header (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAddButton () {
|
function getAddButton () {
|
||||||
if (state.location.current().url !== 'player') {
|
if (state.location.url() !== 'player') {
|
||||||
return hx`
|
return hx`
|
||||||
<i
|
<i
|
||||||
class='icon add'
|
class='icon add'
|
||||||
|
|||||||
@@ -349,10 +349,6 @@ function selectFiles (torrentOrInfoHash, selections) {
|
|||||||
} else {
|
} else {
|
||||||
console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
|
console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
|
||||||
file.deselect()
|
file.deselect()
|
||||||
|
|
||||||
// If we deselected a file, try to nuke it to save disk space
|
|
||||||
var filePath = path.join(torrent.path, file.path)
|
|
||||||
fs.unlink(filePath) // Ignore errors for now
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
static/child.entitlements
Normal file
10
static/child.entitlements
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.inherit</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
20
static/parent.entitlements
Normal file
20
static/parent.entitlements
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.downloads.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>5MAMC8G3L8.io.webtorrent.webtorrent</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Reference in New Issue
Block a user