Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persist DHT nodes #1431

Open
wants to merge 9 commits into
base: master
from
Next

Persist DHT nodes

Persists DHT nodes to disk every 15 minutes.
Loads persisted nodes on construction.
Enabled by default.

Default save file is dht.json,
under crossplatform app data folder provided by app-data-folder.

Adds option dhtState to configure:
* false disables
* true enables with default path
* String specifies path to custom save file
  • Loading branch information
bookmoons committed Jul 14, 2018
commit 03b7ecc11d7de8ced079c95124950b3df4efaf05
@@ -53,12 +53,13 @@ If `opts` is specified, then the default options (shown below) will be overridde

```js
{
maxConns: Number, // Max number of connections per torrent (default=55)
nodeId: String|Buffer, // DHT protocol node ID (default=randomly generated)
peerId: String|Buffer, // Wire protocol peer ID (default=randomly generated)
tracker: Boolean|Object, // Enable trackers (default=true), or options object for Tracker
dht: Boolean|Object, // Enable DHT (default=true), or options object for DHT
webSeeds: Boolean // Enable BEP19 web seeds (default=true)
maxConns: Number, // Max number of connections per torrent (default=55)
nodeId: String|Buffer, // DHT protocol node ID (default=randomly generated)
peerId: String|Buffer, // Wire protocol peer ID (default=randomly generated)
tracker: Boolean|Object, // Enable trackers (default=true), or options object for Tracker
dht: Boolean|Object, // Enable DHT (default=true), or options object for DHT
dhtState: Boolean|String, // Persist DHT nodes (default=true), or save file path

This comment has been minimized.

Copy link
@KayleePop

KayleePop Aug 3, 2018

Contributor

I think this would be clearer as two separate variables. A boolean for whether to persist the state, and a file path for persisting the state

Maybe named (doesn't matter)

{
  persistDht: Boolean,
  persistDhtPath: String
}

This comment has been minimized.

Copy link
@bookmoons

bookmoons Aug 6, 2018

Author

I've updated to split them that way.

webSeeds: Boolean // Enable BEP19 web seeds (default=true)
}
```

102 index.js
@@ -2,15 +2,18 @@

module.exports = WebTorrent

var appDataFolder = require('app-data-folder')
var Buffer = require('safe-buffer').Buffer
var concat = require('simple-concat')
var createTorrent = require('create-torrent')
var debug = require('debug')('webtorrent')
var DHT = require('bittorrent-dht/client') // browser exclude
var EventEmitter = require('events').EventEmitter
var extend = require('xtend')
var fs = require('fs')
var inherits = require('inherits')
var loadIPSet = require('load-ip-set') // browser exclude
var mkdirp = require('mkdirp')
var parallel = require('run-parallel')
var parseTorrent = require('parse-torrent')
var path = require('path')
@@ -78,6 +81,9 @@ function WebTorrent (opts) {
}
self.nodeIdBuffer = Buffer.from(self.nodeId, 'hex')

// Default opts.dhtState to true
if (!('dhtState' in opts)) opts.dhtState = true

self._debugId = self.peerId.toString('hex').substring(0, 7)

self.destroyed = false
@@ -122,9 +128,102 @@ function WebTorrent (opts) {
self._downloadSpeed = speedometer()
self._uploadSpeed = speedometer()

// DHT state save location
var dhtSaveFile

var savingDhtState = false
function saveDhtState () {
if (savingDhtState) return
if (!self.dht) return // Quell after destroy
savingDhtState = true
var dhtState = self.dht.toJSON()
var dhtStateJson = JSON.stringify(dhtState)
mkdirp(
path.dirname(dhtSaveFile),
function handleDhtSaveDirCreated (err) {
if (err) {
savingDhtState = false
return
}
fs.writeFile(
dhtSaveFile,
dhtStateJson,
function handleDhtStateWritten () {
savingDhtState = false
}
)
}
)
}

function readDhtState (file) {
try {
return fs.readFileSync(file)
} catch (e) {
switch (e.code) {
case 'EACCES':
case 'EISDIR':
case 'ENOENT':
case 'EPERM':
return null
default:
throw e
}
}
}

function parseDhtState (dhtStateJson) {
try {
return JSON.parse(dhtStateJson)
} catch (e) {
if (e instanceof SyntaxError) return null
else throw e
}
}

function loadDhtState (file) {
var dhtStateJson = readDhtState(file)
if (!dhtStateJson) return null
var dhtState = parseDhtState(dhtStateJson)
if (!dhtState) return null
return dhtState
}

function loadDhtNodes (file) {
var dhtState = loadDhtState(file)
if (!dhtState) return null
if (!('nodes' in dhtState)) return null
var nodes = dhtState.nodes
if (!Array.isArray(nodes)) return null
if (nodes.length === 0) return null // Don't load an empty nodes list
return nodes
}

if (opts.dht !== false && typeof DHT === 'function' /* browser exclude */) {
var dhtOpts = extend({ nodeId: self.nodeId }, opts.dht)

if (opts.dhtState) {
// Construct state save location
dhtSaveFile =
opts.dhtState === true
? path.join(appDataFolder('webtorrent'), 'dht.json')
: opts.dhtState

if (!('bootstrap' in dhtOpts)) {
// Load persisted state
var nodes = loadDhtNodes(dhtSaveFile)
if (nodes) dhtOpts.bootstrap = nodes
}
}

// use a single DHT instance for all torrents, so the routing table can be reused
self.dht = new DHT(extend({ nodeId: self.nodeId }, opts.dht))
self.dht = new DHT(dhtOpts)

if (opts.dhtState) {
// Persist state periodically
var saveInterval = 15 * 60 * 1000 // 15 minutes
self.saveDhtStateTimer = setInterval(saveDhtState, saveInterval)
}

self.dht.once('error', function (err) {
self._destroy(err)
@@ -428,6 +527,7 @@ WebTorrent.prototype._destroy = function (err, cb) {

if (self.dht) {
tasks.push(function (cb) {
clearInterval(self.saveDhtStateTimer)
self.dht.destroy(cb)
})
}
@@ -27,6 +27,7 @@
},
"dependencies": {
"addr-to-ip-port": "^1.4.2",
"app-data-folder": "^1.0.0",
"bitfield": "^2.0.0",
"bittorrent-dht": "^8.0.0",
"bittorrent-protocol": "^2.1.5",
@@ -40,6 +41,7 @@
"load-ip-set": "^1.2.7",
"memory-chunk-store": "^1.2.0",
"mime": "^2.2.0",
"mkdirp": "^0.5.1",
"multistream": "^2.0.5",
"package-json-versionify": "^1.0.2",
"parse-numeric-range": "^0.0.2",
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.