Subversion Repositories programming

Rev

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