PDMameUpdate/PDMameUpdate.py

319 lines
10 KiB
Python
Executable File

#!/usr/bin/python3
'''
Checks available Torrents on PleasureDome
and updates the local versions if more recent
versions are detected
Basically what it does is:
* Get all torrents in a directory torrents
* Get all torrents from PleasureDome github
* Get all torrents currently active in Transmission
* Intersect the first two lists to get updatable torrents
* And for each updatable torrent:
- remove the old torrent from Transmission (if needed),
- rename the local directory,
- add the new torrent
Supported Torrents:
* MAME
* HBMAME
Work in progress…
* TODO: implement some error handling
Requirements:
* Transmission for Bitorrent
* A proper PDMameUpdate.json file (see PDMameUpdate.template.json)
* Python3 with the libraries below
- bs4
- packaging
- requests
- transmission-clutch
- tabulate
* Linux (untested on other OS, but it might work)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!! Provided with no warranty whatsoever. !!!
!!! Make sure you understand what the script !!!
!!! does and adapt it to your context !!!
!!! Use with caution. !!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
'''
import argparse
from bs4 import BeautifulSoup as bs
import json
import logging
import os
import re
from requests import get
import time
import pathlib
from clutch import Client
from tabulate import tabulate
from collections import defaultdict
from pprint import pformat
from urllib.parse import urlparse
from packaging import version
def open_config_file():
"""Reads configuration from PDMameUpdate.json file"""
locations = (
os.environ.get("PDMAMEUPDATE_CONF"),
"/etc",
os.path.expanduser("~"),
os.curdir
)
for loc in locations:
if loc is not None:
logging.debug("Searching for config file in '%s'", loc)
config_path = loc + '/PDMameUpdate.json'
if pathlib.Path(config_path).is_file():
config_file_loc = config_path
break
if 'config_file_loc' not in locals():
logging.error("Config file not found")
raise FileNotFoundError("Config file not found")
logging.info('Opening config file: '+config_file_loc)
config_file = None
with open(config_file_loc, 'r') as config_file:
config = json.load(config_file)
parameters = [
"mame-directory",
"transmission-password",
"transmission-port",
"transmission-user"
]
for key in parameters:
if key not in config:
logging.error('Missing key in config file: %s', key)
raise ValueError('Invalid config file.')
return config
def fetch_local_torrents():
"""Fetches local torrents versions"""
logging.info('Fetching current MAME versions')
for mdirectory in config['mame-directory']:
logging.debug('checking: %s', mdirectory)
directories = os.listdir(mdirectory)
for directory in directories:
for regexp in regexps:
match = regexp.search(directory)
if match:
version = match.group(0)
name = regexp.sub('#', directory)
torrents[name]['local-version'] = version
torrents[name]['local-name'] = directory
torrents[name]['local-path'] = mdirectory
logging.debug('Found the local torrent versions: %s', pformat(torrents))
def fetch_remote_torrents():
"""Fetches PleasureDome torrents versions"""
logging.info('Opening PleasureDome github feed')
pdurls = [
'https://pleasuredome.github.io/pleasuredome/mame/index.html',
'https://pleasuredome.github.io/pleasuredome/hbmame/index.html'
]
for url in pdurls:
page = get(url)
html = bs(page.text, 'html.parser')
for link in html.find_all('a'):
for regexp in regexps:
match = regexp.search(link.string)
if match:
if urlparse(link.get('href')).scheme == 'magnet':
matched_version = match.group(0)
matched_torrent = torrents[regexp.sub('#', link.string)]
remote_version = matched_torrent.get('remote-version', '')
if matched_version > remote_version:
matched_torrent['remote-version'] = matched_version
matched_torrent['remote-link'] = link.get('href')
matched_torrent['remote-name'] = link.string
else:
logging.debug("Skipping '{}' version '{}'".format(
link.string,
matched_version
))
logging.debug('Found the remote torrent versions: %s', pformat(torrents))
def filter_updatable_torrents():
"""Checks if newer versions are available and get magnet link"""
for torrent, data in list(torrents.items()):
keys_to_check = {'local-version', 'remote-version'}
if not (
keys_to_check.issubset(data.keys())
and
version.parse(data['local-version']) < version.parse(data['remote-version'])
):
del torrents[torrent]
logging.debug(
'The following torrents can be updated: %s',
[t for t in torrents.keys()]
)
def prompt_for_update():
"""Ask for user confirmation before updating"""
output_header = ["Torrent", "From", "To", "Transmission ID"]
output = []
if len(torrents) > 0:
for torrent, data in torrents.items():
output.append([
torrent,
data['local-version'],
data['remote-version'],
data.get('transmission-id', 'N/A')
])
print(tabulate(
output,
headers=output_header,
tablefmt="simple",
disable_numparse=True
))
print('Should I update the torrents listed above? (y/N)')
answer = input()
if answer.lower() != 'y':
logging.info('Quitting: user cancelled update')
print('Quitting…')
exit(0)
else:
logging.info('Quitting: no update candidate')
print('No update found…')
exit(0)
logging.info('User chose to update torrents')
def connect_to_transmission():
"""Connects to Transmission and return a Client object"""
logging.info('Connecting to Transmission Remote Control')
return Client(
username=config['transmission-user'],
password=config['transmission-password'],
port=config['transmission-port']
)
def fetch_transmission_torrents():
"""Gets the torrents id from Transmission"""
logging.info('Listing Transmission torrents')
local_torrents = client.torrent.accessor(
fields=['id', 'name']
).arguments.torrents
for local_torrent in local_torrents:
torrent = local_torrent.dict(exclude_none=True)
for regexp in regexps:
match = regexp.search(torrent['name'])
if match:
name = regexp.sub('#', torrent['name'])
torrents[name]['transmission-id'] = torrent['id']
logging.debug('Found the Transmission torrent ids: %s', pformat(torrents))
def update_torrents():
"""
Updates torrents:
* remove it from Transmission,
* rename the local directory,
* and add the new torrent
"""
logging.info('Updating torrents')
for torrent in torrents.values():
logging.info('Updating torrent : %s', torrent['remote-name'])
old_name = os.path.join(
torrent['local-path'],
torrent['local-name']
)
new_name = os.path.join(
torrent['local-path'],
torrent['remote-name']
)
if 'transmission-id' in torrent:
logging.debug(
'Removing from transmission : %s',
torrent['transmission-id']
)
client.torrent.remove(torrent['transmission-id'])
logging.debug('Renaming %s to %s', old_name, new_name)
os.rename(old_name, new_name)
logging.debug('Adding to transmission : %s', torrent['remote-link'])
arguments = {
"filename" : torrent['remote-link'],
"magnet_link": True,
"download_dir" : torrent['local-path'],
"paused" : True,
}
responce = client.torrent.add(arguments)
logging.debug('clutch responce : %s', responce)
if __name__ == '__main__':
logging.basicConfig(
level=logging.WARNING,
format=' %(asctime)s - %(levelname)s - %(message)s'
)
parser = argparse.ArgumentParser(
description='Update PleasureDome MAME Torrents'
)
parser.add_argument(
'-l', '--log',
action='store_true',
help='Display more log messages'
)
parser.add_argument(
'-d', '--debug',
action='store_true',
help='Display debugging messages'
)
parser.add_argument(
'-c', '--countdown',
action='store_true',
help='Start with a 5 second countdown'
)
args = parser.parse_args()
if args.log:
logging.getLogger().setLevel(logging.INFO)
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
if args.countdown:
print('PDMameUpdate is about to start')
# Useful if you run this script when your machine boots
for i in range(5, 0, -1):
print('{}\r'.format(i), end=''),
time.sleep(1)
regexps = [
re.compile(r'(?<=MAME )[\d.]+'),
re.compile(r'(?<=HBMAME )[\d.]+'),
]
config = open_config_file()
torrents = defaultdict(dict)
client = connect_to_transmission()
fetch_local_torrents()
fetch_remote_torrents()
fetch_transmission_torrents()
filter_updatable_torrents()
prompt_for_update()
update_torrents()