Subversion Repositories programming

Rev

Rev 277 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
129 ira 1
#!/usr/bin/env python
2
 
277 ira 3
# Copyright (c) 2005,2006 Ira W. Snyder (devel@irasnyder.com)
129 ira 4
# License: GNU General Public License v2 (or at your option, any later version)
5
#
6
# Changelog Follows:
7
# - 2005-10-13
8
# - Added get_par2_filenames() to parse par2 files
9
# - Added the parset object to represent each parset.
10
#
11
# - 2005-10-14
12
# - Finished the parset object. It will now verify and extract parsets.
13
# - Small changes to the parset object. This makes the parjoin part
14
#   much more reliable.
15
# - Added the OptionParser to make this nice to run at the command line.
16
# - Made recursiveness an option.
17
# - Made start directory an option.
18
# - Check for appropriate programs before starting.
19
#
134 ira 20
# - 2005-10-17
21
# - Use a regular expression to handle the deletable types.
22
#
23
# - 2005-10-18
24
# - Use regular expressions to handle all finding of files, instead of
25
#   using the glob module.
26
# - Add a config class to handle all the default config stuff sanely.
27
#   This makes it easier to change some of the main parts of the program to
28
#   your specific configuration.
29
# - Move the docrcchecks variable inside the get_par2_filenames() function,
30
#   which is where it belongs anyway.
31
# - Added command-line option to check for required programs at start.
32
#
139 ira 33
# - 2005-10-20
34
# - Added a config option to extract with full path.
35
#
141 ira 36
# - 2005-10-22
37
# - Re-wrote the config class so that there is a config file, which
38
#   resides at ~/.config/rarslave/rarslave.conf by default.
39
# - Added the command-line option -c to write out an updated version
40
#   of the config file (to fill in any missing options with the defaults)
41
# - Added the command-line option -f to write out a new default config file,
42
#   which overwrites any user changes.
43
# - Made all regexes case insensitive.
44
# - Made all command-line options override the config file.
45
#
150 ira 46
# - 2005-10-30
47
# - Added the '-o' option, to output debugging info. Hopefully next time
48
#   someone finds a bug, they can output this and send it to me with a
49
#   description of the bug they're seeing.
50
#
152 ira 51
# - 2005-11-05
52
# - Added an output system to rarslave. This makes a nice status report
53
#   possible at the end of the program run.
54
#
153 ira 55
# - 2005-11-06
56
# - Fixed the rar command so that it can extract files whose names begin
57
#   with a hyphen.
58
#
275 ira 59
# - 2006-03-08
60
# - Make an interactive mode which asks the user before deleting files.
278 ira 61
# - Make multi-file parjoin work.
275 ira 62
#
129 ira 63
 
64
################################################################################
65
# REQUIREMENTS:
66
#
67
# This code requires the programs cfv, par2repair, lxsplit, and rar to be able
68
# to function properly. I will attempt to check that these are in your path.
69
################################################################################
70
 
141 ira 71
import ConfigParser, os
72
 
134 ira 73
class rarslave_config:
74
    """A simple class to hold the default configs for the whole program"""
129 ira 75
 
141 ira 76
    def __read_config(self, filename='~/.config/rarslave/rarslave.conf'):
77
        """Attempt to open and read the rarslave config file"""
134 ira 78
 
141 ira 79
        # Make sure the filename is corrected
80
        filename = os.path.abspath(os.path.expanduser(filename))
81
 
82
        user_config = {}
83
 
84
        # Write the default config if it doesn't exist
85
        if not os.path.isfile(filename):
86
            self.write_config(default=True)
87
 
88
        config = ConfigParser.ConfigParser()
89
        config.read(filename)
90
 
91
        for section in config.sections():
92
            for option in config.options(section):
93
                user_config[(section, option)] = config.get(section, option)
94
 
95
        return user_config
96
 
97
    def write_config(self, filename='~/.config/rarslave/rarslave.conf', default=False):
98
        """Write out the current config to the config file. If you set default=True, then
99
        the default config file will be written."""
100
 
101
        config = ConfigParser.ConfigParser()
102
 
103
        # Correct filename
