rendered paste body#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# rtorstat.py - A simple rtorrent status web page generator.
#
# Project: https://www.codetrax.org/projects/rtorstat
#
# Copyright 2007 George Notaras <gnot [at] g-loaded.eu>, CodeTRAX.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
__version__ = "0.4.1"
__codestatus__ = "stable"
import sys
import os.path
import glob
import datetime
import time
from string import Template
# Requirement
try:
from bencode import bdecode
except ImportError:
try:
from BitTorrent.bencode import bdecode
except ImportError:
sys.stderr.write("%s: ERROR: Missing dependency\nbencode.py was not found in current directory or in local BitTorrent Client (Mainline) installation.\n" % sys.argv[0])
sys.stderr.flush()
sys.exit(1)
TORRENT_ENTRY = """
<div class="torrent">
@COUNT@. <strong>%(name)s</strong> (<em>%(status)s</em>)
<div class="status">
<div class="downloaded" style="width:%(complete)s%%">
%(complete)s%%
</div>
</div>
size: %(size)s - down: %(downsize)s - up: %(upsize)s - ratio: %(ratio)s - peers: %(peers)s
</div>
"""
status_page = Template("""<html>
<head>
<META NAME="GOOGLEBOT" CONTENT="NOARCHIVE">
<META NAME="ROBOTS" CONTENT="NOINDEX,NOFOLLOW">
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
<META HTTP-EQUIV="EXPIRES" CONTENT="Mon, 22 Jul 2000 01:01:01 GMT">
<title>rTorrent Status</title>
<style type="text/css"><!--
html {
background-color: white;
color: black;
font-family: Verdana, Arial, "DejaVu Sans", "Liberation Sans", Helvetica, sans-serif;
font-size: 1em;
}
body {
font-size: 0.8125em;
line-height: 1.25em;
margin: 2em 4em;
padding: 2em;
}
h2 {
margin-top: 30px;
}
.torrent {
margin: 15px 0;
padding: 0 10px;
}
.status {
background: red;
color: red;
font-size: 1em;
width: 200px;
margin: 0;
padding: 0;
}
.downloaded {
float: left;
background: green;
color: white;
font-size: 1em;
text-align: right;
margin: 0;
margin-top: -1.25em;
padding: 0;
}
--></style>
</head>
<body>
<h1>rTorrent Status</h1>
<p>${pagehead}</p>
<h2>Started/Stopped Torrents</h2>
<div>
${torstarted}
</div>
<h2>Finished Torrents</h2>
<div>
${torfinished}
</div>
<div style="width:200px;">
<hr />
<em><span style="font-size:80%;">Created with rtorstat v${version}</span></em>
</div>
</body>
<head>
<META NAME="GOOGLEBOT" CONTENT="NOARCHIVE">
<META NAME="ROBOTS" CONTENT="NOINDEX,NOFOLLOW">
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
<META HTTP-EQUIV="EXPIRES" CONTENT="Mon, 22 Jul 2000 01:01:01 GMT">
</head>
</html>
""")
def sz(size):
if size < 1024**2:
return "%.2f Kb" % (float(size)/float(1024))
else:
if size < 1024**3:
return "%.2f MB" % (float(size)/float(1024**2))
else:
return "%.2f GB" % (float(size)/float(1024**3))
def ignores(ignores_orders):
if ignores_orders:
return "(IGNORES ORDERS)"
return ""
def get_status(torrent, current_time, last_session_save_time):
f = open(torrent, "rb")
t_dict = bdecode(f.read())
f.close()
t_name = t_dict['info']['name']
try:
t_size = sum(int(tfile['length']) for tfile in t_dict['info']['files'])
except:
t_size = t_dict['info']['length'] # when there is only one file in the torrent
t_chunk_len = t_dict['info']['piece length']
total_downloaded = t_dict['rtorrent']['chunks_done'] * t_chunk_len
total_uploaded = t_dict['rtorrent']['total_uploaded']
state_changed = int(t_dict['rtorrent']['state_changed'])
t_state = t_dict['rtorrent']['state']
t_complete = t_dict['rtorrent']['complete'] # 1/0
t_ignores_orders = t_dict['rtorrent']['ignore_commands'] # 1/0
# Get Active Peers
# In order a peer to be characterized as active, the time, which rTorrent
# stores as the time of the last data exchange between the local client
# and the peer, must not be older than the time the session file had
# been saved.
t_peers = t_dict["libtorrent_resume"]["peers"]
peer_last_transaction_times = [int(peer["last"]) for peer in t_peers]
if peer_last_transaction_times:
most_recent_transaction_time = max(peer_last_transaction_times)
oldest_allowed_transaction_time = current_time - last_session_save_time
if most_recent_transaction_time < oldest_allowed_transaction_time:
t_active_peers = 0
else:
t_active_peers = peer_last_transaction_times.count( most_recent_transaction_time )
else:
t_active_peers = 0
share_ratio = 0
complete_percent = 0
if total_uploaded > 0 and total_downloaded > 0:
share_ratio = float(total_uploaded)/float(total_downloaded)
if not t_complete and total_downloaded > 0:
complete_percent = (float(total_downloaded)/float(t_size))*100
if t_complete:
complete_percent = 100
total_downloaded = t_size
# Set state
if t_complete:
if t_active_peers:
ret_status = "SEEDING"
else:
ret_status = "FINISHED"
elif t_state:
ret_status = "STARTED"
else:
ret_status = "STOPPED"
return ret_status, TORRENT_ENTRY % {
"name" : t_name,
"status" : ret_status,
"complete" : int(complete_percent),
"size" : sz(t_size),
"downsize" : sz(total_downloaded),
"upsize" : sz(total_uploaded),
"ratio" : "%.2f" % share_ratio,
"peers" : int(t_active_peers),
}
def add_num_to_list_items(mylist):
for k in range(len(mylist)):
mylist[k] = mylist[k].replace("@COUNT@", str(k+1))
return mylist
def usage():
sys.stderr.write("Usage:\n%s /path/to/session/dir/\n\n" % os.path.basename(sys.argv[0]))
sys.stderr.flush()
sys.exit(1)
def process_torrent_sessions(session_dir, cgi_headers=False):
pagehead = []
started_list = []
finished_list = []
current_time = int(time.time())
session_list = glob.glob(os.path.join(session_dir, "*.torrent"))
# Process saved session files
# Also determine the time session files were last saved by getting the
# modification time of the first session file.
last_session_save_time = 0
session_file_mtime = 0
for torrent in session_list:
if not last_session_save_time:
session_file_mtime = int(os.stat(torrent).st_mtime)
last_session_save_time = current_time - session_file_mtime
# Get torrent status
tor_status, tor_text = get_status(torrent, current_time, last_session_save_time)
if tor_status in ("FINISHED", "SEEDING"):
finished_list.append(tor_text)
else:
started_list.append(tor_text)
# (TODO) Better ordering of torrents based on their state
pagehead.append("Last Update: %s\n" % datetime.datetime.fromtimestamp(session_file_mtime).strftime("%a, %b %d %Y, %H:%M:%S"))
pagehead = "\n".join(pagehead)
if started_list:
started_list = add_num_to_list_items(started_list)
torstarted = "\n".join(started_list)
else:
torstarted = "N/A"
if finished_list:
finished_list = add_num_to_list_items(finished_list)
torfinished = "\n".join(finished_list)
else:
torfinished = "N/A"
page = status_page.substitute( pagehead = pagehead,
torstarted = torstarted,
torfinished = torfinished,
version = "%s/%s" % (__version__, __codestatus__) )
if cgi_headers:
sys.stdout.write("Content-type: text/html\nContent-Length: %s\n\n" % len(page))
sys.stdout.write(page)
sys.stdout.flush()
def main():
if "-h" in sys.argv or "--help" in sys.argv:
usage()
elif len(sys.argv) == 2:
session_dir = sys.argv[1]
if not os.path.exists(session_dir) or not os.path.isdir(session_dir):
sys.stderr.write("ERROR: session path does not exist or is not a directory.\n\n")
sys.stderr.flush()
usage()
else:
usage()
process_torrent_sessions(session_dir)
if __name__ == '__main__':
main()