Ticket #2363: check_recordings.3.pl

File check_recordings.3.pl, 7.2 KB (added by ltd@…, 18 years ago)

contrib/check_recordings.pl take 3

Line 
1#!/usr/bin/perl
2
3# check for recording anomolies -
4#   based somewhat on greg froese's "myth.rebuilddatabase.pl"
5#   -- Lincoln Dale <ltd@interlink.com.au>, September 2006
6
7#  The intent of this script is to be able to find orphaned rows in the 'recorded' table
8#  (entries which don't have matching media files) and orphaned media files (potentially
9#  taking up gigabytes of otherwise usable disk space) which have no matching row in
10#  the 'recorded' db table.
11#
12#  By default, running the script will simply return a list of problems it finds.
13#  Running with --dodbdelete will remove db recorded rows for which there is no matching
14#  media file.  Running with --dodelete will delete media files for which there is no
15#  matching db record.
16#
17#  This script may be useful to fix up some orphaned db entries (causes mythweb to run
18#  verrry slow) as well as reclaim some disk space from some orphaned media files.
19#  (in an ideal world, neither of these would ever happen, but i've seen both happen in reality).
20#  This script makes it easy to keep track of whether it has or hasn't happened, even if you
21#  have thousands of recordings and terabytes of stored media.
22#
23#  no warranties expressed or implied.  if you run this and it deletes all your recordings
24#  and sets mythtv to fill up all your disk space with The Home Shopping Network, its entirely
25#  your fault.
26
27my $progname = "check_recordings.pl";
28my $revision = "0.20";
29
30use DBI;
31use Sys::Hostname;
32use Getopt::Long;
33
34#
35# options
36#
37
38my $opt_host =          hostname;
39my $opt_dbhost =        $opt_host;
40my $opt_database =      "mythconverg";
41my $opt_user =          "mythtv";
42my $opt_pass =          "mythtv";
43my $opt_ext =           "{nuv,mpg,mpeg,avi}";
44my $opt_dir =           "";
45my $opt_dodelete =      0;
46my $opt_dodbdelete =    0;
47my $debug =             0;
48my $opt_help =          0;
49
50GetOptions(
51        'host=s'        => \$opt_host,
52        'dbhost=s'      => \$opt_dbhost,
53        'database=s'    => \$opt_database,
54        'user=s'        => \$opt_user,
55        'pass=s'        => \$opt_pass,
56        'dir=s'         => \$opt_dir,
57        'dodelete'      => \$opt_dodelete,
58        'dodbdelete'    => \$opt_dodbdelete,
59        'debug+'        => \$debug,
60        'help'          => \$opt_help,
61        'h'             => \$opt_help,
62        'v'             => \$opt_help);
63
64if ($opt_help) {
65        print<<EOF
66$progname (rev $revision)
67(checks myth recordings directory for anomalies)
68
69options:
70        --host=(host)           myth backend host ($opt_host)
71        --dbhost=(host)         host where mysql database for backend is ($opt_dbhost)
72        --database=(dbname)     myth database ($opt_database)
73        --user=(user)           mysql mythtv database user ($opt_user)
74        --pass=(pass)           mysql mythtv database password ($opt_pass)
75        --dir=directories       manually specify recording directories (otherwise setting is from database)
76        --debug                 increase debug level
77        --dodbdelete            remove recorded db entries with no matching file (default: don't)
78        --dodelete              delete files with no record (default: don't)
79
80EOF
81;
82        exit(0);
83}
84
85#
86# go go go!
87#
88
89my $valid_recordings = 0;
90my $missing_recordings = 0;
91my $errors = 0;
92my $unknown_files = 0;
93my $known_files = 0;
94my $unknown_size = 0;
95my $known_size = 0;
96
97if (!($dbh = DBI->connect("dbi:mysql:database=$opt_database:host=$opt_dbhost","$opt_user","$opt_pass"))) {
98        die "Cannot connect to database $opt_database on host $opt_dbhost: $!\n";
99}
100
101if ($opt_dir eq "") {
102        my $dir_query = "SELECT data FROM settings WHERE value='RecordFilePrefix' AND hostname=(?)";
103        $sth = $dbh->prepare($dir_query);
104        $sth->execute($opt_host) || die "Could not execute ($dir_query)";
105        if (my @row = $sth->fetchrow_array) {
106                $opt_dir = $row[0];
107        }
108        printf STDERR "Recording directories ($opt_host): $opt_dir\n" if $debug;
109}
110
111if ($opt_dir eq "") {
112        printf "ERROR: no directory found or specified\n";
113        exit 1;
114}
115
116foreach $d (split(/,/,$opt_dir)) {
117        push @dirs,$d;
118}
119
120
121#
122# look in recorded table, make sure we can find every file ..
123#
124
125my $q = "SELECT title, subtitle, starttime, endtime, chanid, basename FROM recorded ORDER BY starttime";
126$sth = $dbh->prepare($q);
127$sth->execute || die "Could not execute ($q): $!\n";
128
129while (my @row=$sth->fetchrow_array) {
130        ($title, $subtitle, $starttime, $endtime, $channel, $basename) = @row;
131
132        # see if we can find it...
133        $loc = find_file($basename);
134        if ($loc eq "") {
135                printf "Missing media: %s (title:%s, start:%s)\n",$basename,$title,$starttime;
136                $missing_recordings++;
137
138                if ($opt_dodbdelete) {
139                        my $sql = sprintf "DELETE FROM recorded WHERE basename LIKE \"%s\" LIMIT 1",$basename;
140                        printf "performing database delete: %s\n",$sql;
141                        $dbh->do($sql) || die "Could not execute $sql: $!\n";
142                }
143        } else {
144                $valid_recordings++;
145                $seen_basename{$basename}++;
146                $seen_basename{$basename.".png"}++; # thumbnail
147        }
148}
149
150#
151# look in recording directories, see if there are extra files not in database
152#
153
154foreach my $this_dir (@dirs) {
155        opendir(DIR, $this_dir) || die "cannot open directory $this_dir: $!\n";
156        foreach $this_file (readdir(DIR)) {
157                if (-f "$this_dir/$this_file") {
158
159                        next if ($this_file eq "nfslockfile.lock");
160
161                        my $this_filesize = -s "$this_dir/$this_file";
162                        if ($seen_basename{$this_file} == 0) {
163                                $sorted_filesizes{$this_filesize} .= sprintf "unknown file [%s]: %s/%s\n",pretty_filesize($this_filesize),$this_dir,$this_file;
164                                $unknown_size += $this_filesize;
165                                $unknown_files++;
166
167                                if ($opt_dodelete) {
168                                        printf STDERR "deleting  [%s]:  %s/%s\n",pretty_filesize($this_filesize),$this_dir,$this_file;
169                                        unlink "$this_dir/$this_file";
170
171                                        if (-f "$this_dir/$this_file") {
172                                                $errors++;
173                                                printf "ERROR: could not delete $this_dir/$this_file\n";
174                                        }
175                                }
176                        } else {
177                                $known_files++;
178                                $known_size += $this_filesize;
179                                printf "KNOWN file [%s]: %s/%s\n",pretty_filesize($this_filesize),$this_dir,$this_file if $debug;
180                        }
181                } else {
182                        printf "NOT A FILE: %s/%s\n",$this_dir,$this_file if $debug;
183                }
184        }
185        closedir DIR;
186}
187
188
189#
190# finished, report results
191#
192
193foreach my $key (sort { $a <=> $b } keys %sorted_filesizes) {
194        printf $sorted_filesizes{$key};
195}
196
197printf "Summary:\n";
198printf "  %d ERRORS ENCOUNTERED (see above for details)\n",$errors if ($errors > 0);
199printf "  %d valid recording%s, %d missing recording%s %s\n",
200        $valid_recordings, ($valid_recordings != 1 ? "s" : ""),
201        $missing_recordings, ($missing_recordings != 1 ? "s" : ""),
202        ($missing_recordings > 0 ? ($opt_dodbdelete ? "were fixed" : "not fixed, check above is valid and use --dodbdelete to fix") : "");
203printf "  %d known media files using %s, %d unknown files using %s %s\n",
204        $known_files, pretty_filesize($known_size),
205        $unknown_files, pretty_filesize($unknown_size),
206        ($unknown_files > 0 ? ($opt_dodelete ? "were fixed" : "not fixed, check above and use --dodelete to fix if the above output is accurate") : "");
207
208exit(0);
209
210###########################################################################
211# filesize bling
212
213sub pretty_filesize
214{
215        local($fsize) = @_;
216        return sprintf "%0.1fGB",($fsize / 1000000000) if ($fsize >= 1000000000);
217        return sprintf "%0.1fMB",($fsize / 1000000) if ($fsize >= 1000000);
218        return sprintf "%0.1fKB",($fsize / 1000) if ($fsize >= 1000);
219        return sprintf "%0.0fB",$fsize;
220}
221
222###########################################################################
223# find a file in directories without globbing
224
225sub find_file
226{
227        local($fname) = @_;
228
229        foreach my $d (@dirs) {
230                my $f = $d."/".$fname;
231                if (-e $f) {
232                        return $f;
233                }
234        }
235        return;
236}
237
238###########################################################################
239
240