104
        filename = os.path.abspath(os.path.expanduser(filename))
105
 
106
        # Reset all config to make sure we write the default one, if necessary
107
        if default:
108
            self.__user_config = {}
109
            print 'Writing default config to %s' % (filename, )
110
 
111
        # [directories] section
112
        config.add_section('directories')
113
        for (s, k) in self.__defaults.keys():
114
            if s == 'directories':
115
                config.set(s, k, self.get_value(s, k))
116
 
117
        # [options] section
118
        config.add_section('options')
119
        for (s, k) in self.__defaults.keys():
120
            if s == 'options':
121
                config.set(s, k, self.get_value(s, k))
122
 
123
        # [regular_expressions] section
124
        config.add_section('regular expressions')
125
        for (s, k) in self.__defaults.keys():
126
            if s == 'regular expressions':
127
                config.set(s, k, self.get_value(s, k))
128
 
129
        # Try to make the ~/.config/rarslave/ directory
130
        if not os.path.isdir(os.path.split(filename)[0]):
131
            try:
132
                os.makedirs(os.path.split(filename)[0])
133
            except:
134
                print 'Could not make directory: %s' % (os.path.split(filename)[0], )
135
                sys.exit()
136
 
137
        # Try to write the config file to disk
138
        try:
139
            fsock = open(filename, 'w')
140
            try:
141
                config.write(fsock)
142
            finally:
143
                fsock.close()
144
        except:
145
            print 'Could not open: %s for writing' % (filename, )
146
            sys.exit()
147
 
148
    def __get_default_val(self, section, key):
149
        return self.__defaults[(section, key)]
150
 
151
    def get_value(self, section, key):
152
        """Get a config value. Attempts to get the value from the user's
153
        config first, and then uses the default."""
154
 
155
        try:
156
            value = self.__user_config[(section, key)]
157
        except:
158
            # This should work, unless you write something stupid
159
            # into the code, so DON'T DO IT
160
            value = self.__get_default_val(section, key)
161
 
162
        # Convert config options to booleans for easier use
163
        if value == 'True':
164
            value = True
165
 
166
        if value == 'False':
167
            value = False
168
 
169
        return value
170
 
134 ira 171
    def __init__(self):
141 ira 172
        self.__defaults = {
173
            ('directories', 'working_directory') : '~/downloads/usenet',
174
            ('options', 'recursive') : True,
175
            ('options', 'check_required_programs') : False,
176
            ('options', 'extract_with_full_path') : False,
275 ira 177
            ('options', 'interactive') : False,
141 ira 178
            ('regular expressions', 'par2_regex') : '.*\.par2$',
179
            ('regular expressions', 'video_file_regex') : '.*\.(avi|ogm|mkv|mp4)$',
180
            ('regular expressions', 'temp_repair_regex') : '.*\.1$',
181
            ('regular expressions', 'remove_regex') : '^.*\.(rar|r\d\d)$' }
134 ira 182
 
141 ira 183
        self.__user_config = self.__read_config()
184
 
185
# This is the global config variable.
134 ira 186
config = rarslave_config()
141 ira 187
 
275 ira 188
# This is the global options variable. (to be set later)
189
options = None
190
 
129 ira 191
################################################################################
152 ira 192
# The rarslave_output class
193
#
194
# This class handles the nice output summary which is printed at the end
195
# of a run
196
################################################################################
197
 
198
class rarslave_output:
199
    # Data structure: list of lists
200
    # [ [status, filename], ... ]
201
    #
202
    # Where status is one of:
203
    # 0: Verified and Extracted Perfectly
204
    # 1: Failed to Verify (and therefore Extract)
205
    # 2: Verified correctly, but failed to Extract
206
    #
207
 
208
    def __init__(self):
209
        self.output_list    = []
210
        self.good_files     = 0
211
        self.unverified     = 0
212
        self.unextractable  = 0
213
        self.corrupt_par2   = 0
214
 
215
    def print_equal_line(self, size=80):
216
        """Print an 80 character line of equal signs"""
217
 
218
        str = ''
219
 
220
        for i in range(size):
221
            str += '='
222
 
223
        print str
224
 
