First functional web interface version.

- Simplify by now all html and javascript.
- Added a simple interface using go templates and plain javascript.
- Improved REST interface for the use case.
- Changed some properties into the config file to make it suitable for
future use cases.

Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com>
This commit is contained in:
Antonio Navarro Perez 2020-05-18 19:42:23 +02:00
parent 80ed4e9e1e
commit ecd524ed3c
34 changed files with 585 additions and 7383 deletions

5
torrent/.gitignore vendored
View file

@ -1,5 +1,2 @@
_DATA
assets_vfsdata.go
assets
front/.cache
**/node_modules/
assets_vfsdata.go

View file

@ -8,3 +8,4 @@ import (
)
var Assets http.FileSystem = http.Dir("assets")
var Templates http.FileSystem = http.Dir("templates")

7
torrent/assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7
torrent/assets/js/Chart.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,39 @@
function FileChunks() {
this.update = function (chunks, totalPieces, hash) {
var dom = document.getElementById("file-chunks-" + hash);
dom.innerHTML = "";
chunks.forEach(chunk => {
dom.appendChild(getPrintedChunk(chunk.status, chunk.numPieces, totalPieces));
});
};
var pieceStatus = {
"H": { class: "bg-warning", tooltip: "checking pieces" },
"P": { class: "bg-info", tooltip: "" },
"C": { class: "bg-success", tooltip: "downloaded pieces" },
"W": { class: "bg-transparent" },
"?": { class: "bg-danger", tooltip: "erroed pieces" },
};
var getPrintedChunk = function (status, pieces, totalPieces) {
var percentage = totalPieces * pieces / 100;
var pcMeta = pieceStatus[status]
var pieceStatusClass = pcMeta.class;
var pieceStatusTip = pcMeta.tooltip;
var div = document.createElement("div");
div.className = "progress-bar " + pieceStatusClass;
div.setAttribute("role", "progressbar");
if (pieceStatusTip) {
div.setAttribute("data-toggle", "tooltip");
div.setAttribute("data-placement", "top");
div.setAttribute("title", pieceStatusTip);
}
div.style.cssText = "width: " + percentage + "%";
return div;
};
};

View file

@ -0,0 +1,35 @@
GeneralChart.init();
var cacheChart = new SingleBarChart("chart-cache", "Cache disk");
var workerChart = new SingleBarChart("chart-workers", "Workers");
fetchData();
setInterval(function () {
fetchData();
}, 2000)
function fetchData() {
fetch('/api/status')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
console.log('Error getting data from server. Response: ' + response.status);
}
}).then(function (stats) {
var download = stats.torrentStats.downloadedBytes / stats.torrentStats.timePassed;
var upload = stats.torrentStats.uploadedBytes / stats.torrentStats.timePassed;
GeneralChart.update(download, upload);
cacheChart.update(stats.cacheFilled, stats.cacheCapacity - stats.cacheFilled);
workerChart.update(0, stats.poolFree - stats.poolCap);
document.getElementById("down-speed-text").innerText =
Humanize.bytes(download, 1024) + "/s";
document.getElementById("up-speed-text").innerText =
Humanize.bytes(upload, 1024) + " /s";
})
.catch(function (error) {
console.log('Error getting status info: ' + error.message);
});
}

View file

@ -0,0 +1,78 @@
var GeneralChart = {
_downloadData: [],
_uploadData: [],
_chart: null,
update: function (download, upload) {
if (this._downloadData.length > 20) {
this._uploadData.shift();
this._downloadData.shift();
}
var date = new Date();
this._downloadData.push({
x: date,
y: download,
});
this._uploadData.push({
x: date,
y: upload,
});
this._chart.update();
},
init: function () {
var ctx = document.getElementById('chart-general-network').getContext('2d');
this._chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: 'Download Speed',
fill: false,
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1,
data: this._downloadData,
},
{
label: 'Upload Speed',
fill: false,
borderWidth: 1,
data: this._uploadData,
},
]
},
options: {
title: {
text: 'Download and Upload speed'
},
scales: {
xAxes: [{
scaleLabel: {
display: false,
},
gridLines: {
display: false,
},
ticks: {
display: false,
},
type: 'time',
}],
yAxes: [{
scaleLabel: {
display: false,
},
type: 'linear',
ticks: {
userCallback: function (tick) {
return Humanize.bytes(tick, 1024) + "/s";
},
beginAtZero: true
},
}]
},
}
});
}
}

