Clean up program name and version code
[rarslave2.git] / rsutil / par2parser.py
1 #!/usr/bin/env python
2 # vim: set ts=4 sts=4 sw=4 textwidth=92:
3
4 """
5 Module which holds PAR2 file parsing functions.
6
7 Much of this code was borrowed from the excellent cfv project.
8 See http://cfv.sourceforge.net/ for a copy.
9 """
10
11 __author__    = "Ira W. Snyder (devel@irasnyder.com)"
12 __copyright__ = "Copyright (c) 2006,2007 Ira W. Snyder (devel@irasnyder.com)"
13 __license__   = "GNU GPL v2 (or, at your option, any later version)"
14
15 #    par2parser.py -- PAR2 file parsing utility
16 #
17 #    Copyright (C) 2006,2007  Ira W. Snyder (devel@irasnyder.com)
18 #
19 #    This program is free software; you can redistribute it and/or modify
20 #    it under the terms of the GNU General Public License as published by
21 #    the Free Software Foundation; either version 2 of the License, or
22 #    (at your option) any later version.
23 #
24 #    This program is distributed in the hope that it will be useful,
25 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
26 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27 #    GNU General Public License for more details.
28 #
29 #    You should have received a copy of the GNU General Public License
30 #    along with this program; if not, write to the Free Software
31 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
32
33
34 import struct, errno, os, md5
35
36 def chompnulls(line):
37         p = line.find('\0')
38         if p < 0: return line
39         else:     return line[:p]
40
41 def get_protected_files (dir, filename):
42         """Get all of the filenames that are protected by the par2
43         file given as the filename"""
44
45         assert os.path.isdir (dir) # MUST be a valid directory
46         assert os.path.isfile (os.path.join (dir, filename))
47
48         full_filename = os.path.join (dir, filename)
49
50         try:
51                 file = open(full_filename, 'rb')
52         except:
53                 print 'Could not open %s' % (full_filename, )
54                 return []
55
56         # We always want to do crc checks
57         docrcchecks = True
58
59         pkt_header_fmt = '< 8s Q 16s 16s 16s'
60         pkt_header_size = struct.calcsize(pkt_header_fmt)
61         file_pkt_fmt = '< 16s 16s 16s Q'
62         file_pkt_size = struct.calcsize(file_pkt_fmt)
63         main_pkt_fmt = '< Q I'
64         main_pkt_size = struct.calcsize(main_pkt_fmt)
65
66         seen_file_ids = {}
67         expected_file_ids = None
68         filenames = []
69
70         # This try is here to ensure that we close the open file before
71         # returning. Since this code was (pretty much) borrowed verbatim
72         # from the cfv project, I didn't want to refactor it to make file
73         # closing more sane, so I just used a try / finally clause.
74         try:
75                 while 1:
76                         d = file.read(pkt_header_size)
77                         if not d:
78                                 break
79
80                         magic, pkt_len, pkt_md5, set_id, pkt_type = struct.unpack(pkt_header_fmt, d)
81
82                         if docrcchecks:
83                                 control_md5 = md5.new()
84                                 control_md5.update(d[0x20:])
85                                 d = file.read(pkt_len - pkt_header_size)
86                                 control_md5.update(d)
87
88                                 if control_md5.digest() != pkt_md5:
89                                         raise EnvironmentError, (errno.EINVAL, \
90                                                 "corrupt par2 file - bad packet hash")
91
92                         if pkt_type == 'PAR 2.0\0FileDesc':
93                                 if not docrcchecks:
94                                         d = file.read(pkt_len - pkt_header_size)
95
96                                 file_id, file_md5, file_md5_16k, file_size = \
97                                         struct.unpack(file_pkt_fmt, d[:file_pkt_size])
98
99                                 if seen_file_ids.get(file_id) is None:
100                                         seen_file_ids[file_id] = 1
101                                         filename = chompnulls(d[file_pkt_size:])
102                                         filenames.append(filename)
103
104                         elif pkt_type == "PAR 2.0\0Main\0\0\0\0":
105                                 if not docrcchecks:
106                                         d = file.read(pkt_len - pkt_header_size)
107
108                                 if expected_file_ids is None:
109                                         expected_file_ids = []
110                                         slice_size, num_files = struct.unpack(main_pkt_fmt, d[:main_pkt_size])
111                                         num_nonrecovery = (len(d)-main_pkt_size)/16 - num_files
112
113                                         for i in range(main_pkt_size,main_pkt_size+(num_files+num_nonrecovery)*16,16):
114                                                 expected_file_ids.append(d[i:i+16])
115
116                         else:
117                                 if not docrcchecks:
118                                         file.seek(pkt_len - pkt_header_size, 1)
119
120                 if expected_file_ids is None:
121                         raise EnvironmentError, (errno.EINVAL, \
122                                 "corrupt or unsupported par2 file - no main packet found")
123
124                 for id in expected_file_ids:
125                         if not seen_file_ids.has_key(id):
126                                 raise EnvironmentError, (errno.EINVAL, \
127                                         "corrupt or unsupported par2 file - " \
128                                         "expected file description packet not found")
129         finally:
130                 file.close ()
131
132         return filenames
133
134 def main ():
135         pass
136
137 if __name__ == '__main__':
138         main ()
139