225
    def print_results_table(self):
226
        """Print a nice table of the results from this run"""
227
 
228
        # Print the table of good files (if we have any)
229
        if self.good_files > 0:
230
            print
231
            self.print_equal_line()
232
            print 'Files that were extracted perfectly'
233
            self.print_equal_line()
234
 
235
            for entry in self.output_list:
236
                if entry[0] == 0:
237
                    print '%s' % (entry[1], )
238
 
239
        # Print the table of unverified files (if we have any)
240
        if self.unverified > 0:
241
            print
242
            self.print_equal_line()
243
            print 'Files that failed to verify (and extract)'
244
            self.print_equal_line()
245
 
246
            for entry in self.output_list:
247
                if entry[0] == 1:
248
                    print '%s' % (entry[1], )
249
 
250
        # Print the table of unextracted files (if we have any)
251
        if self.unextractable > 0:
252
            print
253
            self.print_equal_line()
254
            print 'Files that were verified, but failed to extract'
255
            self.print_equal_line()
256
 
257
            for entry in self.output_list:
258
                if entry[0] == 2:
259
                    print '%s' % (entry[1], )
260
 
261
        # Print the table of corrupt PAR2 files (if we have any)
262
        if self.corrupt_par2 > 0:
263
            print
264
            self.print_equal_line()
265
            print 'Files that had corrupt par2 files'
266
            self.print_equal_line()
267
 
268
            for entry in self.output_list:
269
                if entry[0] == 3:
270
                    print '%s' % (entry[1], )
271
 
272
        # Print a blank line at the end
273
        print
274
 
275
    def add_file(self, status, filename):
276
 
277
        if status == 0:
278
            self.good_files += 1
279
        elif status == 1:
280
            self.unverified += 1
281
        elif status == 2:
282
            self.unextractable += 1
283
        elif status == 3:
284
            self.corrupt_par2 += 1
285
        else:
286
            # We have a bad value, so raise a ValueError
287
            raise ValueError
288
 
289
        self.output_list.append([status, filename])
290
 
291
# This is the global output variable
292
output = rarslave_output()
293
 
294
################################################################################
129 ira 295
# The PAR2 Parser
296
#
297
# This was stolen from cfv (see http://cfv.sourceforge.net/ for a copy)
298
################################################################################
299
 
300
import struct, errno
301
 
302
def chompnulls(line):
303
    p = line.find('\0')
304
    if p < 0: return line
305
    else:     return line[:p]
306
 
307
def get_par2_filenames(filename):
308
    """Get all of the filenames that are protected by the par2
309
    file given as the filename"""
310
 
311
    try:
312
        file = open(filename, 'rb')
313
    except:
314
        print 'Could not open %s' % (filename, )
315
        return []
316
 
134 ira 317
    # We always want to do crc checks
318
    docrcchecks = True
319
 
129 ira 320
    pkt_header_fmt = '< 8s Q 16s 16s 16s'
321
    pkt_header_size = struct.calcsize(pkt_header_fmt)
322
    file_pkt_fmt = '< 16s 16s 16s Q'
323
    file_pkt_size = struct.calcsize(file_pkt_fmt)
324
    main_pkt_fmt = '< Q I'
325
    main_pkt_size = struct.calcsize(main_pkt_fmt)
326
 
327
    seen_file_ids = {}
328
    expected_file_ids = None
329
    filenames = []
330
 
331
    while 1:
332
        d = file.read(pkt_header_size)
333
        if not d:
334
            break
335
 
336
        magic, pkt_len, pkt_md5, set_id, pkt_type = struct.unpack(pkt_header_fmt, d)
337
 
338
        if docrcchecks:
339
            import md5
340
            control_md5 = md5.new()
341
            control_md5.update(d[0x20:])
342
            d = file.read(pkt_len - pkt_header_size)
343
            control_md5.update(d)
344
 
345
            if control_md5.digest() != pkt_md5:
346
                raise EnvironmentError, (errno.EINVAL, \
347
                    "corrupt par2 file - bad packet hash")
348
 
349
        if pkt_type == 'PAR 2.0\0FileDesc':
350
            if not docrcchecks:
