]> code.delx.au - mediapc-tools/commitdiff
MythTV maintenance scripts
authorJames Bunton <jamesbunton@delx.net.au>
Mon, 18 Feb 2019 01:43:38 +0000 (12:43 +1100)
committerJames Bunton <jamesbunton@delx.net.au>
Mon, 18 Feb 2019 01:54:04 +0000 (12:54 +1100)
mythcleandb -- delete orphaned DB recordings that are missing the corresponding files
mythcleanfiles -- delete orphaned recording files that are missing from the DB
mythcleanpng -- delete orphaned recording screenshots
mythsymlink -- update to python2 and use shared code

.gitignore [new file with mode: 0644]
mythcleandb [new file with mode: 0755]
mythcleanfiles [new file with mode: 0755]
mythcleanpng [new file with mode: 0755]
mythlib.py [new file with mode: 0644]
mythsymlink

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..bee8a64
--- /dev/null
@@ -0,0 +1 @@
+__pycache__
diff --git a/mythcleandb b/mythcleandb
new file mode 100755 (executable)
index 0000000..6e000fb
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+import mythlib
+
+def main(dry_run):
+    config_xml = mythlib.get_config()
+    db_connection = mythlib.get_db_connection(config_xml)
+    recordings_dir = mythlib.get_recordings_dir(config_xml, db_connection)
+
+    orphan_db_records = list(find_orphan_db_records(db_connection, recordings_dir))
+    for basename in orphan_db_records:
+        if dry_run:
+            print("Found orphan:", basename)
+        else:
+            print("Deleting orphan:", basename)
+            delete_orphan_record(db_connection, basename)
+
+def print_usage_exit():
+    print("Usage: %s [-f|-n]" % sys.argv[0])
+    sys.exit(1)
+
+def find_orphan_db_records(db_connection, recordings_dir):
+    with db_connection.cursor() as cursor:
+        cursor.execute("""
+            SELECT basename
+            FROM recorded
+            WHERE progend < now()
+        """)
+        for row in cursor:
+            basename = row[0]
+            if not is_valid_record(recordings_dir, basename):
+                yield basename
+
+def is_valid_record(recordings_dir, basename):
+    filename = "%s/%s" % (recordings_dir, basename)
+    try:
+        size = os.stat(filename).st_size
+    except FileNotFoundError:
+        size = 0
+
+    return size > 0
+
+def delete_orphan_record(db_connection, basename):
+    with db_connection.cursor() as cursor:
+        cursor.execute("delete from recorded where basename='%s'" % basename)
+
+if __name__ == "__main__":
+    if len(sys.argv) != 2:
+        print_usage_exit()
+
+    if sys.argv[1] == "-f":
+        dry_run = False
+    elif sys.argv[1] == "-n":
+        dry_run = True
+    else:
+        print_usage_exit()
+
+    main(dry_run)
diff --git a/mythcleanfiles b/mythcleanfiles
new file mode 100755 (executable)
index 0000000..5c259c8
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+import mythlib
+
+def main():
+    config_xml = mythlib.get_config()
+    db_connection = mythlib.get_db_connection(config_xml)
+    recordings_dir = mythlib.get_recordings_dir(config_xml, db_connection)
+
+    basenames = set(fetch_all_basenames(db_connection))
+    orphan_filenames = list(find_orphan_filenames(recordings_dir, basenames))
+
+    if orphan_filenames:
+        print("# Orphaned files:")
+        print("\n".join("rm %s" % filename for filename in orphan_filenames))
+        print()
+
+def fetch_all_basenames(db_connection):
+    with db_connection.cursor() as cursor:
+        cursor.execute("SELECT basename FROM recorded")
+        for row in cursor:
+            yield row[0]
+
+def find_orphan_filenames(recordings_dir, basenames):
+    for filename in os.listdir(recordings_dir):
+        _, ext = os.path.splitext(filename)
+        if ext not in [".mpg", ".ts"]:
+            continue
+
+        if filename not in basenames:
+            yield "%s/%s" % (recordings_dir, filename)
+
+if __name__ == "__main__":
+    main()
diff --git a/mythcleanpng b/mythcleanpng
new file mode 100755 (executable)
index 0000000..1679ee7
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -eu
+
+cd /var/lib/mythtv/recordings
+
+ls *.png.* &> /dev/null && rm -fv *.png.*
+
+for f in *.png; do [ -f "${f%%.png}" ] || rm -fv "$f"; done
diff --git a/mythlib.py b/mythlib.py
new file mode 100644 (file)
index 0000000..747e7c7
--- /dev/null
@@ -0,0 +1,34 @@
+#! python3
+
+import os
+import socket
+
+import lxml.etree
+import MySQLdb
+
+def get_config():
+    with open(os.path.expanduser("~/.mythtv/config.xml")) as f:
+        return lxml.etree.parse(f, lxml.etree.XMLParser(encoding="utf-8"))
+
+def get_db_connection(config_xml):
+    return MySQLdb.connect(
+        host = config_xml.xpath("/Configuration/Database/Host/text()")[0],
+        port = int(config_xml.xpath("/Configuration/Database/Port/text()")[0]),
+        user = config_xml.xpath("/Configuration/Database/UserName/text()")[0],
+        passwd = config_xml.xpath("/Configuration/Database/Password/text()")[0],
+        db = config_xml.xpath("/Configuration/Database/DatabaseName/text()")[0],
+    )
+
+def get_recordings_dir(config_xml, db_connection):
+    try:
+        localhostname = config_xml.xpath("/Configuration/LocalHostName/text()")[0]
+    except IndexError:
+        localhostname = socket.gethostname()
+
+    with db_connection.cursor(MySQLdb.cursors.DictCursor) as cursor:
+        cursor.execute("""
+            SELECT * FROM settings
+            WHERE value='RecordFilePrefix' AND hostname='%s'
+        """ % localhostname)
+
+        return cursor.fetchone()["data"]
index 2bd754e24bbf3ddf75a843bd1616624772519ab3..487099716177a2f3bb4478d6f50b4b6845e6419c 100755 (executable)
@@ -1,73 +1,57 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
+import MySQLdb
 import os
 import random
 import re
