Add locking for long runs

This commit is contained in:
Stefan Bethke 2024-08-04 17:38:06 +00:00
parent 53420ffc31
commit 448ef57b40
2 changed files with 52 additions and 35 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.venv .venv
*~

View file

@ -13,6 +13,7 @@ import http.cookiejar
import datetime import datetime
import getopt import getopt
import errno import errno
import fcntl
import functools import functools
import logging import logging
import logging.handlers import logging.handlers
@ -30,11 +31,13 @@ import urllib.request, urllib.error, urllib.parse
import xml.dom.minidom import xml.dom.minidom
import yaml import yaml
from contextlib import contextmanager
from io import TextIOWrapper from io import TextIOWrapper
class Config: class Config:
config = '~/.tivo/config.yaml' config = '~/.tivo/config.yaml'
lockfile = config + '.lock'
cookies = "cookies.txt" cookies = "cookies.txt"
gig = 1024.0 * 1024 * 1024 gig = 1024.0 * 1024 * 1024
headers = requests.utils.default_headers() headers = requests.utils.default_headers()
@ -382,6 +385,16 @@ class FdLogger(threading.Thread):
self.logger.exception("") self.logger.exception("")
@contextmanager
def exclusive():
with open(os.path.expanduser(config.lockfile), 'w') as f:
try:
fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as e:
raise TivoException('another tivomirror instance is already running')
yield 'locked'
fcntl.lockf(f, fcntl.LOCK_UN)
@timeout(43200) @timeout(43200)
def download_item(item, mak, target): def download_item(item, mak, target):
global config global config
@ -523,47 +536,50 @@ def mirror(toc, downloaddb, one=False):
logger.error("{}: {:.1f} GB available, at least {:.1f} GB needed, stopping".format\ logger.error("{}: {:.1f} GB available, at least {:.1f} GB needed, stopping".format\
(config.targetdir, avail / config.gig, config.minfree / config.gig)) (config.targetdir, avail / config.gig, config.minfree / config.gig))
sys.exit(1) sys.exit(1)
items = toc.getItems() with exclusive() as lock:
logger.info("*** {} shows listed".format(len(items))) items = toc.getItems()
for item in items: logger.info("*** {} shows listed".format(len(items)))
options = wantitem(item, downloaddb) for item in items:
if isinstance(options, str): options = wantitem(item, downloaddb)
logger.debug("*** skipping \"{}\": {}".format(item.name, options)) if isinstance(options, str):
else: logger.debug("*** skipping \"{}\": {}".format(item.name, options))
download_one(item, downloaddb, options) else:
if one: download_one(item, downloaddb, options)
break if one:
break
def download_episode(toc, downloaddb, episode): def download_episode(toc, downloaddb, episode):
items = toc.getItems() with exclusive() as lock:
options = {} items = toc.getItems()
for item in items: options = {}
if item.title == episode or item.name == episode or item.episode == episode: for item in items:
for i in (item.title, item.episode, item.name): if item.title == episode or item.name == episode or item.episode == episode:
if i in IncludeShow.includes: for i in (item.title, item.episode, item.name):
options = IncludeShow.includes[i] if i in IncludeShow.includes:
download_one(item, downloaddb, options) options = IncludeShow.includes[i]
return download_one(item, downloaddb, options)
return
def printtoc(toc, downloaddb): def printtoc(toc, downloaddb):
items = toc.getItems() with exclusive() as lock:
print("*** {} shows listed".format(len(items))) items = toc.getItems()
shows = {} print("*** {} shows listed".format(len(items)))
for item in items: shows = {}
if item.title not in shows: for item in items:
shows[item.title] = [] if item.title not in shows:
shows[item.title].append(item) shows[item.title] = []
for title in sorted(shows): shows[item.title].append(item)
for item in sorted(shows[title], key=lambda i: i.name): for title in sorted(shows):
options = wantitem(item, downloaddb) for item in sorted(shows[title], key=lambda i: i.name):
if isinstance(options, str): options = wantitem(item, downloaddb)
print("{:>7.7s}: {}".format(options, item.name)) if isinstance(options, str):
continue print("{:>7.7s}: {}".format(options, item.name))
print("*** downloading {} ({:.3f} GB)".format(item.name, item.sourcesize / 1e9)) continue
print("*** {} shows listed".format(len(items))) print("*** downloading {} ({:.3f} GB)".format(item.name, item.sourcesize / 1e9))
print("*** {} shows listed".format(len(items)))
def usage(): def usage():