351
                d = file.read(pkt_len - pkt_header_size)
352
 
353
            file_id, file_md5, file_md5_16k, file_size = \
354
                struct.unpack(file_pkt_fmt, d[:file_pkt_size])
355
 
356
            if seen_file_ids.get(file_id) is None:
357
                seen_file_ids[file_id] = 1
358
                filename = chompnulls(d[file_pkt_size:])
359
                filenames.append(filename)
360
 
361
        elif pkt_type == "PAR 2.0\0Main\0\0\0\0":
362
            if not docrcchecks:
363
                d = file.read(pkt_len - pkt_header_size)
364
 
365
            if expected_file_ids is None:
366
                expected_file_ids = []
367
                slice_size, num_files = struct.unpack(main_pkt_fmt, d[:main_pkt_size])
368
                num_nonrecovery = (len(d)-main_pkt_size)/16 - num_files
369
 
370
                for i in range(main_pkt_size,main_pkt_size+(num_files+num_nonrecovery)*16,16):
371
                    expected_file_ids.append(d[i:i+16])
372
 
373
        else:
374
            if not docrcchecks:
375
                file.seek(pkt_len - pkt_header_size, 1)
376
 
377
    if expected_file_ids is None:
378
        raise EnvironmentError, (errno.EINVAL, \
379
            "corrupt or unsupported par2 file - no main packet found")
380
 
381
    for id in expected_file_ids:
382
        if not seen_file_ids.has_key(id):
383
            raise EnvironmentError, (errno.EINVAL, \
384
                "corrupt or unsupported par2 file - " \
385
                "expected file description packet not found")
386
 
387
    return filenames
388
 
389
################################################################################
390
# The parset object
391
#
392
# This is an object based representation of a parset, and will verify itself
393
# and extract itself, if possible.
394
################################################################################
395
 
132 ira 396
import os, glob, re
129 ira 397
 
398
class parset:
399
    def __init__(self, par_filename):
400
        self.parfile = par_filename
401
        self.extra_pars = []
402
        self.files = False
403
        self.used_parjoin = False
404
        self.verified = False
405
        self.extracted = False
406
 
150 ira 407
    def print_debug_info(self):
408
        """Special function for debugging"""
409
        print '========== DEBUG INFO STARTS HERE =========='
220 ira 410
        print '=== parfile ==='
411
        print self.parfile
412
        print
152 ira 413
 
220 ira 414
        print '=== extra_pars ==='
415
        for f in self.extra_pars:
416
            print f
275 ira 417
 
220 ira 418
        print
419
 
420
        print '=== files ==='
421
        for f in self.files:
422
            print f
423
 
424
        print '=========== DEBUG INFO ENDS HERE ==========='
425
 
129 ira 426
    def get_filenames(self):
152 ira 427
        return get_par2_filenames(self.parfile)
129 ira 428
 
429
    def all_there(self):
430
        """Check if all the files for the parset are present.
431
        This will help us decide which par2 checker to use first"""
432
        for f in self.files:
433
            if not os.path.isfile(f):
434
                return False
435
 
436
        # The files were all there
437
        return True
438
 
439
    def verify(self):
440
        """This will verify the parset by the most efficient method first,
441
        and then move to a slower method if that one fails"""
442
 
443
        retval = False #not verified yet
444
 
445
        # if all the files are there, try verifying fast
446
        if self.all_there():
447
            retval = self.__fast_verify()
448
 
449
            if retval == False:
450
                # Failed to verify fast, so try it slow, maybe it needs repair
451
                retval = self.__slow_verify()
452
 
278 ira 453
        # If we've got video files, maybe we should try to parjoin them
454
        elif self.__number_of_video_files() > 0:
129 ira 455
            retval = self.__parjoin()
456
 
457
        else: #not all there, maybe we can slow-repair
458
            retval = self.__slow_verify()
459
 
460
        self.verified = retval
461
        return self.verified
462
 
463
    def __fast_verify(self):
464
        retval = os.system('cfv -v -f "%s"' % (self.parfile, ))
465
 
466
        if retval == 0:
467
            return True #success
468
 
469
        return False #failure
470
 
