Compare commits

..

No commits in common. "c81ce67f92efa22212cce1339635cd9ab1a9f28c" and "d570a0c1ae0c9b8c7187048f0183a787eb0d9251" have entirely different histories.

9 changed files with 49 additions and 168 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
__pycache__/ __pycache__/
*.swp *.swp
*.swo *.swo
sync.sh

View File

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

View File

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

View File

@ -1,5 +0,0 @@
#!/usr/bin/env python3
"""
Configuation settings for Aberrant.
"""
prefix = '/aberrant'

View File

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

View File

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

View File

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

View File

@ -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', {});
}

View File

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