~ubuntu-branches/ubuntu/precise/koffice/precise

« back to all changes in this revision

Viewing changes to tools/scripts/zipsyncer

  • Committer: Bazaar Package Importer
  • Author(s): Alessandro Ghersi
  • Date: 2010-10-27 17:52:57 UTC
  • mfrom: (0.12.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20101027175257-s04zqqk5bs8ckm9o
Tags: 1:2.2.83-0ubuntu1
* Merge with Debian git remaining changes:
 - Add build-deps on librcps-dev, opengtl-dev, libqtgtl-dev, freetds-dev,
   create-resources, libspnav-dev
 - Remove needless build-dep on libwv2-dev
 - koffice-libs recommends create-resources
 - krita recommends pstoedit
 - Keep our patches
* New upstream release 2.3 beta 3
  - Remove debian/patches fixed by upstream
  - Update install files

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python -Qwarnall
 
2
"""ZipSyncer, a tool to keep zip files synchronized with unzipped directories
 
3
 
 
4
Usage examples:
 
5
 
 
6
  zipsyncer listzipped [directory]
 
7
 
 
8
  Scan directory recursively look for zipped files that are supported by
 
9
  zipsyncer.
 
10
 
 
11
  zipsyncer createunzipped [directory]
 
12
 
 
13
  Scan directory recursively, look for zipped files that are supported and unzip
 
14
  them.
 
15
 
 
16
  zipsyncer createzipped [directory]
 
17
 
 
18
  Scan directory recursively, look for unzipped directories that are have the right naming and zip them.
 
19
 
 
20
  zipsyncer sync [directory]
 
21
 
 
22
  Scan directory recursively, look for zipped-unzipped pairs and synchronize
 
23
  them if one has been changed.
 
24
 
 
25
  zipsyncer removezipped [directory]
 
26
  
 
27
  Scan directory recursively, look for zipped-unzipped pairs and delete the
 
28
  zipped part if they are in sync.
 
29
 
 
30
  zipsyncer removeunzipped [directory]
 
31
 
 
32
  Scan directory recursively, look for zipped-unzipped pairs and delete the
 
33
  unzipped part if they are in sync.
 
34
 
 
35
"""
 
36
 
 
37
import struct, zlib, os, base64, time, shutil
 
38
 
 
39
""" Deflate a data blob and remove the pre- and post-fix.
 
40
    This is how files are compressed by the DEFLATE method in zip files.
 
41
"""
 
42
def deflate(data, level):
 
43
        return zlib.compress(data, level)[2:-4]
 
44
 
 
45
""" Find the compression level that compresses to a given size.
 
46
    To recreate a zipfile from the unzipped files, the file has to be compressed
 
47
    to the same size as specified in the original zip file. Depending on the
 
48
    used DEFLATE algorithm, this may or may nor succeed.
 
49
"""
 
50
def compressToTargetSize(data, targetsize):
 
51
        for level in [6, 9, 5, 4, 3, 2, 1, 7, 8, 0, -1]:
 
52
                d = deflate(data, level)
 
53
                if len(d) == targetsize:
 
54
                        return d
 
55
        return None
 
56
 
 
57
def getCRC(data):
 
58
        return zlib.crc32(data) & 0xFFFFFFFF
 
59
 
 
60
def dos2unixtime(dostime, dosdate):
 
61
        """ Convert date/time code to (year, month, day, hour, min, sec) """
 
62
        return ( (dosdata>>9)+1980, (dosdate>>5)&0xF, dosdate&0x1F,
 
63
                dostime>>11, (dostime>>5)&0x3F, (dostime&0x1F) * 2 )
 
64
 
 
65
def unixtime2dos(secondssinceepoch):
 
66
        t = time.gmtime(secondssinceepoch)
 
67
        dosdate = (t[0] - 1980) << 9 | t[1] << 5 | t[2]
 
68
        dostime = t[3] << 11 | t[4] << 5 | t[5] >> 1
 
69
        return (dostime, dosdate)
 
70
 
 
71
class ZipEntry:
 
72
        sigstruct = struct.Struct("<I")
 
73
        class Common:
 
74
                struct = struct.Struct("<HHHHHIIIHH")
 
75
                def __init__(self):
 
76
                        self.versionNeeded = 20
 
77
                        self.flag = 0
 
78
                        self.compressionMethod = 0
 
79
                        self.crc32 = 0
 
80
                        self.compressedSize = 0
 
81
                        self.uncompressedSize = 0
 
82
                        self.extraLength = 0
 
83
                        self.extra = ''
 
84
                def unpack(self, data, offset):
 
85
                        fields = ZipEntry.Common.struct.unpack_from(data, offset)
 
86
                        (self.versionNeeded, self.flag, self.compressionMethod,
 
87
                                self.mtime, self.mdate, self.crc32,
 
88
                                self.compressedSize, self.uncompressedSize,
 
89
                                self.filenameLength, self.extraLength) = fields;
 
90
                        return offset + 26
 
91
                def pack(self):
 
92
                        return ZipEntry.Common.struct.pack(self.versionNeeded, self.flag,
 
93
                                self.compressionMethod, self.mtime, self.mdate, self.crc32,
 
94
                                self.compressedSize, self.uncompressedSize,
 
95
                                self.filenameLength, self.extraLength)
 
96
                def getFields(self):
 
97
                        return map(str, [self.versionNeeded, self.flag,
 
98
                                self.compressionMethod, self.mtime, self.mdate,
 
99
                                self.crc32,
 
100
                                self.compressedSize, self.uncompressedSize,
 
101
                                self.filenameLength, self.extraLength])
 
102
                def setFields(self, fields):
 
103
                        (self.versionNeeded, self.flag, self.compressionMethod,
 
104
                                self.mtime, self.mdate, self.crc32,
 
105
                                self.compressedSize, self.uncompressedSize,
 
106
                                self.filenameLength, self.extraLength) = map(int, fields)
 
107
 
 
108
        class Header(Common):
 
109
                def __init__(self):
 
110
                        ZipEntry.Common.__init__(self)
 
111
                        self.valid = False
 
112
                        self.signature = 0x04034b50
 
113
                def unpack(self, data, offset):
 
114
                        self.valid = False
 
115
                        sig = ZipEntry.sigstruct.unpack_from(data, offset)
 
116
                        if sig[0] != 0x04034b50:
 
117
                                return offset
 
118
                        self.signature = sig[0]
 
119
                        offset = ZipEntry.Common.unpack(self, data, offset + 4)
 
120
                        filenameend = offset + self.filenameLength
 
121
                        extraend = filenameend + self.extraLength
 
122
                        self.filename = data[offset : filenameend]
 
123
                        self.extra = data[filenameend : extraend]
 
124
                        self.valid = True
 
125
                        return extraend
 
126
                def pack(self):
 
127
                        return ZipEntry.sigstruct.pack(self.signature) \
 
128
                                        + ZipEntry.Common.pack(self) + self.filename + self.extra
 
129
                def getFields(self):
 
130
                        return [str(self.signature)] + ZipEntry.Common.getFields(self) \
 
131
                                + [self.filename, base64.b64encode(self.extra)]
 
132
                def setFields(self, fields):
 
133
                        self.signature = int(fields[0])
 
134
                        ZipEntry.Common.setFields(self, fields[1:11])
 
135
                        self.filename = fields[11]
 
136
                        self.extra = base64.b64decode(fields[12])
 
137
                def getSize(self):
 
138
                        return 30 + self.filenameLength + self.extraLength
 
139
 
 
140
        class DataDescriptor:
 
141
                struct = struct.Struct("<III")
 
142
                def __init__(self):
 
143
                        (self.signature, self.crc32, self.compressedSize,
 
144
                                self.uncompressedSize) = (0, 0, 0, 0)
 
145
                def unpack(self, flag, data, offset):
 
146
                        self.signature = 0
 
147
                        if len(data) - offset > 4:
 
148
                                sig = ZipEntry.sigstruct.unpack_from(data, offset)
 
149
                                if sig[0] == 0x08074b50:
 
150
                                        self.signature = 0x08074b50
 
151
                                        offset += 4
 
152
                        if flag & 8 or self.signature:
 
153
                                d = ZipEntry.DataDescriptor.struct.unpack_from(data, offset)
 
154
                                offset += 12
 
155
                        else:
 
156
                                d = (0, 0, 0)
 
157
                        (self.crc32, self.compressedSize, self.uncompressedSize) = d
 
158
                        return offset
 
159
                def pack(self):
 
160
                        if self.signature:
 
161
                                return ZipEntry.sigstruct.pack(self.signature) \
 
162
                                                + ZipEntry.DataDescriptor.struct.pack(self.crc32,
 
163
                                                        self.compressedSize,
 
164
                                                        self.uncompressedSize)
 
165
                        if self.crc32 or self.compressedSize \
 
166
                                        or self.uncompressedSize:
 
167
                                return ZipEntry.DataDescriptor.struct.pack(
 
168
                                        self.crc32, self.compressedSize,
 
169
                                        self.uncompressedSize)
 
170
                        return ''
 
171
                def getFields(self):
 
172
                        return map(str, [self.signature, self.crc32,
 
173
                                self.compressedSize, self.uncompressedSize])
 
174
                def setFields(self, fields):
 
175
                        (self.signature, self.crc32, self.compressedSize,
 
176
                                self.uncompressedSize) = map(int, fields)
 
177
                def getSize(self):
 
178
                        if self.signature: return 16
 
179
                        if self.crc32: return 12
 
180
                        return 0
 
181
 
 
182
        class CentralDirectoryData(Common):
 
183
                struct1 = struct.Struct("<IH")
 
184
                struct2 = struct.Struct("<HHHII")
 
185
                def __init__(self):
 
186
                        ZipEntry.Common.__init__(self)
 
187
                        self.valid = False
 
188
                        self.signature = 0x02014b50
 
189
                        self.version = 20
 
190
                        self.commentLength = 0
 
191
                        self.disk = 0
 
192
                        self.internalAttr = 0
 
193
                        self.externalAttr = 0
 
194
                        self.comment = ''
 
195
                def unpack(self, data, offset):
 
196
                        self.valid = False
 
197
                        if len(data) - offset < 6:
 
198
                                return offset
 
199
                        sig = ZipEntry.CentralDirectoryData.struct1.unpack_from(
 
200
                                        data, offset)
 
201
                        if sig[0] != 0x02014b50:
 
202
                                return offset
 
203
                        (self.signature, self.version) = sig
 
204
                        offset = ZipEntry.Common.unpack(self, data, offset + 6)
 
205
                        (self.commentLength, self.disk, self.internalAttr,
 
206
                                self.externalAttr, self.offset
 
207
                                        ) = ZipEntry.CentralDirectoryData.struct2.unpack_from(
 
208
                                        data, offset)
 
209
                        offset += 14
 
210
                        filenameend = offset + self.filenameLength
 
211
                        extraend = filenameend + self.extraLength
 
212
                        commentend = extraend + self.commentLength
 
213
                        self.filename = data[offset : filenameend]
 
214
                        self.extra = data[filenameend : extraend]
 
215
                        self.comment = data[extraend : commentend]
 
216
                        self.valid = True
 
217
                        return commentend
 
218
                def pack(self):
 
219
                        return ZipEntry.CentralDirectoryData.struct1.pack(
 
220
                                        self.signature, self.version) \
 
221
                                + ZipEntry.Common.pack(self) \
 
222
                                + ZipEntry.CentralDirectoryData.struct2.pack(
 
223
                                        self.commentLength, self.disk, self.internalAttr,
 
224
                                        self.externalAttr, self.offset) \
 
225
                                + self.filename + self.extra + self.comment
 
226
                def getFields(self):
 
227
                        return map(str, [self.signature, self.version]) \
 
228
                                + ZipEntry.Common.getFields(self) \
 
229
                                + map(str, [self.commentLength, self.disk,
 
230
                                        self.internalAttr,
 
231
                                        self.externalAttr, self.offset]) \
 
232
                                + [self.filename, base64.b64encode(self.extra),
 
233
                                        base64.b64encode(self.comment)]
 
234
                def setFields(self, fields):
 
235
                        self.signature = int(fields[0])
 
236
                        self.version = int(fields[1])
 
237
                        ZipEntry.Common.setFields(self, fields[2:12])
 
238
                        (self.commentLength, self.disk, self.internalAttr,
 
239
                                self.externalAttr, self.offset) = map(int, fields[12:17])
 
240
                        self.filename = fields[17]
 
241
                        self.extra = base64.b64decode(fields[18])
 
242
                        self.comment = base64.b64decode(fields[19])
 
243
                def getSize(self):
 
244
                        return 46 + self.filenameLength + self.extraLength \
 
245
                                + self.commentLength
 
246
 
 
247
        def __init__(self):
 
248
                self.reset()
 
249
        def reset(self):
 
250
                self.header = ZipEntry.Header()
 
251
                self.datadescriptor = ZipEntry.DataDescriptor()
 
252
                self.data = ''
 
253
                self.cddata = ZipEntry.CentralDirectoryData()
 
254
        def setHeader(self, header, filename, extra):
 
255
                self.header = ZipEntry.Header(header, filename, extra)
 
256
        def setData(self, data):
 
257
                self.data = data
 
258
        def setDataDescriptor(self, sig, datadescriptor):
 
259
                self.datadescriptor = ZipEntry.DataDescriptor(sig,
 
260
                                datadescriptor)
 
261
        def setCentralDirectoryData(self, entry, filename, extra, comment):
 
262
                self.cddata = ZipEntry.CentralDirectoryData(entry, filename,
 
263
                                extra, comment)
 
264
        def unpackHeader(self, data, offset):
 
265
                self.valid = False
 
266
                # read header
 
267
                self.header = ZipEntry.Header()
 
268
                offset = self.header.unpack(data, offset)
 
269
                if not self.header.valid:
 
270
                        return offset
 
271
                # read data
 
272
                if self.header.compressionMethod == 8: # deflate
 
273
                        decompressobj = zlib.decompressobj(-15)
 
274
                        self.data = decompressobj.decompress(data[offset:])
 
275
                        left = decompressobj.unused_data
 
276
                        offset = len(data) - len(left)
 
277
                elif self.header.compressionMethod == 0: # no compression
 
278
                        size = self.header.uncompressedSize
 
279
                        self.data = data[offset : offset + size ]
 
280
                        offset += size
 
281
                else:
 
282
                        self.error = "compression method not supported"
 
283
                        return None
 
284
 
 
285
                # read data descriptor
 
286
                self.datadescriptor = ZipEntry.DataDescriptor()
 
287
                offset = self.datadescriptor.unpack(self.header.flag, data, offset)
 
288
                self.valid = True
 
289
                return offset
 
290
        def packHeader(self):
 
291
                d = self.data
 
292
                if self.header.compressionMethod == 8:
 
293
                        compressedSize = self.datadescriptor.compressedSize \
 
294
                                        if self.datadescriptor.compressedSize \
 
295
                                        else self.header.compressedSize
 
296
                        d = compressToTargetSize(d, compressedSize)
 
297
                        if not d:
 
298
                                self.error = 'deflating to target size failed'
 
299
                                return ''
 
300
                return self.header.pack() + d + self.datadescriptor.pack()
 
301
        def unpackEntry(self, data, offset):
 
302
                self.valid = False
 
303
                self.cddata = ZipEntry.CentralDirectoryData()
 
304
                offset = self.cddata.unpack(data, offset)
 
305
                if not self.cddata.valid:
 
306
                        return None
 
307
                self.valid = True
 
308
                return offset
 
309
        def packEntry(self):
 
310
                return self.cddata.pack()
 
311
        def getFields(self):
 
312
                return self.header.getFields() + self.datadescriptor.getFields() \
 
313
                        + self.cddata.getFields()
 
314
        def setFields(self, fields):
 
315
                self.header.setFields(fields[:13])
 
316
                self.datadescriptor.setFields(fields[13:17])
 
317
                self.cddata.setFields(fields[17:])
 
318
        def setEntry(self, path, mtime):
 
319
                self.header.filenameLength = self.cddata.filenameLength = len(path)
 
320
                self.header.filename = self.cddata.filename = path
 
321
                (self.header.mtime, self.header.mdate) \
 
322
                        = (self.cddata.mtime, self.cddata.mdate) \
 
323
                        = unixtime2dos(mtime)
 
324
        def setDirectory(self, offset, path, mtime):
 
325
                self.setEntry(offset, path, mtime)
 
326
        def setFile(self, path, mtime, data, compresslevel):
 
327
                self.setEntry(path, mtime)
 
328
                self.data = data
 
329
                if compresslevel:
 
330
                        self.cddata.compressionMethod = 8
 
331
                        self.cddata.compressedSize = len(deflate(data, compresslevel))
 
332
        def updateOffsetEtc(self, offset):
 
333
                self.cddata.offset = offset
 
334
                self.cddata.uncompressedSize = len(self.data)
 
335
                self.cddata.crc32 = getCRC(self.data)
 
336
                csize = self.cddata.uncompressedSize
 
337
                if self.cddata.compressionMethod:
 
338
                        cdata = compressToTargetSize(self.data,
 
339
                                self.cddata.compressedSize)
 
340
                        if not cdata:
 
341
                                cdata = deflate(self.data, 6)
 
342
                        csize = len(cdata)
 
343
                self.cddata.compressedSize = csize
 
344
                if self.datadescriptor.compressedSize:
 
345
                        o = self.datadescriptor
 
346
                else:
 
347
                        o = self.header
 
348
                o.crc32 = self.cddata.crc32
 
349
                o.uncompressedSize = self.cddata.uncompressedSize
 
350
                o.compressedSize = self.cddata.compressedSize
 
351
                
 
352
        def getHeaderSize(self):
 
353
                return self.header.getSize() + self.cddata.compressedSize \
 
354
                        + self.datadescriptor.getSize()
 
355
 
 
356
class ZipData:
 
357
 
 
358
        def __init__(self):
 
359
                self.reset()
 
360
 
 
361
        def reset(self):
 
362
                """ True if the data in @entries and @filedata constitutes a
 
363
                    valid, supported zip file. """
 
364
                self.valid = False
 
365
        
 
366
                """ A string describing the error that caused the object to be
 
367
                    invalid. """
 
368
                self.error = 'No entries.'
 
369
        
 
370
                """ Metadata for all entries. """
 
371
                self.entries = []
 
372
        
 
373
                """ Raw uncompressed data for all entries. """
 
374
                self.filedata = []
 
375
 
 
376
                """ Data from the end of central directory record """
 
377
                self.fileinfo = 9*[None]
 
378
                self.fileinfo[0] = 0x06054b50
 
379
                self.fileinfo[1] = 0
 
380
                self.fileinfo[2] = 0
 
381
                self.fileinfo[7] = 0
 
382
                self.fileinfo[8] = ''
 
383
 
 
384
        def setFromFileContents(self, data):
 
385
                self.reset()
 
386
 
 
387
                # parse the full entries
 
388
                offset = 0
 
389
                while offset < len(data):
 
390
                        entry = ZipEntry()
 
391
                        offset = entry.unpackHeader(data, offset)
 
392
                        if entry.valid:
 
393
                                self.entries.append(entry)
 
394
                        else:
 
395
                                break
 
396
                        
 
397
                if len(self.entries) == 0:
 
398
                        self.error = "No entries."
 
399
                        return
 
400
 
 
401
                # parse central directory
 
402
                for e in self.entries:
 
403
                        offset = e.unpackEntry(data, offset)
 
404
                        if not e.valid:
 
405
                                return
 
406
 
 
407
                # parse end of central directory
 
408
                if offset + 22 > len(data):
 
409
                        self.error = "premature end of zipfile"
 
410
                        return
 
411
                dirend = struct.unpack_from("<IHHHHIIH", data, offset)
 
412
                if dirend[0] != 0x06054b50:
 
413
                        self.error = 'invalid end of central directory'
 
414
                        return
 
415
                offset += 22
 
416
                l = dirend[7]
 
417
                zipcomment = data[offset:offset+l]
 
418
                offset += l
 
419
 
 
420
                if offset != len(data):
 
421
                        self.error = "trailing data in zip file"
 
422
                        return
 
423
                if len(data) != dirend[5] + dirend[6] + dirend[7] + 22:
 
424
                        self.error = 'zip file invalid or not supported'
 
425
                        return
 
426
                self.fileinfo = list(dirend) + [zipcomment]
 
427
 
 
428
                self.error = None
 
429
                recreated = self.recreate()
 
430
#               for i in range(len(recreated)):
 
431
#                       if recreated[i] != data[i]:
 
432
#                               print 'error at pos ' + str(i)
 
433
#                               break
 
434
#               print str(len(data)) + ' ' + str(len(recreated))
 
435
                if self.error:
 
436
                        return
 
437
                if recreated != data:
 
438
                        #print str(len(recreated))+' '+str(len(data))
 
439
                        #for i in range(0, min(len(recreated),len(data))):
 
440
                        #       if recreated[i] != data[i]:
 
441
                        #               print 'pos ' + hex(i)
 
442
                        self.error = "roundtripping fails"
 
443
                        return
 
444
 
 
445
                self.valid = True
 
446
                self.error = None
 
447
 
 
448
        def containsPath(self, path):
 
449
                for e in self.entries:
 
450
                        if e.header.filename == path:
 
451
                                return True
 
452
                return False
 
453
 
 
454
        def addDirectory(self, basedir, dir):
 
455
                p = os.path.relpath(dir, basedir) + '/'
 
456
                if self.containsPath(p):
 
457
                        return
 
458
                print 'adding dir ' + p
 
459
                mtime = os.path.getmtime(dir)
 
460
                e = ZipEntry()
 
461
                offset = 0
 
462
                e.setDirectory(offset, p, mtime)
 
463
                self.entries.append(e)
 
464
 
 
465
        def addFile(self, basedir, file, compresslevel):
 
466
                p = os.path.relpath(file, basedir)
 
467
                if self.containsPath(p):
 
468
                        return
 
469
                print 'adding file "' + p + '"'
 
470
                mtime = os.path.getmtime(file)
 
471
                f = open(file, 'rb')
 
472
                data = f.read()
 
473
                f.close()
 
474
                e = ZipEntry()
 
475
                e.setFile(p, mtime, data, compresslevel)
 
476
                self.entries.append(e)
 
477
 
 
478
        def setFromDirectory(self, basedir, zipdatafile):
 
479
                # first the original entry description
 
480
                if os.path.isfile(zipdatafile):
 
481
                        self.readFromDataFile(zipdatafile)
 
482
                # adapt it to the current directory files
 
483
                i = 0
 
484
                while i < len(self.entries):
 
485
                        # if an entry does not exist anymore, remove it
 
486
                        e = self.entries[i]
 
487
                        p = os.path.join(basedir, e.header.filename)
 
488
                        if e.header.filename.endswith('/'):
 
489
                                # always keep directories as zip entries,
 
490
                                # directory entries must be removed by hand
 
491
                                i += 1
 
492
                        elif os.path.isfile(p):
 
493
                                f = open(p, 'rb')
 
494
                                e.data = f.read()
 
495
                                f.close()
 
496
                                # read data into filedata
 
497
                                i += 1
 
498
                        else:
 
499
                                del self.entries[i]
 
500
                # if the archive is empty so far and, the file 'mimetype'
 
501
                # exists, add it first, in uncompressed form
 
502
                p = os.path.join(basedir, 'mimetype')
 
503
                if os.path.isfile(p):
 
504
                        self.addFile(basedir, p, 0)
 
505
                # add all directories and files that are not there yet
 
506
                for root, directories, files in os.walk(basedir):
 
507
                        # directory entries are not created
 
508
                        #for d in directories:
 
509
                        #       p = os.path.join(root, d)
 
510
                        #       self.addDirectory(basedir, p)
 
511
                        for f in files:
 
512
                                p = os.path.join(root, f)
 
513
                                self.addFile(basedir, p, 6)
 
514
 
 
515
        def recreate(self):
 
516
                self.updateOffsetsAndSizes()
 
517
                filesize = 22 + self.fileinfo[5] + self.fileinfo[6]
 
518
                data = ''
 
519
 
 
520
                for e in self.entries:
 
521
                        data += e.packHeader()
 
522
                for e in self.entries:
 
523
                        data += e.packEntry()
 
524
 
 
525
                fi = self.fileinfo
 
526
                data += struct.pack("<IHHHHIIH", fi[0], fi[1], fi[2], fi[3], fi[4], fi[5], fi[6], fi[7]) + fi[8]
 
527
 
 
528
                return data
 
529
 
 
530
        def updateOffsetsAndSizes(self):
 
531
                total = 0
 
532
                for e in self.entries:
 
533
                        e.updateOffsetEtc(total)
 
534
                        total += e.getHeaderSize()
 
535
                cdstart = total
 
536
                for e in self.entries:
 
537
                        total += e.cddata.getSize()
 
538
 
 
539
                self.fileinfo[3] = self.fileinfo[4] = len(self.entries)
 
540
                self.fileinfo[5] = total - cdstart
 
541
                self.fileinfo[6] = cdstart
 
542
 
 
543
        def writeToDirectory(self, dirpath):
 
544
                for e in self.entries:
 
545
                        p = os.path.join(dirpath, e.header.filename)
 
546
                        if os.path.commonprefix([p, dirpath]) != dirpath:
 
547
                                # error, zip file would lie outside of parentdir
 
548
                                return
 
549
                        if p.endswith('/'):
 
550
                                try:
 
551
                                        os.makedirs(p)
 
552
                                except:
 
553
                                        None
 
554
                                continue
 
555
                        try:
 
556
                                os.makedirs(os.path.dirname(p))
 
557
                        except:
 
558
                                None
 
559
                        f = open(p, 'wb')
 
560
                        f.write(e.data)
 
561
                        f.close()
 
562
                None
 
563
        def writeToDataFile(self, zipdatafile):
 
564
                f = open(zipdatafile, 'w')
 
565
                # write file specific line with 9 fields first
 
566
                for i in range(8):
 
567
                        f.write(str(self.fileinfo[i]) + '\t')
 
568
                f.write(base64.b64encode(self.fileinfo[8]) + '\n')
 
569
                # write one line with 37 fields per entry
 
570
                for e in self.entries:
 
571
                        f.write('\t'.join(e.getFields()) + '\n')
 
572
                f.close()
 
573
        def readFromDataFile(self, zipdatafile):
 
574
                self.reset()
 
575
                f = open(zipdatafile, 'r')
 
576
                line = f.readline()
 
577
                fields = line.split('\t')
 
578
                for i in range(8):
 
579
                        self.fileinfo[i] = int(fields[i])
 
580
                self.fileinfo[8] = base64.b64decode(fields[8])
 
581
                if (len(fields) != 9):
 
582
                        self.error = 'First line does not have 9 entries.'
 
583
                for line in f:
 
584
                        fields = line.split('\t')
 
585
                        if (len(fields) != 37):
 
586
                                self.error = 'Entry line does not have 37 entries.'
 
587
                        e = ZipEntry()
 
588
                        e.setFields(fields)
 
589
                        self.entries.append(e)
 
590
                f.close()
 
591
                self.filedata = len(self.entries)*['']
 
592
 
 
593
def filenameToDirname(filename, extensions):
 
594
        ext = filter(lambda e: filename.endswith('.' + e), extensions)
 
595
        if len(ext) == 1:
 
596
                l = len(ext[0])
 
597
                return filename[:-l-1] + '_' + ext[0]
 
598
        return None
 
599
 
 
600
def dirnameToFilename(dirname, extensions):
 
601
        ext = filter(lambda e: dirname.endswith('_' + e), extensions)
 
602
        if len(ext) == 1:
 
603
                l = len(ext[0])
 
604
                return dirname[:-l-1] + '.' + ext[0]
 
605
        return None
 
606
 
 
607
"""
 
608
    List all files and directories that are potentially supported.
 
609
    The list is created on the extension of the file and trailing part of the
 
610
    name of the directory
 
611
"""
 
612
def scanDirectory(rootdir, extensions):
 
613
        if os.path.isfile(rootdir):
 
614
                return [rootdir]
 
615
 
 
616
        filext = map(lambda e: "." + e, extensions)
 
617
        list = []
 
618
        for root, directories, files in os.walk(rootdir):
 
619
                for file in files:
 
620
                        if file.startswith('.'):
 
621
                                continue
 
622
                        if any(map(lambda e: file.endswith(e), filext)):
 
623
                                list.append(os.path.join(root, file))
 
624
                for dir in directories:
 
625
                        file = dirnameToFilename(dir, extensions)
 
626
                        if file:
 
627
                                list.append(os.path.join(root, file))
 
628
 
 
629
        # remove duplicates by converting to a set
 
630
        return frozenset(list)
 
631
 
 
632
def readZipData(filepath):
 
633
        if not os.path.exists(filepath):
 
634
                return
 
635
 
 
636
        try:
 
637
                fd = open(filepath, "rb")
 
638
                magic = fd.read(4)
 
639
                if magic != 'PK\3\4':
 
640
                        return
 
641
                fd.seek(0)
 
642
                data = fd.read()
 
643
                fd.close()
 
644
        except:
 
645
                return
 
646
        return data
 
647
 
 
648
def writeZipped(data, filepath):
 
649
        fd = open(filepath, "wb")
 
650
        fd.write(data)
 
651
        fd.close()
 
652
 
 
653
def writeUnzipped(data, dirpath, descriptionfile):
 
654
        zipdata = ZipData()
 
655
        zipdata.setFromFileContents(data)
 
656
        if not zipdata.valid:
 
657
                return
 
658
        zipdata.writeToDirectory(dirpath)
 
659
        zipdata.writeToDataFile(descriptionfile)
 
660
 
 
661
def listzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
 
662
        # if there is a problem reading, simply do not list the file
 
663
        data = readZipData(filepath)
 
664
        if not data:
 
665
                return
 
666
        zipdata = ZipData()
 
667
        zipdata.setFromFileContents(data)
 
668
        if zipdata.valid:
 
669
                print filepath
 
670
 
 
671
def createzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
 
672
        # check that no file exists yet
 
673
        if os.path.isfile(filepath) or os.path.isfile(hiddenfile):
 
674
                return
 
675
 
 
676
        zipdata = ZipData()
 
677
        try:
 
678
                zipdata.setFromDirectory(dirpath, descriptionfile)
 
679
        except:
 
680
                raise
 
681
                return
 
682
        data = zipdata.recreate()
 
683
        writeZipped(data, filepath)
 
684
        shutil.copy(filepath, hiddenfile)
 
685
 
 
686
def createunzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
 
687
        # check that no directory exists yet
 
688
        if os.path.isdir(dirpath) or os.path.isfile(hiddenfile) \
 
689
                        or os.path.isfile(descriptionfile):
 
690
                return
 
691
 
 
692
        # if there is a problem reading, simply do not unzip the file
 
693
        data = readZipData(filepath)
 
694
        if not data:
 
695
                return
 
696
        writeUnzipped(data, dirpath, descriptionfile)
 
697
        shutil.copy(filepath, hiddenfile)
 
698
 
 
699
""" Find which file is the newest, that is which is different from the other
 
700
    two. Returns None if all are equal, 'Error' when it cannot be determined,
 
701
    e.g. because one version does not exist. 'unzipped' when the unzipped
 
702
    version is different, 'zipped' when the zipped version is different and
 
703
    'both' when no version resembles the hidden file. """
 
704
def findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile):
 
