Ticket #4599: myth_archive.pl

File myth_archive.pl, 19.0 KB (added by Mark Buechler <Mark.Buechler@…>, 12 years ago)

The perl script.

Line 
1#!/usr/bin/perl
2$Version  = 'MythTV Recording Archiver v0.3';
3
4# MythTV Recording Archiver
5#
6# Written by Mark R. Buechler 03/01/07
7# License: GPL2
8
9sub usage
10  {
11    die <<"EndUsage";
12$Version
13
14Usage:
15
16Required: (at least one of)
17     -archive           : Run in archive mode.
18     -cleanup <mode>    : Run in cleanup mode where <mode> is a comma seperated
19                          list of abandoned, zerobyte, metadata, deleted or all
20
21Archive Arguments:
22     -from <group>      : Archive recordings from storage group <group>.
23     -to <group>        : Archive recordings to storage group <group>.
24
25Archive Options:
26     -ignorelivetv      : Ignore LiveTV recordings.
27     -hostname <host>   : Only archive recordings which exist on
28                          Myth backend <host>.
29Debug:
30     -debug             : Don't archive or delete anything.
31
32Cleanup Modes:
33        abandoned - Delete recordings with missing metadata.
34        zerobyte  - Delete all zero-byte recordings and associated metadata.
35        metadata  - Delete metadata for recordings which don't exist.
36        deleted   - Delete recordins and metadata which exist in the 'Deleted'
37                     storage group.
38        all       - Perform all cleanup types.
39
40Examples:
41    Archive recordings from the LiveTV group to the Default group:
42       myth_archive.pl -archive -from LiveTV -to Default -ignorelivetv
43
44    Perform some cleanup of orphoned (abandoned) recordings:
45       myth_archive.pl -cleanup abandoned
46
47Notes:
48       If -hostname option is not used, you must ensure that storage group
49        paths are identical across all MythTV backends.
50
51EndUsage
52  }
53
54use Getopt::Long;
55use Carp qw(cluck);
56use IO::File;
57use DirHandle;
58use DBI;
59
60use File::Copy;
61use strict;
62
63# Pull in the MythTV module
64#use lib '/opt/mns/mythtv/share/perl';
65use MythTV;
66
67my $TRUE  = 1;
68my $FALSE = 0;
69
70my $LIVETV_AUTOEXPIRE = 9999;
71my $DELETED_RECGROUP = 'Deleted';
72
73# Operational modes
74my $MODE_ABANDONED = 0x01;
75my $MODE_ZEROBYTE  = 0x02;
76my $MODE_METADATA  = 0x04;
77my $MODE_DELETED   = 0x08;
78my $MODE_ALL       = 0x0f;
79my $MODE_ARCHIVE   = 0x10;
80
81my $DBH;
82my $MYTH;
83my $_DEBUG_;
84
85&main();
86
87sub getArgs {
88        my $modes;
89        my $from;
90        my $to;
91        my $hostname;
92        my $ignorelivetv;
93        my $cleanupmode;
94        my $archivemode;
95        my $batchmode;
96        my $force;
97        my $debug;
98
99        my $p = new Getopt::Long::Parser;
100
101        if (!$p->getoptions('from=s'            => \$from,
102                            'to=s'              => \$to,
103                            'hostname=s'        => \$hostname,
104                            'ignorelivetv'      => \$ignorelivetv,
105                            'archive'           => \$archivemode,
106                            'cleanup=s'         => \$cleanupmode,
107                            'batch'             => \$batchmode,
108                            'force'             => \$force,
109                            'debug'             => \$debug)) {
110                print "Invalid parameters given.\n";
111                usage();
112        }
113
114        $force = $TRUE if (defined($force));
115        $ignorelivetv = $TRUE if (defined($ignorelivetv));
116        $batchmode = $TRUE if (defined($batchmode));
117        $cleanupmode =~ tr/A-Z/a-z/;
118        $_DEBUG_ = $TRUE if (defined($debug));
119
120        if ($archivemode && (!$from || !$to)) {
121                print "Please specify -from and -to with -archive.\n";
122                usage();
123        }
124
125        if ($cleanupmode && $hostname && !$force) {
126                print "\nDANGER: This can be a BAD idea! Use -debug before trying this!\n".
127                  "If you really want this, specify -force with -cleanup and -hostname.\n\n";
128                exit 1;
129        }
130
131        if ($cleanupmode) {
132                foreach my $mode (split(/\,/, $cleanupmode)) {
133                        if ($mode eq 'abandoned') {
134                                $modes |= $MODE_ABANDONED;
135                        } elsif ($mode eq 'zerobyte') {
136                                $modes |= $MODE_ZEROBYTE;
137                        } elsif ($mode eq 'metadata') {
138                                $modes |= $MODE_METADATA;
139                        } elsif ($mode eq 'deleted') {
140                                $modes |= $MODE_DELETED;
141                        } elsif ($mode eq 'all') {
142                                $modes |= $MODE_ALL;
143                        } else {
144                                print "Invalid mode '$mode' specified.\n";
145                                usage();
146                        }
147                }
148        }
149
150        if (defined($archivemode)) {
151                $modes |= $MODE_ARCHIVE;
152        }
153
154        if (($from eq 'LiveTV') && !$ignorelivetv && !$force) {
155                print "Your source group is $from but you are not ignoring LiveTV recordings, ".
156                  "please use -force option.\n";
157                exit 1;
158        }
159
160        return($modes, $from, $to, $hostname, $ignorelivetv, $batchmode);
161}
162
163sub main {
164        my($modes, $from, $to, $hostname, $ignorelivetv, $batchmode) = getArgs();
165        my $samefs = $FALSE;
166        my %recordingLookup;
167        my %abandoned;
168
169        print modesToString($modes);
170
171        $MYTH = new MythTV();
172
173        $DBH = $MYTH->{'dbh'};
174
175        my $storage = getStorageGroups($hostname);
176        my $recordings = getRecordings($hostname);
177        my($stored, $cifs, $thumbs) = getStoredRecordings($storage);
178
179        if ($modes & $MODE_ARCHIVE) {
180                my $fromDir = $$storage{$from};
181                my $toDir = $$storage{$to};
182
183                die("FATAL: Storage group '$from' is not defined in database, aborting!\n") if (!$fromDir);
184                die("FATAL: Storage group '$to' is not defined in database, aborting!\n") if (!$toDir);
185
186                my($mpFrom, undef, undef, undef) = getMountpoint($fromDir);
187                my($mpTo, undef, undef, undef) = getMountpoint($toDir);
188
189                if ($mpFrom eq $mpTo) {
190                        print "-> From and to storage groups reside on the same filesystem, ".
191                          "moving recordings rather than copying/deleting.\n\n";
192                        $samefs = $TRUE;
193                } else {
194                        print "-> From and to storage groups reside on different filesystems, ".
195                          "copying/deleting recordings rather than moving.\n\n";
196                }
197        }
198
199        # Some recordings may have moved, figure out which ones and update them
200        foreach my $group (keys %{$recordings}) {
201                foreach my $recording (keys %{$$recordings{$group}}) {
202                        my $recgroup = $$recordings{$group}->{$recording}->{'RECGROUP'};
203                        my $chanid = $$recordings{$group}->{$recording}->{'CHANID'};
204                        my $starttime = $$recordings{$group}->{$recording}->{'START'};
205                        my $title = $$recordings{$group}->{$recording}->{'TITLE'};
206                        my $subtitle = $$recordings{$group}->{$recording}->{'SUBTITLE'};
207
208                        $recordingLookup{$recording}++;
209
210                        my $_group = findRecording($storage, $recording, $chanid, $starttime);
211
212                        if (!defined($_group)) {
213                                if ($modes & $MODE_METADATA) {
214                                        print "\tDeleting meta-data for missing recording '$recording' in group '$group'.\n";
215
216                                        deleteFromDb($chanid, $starttime, $recording, $recgroup);
217                                } else {
218                                        print "\tStorage group '$group': problem found with recording title '$title - ".
219                                          "$subtitle'.\n";
220                                }
221
222                                next;
223                        }
224
225                        updateStorageGroup($chanid, $starttime, $recording, $group)
226                          if (defined($_group) && ($group ne $_group));
227                }
228        }
229
230        # Update
231        $recordings = getRecordings($hostname);
232
233        # Catch recording files still lingering around
234        my %totalSize;
235        foreach my $group (keys %{$stored}) {
236                foreach my $recording (keys %{$$stored{$group}}) {
237                        if (!$recordingLookup{$recording}) {
238                                my $size = $$stored{$group}->{$recording};
239                                $abandoned{$group}->{$recording} = $size;
240                                print "WARNING: Found recording file '$recording' in storage group '$group'\n  ".
241                                  "of size $size with no entry in database!\n";
242                                $totalSize{$group} += $size;
243                        }
244                }
245        }
246
247        foreach my $group (keys %{$cifs}) {
248                foreach my $recording (keys %{$$cifs{$group}}) {
249                        my $size = $$cifs{$group}->{$recording};
250                        $abandoned{$group}->{$recording} = $size;
251                        print "WARNING: Found recording file '$recording' in storage group '$group'\n  ".
252                          "of size $size with no entry in database!\n";
253                        $totalSize{$group} += $size;
254                }
255        }
256
257        foreach my $group (keys %totalSize) {
258                my $size = $totalSize{$group};
259                print "Found a total of $size bytes in storage group '$group' with no database entries!\n";
260        }
261
262        if ($modes & $MODE_ABANDONED) {
263                if (!$batchmode) {
264                        print "Preparing to cleanup recordings, press ctrl-c within 10 seconds to abort.\n";
265                        sleep 10;
266                }
267
268                foreach my $group (keys %abandoned) {
269                        foreach my $recording (keys %{$abandoned{$group}}) {
270                                my $size = $abandoned{$group}->{$recording};
271                                print "\tDeleting recording '$recording' in storage group '$group' of size $size bytes..\n";
272                                unlinkFile($$storage{$group}, $recording);
273
274                                if ($$thumbs{$recording}) {
275                                        foreach my $thumb (@{$$thumbs{$recording}}) {
276                                                print "\tDeleting thumbnail '$thumb'..\n";
277                                                unlinkFile($$storage{$group}, $thumb);
278                                        }
279                                }
280                        }
281                }
282
283                if ($modes & $MODE_DELETED) {
284                        # Take care of recordings in the 'Deleted' group.
285                        foreach my $group (keys %{$recordings}) {
286                                foreach my $recording (keys %{$$recordings{$group}}) {
287                                        my $recgroup = $$recordings{$group}->{$recording}->{'RECGROUP'};
288                                        my $chanid = $$recordings{$group}->{$recording}->{'CHANID'};
289                                        my $starttime = $$recordings{$group}->{$recording}->{'START'};
290
291                                        if ($recgroup eq $DELETED_RECGROUP) {
292                                                print "\tDeleting recording '$recording' in storage group '$group' ".
293                                                  "in recgroup '$recgroup'..\n";
294                                                unlinkFile($$storage{$group}, $recording);
295
296                                                print "\tDeleting metadata for recording '$recording' in storage ".
297                                                  "group '$group' in recgroup '$recgroup'..\n";
298                                                deleteFromDb($chanid, $starttime, $recording, $DELETED_RECGROUP);
299
300                                                if ($$thumbs{$recording}) {
301                                                        foreach my $thumb (@{$$thumbs{$recording}}) {
302                                                                print "\tDeleting thumbnail '$thumb'..\n";
303                                                                unlinkFile($$storage{$group}, $thumb);
304                                                        }
305                                                }
306                                        }
307                                }
308                        }
309                }
310        }
311
312        foreach my $recording (keys %{$$recordings{$from}}) {
313                my $size = recordingSize($$storage{$from}, $recording);
314                my $recgroup = $$recordings{$from}->{$recording}->{'RECGROUP'};
315                my $chanid = $$recordings{$from}->{$recording}->{'CHANID'};
316                my $starttime = $$recordings{$from}->{$recording}->{'START'};
317                my $title = $$recordings{$from}->{$recording}->{'TITLE'};
318                my $subtitle = $$recordings{$from}->{$recording}->{'SUBTITLE'};
319                my $islivetv = $$recordings{$from}->{$recording}->{'LIVETV'};
320
321                $islivetv = 'LiveTV' if ($islivetv);
322
323                if ($size == 0) {
324                        if ($modes & $MODE_ZEROBYTE) {
325                                my $recgroup = $$recordings{$from}->{$recording}->{'RECGROUP'};
326                                print "\tDeleting zero-byte recording '$recording' in storage group '$from' ".
327                                  "in recgroup '$recgroup'..\n";
328                                unlinkFile($$storage{$from}, $recording);
329
330                                print "\tDeleting metadata for recording '$recording' in storage group '$from' ".
331                                  "in recgroup '$recgroup'..\n";
332                                deleteFromDb($chanid, $starttime, $recording, $recgroup);
333
334                                if ($$thumbs{$recording}) {
335                                        foreach my $thumb (@{$$thumbs{$recording}}) {
336                                                print "\tDeleting thumbnail '$thumb'..\n";
337                                                unlinkFile($$storage{$from}, $thumb);
338                                        }
339                                }
340                        } else {
341                                print "WARNING: File '$recording' for $islivetv recording of '$title - $subtitle' ".
342                                  "is 0 bytes in size!\n";
343                        }
344                } elsif ($modes & $MODE_ARCHIVE) {
345                        if (!$ignorelivetv || ($ignorelivetv && !$islivetv)) {
346                                my $fromDir = $$storage{$from};
347                                my $toDir = $$storage{$to};
348
349                                print "\tArchiving recording '$recording':\n\t\tTitle '$title - $subtitle' from ".
350                                  "'$fromDir' to '$toDir'..\n";
351
352                                if (!archiveFile($fromDir, $toDir, $recording, $samefs)) {
353                                        updateStorageGroup($chanid, $starttime, $recording, $to);
354                                }
355
356                                if ($$thumbs{$recording}) {
357                                        foreach my $thumb (@{$$thumbs{$recording}}) {
358                                                archiveFile($fromDir, $toDir, $thumb, $samefs);
359                                        }
360                                }
361                        }
362                }
363        }
364
365        # Now print some stats..
366        print "\n\tStorage Group\tSize\tPortion\n";
367        print "\t----------------------------------------\n";
368
369        my $total = 0;
370        foreach my $group (keys %{$recordings}) {
371                next if (!$group);
372                my $group_s = 0;
373                my(undef, $mp_size, undef, undef) = getMountpoint($$storage{$group});
374                $mp_size = $mp_size * 1024;
375                $mp_size -= (.05 * $mp_size);
376
377                foreach my $recording (keys %{$$recordings{$group}}) {
378                        my $size = recordingSize($$storage{$group}, $recording);
379                        $group_s += $size;
380                }
381
382                my $portion = round((($group_s / $mp_size) * 100), 2) if ($mp_size);
383
384                print "\t$group\t\t$group_s\t$portion%\n";
385                $total += $group_s;
386        }
387
388        print "\t----------------------------------------\n";
389        print "\tTotal:\t\t$total\n\n";
390
391        print "All done!\n";
392}
393
394sub getStorageGroups {
395        my $hostname = shift;
396        my %groups;
397        my $where;
398
399        $where = "WHERE hostname = '$hostname'" if ($hostname);
400
401        my $sth = $DBH->prepare("SELECT groupname, dirname FROM storagegroup $where");
402        $sth->execute();
403
404        while (my $row = $sth->fetchrow_hashref()) {
405                my $path = $row->{'dirname'};
406                $path =~ s/\/+$//;
407
408                $groups{$row->{'groupname'}} = $path;
409        }
410
411        $sth->finish();
412
413        return \%groups;
414}
415
416sub getRecordings {
417        my $hostname = shift;
418        my %recordings;
419        my $where;
420
421        $where = "WHERE hostname = '$hostname'" if ($hostname);
422
423        my $sth = $DBH->prepare("SELECT chanid, starttime, basename, title, subtitle, ".
424          "autoexpire, storagegroup, recgroup FROM recorded $where");
425        $sth->execute();
426
427        while (my $row = $sth->fetchrow_hashref()) {
428                $recordings{$row->{'storagegroup'}}->{$row->{'basename'}}->{'TITLE'} =
429                  $row->{'title'};
430                $recordings{$row->{'storagegroup'}}->{$row->{'basename'}}->{'SUBTITLE'} =
431                  $row->{'subtitle'};
432                $recordings{$row->{'storagegroup'}}->{$row->{'basename'}}->{'CHANID'} =
433                  $row->{'chanid'};
434                $recordings{$row->{'storagegroup'}}->{$row->{'basename'}}->{'START'} =
435                  $row->{'starttime'};
436                $recordings{$row->{'storagegroup'}}->{$row->{'basename'}}->{'RECGROUP'} =
437                  $row->{'recgroup'};
438                $recordings{$row->{'storagegroup'}}->{$row->{'basename'}}->{'LIVETV'} =
439                  ($row->{'autoexpire'} >= $LIVETV_AUTOEXPIRE) ? $TRUE : $FALSE;
440        }
441
442        $sth->finish();
443
444        return \%recordings;
445}
446
447sub getStoredRecordings {
448        my $storage = shift;
449        my %stored;
450        my %cifs;
451        my %thumbs;
452
453        foreach my $group (keys %{$storage}) {
454                my $path = $$storage{$group};
455                my $dir = new DirHandle($path);
456
457                die("FATAL: Directory '$path' for storage group '$group' does not exist!\n")
458                  if (!defined($dir));
459
460                while (defined(my $entry = $dir->read())) {
461                        next if (-d $entry);
462                        next if (($entry !~ /\.mpg$/i) &&
463                                 ($entry !~ /\.nuv$/i) &&
464                                 ($entry !~ /\.png$/i) &&
465                                 ($entry !~ /^cifs/));
466
467                        if ($entry =~ /\.mpg$/) {
468                                $stored{$group}->{$entry} = recordingSize($path, $entry);
469                        } elsif ($entry =~ /\.png$/) {
470                                if ($entry =~ /^(.+\.(mpg|nuv))\./i) {
471                                        push @{$thumbs{$1}}, $entry;
472                                }
473                        } elsif ($entry =~ /^cifs/) {
474                                $cifs{$group}->{$entry} = recordingSize($path, $entry);
475                        }
476                }
477        }
478
479        return(\%stored, \%cifs, \%thumbs);
480}
481
482sub updateStorageGroup {
483        my $chanid = shift;
484        my $starttime = shift;
485        my $recording = shift;
486        my $group = shift;
487
488        my $sql = "UPDATE recorded SET storagegroup = '$group' ".
489          "WHERE chanid = $chanid AND starttime = '$starttime' AND basename = '$recording'";
490
491        if ($_DEBUG_) {
492                print "DBG: updateStorageGroup(): $sql\n";
493        } else {
494                my $sth = $DBH->prepare($sql);
495                $sth->execute();
496                $sth->finish();
497        }
498}
499
500sub findRecording {
501        my $storage = shift;
502        my $recording = shift;
503        my $chanid = shift;
504        my $starttime = shift;
505        my $found;
506
507        foreach my $group (keys %{$storage}) {
508                my $directory = $$storage{$group};
509
510                if (-e "$directory/$recording") {
511                        if ($found) {
512                                print "WARNING: Found recording '$recording' in multiple groups, ignoring!\n";
513                                return undef;
514                        }
515
516                        $found = $group;
517                }
518        }
519
520        if (!$found) {
521                print "WARNING: Unable to find recording '$recording' in any storage group!\n";
522                return undef;
523        }
524
525        return $found;
526}
527
528sub deleteFromDb {
529        my $chanid = shift;
530        my $starttime = shift;
531        my $basename = shift;
532        my $recgroup = shift;
533
534        doSql("DELETE FROM recorded WHERE chanid = $chanid AND starttime = '$starttime' AND ".
535          "basename = '$basename' AND recgroup = '$recgroup'");
536        doSql("DELETE FROM recordedcredits WHERE chanid = $chanid AND starttime = '$starttime'");
537        doSql("DELETE FROM recordedmarkup WHERE chanid = $chanid AND starttime = '$starttime'");
538        doSql("DELETE FROM recordedprogram WHERE chanid = $chanid AND starttime = '$starttime'");
539        doSql("DELETE FROM recordedrating WHERE chanid = $chanid AND starttime = '$starttime'");
540        doSql("DELETE FROM recordedseek WHERE chanid = $chanid AND starttime = '$starttime'");
541}
542
543sub archiveFile {
544        my $fromDir = shift;
545        my $toDir = shift;
546        my $file = shift;
547        my $samefs = shift;
548
549        if (!-e "$fromDir/$file") {
550                print "WARNING: Attempting to archive the file '$fromDir/$file' which doesn't exist!\n";
551                return $TRUE;
552        }
553
554        if (-e "$toDir/$file") {
555                print "WARNING: Recording '$file' already exists in destination group, ignoring!\n";
556                return $TRUE;
557        }
558
559        if (!$samefs && !checkForSpace($fromDir, $toDir, $file)) {
560                print "Storage group directory '$toDir' is too full to achive recording '$file', ignoring.\n";
561                return $TRUE;
562        }
563
564        if ($_DEBUG_) {
565                print "DBG: Would archive '$fromDir/$file' to '$toDir/$file' mode ".($samefs ? "mv" : "cp")."\n";
566        } else {
567                if ($samefs) {
568                        if (!move("$fromDir/$file", "$toDir/$file")) {
569                                print "WARNING: Failed to archive/move recording $file from $fromDir to $toDir: $!\n";
570                                return $TRUE;
571                        }
572                } else {
573                        if (!copy("$fromDir/$file", "$toDir/$file")) {
574                                print "WARNING: Failed to archive/copy recording $file from $fromDir to $toDir: $!\n";
575                                return $TRUE;
576                        }
577                }       
578        }
579
580        my $orig_size = recordingSize($fromDir, $file);
581        my $copy_size = recordingSize($toDir, $file);
582
583        if (!$samefs && ($orig_size != $copy_size)) {
584                print "WARNING: Failed to copy recording '$file' from $fromDir to $toDir, file sizes don't match!\n"
585                  if (!$_DEBUG_);
586                return $TRUE;
587        } elsif (!$samefs) {
588                unlinkFile($fromDir, $file);
589        }
590
591        return $FALSE;
592}
593
594sub unlinkFile {
595        my $fromDir = shift;
596        my $file = shift;
597
598        return $TRUE if (!$fromDir || !$file);
599
600        if (-e "$fromDir/$file") {
601                if ($_DEBUG_) {
602                        print "DBG: Would unlink \"$fromDir/$file\"\n";
603                } else {
604                        if (!unlink "$fromDir/$file") {
605                                print "WARNING: Failed to unlink file $file in $fromDir: $!\n";
606                                return $TRUE;
607                        }
608                }
609        }
610
611        return $FALSE;
612}
613
614sub isFileInUse {
615        my $file = shift;
616
617        my $result = `fuser $file`;
618
619        $result =~ s/\s+$//;
620
621        my(undef, $pids) = split(/\:/, $result);
622
623        return $pids;
624}
625
626sub checkForSpace {
627        my $fromDir = shift;
628        my $toDir = shift;
629        my $file = shift;
630        my $mount;
631        my $available;
632        my $percentage;
633
634        my(undef, undef, $available, $percentage) = getMountpoint($toDir);
635
636        my $file_size = recordingSize($fromDir, $file);
637
638        return $FALSE if (($available < $file_size) || ($percentage > 98));
639        return $TRUE;
640}
641
642sub getMountpoint {
643        my $directory = shift;
644        my $mp;
645        my $size;
646        my $free;
647        my $percentage;
648
649        my $buff = `df -kP $directory`;
650
651        foreach my $line (split(/\n/, $buff)) {
652                ($mp, $size, undef, $free, $percentage) = split(/\s+/, $line, 5)
653                  if ($line !~ /^Filesystem/);
654        }
655
656        $free = $free * 1024;
657        $percentage =~ s/\%//;
658
659        return($mp, $size, $free, $percentage);
660}
661
662sub recordingSize {
663        my $directory = shift;
664        my $recording = shift;
665
666        if (!-e "$directory/$recording") {
667                return -1;
668        }
669
670        my $size = -s "$directory/$recording";
671
672        return $size;
673}
674
675sub round {
676        my $num = shift;
677        my $place = shift;
678
679        my($whole, $decimal) = split(/\./, $num);
680        my $decimal_t = substr($decimal, 0, $place);
681
682        if ($decimal_t) {
683                my $rounder = substr($decimal, $place, 1);
684                $decimal_t += 1 if ($rounder && $rounder > 5);
685        } else {
686                $decimal_t = 0;
687        }
688
689        return $whole.".".$decimal_t;
690}
691
692sub modesToString {
693        my $modes = shift;
694
695        if (!$modes) {
696                return "\nNo mode specified, checking only.\n\n";
697        }
698
699        my $string = "\nWorking in the following modes:\n";
700
701        if ($modes & $MODE_ARCHIVE) {
702                $string .= "\t-> Archive Recordings\n";
703        }
704
705        if ($modes & $MODE_ABANDONED) {
706                $string .= "\t-> Delete abandoned recordings\n";
707        }
708
709        if ($modes & $MODE_ZEROBYTE) {
710                $string .= "\t-> Delete zero-byte recordings\n";
711        }
712
713        if ($modes & $MODE_METADATA) {
714                $string .= "\t-> Delete meta-data for missing recordings\n";
715        }
716
717        if ($modes & $MODE_DELETED) {
718                $string .= "\t-> Delete recordings in 'Deleted' group\n";
719        }
720
721        $string .= "\n";
722
723        return $string;
724}
725
726sub doSql {
727        my $sql = shift;
728
729        if ($_DEBUG_) {
730                print "DBG: $sql\n";
731        } else {
732                my $sth = $DBH->prepare($sql);
733
734                $sth->execute();
735                $sth->finish();
736        }
737}