From 89d38844d38b931501bdfc70a3bc4ec31740a430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20M=E4kinen?= Date: Wed, 26 Sep 2012 11:40:18 +0300 Subject: [PATCH] Fixed mythorphans cron job. --- mythtv/files/myth.find_orphans.pl | 294 ++++++++++++++++++++++++++++++ mythtv/files/mythorphans | 2 +- mythtv/manifests/init.pp | 14 +- 3 files changed, 306 insertions(+), 4 deletions(-) create mode 100755 mythtv/files/myth.find_orphans.pl diff --git a/mythtv/files/myth.find_orphans.pl b/mythtv/files/myth.find_orphans.pl new file mode 100755 index 0000000..6932c1d --- /dev/null +++ b/mythtv/files/myth.find_orphans.pl @@ -0,0 +1,294 @@ +#!/usr/bin/perl + +# check for recording anomalies - +# based somewhat on greg froese's "myth.rebuilddatabase.pl" +# -- Lincoln Dale , September 2006 +# 2007-03-11: Added pretty print of unknown files vs. orphaned thumbnails. +# (Robert Kulagowski) 2008-02-15: Added dryrun and rerecord options (David +# George) + +# The intent of this script is to be able to find orphaned rows in the +# 'recorded' table (entries which don't have matching media files) and +# orphaned media files (potentially taking up gigabytes of otherwise usable +# disk space) which have no matching row in the 'recorded' db table. +# +# By default, running the script will simply return a list of problems it +# finds. Running with --dodbdelete will remove db recorded rows for which +# there is no matching media file. Running with --dodelete will delete +# media files for which there is no matching db record. +# +# This script may be useful to fix up some orphaned db entries (causes +# mythweb to run very slowly) as well as reclaim some disk space from some +# orphaned media files. (in an ideal world, neither of these would ever +# happen, but I've seen both happen in reality). This script makes it easy +# to keep track of whether it has or hasn't happened, even if you have +# thousands of recordings and terabytes of stored media. +# +# no warranties expressed or implied. if you run this and it deletes all +# your recordings and sets mythtv to fill up all your disk space with The +# Home Shopping Network, its entirely your fault. +# +# The dryrun option will allow you to see the db entries/files that will be +# deleted without actually executing them. +# The rerecord option is useful if you lose a hard drive in your storage +# group to tell the scheduler to re-record the lost programs (if they happen +# to be shown again). + +my $progname = "myth.find_orphans.pl"; +my $revision = "0.21"; + +use DBI; +use Sys::Hostname; +use Getopt::Long; + +# +# options +# + +my $opt_host = hostname; +my $opt_dbhost = $opt_host; +my $opt_database = "mythconverg"; +my $opt_user = "mythtv"; +my $opt_pass = "mythtv"; +my $opt_ext = "{nuv,mpg,mpeg,avi}"; +my $opt_dir = ""; +my $opt_dodelete = 0; +my $opt_dodbdelete = 0; +my $debug = 0; +my $opt_help = 0; +my $opt_dryrun = 0; +my $opt_rerecord = 0; + +GetOptions( + 'host=s' => \$opt_host, + 'dbhost=s' => \$opt_dbhost, + 'database=s' => \$opt_database, + 'user=s' => \$opt_user, + 'pass=s' => \$opt_pass, + 'dir=s' => \$opt_dir, + 'dodelete' => \$opt_dodelete, + 'dodbdelete' => \$opt_dodbdelete, + 'dryrun' => \$opt_dryrun, + 'rerecord' => \$opt_rerecord, + 'debug+' => \$debug, + 'help' => \$opt_help, + 'h' => \$opt_help, + 'v' => \$opt_help); + +if ($opt_help) { + print<connect("dbi:mysql:database=$opt_database:host=$opt_dbhost","$opt_user","$opt_pass"))) { + die "Cannot connect to database $opt_database on host $opt_dbhost: $!\n"; +} + +if ($opt_dir eq "") { + &dir_lookup("SELECT dirname FROM storagegroup WHERE hostname=(?) AND groupname != 'DB Backups'"); + &dir_lookup("SELECT data FROM settings WHERE value='RecordFilePrefix' AND hostname=(?)"); + + printf STDERR "Recording directories ($opt_host): $opt_dir\n" if $debug; +} + +if ($opt_dir eq "") { + printf "ERROR: no directory found or specified\n"; + exit 1; +} + +foreach $d (split(/,/,$opt_dir)) { + $d =~ s/\/$//g; # strip trailing / + $dirs{$d}++; +} + + +# +# look in recorded table, make sure we can find every file .. +# + +my $q = "SELECT title, subtitle, description, starttime, endtime, chanid, basename FROM recorded WHERE hostname=(?) ORDER BY starttime"; +$sth = $dbh->prepare($q); +$sth->execute($opt_host) || die "Could not execute ($q): $!\n"; + +while (my @row=$sth->fetchrow_array) { + ($title, $subtitle, $description ,$starttime, $endtime, $channel, $basename) = @row; + + # see if we can find it... + $loc = find_file($basename); + if ($loc eq "") { + printf "Missing media: %s (title:%s, start:%s)\n",$basename,$title,$starttime; + $missing_recordings++; + + if ($opt_dodbdelete) { + $title =~ s/"/\\"/g; + $subtitle =~ s/"/\\"/g; + $description =~ s/"/\\"/g; + my $sql = sprintf "DELETE FROM oldrecorded WHERE title LIKE \"%s\" AND subtitle LIKE \"%s\" AND description LIKE \"%s\" LIMIT 1", $title, $subtitle, $description; + printf "unmarking program as recorded: %s\n",$sql; + $dbh->do($sql) || die "Could not execute $sql: $!\n"; + my $sql = sprintf "DELETE FROM recorded WHERE basename LIKE \"%s\" LIMIT 1",$basename; + printf "performing database delete: %s\n",$sql; + if (!$opt_dryrun) { + $dbh->do($sql) || die "Could not execute $sql: $!\n"; + } + + if ($opt_rerecord) { + my $sql = sprintf "UPDATE oldrecorded SET duplicate = 0 where title = \"%s\" and starttime = \"%s\" and chanid = \"%s\"", + $title, $starttime, $channel; + printf "updating oldrecorded: %s\n", $sql; + if (!$opt_dryrun) { + $dbh->do($sql) || die "Could not execute $sql: $!\n"; + } + } + } + } else { + $valid_recordings++; + $seen_basename{$basename}++; + $seen_basename{$basename.".png"}++; # thumbnail + } +} + +# +# look in recording directories, see if there are extra files not in database +# + +foreach my $this_dir (keys %dirs) { + opendir(DIR, $this_dir) || die "cannot open directory $this_dir: $!\n"; + foreach $this_file (readdir(DIR)) { + if (-f "$this_dir/$this_file") { + + next if ($this_file eq "nfslockfile.lock"); + + my $this_filesize = -s "$this_dir/$this_file"; + if ($seen_basename{$this_file} == 0) { + $sorted_filesizes{$this_filesize} .= sprintf "unknown file [%s]: %s/%s\n",pretty_filesize($this_filesize),$this_dir,$this_file; + $unknown_size += $this_filesize; + if (substr($this_file,-4) eq ".png") { + $unknown_thumbnail++; + } + else { + $unknown_files++; + } + + if ($opt_dodelete) { + printf STDERR "deleting [%s]: %s/%s\n",pretty_filesize($this_filesize),$this_dir,$this_file; + + if (!$opt_dryrun) { + unlink "$this_dir/$this_file"; + + if (-f "$this_dir/$this_file") { + $errors++; + printf "ERROR: could not delete $this_dir/$this_file\n"; + } + } + } + } else { + $known_files++; + $known_size += $this_filesize; + printf "KNOWN file [%s]: %s/%s\n",pretty_filesize($this_filesize),$this_dir,$this_file if $debug; + } + } else { + printf "NOT A FILE: %s/%s\n",$this_dir,$this_file if $debug; + } + } + closedir DIR; +} + + +# +# finished, report results +# + +foreach my $key (sort { $a <=> $b } keys %sorted_filesizes) { + printf $sorted_filesizes{$key}; +} + +printf "Summary:\n"; +printf " Host: %s, Directories: %s\n", $opt_host, join(" ",keys %dirs); +printf " %d ERRORS ENCOUNTERED (see above for details)\n",$errors if ($errors > 0); +printf " %d valid recording%s, %d missing recording%s %s\n", + $valid_recordings, ($valid_recordings != 1 ? "s" : ""), + $missing_recordings, ($missing_recordings != 1 ? "s" : ""), + ($missing_recordings > 0 ? ($opt_dodbdelete ? "were fixed" : "not fixed, check above is valid and use --dodbdelete to fix") : ""); +printf " %d known media files using %s\n %d orphaned thumbnails with no corresponding recording\n %d unknown files using %s %s\n", + $known_files, pretty_filesize($known_size), + $unknown_thumbnail,$unknown_files, pretty_filesize($unknown_size), + ($unknown_files > 0 ? ($opt_dodelete ? "were fixed" : "not fixed, check above and use --dodelete to clean up if the above output is accurate") : ""); + +exit(0); + +########################################################################### +# filesize bling + +sub pretty_filesize +{ + local($fsize) = @_; + return sprintf "%0.1fGB",($fsize / 1000000000) if ($fsize >= 1000000000); + return sprintf "%0.1fMB",($fsize / 1000000) if ($fsize >= 1000000); + return sprintf "%0.1fKB",($fsize / 1000) if ($fsize >= 1000); + return sprintf "%0.0fB",$fsize; +} + +########################################################################### +# find a file in directories without globbing + +sub find_file +{ + local($fname) = @_; + + foreach my $d (keys %dirs) { + my $f = $d."/".$fname; + if (-e $f) { + return $f; + } + } + return; +} + +########################################################################### + +sub dir_lookup +{ + my $query = shift; + + $sth = $dbh->prepare($query); + $sth->execute($opt_host) || die "Could not execute ($dir_query)"; + while (my @row = $sth->fetchrow_array) { + $opt_dir .= "," if ($opt_dir ne ""); + $opt_dir .= $row[0]; + } +} + +########################################################################### + diff --git a/mythtv/files/mythorphans b/mythtv/files/mythorphans index 9d5ee70..a6e367e 100755 --- a/mythtv/files/mythorphans +++ b/mythtv/files/mythorphans @@ -19,7 +19,7 @@ mysql -h "${DBHostName}" -u"${DBUserName}" -p"${DBPassword}" -s \ "${DBName}" | egrep -q "^[1-9][0-9]*\$" || exit 0 # find orphans and print stats if found -perl /usr/share/doc/mythtv-docs-${MYTHVERSION}/contrib/maintenance/myth.find_orphans.pl \ +perl /usr/local/bin/myth.find_orphans.pl \ --dbhost="${DBHostName}" \ --database="${DBName}" \ --user="${DBUserName}" \ diff --git a/mythtv/manifests/init.pp b/mythtv/manifests/init.pp index 0f3d9e9..c6375d3 100644 --- a/mythtv/manifests/init.pp +++ b/mythtv/manifests/init.pp @@ -118,11 +118,19 @@ class mythtv::backend { } file { "/etc/cron.daily/mythorphans": + ensure => present, + source => "puppet:///modules/mythtv/mythorphans", + mode => "0755", + owner => "root", + group => "root", + require => File["/usr/local/bin/myth.find_orphans.pl"], + } + file { "/usr/local/bin/myth.find_orphans.pl": ensure => present, - source => "puppet:///modules/mythtv/mythorphans", + source => "puppet:///modules/mythtv/myth.find_orphans.pl", mode => "0755", - owner => root, - group => root, + owner => "root", + group => "root", } }