705
        d = dict(source='error', data='')
 
706
        # check that an unzipped version and a hidden file exist
 
707
        if not os.path.isdir(dirpath) or not os.path.isfile(hiddenfile) \
 
708
                        or not os.path.isfile(filepath):
 
709
                return d
 
710
 
 
711
        # check that the files are in sync
 
712
        hidden = readZipData(hiddenfile)
 
713
        zipped = readZipData(filepath)
 
714
        zipdata = ZipData()
 
715
        try:
 
716
                zipdata.setFromDirectory(dirpath, descriptionfile)
 
717
        except:
 
718
                return d
 
719
        unzipped = zipdata.recreate()
 
720
        if hidden == zipped:
 
721
                d['data'] = unzipped
 
722
                if hidden == unzipped:
 
723
                        d['source'] = None
 
724
                        return d
 
725
                d['source'] = 'unzipped'
 
726
                return d
 
727
        if hidden == unzipped:
 
728
                d['data'] = unzipped
 
729
                d['source'] = 'zipped'
 
730
                return d
 
731
        d['source'] = 'both'
 
732
        return d
 
733
 
 
734
def syncFunction(filepath, dirpath, descriptionfile, hiddenfile):
 
735
        d = findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile)
 
736
        if d['source'] == 'both':
 