471
    def __slow_verify(self):
472
        retval = os.system('par2repair "%s"' % (self.parfile, ))
473
 
474
        if retval == 0:
475
            return True #success
476
 
477
        return False #failure
478
 
479
    def __parjoin(self):
480
 
278 ira 481
        # Join all of the files
482
        for f in self.files:
483
            if self.__is_video_file(f):
484
                self.__parjoin_single(f)
485
 
486
        # Re-verify the parset
129 ira 487
        retval = self.__fast_verify()
488
 
489
        if retval == False:
490
            # Failed to verify fast, so try it slow, maybe it needs repair
491
            retval = self.__slow_verify()
492
 
278 ira 493
        # Failed to verify, so remove all of the lxsplit created files
494
        if retval == False:
495
            for f in self.files:
496
                if self.__is_video_file(f):
497
                    try:
498
                        os.remove(f)
499
                    except OSError:
500
                        print 'Failed to remove file: %s' % (f, )
129 ira 501
 
502
        self.used_parjoin = retval
503
        self.verified = retval
504
        return self.verified
505
 
278 ira 506
    def __parjoin_single(self, base_file):
507
 
508
        parjoinable = False
509
        regex = re.compile("^%s\.001$" % (re.escape(base_file), ), re.IGNORECASE)
510
 
511
        for f in os.listdir(os.getcwd()):
512
            if regex.match(f):
513
                parjoinable = True
514
                break
515
 
516
        if parjoinable:
517
            retval = os.system('lxsplit -j "%s.001"' % (base_file, ))
518
            return retval
519
 
520
        return False
521
 
522
    def __is_video_file(self, file):
523
 
141 ira 524
        regex = re.compile(
525
                config.get_value('regular expressions', 'video_file_regex'),
526
                re.IGNORECASE)
527
 
278 ira 528
        if regex.match(file):
529
            return True
530
 
531
        return False
532
 
533
    def __number_of_video_files(self):
534
 
535
        count = 0
536
 
129 ira 537
        for f in self.files:
278 ira 538
            if self.__is_video_file(f):
539
                count += 1
540
 
541
        return count
542
 
543
    def __remove_split_files(self, base_name, files_to_remove):
544
        """Store base_name.{001,002, ... , 999} files in the list
545
        $files_to_remove"""
546
 
547
        base_name_regex = "^%s\.\d\d\d$" % (re.escape(base_name), )
548
 
549
        regex = re.compile(base_name_regex, re.IGNORECASE)
550
 
551
        for f in os.listdir(os.getcwd()):
134 ira 552
            if regex.match(f):
278 ira 553
                files_to_remove.append(f)
129 ira 554
 
555
    def __remove_currentset(self):
556
        """Remove all of the files that are extractable, as well as the pars.
557
        Leave everything else alone"""
558
 
559
        if not self.extracted:
560
            print 'Did not extract yet, not removing currentset'
561
            return
562
 
275 ira 563
        files_to_remove = []
564
 
129 ira 565
        # remove the main par
275 ira 566
        files_to_remove.append(self.parfile)
129 ira 567
 
568
        # remove all of the extra pars
569
        for i in self.extra_pars:
275 ira 570
            files_to_remove.append(i)
129 ira 571
 
572
        # remove any rars that are associated (leave EVERYTHING else)
134 ira 573
        # This regex matches both old and new style rar(s) by default.
141 ira 574
        regex = re.compile(
575
                config.get_value('regular expressions', 'remove_regex'),
576
                re.IGNORECASE)
134 ira 577
 
129 ira 578
        for i in self.files:
132 ira 579
            if regex.match(i):
275 ira 580
                files_to_remove.append(i)
129 ira 581
 
134 ira 582
        # remove any .{001,002,...} files (from parjoin)
129 ira 583
        if self.used_parjoin:
278 ira 584
            for f in self.files:
585
                self.__remove_split_files(f, files_to_remove)
129 ira 586
 
587
        # remove any temp repair files
141 ira 588
        regex = re.compile(
589
                config.get_value('regular expressions', 'temp_repair_regex'),
590
                re.IGNORECASE)
