#! /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 , 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 = """
@COUNT@. %(name)s (%(status)s)
 
%(complete)s%%
size: %(size)s - down: %(downsize)s - up: %(upsize)s - ratio: %(ratio)s - peers: %(peers)s
""" status_page = Template(""" rTorrent Status

rTorrent Status

${pagehead}

Started/Stopped Torrents

${torstarted}

Finished Torrents

${torfinished}

Created with rtorstat v${version}
""") 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()