View file

@ -4,7 +4,7 @@ function logn(n, b) {
return Math.log(n) / Math.log(b);
}
var humanize = {
var Humanize = {
bytes: function (s, base) {
if (s < 10) {
return s.toFixed(0) + " B";
@ -18,9 +18,7 @@ var humanize = {
if (val < 10) {
f = val.toFixed(1);
}
console.log("OUT", f + suffix);
return f + suffix;
}
};
export default humanize;

View file

@ -0,0 +1,32 @@
var fileChunks = new FileChunks();
fetchData();
setInterval(function () {
fetchData();
}, 2000)
function fetchData() {
fetch('/api/routes')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
console.log('Error getting data from server. Response: ' + response.status);
}
}).then(function (routes) {
routes.forEach(route => {
route.torrentStats.forEach(torrentStat => {
fileChunks.update(torrentStat.pieceChunks, torrentStat.totalPieces, torrentStat.hash);
var download = torrentStat.downloadedBytes / torrentStat.timePassed;
var upload = torrentStat.uploadedBytes / torrentStat.timePassed;
document.getElementById("up-down-speed-text-" + torrentStat.hash).innerText =
Humanize.bytes(download, 1024) + "/s down, " + Humanize.bytes(upload, 1024) + "/s up";
});
});
})
.catch(function (error) {
console.log('Error getting status info: ' + error.message);
});
}

View file

@ -0,0 +1,48 @@
function SingleBarChart(id, name) {
var ctx = document.getElementById(id).getContext('2d');
this._used = [];
this._free = [];
this._chart = new Chart(ctx, {
type: 'bar',
data: {
labels:[name],
datasets: [{
backgroundColor: "gray",
label: "used",
data: this._used,
},
{
backgroundColor: "green",
label: "free",
data: this._free,
}],
},
options: {
legend: {
display: false,
},
animation: false,
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
display: true,
ticks: {
beginAtZero: true,
}
}]
}
},
});
this.update = function (used, free) {
this._used.shift();
this._free.shift();
this._used.push(used);
this._free.push(free);
this._chart.update();
};
}

View file

@ -18,4 +18,13 @@ func main() {
if err != nil {
log.Fatalln(err)
}
err := vfsgen.Generate(distribyted.Assets, vfsgen.Options{
BuildTags: "release",
VariableName: "Templates",
PackageName: "distribyted",
})
if err != nil {
log.Fatalln(err)
}
}

View file