-import socket
 import sys
 
-import lxml.etree
-import MySQLdb
-
-os.chdir(sys.argv[1])
-
-# Remove any symlinks and empty dirs in the tree
-for dirpath, dirnames, filenames in os.walk(".", topdown=False):
-    for filename in filenames:
-        filename = os.path.join(dirpath, filename)
-        if os.path.islink(filename):
-            os.unlink(filename)
-    for dirname in dirnames:
-        dirname = os.path.join(dirpath, dirname)
-        os.rmdir(dirname)
-
-# Connect to the MythTV database based on the MythTV config
-with open(os.path.expanduser("~/.mythtv/config.xml")) as f:
-    config_xml = lxml.etree.parse(f, lxml.etree.XMLParser(encoding="utf-8"))
-
-db_connection = MySQLdb.connect(
-    host = config_xml.xpath("/Configuration/Database/Host/text()")[0],
-    port = int(config_xml.xpath("/Configuration/Database/Port/text()")[0]),
-    user = config_xml.xpath("/Configuration/Database/UserName/text()")[0],
-    passwd = config_xml.xpath("/Configuration/Database/Password/text()")[0],
-    db = config_xml.xpath("/Configuration/Database/DatabaseName/text()")[0],
-)
-cursor = db_connection.cursor(MySQLdb.cursors.DictCursor)
-
-# Regexp for what is allowed in the symlink name
-unsafe = re.compile("[^a-zA-Z0-9\-_ ,\\.]+")
-
-try:
-    localhostname = config_xml.xpath("/Configuration/LocalHostName/text()")[0]
-except IndexError:
-    localhostname = socket.gethostname()
-
-# Find the recordings directory
-cursor.execute("""
-    SELECT * FROM settings
-    WHERE value='RecordFilePrefix' AND hostname='%s'
-""" % localhostname)
-recordingsdir = cursor.fetchone()["data"]
-
-# Now find all the recordings we have at the moment
-cursor.execute("""
-    SELECT
-        title,
-        subtitle,
-        CONVERT_TZ(starttime, 'UTC', '%s') as starttime,
-        basename,
-        watched
-    FROM recorded
-""" % "Australia/Sydney")
-for row in cursor:
+import mythlib
+
+def main(destdir):
+    os.chdir(destdir)
+
+    clean_dir()
+
+    config_xml = mythlib.get_config()
+    db_connection = mythlib.get_db_connection(config_xml)
+    recordings_dir = mythlib.get_recordings_dir(config_xml, db_connection)
+
+    for_each_recording(db_connection, handle_recording, recordings_dir)
+
+def clean_dir():
+    for dirpath, dirnames, filenames in os.walk(".", topdown=False):
+        for filename in filenames:
+            filename = os.path.join(dirpath, filename)
+            if os.path.islink(filename):
+                os.unlink(filename)
+        for dirname in dirnames:
+            dirname = os.path.join(dirpath, dirname)
+            os.rmdir(dirname)
+
+def for_each_recording(db_connection, fn, *args):
+    with db_connection.cursor(MySQLdb.cursors.DictCursor) as cursor:
+        cursor.execute("""
+            SELECT
+                title,
+                subtitle,
+                CONVERT_TZ(starttime, 'UTC', '%s') as starttime,
+                basename,
+                watched
+            FROM recorded
+        """ % "Australia/Sydney")
+        for row in cursor:
+            fn(row, *args)
+
+def handle_recording(row, recordings_dir):
     title = row["title"]
     starttime = str(row["starttime"]).replace(":", "-")
     subtitle = row["subtitle"]
     basename = row["basename"]
     watched = bool(row["watched"])
 
-    title = unsafe.sub("", title)
-    subtitle = unsafe.sub("", subtitle)
+    title = sanitize_filename(title)
+    subtitle = sanitize_filename(subtitle)
     extn = os.path.splitext(basename)[1]
 
 
@@ -78,23 +62,36 @@ for row in cursor:
     if watched:
         filename = "watched/" + filename
 
-    source = "%s/%s" % (recordingsdir, basename)
+    source = "%s/%s" % (recordings_dir, basename)
     dest = "%s/%s" % (title, filename)
 
     if not os.path.isfile(source):
-        continue
+        return
 
     if os.path.isfile(dest):
-        dest = os.path.splitext(dest)[0] + ' - unique' + str(random.randint(1000, 9999)) + extn
+        dest = os.path.splitext(dest)[0] + " - unique" + str(random.randint(1000, 9999)) + extn
 
     dirnames = dest.split("/")[:-1]
-    for i in xrange(1, len(dirnames)+1):
+    for i in range(1, len(dirnames)+1):
         dirname = "/".join(dirnames[:i])
         if not os.path.isdir(dirname):
             os.mkdir(dirname)
 
     try:
         os.symlink(source, dest)
-    except Exception, e:
-        print e, "--", source, "->", dest
+    except Exception as e:
+        print(e, "--", source, "->", dest)
+        sys.exit(1)
+
+def sanitize_filename(filename):
+    unsafe = re.compile("[^a-zA-Z0-9\-_ ,\\.]+")
+    return unsafe.sub("", filename)
+
+if __name__ == "__main__":
+    try:
+        destdir = sys.argv[1]
+    except:
+        print("Usage: %s destdir" % sys.argv[0])
         sys.exit(1)
+
+    main(destdir)