Subversion Repositories programming

Rev

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