@ -9,8 +9,8 @@ type binaryFileSystem struct {
http.FileSystem
}
func NewBinaryFileSystem() *binaryFileSystem {
return &binaryFileSystem{Assets}
func NewBinaryFileSystem(fs http.FileSystem) *binaryFileSystem {
return &binaryFileSystem{fs}
}
func (fs *binaryFileSystem) Exists(prefix string, filepath string) bool {

View file

@ -3,6 +3,7 @@ package main
import (
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"syscall"
@ -19,6 +20,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/goccy/go-yaml"
"github.com/panjf2000/ants/v2"
"github.com/shurcooL/httpfs/html/vfstemplate"
)
func main() {
@ -71,7 +73,7 @@ func main() {
}
ss := stats.NewTorrent()
mountService := mount.NewTorrent(c, pool, ss)
mountService := mount.NewHandler(c, pool, ss)
sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
@ -96,15 +98,25 @@ func main() {
}
r := gin.Default()
fs := distribyted.NewBinaryFileSystem()
file, err := fs.Open("index.html")
assets := distribyted.NewBinaryFileSystem(distribyted.Assets)
r.Use(static.Serve("/assets", assets))
t, err := vfstemplate.ParseGlob(distribyted.Templates, nil, "*")
if err != nil {
log.Println("PUES SI QUE NO ESTá", err)
} else {
log.Println("FILE", file)
log.Fatal(err)
}
r.Use(static.Serve("/", fs))
r.SetHTMLTemplate(t)
// r.LoadHTMLGlob("templates/*")
r.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
r.GET("/routes", func(c *gin.Context) {
c.HTML(http.StatusOK, "routes.html", ss.RoutesStats())
})
r.GET("/api/status", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
@ -113,17 +125,12 @@ func main() {
"cacheCapacity": fc.Info().Capacity / 1024 / 1024,
"poolCap": pool.Cap(),
"poolFree": pool.Free(),
"torrentStats": ss.Global(),
"torrentStats": ss.GlobalStats(),
})
})
r.GET("/api/status/:torrent", func(ctx *gin.Context) {
hash := ctx.Param("torrent")
stats, err := ss.Torrent(hash)
if err != nil {
ctx.AbortWithError(404, err)
}
r.GET("/api/routes", func(ctx *gin.Context) {
stats := ss.RoutesStats()
ctx.JSON(200, stats)
})

View file

@ -2,13 +2,17 @@
max-cache-size: 1024
metadata-folder-name: ./_DATA/metadata
mountPoints:
- path: ./_DATA/snes
magnets:
- uri: "magnet:?xt=urn:btih:3F8CA07909879B3102EFE1CD43E233C48E489519&dn=677+Super+Nintendo+%2F+SNES+ROMs&tr=udp%3A%2F%2Ftracker.ccc.se%3A80&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"
- path: ./_DATA/nes
magnets:
- uri: "magnet:?xt=urn:btih:54F1FFB2861EE322B8641CEE232538A357341578&dn=706+Nintendo+%2F+NES+ROMs&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Ffr33domtracker.h33t.com%3A3310%2Fannounce&tr=udp%3A%2F%2Ftracker.istole.it%3A80%2Fannounce&tr=http%3A%2F%2Finferno.demonoid.me%3A3417%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"
- path: ./_DATA/psx
magnets:
# - uri: "magnet:?xt=urn:btih:3D41D4E6024AA4AB905BF0E6354D57F680C654F3&dn=Sony%20PlayStation%20%28PAL%29%20%5bRedump%5d&tr=http%3a%2f%2fbt2.t-ru.org%2fann%3fmagnet"
- uri: "magnet:?xt=urn:btih:852299c530aaed8fa06bdf32d9bd909e0bb76fe7&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337"
torrents:
- magnetUri: "magnet:?xt=urn:btih:3D41D4E6024AA4AB905BF0E6354D57F680C654F3&dn=Sony%20PlayStation%20%28PAL%29%20%5bRedump%5d&tr=http%3a%2f%2fbt2.t-ru.org%2fann%3fmagnet"
- magnetUri: "magnet:?xt=urn:btih:852299c530aaed8fa06bdf32d9bd909e0bb76fe7&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337"
- path: ./_DATA/wiki
torrents:
- torrentPath: "/home/ajnavarro/Downloads/347B211B61AC09B07A7747C85D24CA7FBD5355C1.torrent"
- path: ./_DATA/snes
torrents:
- magnetUri: "magnet:?xt=urn:btih:3F8CA07909879B3102EFE1CD43E233C48E489519&dn=677+Super+Nintendo+%2F+SNES+ROMs&tr=udp%3A%2F%2Ftracker.ccc.se%3A80&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=http%3A%2F%2Ftracker.ccc.de%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"
- path: ./_DATA/nes
torrents:
- magnetUri: "magnet:?xt=urn:btih:54F1FFB2861EE322B8641CEE232538A357341578&dn=706+Nintendo+%2F+NES+ROMs&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80%2Fannounce&tr=udp%3A%2F%2Ffr33domtracker.h33t.com%3A3310%2Fannounce&tr=udp%3A%2F%2Ftracker.istole.it%3A80%2Fannounce&tr=http%3A%2F%2Finferno.demonoid.me%3A3417%2Fannounce&tr=udp%3A%2F%2Ftracker.zer0day.to%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Fcoppersurfer.tk%3A6969%2Fannounce"

View file

@ -9,11 +9,12 @@ type Root struct {
}
type MountPoint struct {
Path string `yaml:"path"`
Magnets []struct {
URI string `yaml:"uri"`
FolderName string `yaml:"folderName,omitempty"`
} `yaml:"magnets"`
Path string `yaml:"path"`
Torrents []struct {
MagnetURI string `yaml:"magnetUri"`
TorrentPath string `yaml:"torrentPath"`
FolderName string `yaml:"folderName,omitempty"`
} `yaml:"torrents"`
}
func AddDefaults(r *Root) *Root {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,25 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
<div class="container">
<canvas id="chart-general-network"></canvas>
<canvas id="chart-file-chunks"></canvas>
</div>
<script src="js/main.js"></script>
</body>
</html>

View file

@ -1,165 +0,0 @@
import Humanize from './humanize.js';
import Chart from 'chart.js';
import _ from 'chartjs-plugin-stacked100';
var ctx = document.getElementById('chart-general-network').getContext('2d');
var downloadData = [];
var uploadData = [];
var labels = [];
var chart = new Chart(ctx, {
type: 'line',
labels: labels,
data: {
datasets: [
{
label: 'Download Speed',
fill: false,
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1,
data: downloadData,
},
{
label: 'Upload Speed',
fill: false,
borderWidth: 1,
data: uploadData,
},
]
},
options: {
title: {
text: 'Download and Upload speed'
},
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: 'Date'
},
type: 'time',
time: {
// parser: timeFormat,
tooltipFormat: 'll HH:mm'
},
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: 'value'
},
type: 'linear',
ticks: {
userCallback: function (tick) {
return Humanize.bytes(tick, 1024) + "/s";
},
beginAtZero: true
},
}]
},
}
});
var piecesData = []
var fileChart = new Chart(document.getElementById("chart-file-chunks"), {
type: "horizontalBar",
data: {
labels: ["File"],
datasets: piecesData,
},
options: {
animation: {
duration: 0
},
plugins: {
stacked100: { enable: true }
}
}
});
setInterval(function () {
fetch('/api/status/852299c530aaed8fa06bdf32d9bd909e0bb76fe7')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
console.log('Error getting data from server. Response: ' + response.status);
}
}).then(function (stats) {
piecesData.length = 0;
stats.PieceChunks.forEach(element => {
var label, color;
switch (element.Status) {
case "H":
label = "checking";
color = "#8a5999";
break;
case "P":
label = "partial";
color = "#be9600";
break;
case "C":
label = "complete";
color = "#208f09";
break;
case "W":
label = "waiting";
color = "#8a5999";
break;
case "?":
label = "error";
color = "#ff5f5c";
break;
default:
label = "unknown";
color = "gray";
break;
}
piecesData.push({
label: label,
data: [element.NumPieces],
backgroundColor: color,
});
});
fileChart.update();
})
}, 2000)
setInterval(function () {
fetch('/api/status')
.then(function (response) {
if (response.ok) {
return response.json();
} else {
console.log('Error getting data from server. Response: ' + response.status);
}
}).then(function (stats) {
if (downloadData.length > 20) {
uploadData.shift();
downloadData.shift();
labels.shift();
}
var date = new Date();
downloadData.push({
x: date,
y: stats.torrentStats.DownloadedBytes / stats.torrentStats.TimePassed,
});
uploadData.push({
x: date,
y: stats.torrentStats.UploadedBytes / stats.torrentStats.TimePassed,
});
labels.push(date);
chart.update();
})
.catch(function (error) {
console.log('Error getting status info: ' + error.message);
});
}, 2000)

