All pastes #1232173 Raw Edit

Miscellany

public text v1 · immutable
#1232173 ·published 2008-10-20 20:32 UTC
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">&nbsp;
		<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()