From b2277e54f631917093f7fe9f6458366c04629982 Mon Sep 17 00:00:00 2001 From: Mark Hoekveen Date: Wed, 18 Dec 2024 19:53:24 +0100 Subject: [PATCH] adds tclean For cleaning torrent files from transmission. Needs some more work. --- bin/tclean | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100755 bin/tclean diff --git a/bin/tclean b/bin/tclean new file mode 100755 index 0000000..f2d6a81 --- /dev/null +++ b/bin/tclean @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 + +import subprocess +import argparse +from datetime import datetime + +torrents = [] + +def get_torrent_list(args): + try: + process = subprocess.run(['transmission-remote', f'{args.host}:{args.port}', '-l'], capture_output=True, text=True, check=True) + return process.stdout.splitlines() + except subprocess.CalledProcessError as e: + print(f"Command failed with return code {e.returncode}") + print(f"Error output: {e.stderr}") + exit() + +def parse_torrent_list(list): + id = 0 + for line in list: + if id == 0: + id += 1 + continue + data = [x.strip() for x in line.split(" ") if x] + print(line) + + if "Sum" in data[0]: + continue + + torrent = { + "id": id, + "size": data[2], + "ratio": data[6], + "name": data[8] + } + print(torrent) + torrents.append(torrent) + id += 1 + +def add_torrent_info(args): + try: + process = subprocess.run(['transmission-remote', f'{args.host}:{args.port}', '-t', 'all', '-i'], capture_output=True, text=True, check=True) + torrent_info = process.stdout.splitlines() + except subprocess.CalledProcessError as e: + print(f"Command failed with return code {e.returncode}") + print(f"Error output: {e.stderr}") + exit() + + id = 1 # Will be read from output. + current_torrent = None + for info in torrent_info: + try: + if "Id: " in info: + print(info) + id = int(info.split(": ")[1].strip()) + print(id) + if 0 <= id-1 < len(torrents): + current_torrent = torrents[id-1] + else: + current_torrent = None + continue + + if current_torrent is None: + continue + + if "Date added" in info: + date_str = info.split(": ")[1].strip() + current_torrent["date_added"] = datetime.strptime(date_str, "%a %b %d %H:%M:%S %Y") + elif "Latest activity" in info: + date_str = info.split(": ")[1].strip() + current_torrent["latest_activity"] = datetime.strptime(date_str, "%a %b %d %H:%M:%S %Y") + elif "Source" in info: + current_torrent["source"] = info.split(": ")[1].strip() + except (IndexError, ValueError, KeyError) as e: + print(f"Error processing torrent info: {e}") + continue + +def calculate_deletion_score(torrent): + """ + Calculate a deletion score where higher scores indicate better deletion candidates. + Factors considered: + - Size (larger = higher score) + - Age (older = higher score) + - Inactivity period (longer inactive = higher score) + - Ratio (lower = higher score, but with diminishing returns after 1.0) + """ + now = datetime.now() + #print(torrent) + # Convert size to GB for scoring (assuming size is in format like "1.5 GB" or "700 MB") + try: + size_str = torrent.get('size', '0 GB') + size_num = float(size_str.split()[0]) + if 'MB' in size_str: + size_gb = size_num / 1024 + else: + size_gb = size_num + except (ValueError, IndexError): + size_gb = 0 + + # Calculate age in days + try: + age_days = (now - torrent.get('date_added', now)).days + except (TypeError, AttributeError): + age_days = 0 + + # Calculate days since last activity + try: + inactive_days = (now - torrent.get('latest_activity', now)).days + except (TypeError, AttributeError): + inactive_days = 0 + + try: + ratio = float(torrent.get('ratio', '0')) + except ValueError: + ratio = 0 + + # Calculate individual scores + size_score = size_gb * 10 # points per GB + age_score = min(age_days / 7, 52) * 1 # 1 points per week, max 1 year + inactive_score = min(inactive_days / 7, 12) * 8 # 8 points per week inactive, max 12 weeks + ratio_score = 1000 * (1 - min(ratio, 1.0)) # 100 points at ratio 0, 0 points at ratio >= 1 + + # Combine scores + total_score = size_score + age_score + inactive_score + ratio_score + + return total_score + + +def get_deletion_candidates(torrents, limit=10): + """ + Sort torrents by deletion score and return the top candidates. + """ + scored_torrents = [(t, calculate_deletion_score(t)) for t in torrents] + sorted_torrents = sorted(scored_torrents, key=lambda x: x[1], reverse=True) + + # Print results + print("\nTop deletion candidates:") + print("-" * 120) + print(f"{'Score':>8} {'Size':>10} {'Ratio':>6} {'Age(d)':>7} {'Inactive(d)':>11} {'Size_pts':>9} {'Age_pts':>8} {'Inact_pts':>9} {'Ratio_pts':>9} {'Name':<30}") + print("-" * 120) + + for torrent, score in sorted_torrents[:limit]: + now = datetime.now() + age_days = (now - torrent['date_added']).days + inactive_days = (now - torrent['latest_activity']).days + + size_str = torrent['size'] + size_num = float(size_str.split()[0]) + if 'MB' in size_str: + size_gb = size_num / 1024 + else: + size_gb = size_num + + size_score = size_gb * 10 + age_score = min(age_days / 7, 52) * 1 + inactive_score = min(inactive_days / 7, 12) * 8 + ratio = float(torrent['ratio']) + ratio_score = 100 * (1 - min(ratio, 1.0)) + + print(f"{score:8.1f} {torrent['size']:>10} {torrent['ratio']:>6} {age_days:>7d} {inactive_days:>11d} {size_score:>9.1f} {age_score:>8.1f} {inactive_score:>9.1f} {ratio_score:>9.1f} {torrent['name'][:30]}") + + return [t[0] for t in sorted_torrents] + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--host', default='localhost', help='Transmission host') + parser.add_argument('--port', default='9091', help='Transmission port') + parser.add_argument('--num', default='10', help='Amount of candidates to show') + args = parser.parse_args() + + torrent_list = get_torrent_list(args) + parse_torrent_list(torrent_list) + add_torrent_info(args) + #print(torrents) + candidates = get_deletion_candidates(torrents, int(args.num)) + +if __name__ == "__main__": + main()