File diff suppressed because it is too large Load diff

View file

@ -1,20 +0,0 @@
{
"name": "distribyted",
"version": "0.1.0",
"description": "",
"dependencies": {
"bootstrap": "^4.4.1",
"bootstrap-vue": "^2.14.0",
"chart.js": "^2.9.3",
"chartjs-plugin-stacked100": "^0.7.1",
"portal-vue": "^2.1.7",
"vue": "^2.6.11"
},
"scripts": {
"dev": "parcel watch index.html --out-dir ../assets",
"build": "parcel build index.html"
},
"devDependencies": {
"parcel-bundler": "^1.12.4"
}
}

View file

@ -10,7 +10,6 @@ require (
github.com/anacrolix/missinggo/v2 v2.4.1-0.20200227072623-f02f6484f997
github.com/anacrolix/multiless v0.0.0-20200413040533-acfd16f65d5d // indirect
github.com/anacrolix/torrent v1.15.2
github.com/dustin/go-humanize v1.0.0
github.com/elliotchance/orderedmap v1.2.2 // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2
@ -25,6 +24,7 @@ require (
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/panjf2000/ants/v2 v2.3.1
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/tinylib/msgp v1.1.2 // indirect
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f // indirect

View file

@ -310,6 +310,8 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=

View file

@ -1,6 +1,7 @@
package mount
import (
"fmt"
"log"
"os"
@ -13,7 +14,7 @@ import (
"github.com/panjf2000/ants/v2"
)
type Torrent struct {
type Handler struct {
c *torrent.Client
s *stats.Torrent
opts *fs.Options
@ -22,8 +23,8 @@ type Torrent struct {
servers map[string]*fuse.Server
}
func NewTorrent(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Torrent {
return &Torrent{
func NewHandler(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Handler {
return &Handler{
c: c,
s: s,
opts: &fs.Options{},
@ -32,17 +33,31 @@ func NewTorrent(c *torrent.Client, pool *ants.Pool, s *stats.Torrent) *Torrent {
}
}
func (s *Torrent) Mount(mpc *config.MountPoint) error {
func (s *Handler) Mount(mpc *config.MountPoint) error {
var torrents []*torrent.Torrent
for _, magnet := range mpc.Magnets {
t, err := s.c.AddMagnet(magnet.URI)
for _, mpcTorrent := range mpc.Torrents {
var t *torrent.Torrent
var err error
switch {
case mpcTorrent.MagnetURI != "":
t, err = s.c.AddMagnet(mpcTorrent.MagnetURI)
break
case mpcTorrent.TorrentPath != "":
t, err = s.c.AddTorrentFromFile(mpcTorrent.TorrentPath)
break
default:
err = fmt.Errorf("no magnet URI or torrent path provided")
}
if err != nil {
return err
}
log.Println("getting torrent info", t.Name())
<-t.GotInfo()
s.s.Add(t)
s.s.Add(mpc.Path, t)
log.Println("torrent info obtained", t.Name())
torrents = append(torrents, t)
@ -65,7 +80,7 @@ func (s *Torrent) Mount(mpc *config.MountPoint) error {
return nil
}
func (s *Torrent) Close() {
func (s *Handler) Close() {
for path, server := range s.servers {
log.Println("unmounting", path)
err := server.Unmount()

57
torrent/package-lock.json generated Normal file
View file

@ -0,0 +1,57 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"chart.js": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz",
"integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==",
"requires": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"chartjs-color": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
"requires": {
"chartjs-color-string": "^0.6.0",
"color-convert": "^1.9.3"
}
},
"chartjs-color-string": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
"requires": {
"color-name": "^1.0.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"requires": {
"color-name": "1.1.3"
},
"dependencies": {
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
}
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"moment": {
"version": "2.25.3",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz",
"integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg=="
}
}
}

View file

@ -2,12 +2,10 @@ package stats
import (
"errors"
"fmt"
"log"
"time"
"github.com/anacrolix/torrent"
"github.com/dustin/go-humanize"
)
var ErrTorrentNotFound = errors.New("torrent not found")
@ -23,39 +21,29 @@ const (
)
type PieceChunk struct {
Status PieceStatus
NumPieces int
Status PieceStatus `json:"status"`
NumPieces int `json:"numPieces"`
}
type TorrentStats struct {
DownloadedBytes int64
UploadedBytes int64
TimePassed float64
PieceChunks []*PieceChunk
Name string `json:"name"`
Hash string `json:"hash"`
DownloadedBytes int64 `json:"downloadedBytes"`
UploadedBytes int64 `json:"uploadedBytes"`
TimePassed float64 `json:"timePassed"`
PieceChunks []*PieceChunk `json:"pieceChunks"`
TotalPieces int `json:"totalPieces"`
}
type GlobalTorrentStats struct {
DownloadedBytes int64
UploadedBytes int64
TimePassed float64
DownloadedBytes int64 `json:"downloadedBytes"`
UploadedBytes int64 `json:"uploadedBytes"`
TimePassed float64 `json:"timePassed"`
}
func (s *GlobalTorrentStats) speed(bytes int64) float64 {
var bs float64
t := s.TimePassed
if t != 0 {
bs = float64(bytes) / t
}
return bs
}
func (s *GlobalTorrentStats) DownloadSpeed() string {
return fmt.Sprintf(" %s/s", humanize.IBytes(uint64(s.speed(s.DownloadedBytes))))
}
func (s *GlobalTorrentStats) UploadSpeed() string {
return fmt.Sprintf(" %s/s", humanize.IBytes(uint64(s.speed(s.UploadedBytes))))
type RouteStats struct {
Name string `json:"name"`
TorrentStats []*TorrentStats `json:"torrentStats"`
}
type stats struct {
@ -67,26 +55,31 @@ type stats struct {
}
type Torrent struct {
torrents map[string]*torrent.Torrent
previousStats map[string]*stats
torrents map[string]*torrent.Torrent
torrentsByRoute map[string][]*torrent.Torrent
previousStats map[string]*stats
gTime time.Time
}
func NewTorrent() *Torrent {
return &Torrent{
gTime: time.Now(),
torrents: make(map[string]*torrent.Torrent),
previousStats: make(map[string]*stats),
gTime: time.Now(),
torrents: make(map[string]*torrent.Torrent),
torrentsByRoute: make(map[string][]*torrent.Torrent),
previousStats: make(map[string]*stats),
}
}
func (s *Torrent) Add(t *torrent.Torrent) {
func (s *Torrent) Add(route string, t *torrent.Torrent) {
s.torrents[t.InfoHash().String()] = t
s.previousStats[t.InfoHash().String()] = &stats{}
tbr := s.torrentsByRoute[route]
s.torrentsByRoute[route] = append(tbr, t)
}
func (s *Torrent) Torrent(hash string) (*TorrentStats, error) {
func (s *Torrent) Stats(hash string) (*TorrentStats, error) {
t, ok := s.torrents[hash]
if !(ok) {
return nil, ErrTorrentNotFound
@ -97,16 +90,27 @@ func (s *Torrent) Torrent(hash string) (*TorrentStats, error) {
return s.stats(now, t, true), nil
}
func (s *Torrent) List() []string {
var result []string
for hash := range s.torrents {
result = append(result, hash)
func (s *Torrent) RoutesStats() []*RouteStats {
now := time.Now()
var out []*RouteStats
for r, tl := range s.torrentsByRoute {
var tStats []*TorrentStats
for _, t := range tl {
ts := s.stats(now, t, true)
tStats = append(tStats, ts)
}
rs := &RouteStats{
Name: r,
TorrentStats: tStats,
}
out = append(out, rs)
}
return result
return out
}
func (s *Torrent) Global() *GlobalTorrentStats {
func (s *Torrent) GlobalStats() *GlobalTorrentStats {
now := time.Now()
var totalDownload int64
@ -155,7 +159,7 @@ func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *Torrent
}
ts.TimePassed = now.Sub(prev.time).Seconds()
var totalPieces int
if chunks {
var pch []*PieceChunk
for _, psr := range t.PieceStateRuns() {
@ -177,10 +181,14 @@ func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *Torrent
Status: s,
NumPieces: psr.Length,
})
totalPieces += psr.Length
}
ts.PieceChunks = pch
}
ts.Hash = t.InfoHash().String()
ts.Name = t.Name()
ts.TotalPieces = totalPieces
return ts
}

View file

@ -0,0 +1,7 @@
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="assets/js/bootstrap.bundle.min.js"></script>

View file

@ -0,0 +1,8 @@
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<title>{{.Title}}</title>

View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
{{template "common_header.html"}}
</head>
<body>
{{template "navbar.html"}}
<div class="container">
<div class="row">
<div class="col">
<canvas id="chart-general-network"></canvas>
</div>
</div>
<div class="row">
<div class="col-sm">
<canvas id="chart-cache" height="400"></canvas>
</div>
<div class="col-sm">
<h3>
<svg class="bi bi-arrow-down" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.646 9.646a.5.5 0 01.708 0L8 12.293l2.646-2.647a.5.5 0 01.708.708l-3 3a.5.5 0 01-.708 0l-3-3a.5.5 0 010-.708z"
clip-rule="evenodd" />
<path fill-rule="evenodd" d="M8 2.5a.5.5 0 01.5.5v9a.5.5 0 01-1 0V3a.5.5 0 01.5-.5z"
clip-rule="evenodd" />
</svg>
<span id="down-speed-text"></span>
</h3>
</div>
<div class="col-sm">
<h3>
<svg class="bi bi-arrow-up" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8 3.5a.5.5 0 01.5.5v9a.5.5 0 01-1 0V4a.5.5 0 01.5-.5z"
clip-rule="evenodd" />
<path fill-rule="evenodd"
d="M7.646 2.646a.5.5 0 01.708 0l3 3a.5.5 0 01-.708.708L8 3.707 5.354 6.354a.5.5 0 11-.708-.708l3-3z"
clip-rule="evenodd" />
</svg>
<span id="up-speed-text"></span>
</h3>
</div>
<div class="col-sm">
<canvas id="chart-workers" height="400"></canvas>
</div>
</div>
</div>
{{template "common_footer.html"}}
<script src="assets/js/Chart.bundle.min.js"></script>
<script src="assets/js/humanize.js"></script>
<script src="assets/js/general_chart.js"></script>
<script src="assets/js/single_bar_chart.js"></script>
<script src="assets/js/general.js"></script>
</body>
</html>

View file

@ -0,0 +1,17 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Distribyted</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">General</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/routes">Routes</a>
</li>
</ul>
</div>
</nav>

View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
{{template "common_header.html"}}
</head>
<body>
{{template "navbar.html"}}
<div class="container">
{{if not .}}
<div class="alert alert-warning" role="alert">
No routes found.
</div>
{{end}}
{{range .}}
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{.Name}}</h5>
{{range .TorrentStats}}
<div class="card shadow">
<div class="card-body">
<div class="row">
<div class="col">
<h5>{{.Name}}</h5>
</div>
<div class="col">
<p id="up-down-speed-text-{{.Hash}}">...</p>
</div>
<div class="col">
<div id="file-chunks-{{.Hash}}" class="progress">
</div>
</div>
</div>
</div>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
{{template "common_footer.html"}}
<script src="assets/js/humanize.js"></script>
<script src="assets/js/file_chunks.js"></script>
<script src="assets/js/routes.js"></script>
</body>
</body>
</html>