737
                print 'Conflict for ' + filepath
 
738
        elif d['source'] == 'zipped':
 
739
                writeUnzipped(d['data'], dirpath, descriptionfile)
 
740
                shutil.copy(filepath, hiddenfile)
 
741
        elif d['source'] == 'unzipped' or d['source'] == None:
 
742
                writeZipped(d['data'], filepath)
 
743
                shutil.copy(filepath, hiddenfile)
 
744
 
 
745
def removezippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
 
746
        # only delete a version of there is no different version
 
747
        d = findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile)
 
748
        if d['source'] != None:
 
749
                return
 
750
        os.remove(filepath)
 
751
        os.remove(hiddenfile)
 
752
 
 
753
def removeunzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
 
754
        # only delete a version of there is no different version
 
755
        d = findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile)
 
756
        if d['source'] != None:
 
757
                return
 
758
        os.remove(hiddenfile)
 
759
        if os.path.isfile(descriptionfile):
 
760
                os.remove(descriptionfile)
 
761
        shutil.rmtree(dirpath)
 
762
 
 
763
if __name__ == '__main__':
 
764
        import sys
 
765
 
 
766
        if len(sys.argv) < 2:
 
767
                print 'Bad usage'
 
768
                exit(1)
 
769
 
 
770
        command = sys.argv[1]
 