275 ira 591
        [files_to_remove.append(f) for f in os.listdir(os.getcwd()) if regex.match(f)]
129 ira 592
 
275 ira 593
        # interactively remove files
594
        if options.interactive:
595
 
596
            print # blank line
597
            for f in files_to_remove:
598
                print f
599
 
600
            print '========================================'
601
 
602
            done = False
603
            while not done:
604
                s = raw_input("Delete files [y,n]: ")
605
                s.lower()
606
 
607
                if s == 'y' or s == 'yes':
608
                    done = True
609
                    self.__remove_list_of_files(files_to_remove)
610
                elif s == 'n' or s == 'no':
611
                    done = True
612
                    print 'Not removing files'
613
                else:
614
                    print 'Bad selection, try again...'
615
        else:
616
            self.__remove_list_of_files(files_to_remove)
617
 
618
    def __remove_list_of_files(self, files_to_remove):
619
        """Remove all files in the list"""
620
 
276 ira 621
        # remove duplicates from the list
622
        temp = []
623
 
275 ira 624
        for f in files_to_remove:
276 ira 625
            if f not in temp:
626
                temp.append(f)
275 ira 627
 
276 ira 628
        files_to_remove = temp
629
 
630
        # remove the files
631
        for f in files_to_remove:
632
            try:
633
                os.remove(f)
634
            except OSError:
635
                print 'WW: Problem deleting: %s' % f
636
 
129 ira 637
    def __get_extract_file(self):
638
        """Find the first extractable file"""
639
        for i in self.files:
640
            if os.path.splitext(i)[1] == '.rar':
641
                return i
642
 
643
        return None
644
 
645
    def extract(self):
646
        """Attempt to extract all of the files related to this parset"""
647
        if not self.verified:
648
            self.extracted = False
152 ira 649
            output.add_file(1, self.parfile)
129 ira 650
            return False #failed to extract
651
 
652
        extract_file = self.__get_extract_file()
653
 
654
        if extract_file != None:
141 ira 655
            if config.get_value('options', 'extract_with_full_path'):
153 ira 656
                retval = os.system('rar x -o+ -- "%s"' % (extract_file, ))
139 ira 657
            else:
153 ira 658
                retval = os.system('rar e -o+ -- "%s"' % (extract_file, ))
129 ira 659
 
660
            if retval != 0:
152 ira 661
                output.add_file(2, self.parfile)
129 ira 662
                self.extracted = False
663
                return self.extracted
664
 
665
        # we extracted ok, so remove the currentset
666
        self.extracted = True
667
        self.__remove_currentset()
668
 
152 ira 669
        output.add_file(0, self.parfile)
670
 
129 ira 671
        return self.extracted
672
 
673
 
674
################################################################################
675
# The rarslave program itself
676
################################################################################
677
 
134 ira 678
import os, sys
129 ira 679
from optparse import OptionParser
680
 
681
def check_required_progs():
682
    """Check if the required programs are installed"""
683
 
684
    shell_not_found = 32512
685
    needed = []
686
 
687
    if os.system('cfv --help > /dev/null 2>&1') == shell_not_found:
688
        needed.append('cfv')
689
 
690
    if os.system('par2repair --help > /dev/null 2>&1') == shell_not_found:
691
        needed.append('par2repair')
692
 
693
    if os.system('lxsplit --help > /dev/null 2>&1') == shell_not_found:
694
        needed.append('lxpsplit')
695
 
696
    if os.system('rar --help > /dev/null 2>&1') == shell_not_found:
697
        needed.append('rar')
698
 
699
    if needed:
700
        for n in needed:
701
            print 'Needed program "%s" not found in $PATH' % (n, )
702
 
703
        sys.exit(1)
704
 
705
def get_parsets():
706
    """Get a representation of each parset in the current directory, and
707
    return them as a list of parset instances"""
708
 
141 ira 709
    regex = re.compile(
710
            config.get_value('regular expressions', 'par2_regex'),
711
            re.IGNORECASE)
134 ira 712
    par2files = [f for f in os.listdir(os.getcwd()) if regex.match(f)]
129 ira 713
 
714
    parsets = []
715
 
716
    for i in par2files:
132 ira 717
        try:
718
            filenames = get_par2_filenames(i)
719
            create_new = True
720
        except EnvironmentError:
152 ira 721
            output.add_file(3, i)
132 ira 722
            continue
129 ira 723
 
724
        # if we already have an instance for this set, append
725
        # this par file to the extra_pars field
726
        for j in parsets:
727
            if j.files == filenames:
728
                j.extra_pars.append(i)
729
                create_new = False
730
 
731
        # we haven't seen this set yet, so we'll create it now
732
        if create_new == True:
733
            cur = parset(i)
734
            cur.files = filenames
735
            parsets.append(cur)
736
 
737
    return parsets
738
 
275 ira 739
def directory_worker(dir):
129 ira 740
    """Attempts to find, verify, and extract every parset in the directory
741
    given as a parameter"""
742
 
743
    cwd = os.getcwd()
744
    os.chdir(dir)
745
 
746
    parsets = get_parsets()
747
 
150 ira 748
    # Print debug info if we're supposed to
749
    if options.debug_info:
750
        for p in parsets:
220 ira 751
            p.print_debug_info()
129 ira 752
 
150 ira 753
    # No debug info
754
    else:
152 ira 755
 
150 ira 756
        # Verify each parset
757
        for p in parsets:
758
            p.verify()
129 ira 759
 
150 ira 760
        # Attempt to extract each parset
761
        for p in parsets:
762
            p.extract()
763
 
129 ira 764
    os.chdir(cwd)
765
 
766
def main():
767
 
768
    # Build the OptionParser
769
    parser = OptionParser()
134 ira 770
    parser.add_option('-n', '--not-recursive',
771
                      action='store_false', dest='recursive',
141 ira 772
                      default=config.get_value('options', 'recursive'),
773
                      help="Don't run recursively")
774
 
134 ira 775
    parser.add_option('-d', '--work-dir',
141 ira 776
                      dest='work_dir',
777
                      default=config.get_value('directories', 'working_directory'),
134 ira 778
                      help="Start running at DIR", metavar='DIR')
141 ira 779
 
134 ira 780
    parser.add_option('-p', '--check-required-programs',
781
                       action='store_true', dest='check_progs',
141 ira 782
                       default=config.get_value('options', 'check_required_programs'),
783
                       help="Check for required programs")
129 ira 784
 
141 ira 785
    parser.add_option('-f', '--write-default-config',
786
                      action='store_true', dest='write_def_config',
787
                      default=False, help="Write out a new default config")
788
 
789
    parser.add_option('-c', '--write-new-config',
790
                      action='store_true', dest='write_config',
791
                      default=False, help="Write out the current config")
792
 
150 ira 793
    parser.add_option('-o', '--output-debug-info',
794
                       action='store_true', dest='debug_info',
795
                       default=False,
796
                       help="Output debug info for every parset, then exit")
797
 
275 ira 798
    parser.add_option('-i', '--interactive', dest='interactive', action='store_true',
799
                      default=config.get_value('options', 'interactive'),
800
                      help="Confirm before removing files")
801
 
129 ira 802
    # Parse the given options
275 ira 803
    global options
129 ira 804
    (options, args) = parser.parse_args()
805
 
806
    # Fix up the working directory
807
    options.work_dir = os.path.abspath(os.path.expanduser(options.work_dir))
808
 
809
    # Check that we have the required programs installed
134 ira 810
    if options.check_progs:
811
        check_required_progs()
129 ira 812
 
141 ira 813
    # Write out a new default config, if we need it
814
    if options.write_def_config:
815
        config.write_config(default=True)
816
 
817
    # Write out the current config (adds new options to an existing config)
818
    if options.write_config:
819
        config.write_config()
820
 
129 ira 821
    # Run rarslave!
822
    if options.recursive:
823
        for root, dirs, files in os.walk(options.work_dir):
275 ira 824
            directory_worker(root)
129 ira 825
    else:
275 ira 826
        directory_worker(options.work_dir)
129 ira 827
 
152 ira 828
    # Print the results
829
    output.print_results_table()
830
 
129 ira 831
if __name__ == '__main__':
832
    main()
833