Compare commits
No commits in common. "c81ce67f92efa22212cce1339635cd9ab1a9f28c" and "d570a0c1ae0c9b8c7187048f0183a787eb0d9251" have entirely different histories.
c81ce67f92
...
d570a0c1ae
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
sync.sh
|
|
||||||
|
|
|
@ -3,11 +3,11 @@ It's a WebGUI for rTorrent.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
Python 3.7+
|
Python 3.7+
|
||||||
Python packages: `gunicorn aiohttp aiohttp_jinja2`
|
Python packages: `flask gunicorn`
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
1. Get on the floor
|
1. Get on the floor
|
||||||
2. Walk the dinosaur
|
2. Walk the dinosaur
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
`gunicorn aberrant:app --bind localhost:5250 --worker-class aiohttp.GunicornWebWorker`
|
`gunicorn -b localhost:5250 -e SCRIPT_NAME=/aberrant aberrant:app`
|
||||||
|
|
66
aberrant.py
66
aberrant.py
|
@ -3,63 +3,35 @@
|
||||||
The primary module for serving the Aberrant application.
|
The primary module for serving the Aberrant application.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
|
import signal
|
||||||
|
|
||||||
import jinja2
|
from flask import Flask, render_template, send_from_directory
|
||||||
import aiohttp_jinja2
|
|
||||||
from aiohttp import web, WSMsgType
|
|
||||||
from aiohttp_jinja2 import render_template
|
|
||||||
|
|
||||||
import config
|
|
||||||
import events
|
|
||||||
import rtorrent
|
import rtorrent
|
||||||
|
|
||||||
app = web.Application()
|
app = Flask(__name__)
|
||||||
app.on_shutdown.append(rtorrent.stop_watch)
|
app.jinja_env.trim_blocks = True
|
||||||
aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('templates'))
|
app.jinja_env.lstrip_blocks = True
|
||||||
|
app.jinja_env.undefined = "StrictUndefined"
|
||||||
rtorrent.init()
|
rtorrent.init()
|
||||||
|
signal.signal(signal.SIGINT, rtorrent.stop_watch)
|
||||||
|
|
||||||
routes = web.RouteTableDef()
|
@app.route("/")
|
||||||
|
def index():
|
||||||
@routes.get(config.prefix + "/", name='index')
|
|
||||||
async def index(request):
|
|
||||||
"""The index page."""
|
"""The index page."""
|
||||||
torrents = rtorrent.get_active()
|
torrents = rtorrent.get_active()
|
||||||
tracker_stats = rtorrent.get_stats()
|
tracker_stats = rtorrent.get_stats()
|
||||||
return render_template("index.html", request, locals())
|
return render_template("index.html", **locals())
|
||||||
|
|
||||||
@routes.get(config.prefix + "/get_active_torrents", name='active-torrents')
|
@app.route("/static/<path:path>")
|
||||||
async def get_active_torrents(request):
|
def send_static(path):
|
||||||
|
"""Sends static files."""
|
||||||
|
return send_from_directory("static", path)
|
||||||
|
|
||||||
|
@app.route("/get_active_torrents")
|
||||||
|
def get_active_torrents():
|
||||||
"""Returns all active torrents formatted as JSON."""
|
"""Returns all active torrents formatted as JSON."""
|
||||||
data = [vars(t) for t in rtorrent.get_active()]
|
return json.dumps([vars(t) for t in rtorrent.get_active()])
|
||||||
return web.json_response(data)
|
|
||||||
|
|
||||||
@routes.get(config.prefix + '/ws', name='ws')
|
|
||||||
async def websocket_handler(request):
|
|
||||||
"""The websocket endpoint."""
|
|
||||||
ws = web.WebSocketResponse()
|
|
||||||
ws_ready = ws.can_prepare(request)
|
|
||||||
if not ws_ready.ok:
|
|
||||||
return web.Response(text="Cannot start websocket.")
|
|
||||||
await ws.prepare(request)
|
|
||||||
|
|
||||||
async for msg in ws:
|
|
||||||
if msg.type == WSMsgType.TEXT:
|
|
||||||
try:
|
|
||||||
data = json.loads(msg.data)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
event = data.get('event')
|
|
||||||
if not event or event not in events.events.keys():
|
|
||||||
continue
|
|
||||||
|
|
||||||
await events.events[event](ws, data.get('data'))
|
|
||||||
else: # TODO: handle differnt message types properly
|
|
||||||
break
|
|
||||||
await ws.close()
|
|
||||||
return ws
|
|
||||||
|
|
||||||
app.router.add_routes(routes)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
aiohttp.web.run_app(app, host='0.0.0.0', port=5250)
|
app.run(host='0.0.0.0', port=5250)
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Configuation settings for Aberrant.
|
|
||||||
"""
|
|
||||||
prefix = '/aberrant'
|
|
18
events.py
18
events.py
|
@ -1,18 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
WebSocket events.
|
|
||||||
"""
|
|
||||||
import types
|
|
||||||
|
|
||||||
import rtorrent
|
|
||||||
|
|
||||||
async def active_torrents(ws, data):
|
|
||||||
"""Returns active torrent information."""
|
|
||||||
data = [vars(t) for t in rtorrent.get_active()]
|
|
||||||
res = {'event': 'active_torrents', 'data': data}
|
|
||||||
await ws.send_json(res)
|
|
||||||
|
|
||||||
events = {}
|
|
||||||
for obj in dir():
|
|
||||||
if type(locals()[obj]) == types.FunctionType:
|
|
||||||
events[locals()[obj].__name__] = locals()[obj]
|
|
|
@ -153,7 +153,7 @@ def init():
|
||||||
WATCH_HANDLE = Watch()
|
WATCH_HANDLE = Watch()
|
||||||
WATCH_HANDLE.start()
|
WATCH_HANDLE.start()
|
||||||
|
|
||||||
async def stop_watch(*args, **kwargs):
|
def stop_watch(*args, **kwargs):
|
||||||
"""Stops the watch thread."""
|
"""Stops the watch thread."""
|
||||||
global WATCH_HANDLE
|
global WATCH_HANDLE
|
||||||
WATCH_HANDLE.stop()
|
WATCH_HANDLE.stop()
|
||||||
|
|
|
@ -14,21 +14,15 @@ tr {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
#torrents td {
|
.name {
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totalSize, .state, .downrate, .downPercent, .eta, .uprate, .tracker, .rt_id {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 10%;
|
width: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#torrents .name {
|
|
||||||
padding-left: 0.5em;
|
|
||||||
text-align: left;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#torrents .down_percent:after {
|
|
||||||
content: "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
#tracker_stats {
|
#tracker_stats {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
function load() {
|
function load() {
|
||||||
//let intervalID = window.setInterval(get_active_torrents_ajax, 20000);
|
|
||||||
let intervalID = window.setInterval(get_active_torrents, 20000);
|
let intervalID = window.setInterval(get_active_torrents, 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_active_torrents_ajax() {
|
function get_active_torrents() {
|
||||||
let httpRequest;
|
let httpRequest;
|
||||||
httpRequest = new XMLHttpRequest();
|
httpRequest = new XMLHttpRequest();
|
||||||
httpRequest.onreadystatechange = function() {
|
httpRequest.onreadystatechange = function() {
|
||||||
|
@ -14,14 +13,14 @@ function get_active_torrents_ajax() {
|
||||||
for (let i = 0; i < torrents.length; i++) {
|
for (let i = 0; i < torrents.length; i++) {
|
||||||
html_str += '<tr>'
|
html_str += '<tr>'
|
||||||
html_str += '<td class="name">' + torrents[i].name + '</td>';
|
html_str += '<td class="name">' + torrents[i].name + '</td>';
|
||||||
html_str += '<td class="total_size_str">' + torrents[i].total_size_str + '</td>';
|
html_str += '<td class="totalSize">' + torrents[i].total_size_str + '</td>';
|
||||||
html_str += '<td class="state">' + torrents[i].state + '</td>';
|
html_str += '<td class="state">' + torrents[i].state + '</td>';
|
||||||
html_str += '<td class="downrate_str">' + torrents[i].downrate_str + '</td>';
|
html_str += '<td class="downrate">' + torrents[i].downrate_str + '</td>';
|
||||||
html_str += '<td class="down_percent">' + torrents[i].down_percent + '%</td>';
|
html_str += '<td class="downPercent">' + torrents[i].down_percent + '%</td>';
|
||||||
html_str += '<td class="eta_str">' + torrents[i].eta_str + '</td>';
|
html_str += '<td class="eta">' + torrents[i].eta_str + '</td>';
|
||||||
html_str += '<td class="uprate_str">' + torrents[i].uprate_str + '</td>';
|
html_str += '<td class="uprate">' + torrents[i].uprate_str + '</td>';
|
||||||
html_str += '<td class="tracker">' + torrents[i].tracker + '</td>';
|
html_str += '<td class="tracker">' + torrents[i].tracker + '</td>';
|
||||||
html_str += '<td class="rtorrent_id">' + torrents[i].rtorrent_id + '</td>';
|
html_str += '<td class="rt_id">' + torrents[i].rtorrent_id + '</td>';
|
||||||
html_str += '</tr>';
|
html_str += '</tr>';
|
||||||
}
|
}
|
||||||
document.getElementById('torrents').children[1].innerHTML = html_str;
|
document.getElementById('torrents').children[1].innerHTML = html_str;
|
||||||
|
@ -29,49 +28,3 @@ function get_active_torrents_ajax() {
|
||||||
httpRequest.open('GET', get_torrents_uri, true);
|
httpRequest.open('GET', get_torrents_uri, true);
|
||||||
httpRequest.send();
|
httpRequest.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
var socket = new WebSocket('wss://' + window.location.hostname + ws_uri);
|
|
||||||
socket.oldSend = socket.send;
|
|
||||||
socket.send = function(event_title, data) {
|
|
||||||
data = JSON.stringify({event: event_title, data: data});
|
|
||||||
socket.oldSend.apply(this, [data]);
|
|
||||||
}
|
|
||||||
socket.events = {};
|
|
||||||
socket.onmessage = function(e) {
|
|
||||||
let data = JSON.parse(e.data);
|
|
||||||
let event = data.event;
|
|
||||||
data = data.data;
|
|
||||||
if (socket.events[event] === undefined) { return; }
|
|
||||||
socket.events[event](data);
|
|
||||||
}
|
|
||||||
socket.onclose = function(e) {
|
|
||||||
console.log('WebSocket lost connection to server. Re-trying...');
|
|
||||||
// TODO: reconnect
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Websocket receive */
|
|
||||||
socket.events['active_torrents'] = function(data) {
|
|
||||||
let table = document.querySelector('#torrents tbody');
|
|
||||||
while (table.firstChild) {
|
|
||||||
table.removeChild(table.firstChild);
|
|
||||||
}
|
|
||||||
data.forEach(function(torrent) {
|
|
||||||
let template = document.querySelector('#torrent_template');
|
|
||||||
let node = document.importNode(template.content, true);
|
|
||||||
for (let field of node.children[0].children) {
|
|
||||||
field.textContent = torrent[field.className];
|
|
||||||
}
|
|
||||||
table.appendChild(node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
socket.events['tracker_stats'] = function(data) {
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Websocket send */
|
|
||||||
function get_active_torrents() {
|
|
||||||
socket.send('active_torrents', {});
|
|
||||||
}
|
|
||||||
function get_tracker_stats() {
|
|
||||||
socket.send('tracker_stats', {});
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,12 +2,11 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Aberrant</title>
|
<title>Aberrant</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/static/aberrant.css">
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='aberrant.css') }}">
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='aberrant.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
const get_torrents_uri = "{{ request.app.router['active-torrents'].url_for() }}";
|
const get_torrents_uri = "{{ url_for('get_active_torrents') }}";
|
||||||
const ws_uri = "{{ request.app.router['ws'].url_for() }}";
|
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript" src="/static/aberrant.js"></script>
|
|
||||||
<script>window.onload = load;</script>
|
<script>window.onload = load;</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -15,45 +14,32 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="name">Name</th>
|
<th class="name">Name</th>
|
||||||
<th class="total_size_str">Size</th>
|
<th class="sizeTotal">Size</th>
|
||||||
<th class="state">State</th>
|
<th class="state">State</th>
|
||||||
<th class="downrate_str">DL</th>
|
<th class="downrate">DL</th>
|
||||||
<th class="down_percent"></th>
|
<th class="downPercent">%</th>
|
||||||
<th class="eta_str">ETA</th>
|
<th class="eta">ETA</th>
|
||||||
<th class="uprate_str">UL</th>
|
<th class="uprate">UL</th>
|
||||||
<th class="tracker">Tracker</th>
|
<th class="tracker">Tracker</th>
|
||||||
<th class="rtorrent_id">RT id</th>
|
<th class="rt_id">RT id</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for torrent in torrents %}
|
{% for torrent in torrents %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="name">{{ torrent.name }}</td>
|
<td class="name">{{ torrent.name }}</td>
|
||||||
<td class="total_size_str">{{ torrent.total_size_str }}</td>
|
<td class="totalSize">{{ torrent.total_size_str }}</td>
|
||||||
<td class="state">{{ torrent.state }}</td>
|
<td class="state">{{ torrent.state }}</td>
|
||||||
<td class="downrate_str">{{ torrent.downrate_str }}</td>
|
<td class="downrate">{{ torrent.downrate_str }}</td>
|
||||||
<td class="down_percent">{{ torrent.down_percent }}</td>
|
<td class="downPercent">{{ torrent.down_percent }}%</td>
|
||||||
<td class="eta_str">{{ torrent.eta_str }}</td>
|
<td class="eta">{{ torrent.eta_str }}</td>
|
||||||
<td class="uprate_str">{{ torrent.uprate_str }}</td>
|
<td class="uprate">{{ torrent.uprate_str }}</td>
|
||||||
<td class="tracker">{{ torrent.tracker }}</td>
|
<td class="tracker">{{ torrent.tracker }}</td>
|
||||||
<td class="rtorrent_id">{{ torrent.rtorrent_id }}</td>
|
<td class="rt_id">{{ torrent.rtorrent_id }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<template id="torrent_template">
|
|
||||||
<tr>
|
|
||||||
<td class="name"></td>
|
|
||||||
<td class="total_size_str"></td>
|
|
||||||
<td class="state"></td>
|
|
||||||
<td class="downrate_str"></td>
|
|
||||||
<td class="down_percent"></td>
|
|
||||||
<td class="eta_str"></td>
|
|
||||||
<td class="uprate_str"></td>
|
|
||||||
<td class="tracker"></td>
|
|
||||||
<td class="rtorrent_id"></td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
<br>
|
<br>
|
||||||
<table id="tracker_stats">
|
<table id="tracker_stats">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user