771
        if len(sys.argv) == 2:
 
772
                directories = ['.']
 
773
        else:
 
774
                directories = sys.argv[2:]
 
775
 
 
776
        commands = {'listzipped': listzippedFunction,
 
777
                'createzipped': createzippedFunction,
 
778
                'createunzipped': createunzippedFunction,
 
779
                'sync': syncFunction,
 
780
                'removezipped': removezippedFunction,
 
781
                'removeunzipped': removeunzippedFunction}
 
782
 
 
783
        if not command in commands:
 
784
                print 'invalid command "' + command + '"'
 
785
                exit(1)
 
786
 
 
787
        commandFunction = commands[command]
 
788
 
 
789
        extensions = ["odt", "odp", "ods", "odg", "jar", "zip"]
 
790
 
 
791
        for directory in directories:
 
792
                fileList = scanDirectory(directory, extensions)
 
793
                for file in fileList:
 
794
                        dir = filenameToDirname(file, extensions)
 
795
                        descriptionfile = dir + '.cd'
 
796
                        if file.find('/') == -1:
 
797
                                hiddenfile = '.' + file
 
798
                        else:
 
799
                                hiddenfile = '/.'.join(os.path.split(file))
 
800
                        commandFunction(file, dir, descriptionfile, hiddenfile)