File indexing completed on 2024-05-12 16:28:27

0001 #!/usr/bin/python -Qwarnall
0002 """ZipSyncer, a tool to keep zip files synchronized with unzipped directories
0003 
0004 Usage examples:
0005 
0006   zipsyncer listzipped [directory]
0007 
0008   Scan directory recursively look for zipped files that are supported by
0009   zipsyncer.
0010 
0011   zipsyncer createunzipped [directory]
0012 
0013   Scan directory recursively, look for zipped files that are supported and unzip
0014   them.
0015 
0016   zipsyncer createzipped [directory]
0017 
0018   Scan directory recursively, look for unzipped directories that are have the right naming and zip them.
0019 
0020   zipsyncer sync [directory]
0021 
0022   Scan directory recursively, look for zipped-unzipped pairs and synchronize
0023   them if one has been changed.
0024 
0025   zipsyncer removezipped [directory]
0026   
0027   Scan directory recursively, look for zipped-unzipped pairs and delete the
0028   zipped part if they are in sync.
0029 
0030   zipsyncer removeunzipped [directory]
0031 
0032   Scan directory recursively, look for zipped-unzipped pairs and delete the
0033   unzipped part if they are in sync.
0034 
0035 """
0036 
0037 import struct, zlib, os, base64, time, shutil
0038 
0039 """ Deflate a data blob and remove the pre- and post-fix.
0040     This is how files are compressed by the DEFLATE method in zip files.
0041 """
0042 def deflate(data, level):
0043     return zlib.compress(data, level)[2:-4]
0044 
0045 """ Find the compression level that compresses to a given size.
0046     To recreate a zipfile from the unzipped files, the file has to be compressed
0047     to the same size as specified in the original zip file. Depending on the
0048     used DEFLATE algorithm, this may or may nor succeed.
0049 """
0050 def compressToTargetSize(data, targetsize):
0051     for level in [6, 9, 5, 4, 3, 2, 1, 7, 8, 0, -1]:
0052         d = deflate(data, level)
0053         if len(d) == targetsize:
0054             return d
0055     return None
0056 
0057 def getCRC(data):
0058     return zlib.crc32(data) & 0xFFFFFFFF
0059 
0060 def dos2unixtime(dostime, dosdate):
0061     """ Convert date/time code to (year, month, day, hour, min, sec) """
0062     return ( (dosdata>>9)+1980, (dosdate>>5)&0xF, dosdate&0x1F,
0063         dostime>>11, (dostime>>5)&0x3F, (dostime&0x1F) * 2 )
0064 
0065 def unixtime2dos(secondssinceepoch):
0066     t = time.gmtime(secondssinceepoch)
0067     dosdate = (t[0] - 1980) << 9 | t[1] << 5 | t[2]
0068     dostime = t[3] << 11 | t[4] << 5 | t[5] >> 1
0069     return (dostime, dosdate)
0070 
0071 class ZipEntry:
0072     sigstruct = struct.Struct("<I")
0073     class Common:
0074         struct = struct.Struct("<HHHHHIIIHH")
0075         def __init__(self):
0076             self.versionNeeded = 20
0077             self.flag = 0
0078             self.compressionMethod = 0
0079             self.crc32 = 0
0080             self.compressedSize = 0
0081             self.uncompressedSize = 0
0082             self.extraLength = 0
0083             self.extra = ''
0084         def unpack(self, data, offset):
0085             fields = ZipEntry.Common.struct.unpack_from(data, offset)
0086             (self.versionNeeded, self.flag, self.compressionMethod,
0087                 self.mtime, self.mdate, self.crc32,
0088                 self.compressedSize, self.uncompressedSize,
0089                 self.filenameLength, self.extraLength) = fields;
0090             return offset + 26
0091         def pack(self):
0092             return ZipEntry.Common.struct.pack(self.versionNeeded, self.flag,
0093                 self.compressionMethod, self.mtime, self.mdate, self.crc32,
0094                 self.compressedSize, self.uncompressedSize,
0095                 self.filenameLength, self.extraLength)
0096         def getFields(self):
0097             return map(str, [self.versionNeeded, self.flag,
0098                 self.compressionMethod, self.mtime, self.mdate,
0099                 self.crc32,
0100                 self.compressedSize, self.uncompressedSize,
0101                 self.filenameLength, self.extraLength])
0102         def setFields(self, fields):
0103             (self.versionNeeded, self.flag, self.compressionMethod,
0104                 self.mtime, self.mdate, self.crc32,
0105                 self.compressedSize, self.uncompressedSize,
0106                 self.filenameLength, self.extraLength) = map(int, fields)
0107 
0108     class Header(Common):
0109         def __init__(self):
0110             ZipEntry.Common.__init__(self)
0111             self.valid = False
0112             self.signature = 0x04034b50
0113         def unpack(self, data, offset):
0114             self.valid = False
0115             sig = ZipEntry.sigstruct.unpack_from(data, offset)
0116             if sig[0] != 0x04034b50:
0117                 return offset
0118             self.signature = sig[0]
0119             offset = ZipEntry.Common.unpack(self, data, offset + 4)
0120             filenameend = offset + self.filenameLength
0121             extraend = filenameend + self.extraLength
0122             self.filename = data[offset : filenameend]
0123             self.extra = data[filenameend : extraend]
0124             self.valid = True
0125             return extraend
0126         def pack(self):
0127             return ZipEntry.sigstruct.pack(self.signature) \
0128                     + ZipEntry.Common.pack(self) + self.filename + self.extra
0129         def getFields(self):
0130             return [str(self.signature)] + ZipEntry.Common.getFields(self) \
0131                 + [self.filename, base64.b64encode(self.extra)]
0132         def setFields(self, fields):
0133             self.signature = int(fields[0])
0134             ZipEntry.Common.setFields(self, fields[1:11])
0135             self.filename = fields[11]
0136             self.extra = base64.b64decode(fields[12])
0137         def getSize(self):
0138             return 30 + self.filenameLength + self.extraLength
0139 
0140     class DataDescriptor:
0141         struct = struct.Struct("<III")
0142         def __init__(self):
0143             (self.signature, self.crc32, self.compressedSize,
0144                 self.uncompressedSize) = (0, 0, 0, 0)
0145         def unpack(self, flag, data, offset):
0146             self.signature = 0
0147             if len(data) - offset > 4:
0148                 sig = ZipEntry.sigstruct.unpack_from(data, offset)
0149                 if sig[0] == 0x08074b50:
0150                     self.signature = 0x08074b50
0151                     offset += 4
0152             if flag & 8 or self.signature:
0153                 d = ZipEntry.DataDescriptor.struct.unpack_from(data, offset)
0154                 offset += 12
0155             else:
0156                 d = (0, 0, 0)
0157             (self.crc32, self.compressedSize, self.uncompressedSize) = d
0158             return offset
0159         def pack(self):
0160             if self.signature:
0161                 return ZipEntry.sigstruct.pack(self.signature) \
0162                         + ZipEntry.DataDescriptor.struct.pack(self.crc32,
0163                             self.compressedSize,
0164                             self.uncompressedSize)
0165             if self.crc32 or self.compressedSize \
0166                     or self.uncompressedSize:
0167                 return ZipEntry.DataDescriptor.struct.pack(
0168                     self.crc32, self.compressedSize,
0169                     self.uncompressedSize)
0170             return ''
0171         def getFields(self):
0172             return map(str, [self.signature, self.crc32,
0173                 self.compressedSize, self.uncompressedSize])
0174         def setFields(self, fields):
0175             (self.signature, self.crc32, self.compressedSize,
0176                 self.uncompressedSize) = map(int, fields)
0177         def getSize(self):
0178             if self.signature: return 16
0179             if self.crc32: return 12
0180             return 0
0181 
0182     class CentralDirectoryData(Common):
0183         struct1 = struct.Struct("<IH")
0184         struct2 = struct.Struct("<HHHII")
0185         def __init__(self):
0186             ZipEntry.Common.__init__(self)
0187             self.valid = False
0188             self.signature = 0x02014b50
0189             self.version = 20
0190             self.commentLength = 0
0191             self.disk = 0
0192             self.internalAttr = 0
0193             self.externalAttr = 0
0194             self.comment = ''
0195         def unpack(self, data, offset):
0196             self.valid = False
0197             if len(data) - offset < 6:
0198                 return offset
0199             sig = ZipEntry.CentralDirectoryData.struct1.unpack_from(
0200                     data, offset)
0201             if sig[0] != 0x02014b50:
0202                 return offset
0203             (self.signature, self.version) = sig
0204             offset = ZipEntry.Common.unpack(self, data, offset + 6)
0205             (self.commentLength, self.disk, self.internalAttr,
0206                 self.externalAttr, self.offset
0207                     ) = ZipEntry.CentralDirectoryData.struct2.unpack_from(
0208                     data, offset)
0209             offset += 14
0210             filenameend = offset + self.filenameLength
0211             extraend = filenameend + self.extraLength
0212             commentend = extraend + self.commentLength
0213             self.filename = data[offset : filenameend]
0214             self.extra = data[filenameend : extraend]
0215             self.comment = data[extraend : commentend]
0216             self.valid = True
0217             return commentend
0218         def pack(self):
0219             return ZipEntry.CentralDirectoryData.struct1.pack(
0220                     self.signature, self.version) \
0221                 + ZipEntry.Common.pack(self) \
0222                 + ZipEntry.CentralDirectoryData.struct2.pack(
0223                     self.commentLength, self.disk, self.internalAttr,
0224                     self.externalAttr, self.offset) \
0225                 + self.filename + self.extra + self.comment
0226         def getFields(self):
0227             return map(str, [self.signature, self.version]) \
0228                 + ZipEntry.Common.getFields(self) \
0229                 + map(str, [self.commentLength, self.disk,
0230                     self.internalAttr,
0231                     self.externalAttr, self.offset]) \
0232                 + [self.filename, base64.b64encode(self.extra),
0233                     base64.b64encode(self.comment)]
0234         def setFields(self, fields):
0235             self.signature = int(fields[0])
0236             self.version = int(fields[1])
0237             ZipEntry.Common.setFields(self, fields[2:12])
0238             (self.commentLength, self.disk, self.internalAttr,
0239                 self.externalAttr, self.offset) = map(int, fields[12:17])
0240             self.filename = fields[17]
0241             self.extra = base64.b64decode(fields[18])
0242             self.comment = base64.b64decode(fields[19])
0243         def getSize(self):
0244             return 46 + self.filenameLength + self.extraLength \
0245                 + self.commentLength
0246 
0247     def __init__(self):
0248         self.reset()
0249     def reset(self):
0250         self.header = ZipEntry.Header()
0251         self.datadescriptor = ZipEntry.DataDescriptor()
0252         self.data = ''
0253         self.cddata = ZipEntry.CentralDirectoryData()
0254     def setHeader(self, header, filename, extra):
0255         self.header = ZipEntry.Header(header, filename, extra)
0256     def setData(self, data):
0257         self.data = data
0258     def setDataDescriptor(self, sig, datadescriptor):
0259         self.datadescriptor = ZipEntry.DataDescriptor(sig,
0260                 datadescriptor)
0261     def setCentralDirectoryData(self, entry, filename, extra, comment):
0262         self.cddata = ZipEntry.CentralDirectoryData(entry, filename,
0263                 extra, comment)
0264     def unpackHeader(self, data, offset):
0265         self.valid = False
0266         # read header
0267         self.header = ZipEntry.Header()
0268         offset = self.header.unpack(data, offset)
0269         if not self.header.valid:
0270             return offset
0271         # read data
0272         if self.header.compressionMethod == 8: # deflate
0273             decompressobj = zlib.decompressobj(-15)
0274             self.data = decompressobj.decompress(data[offset:])
0275             left = decompressobj.unused_data
0276             offset = len(data) - len(left)
0277         elif self.header.compressionMethod == 0: # no compression
0278             size = self.header.uncompressedSize
0279             self.data = data[offset : offset + size ]
0280             offset += size
0281         else:
0282             self.error = "compression method not supported"
0283             return None
0284 
0285         # read data descriptor
0286         self.datadescriptor = ZipEntry.DataDescriptor()
0287         offset = self.datadescriptor.unpack(self.header.flag, data, offset)
0288         self.valid = True
0289         return offset
0290     def packHeader(self):
0291         d = self.data
0292         if self.header.compressionMethod == 8:
0293             compressedSize = self.datadescriptor.compressedSize \
0294                     if self.datadescriptor.compressedSize \
0295                     else self.header.compressedSize
0296             d = compressToTargetSize(d, compressedSize)
0297             if not d:
0298                 self.error = 'deflating to target size failed'
0299                 return ''
0300         return self.header.pack() + d + self.datadescriptor.pack()
0301     def unpackEntry(self, data, offset):
0302         self.valid = False
0303         self.cddata = ZipEntry.CentralDirectoryData()
0304         offset = self.cddata.unpack(data, offset)
0305         if not self.cddata.valid:
0306             return None
0307         self.valid = True
0308         return offset
0309     def packEntry(self):
0310         return self.cddata.pack()
0311     def getFields(self):
0312         return self.header.getFields() + self.datadescriptor.getFields() \
0313             + self.cddata.getFields()
0314     def setFields(self, fields):
0315         self.header.setFields(fields[:13])
0316         self.datadescriptor.setFields(fields[13:17])
0317         self.cddata.setFields(fields[17:])
0318     def setEntry(self, path, mtime):
0319         self.header.filenameLength = self.cddata.filenameLength = len(path)
0320         self.header.filename = self.cddata.filename = path
0321         (self.header.mtime, self.header.mdate) \
0322             = (self.cddata.mtime, self.cddata.mdate) \
0323             = unixtime2dos(mtime)
0324     def setDirectory(self, offset, path, mtime):
0325         self.setEntry(offset, path, mtime)
0326     def setFile(self, path, mtime, data, compresslevel):
0327         self.setEntry(path, mtime)
0328         self.data = data
0329         if compresslevel:
0330             self.cddata.compressionMethod = 8
0331             self.cddata.compressedSize = len(deflate(data, compresslevel))
0332     def updateOffsetEtc(self, offset):
0333         self.cddata.offset = offset
0334         self.cddata.uncompressedSize = len(self.data)
0335         self.cddata.crc32 = getCRC(self.data)
0336         csize = self.cddata.uncompressedSize
0337         if self.cddata.compressionMethod:
0338             cdata = compressToTargetSize(self.data,
0339                 self.cddata.compressedSize)
0340             if not cdata:
0341                 cdata = deflate(self.data, 6)
0342             csize = len(cdata)
0343         self.cddata.compressedSize = csize
0344         if self.datadescriptor.compressedSize:
0345             o = self.datadescriptor
0346         else:
0347             o = self.header
0348         o.crc32 = self.cddata.crc32
0349         o.uncompressedSize = self.cddata.uncompressedSize
0350         o.compressedSize = self.cddata.compressedSize
0351         
0352     def getHeaderSize(self):
0353         return self.header.getSize() + self.cddata.compressedSize \
0354             + self.datadescriptor.getSize()
0355 
0356 class ZipData:
0357 
0358     def __init__(self):
0359         self.reset()
0360 
0361     def reset(self):
0362         """ True if the data in @entries and @filedata constitutes a
0363             valid, supported zip file. """
0364         self.valid = False
0365     
0366         """ A string describing the error that caused the object to be
0367             invalid. """
0368         self.error = 'No entries.'
0369     
0370         """ Metadata for all entries. """
0371         self.entries = []
0372     
0373         """ Raw uncompressed data for all entries. """
0374         self.filedata = []
0375 
0376         """ Data from the end of central directory record """
0377         self.fileinfo = 9*[None]
0378         self.fileinfo[0] = 0x06054b50
0379         self.fileinfo[1] = 0
0380         self.fileinfo[2] = 0
0381         self.fileinfo[7] = 0
0382         self.fileinfo[8] = ''
0383 
0384     def setFromFileContents(self, data):
0385         self.reset()
0386 
0387         # parse the full entries
0388         offset = 0
0389         while offset < len(data):
0390             entry = ZipEntry()
0391             offset = entry.unpackHeader(data, offset)
0392             if entry.valid:
0393                 self.entries.append(entry)
0394             else:
0395                 break
0396             
0397         if len(self.entries) == 0:
0398             self.error = "No entries."
0399             return
0400 
0401         # parse central directory
0402         for e in self.entries:
0403             offset = e.unpackEntry(data, offset)
0404             if not e.valid:
0405                 return
0406 
0407         # parse end of central directory
0408         if offset + 22 > len(data):
0409             self.error = "premature end of zipfile"
0410             return
0411         dirend = struct.unpack_from("<IHHHHIIH", data, offset)
0412         if dirend[0] != 0x06054b50:
0413             self.error = 'invalid end of central directory'
0414             return
0415         offset += 22
0416         l = dirend[7]
0417         zipcomment = data[offset:offset+l]
0418         offset += l
0419 
0420         if offset != len(data):
0421             self.error = "trailing data in zip file"
0422             return
0423         if len(data) != dirend[5] + dirend[6] + dirend[7] + 22:
0424             self.error = 'zip file invalid or not supported'
0425             return
0426         self.fileinfo = list(dirend) + [zipcomment]
0427 
0428         self.error = None
0429         recreated = self.recreate()
0430 #       for i in range(len(recreated)):
0431 #           if recreated[i] != data[i]:
0432 #               print 'error at pos ' + str(i)
0433 #               break
0434 #       print str(len(data)) + ' ' + str(len(recreated))
0435         if self.error:
0436             return
0437         if recreated != data:
0438             #print str(len(recreated))+' '+str(len(data))
0439             #for i in range(0, min(len(recreated),len(data))):
0440             #   if recreated[i] != data[i]:
0441             #       print 'pos ' + hex(i)
0442             self.error = "roundtripping fails"
0443             return
0444 
0445         self.valid = True
0446         self.error = None
0447 
0448     def containsPath(self, path):
0449         for e in self.entries:
0450             if e.header.filename == path:
0451                 return True
0452         return False
0453 
0454     def addDirectory(self, basedir, dir):
0455         p = os.path.relpath(dir, basedir) + '/'
0456         if self.containsPath(p):
0457             return
0458         print 'adding dir ' + p
0459         mtime = os.path.getmtime(dir)
0460         e = ZipEntry()
0461         offset = 0
0462         e.setDirectory(offset, p, mtime)
0463         self.entries.append(e)
0464 
0465     def addFile(self, basedir, file, compresslevel):
0466         p = os.path.relpath(file, basedir)
0467         if self.containsPath(p):
0468             return
0469         print 'adding file "' + p + '"'
0470         mtime = os.path.getmtime(file)
0471         f = open(file, 'rb')
0472         data = f.read()
0473         f.close()
0474         e = ZipEntry()
0475         e.setFile(p, mtime, data, compresslevel)
0476         self.entries.append(e)
0477 
0478     def setFromDirectory(self, basedir, zipdatafile):
0479         # first the original entry description
0480         if os.path.isfile(zipdatafile):
0481             self.readFromDataFile(zipdatafile)
0482         # adapt it to the current directory files
0483         i = 0
0484         while i < len(self.entries):
0485             # if an entry does not exist anymore, remove it
0486             e = self.entries[i]
0487             p = os.path.join(basedir, e.header.filename)
0488             if e.header.filename.endswith('/'):
0489                 # always keep directories as zip entries,
0490                 # directory entries must be removed by hand
0491                 i += 1
0492             elif os.path.isfile(p):
0493                 f = open(p, 'rb')
0494                 e.data = f.read()
0495                 f.close()
0496                 # read data into filedata
0497                 i += 1
0498             else:
0499                 del self.entries[i]
0500         # if the archive is empty so far and, the file 'mimetype'
0501         # exists, add it first, in uncompressed form
0502         p = os.path.join(basedir, 'mimetype')
0503         if os.path.isfile(p):
0504             self.addFile(basedir, p, 0)
0505         # add all directories and files that are not there yet
0506         for root, directories, files in os.walk(basedir):
0507             # directory entries are not created
0508             #for d in directories:
0509             #   p = os.path.join(root, d)
0510             #   self.addDirectory(basedir, p)
0511             for f in files:
0512                 p = os.path.join(root, f)
0513                 self.addFile(basedir, p, 6)
0514 
0515     def recreate(self):
0516         self.updateOffsetsAndSizes()
0517         filesize = 22 + self.fileinfo[5] + self.fileinfo[6]
0518         data = ''
0519 
0520         for e in self.entries:
0521             data += e.packHeader()
0522         for e in self.entries:
0523             data += e.packEntry()
0524 
0525         fi = self.fileinfo
0526         data += struct.pack("<IHHHHIIH", fi[0], fi[1], fi[2], fi[3], fi[4], fi[5], fi[6], fi[7]) + fi[8]
0527 
0528         return data
0529 
0530     def updateOffsetsAndSizes(self):
0531         total = 0
0532         for e in self.entries:
0533             e.updateOffsetEtc(total)
0534             total += e.getHeaderSize()
0535         cdstart = total
0536         for e in self.entries:
0537             total += e.cddata.getSize()
0538 
0539         self.fileinfo[3] = self.fileinfo[4] = len(self.entries)
0540         self.fileinfo[5] = total - cdstart
0541         self.fileinfo[6] = cdstart
0542 
0543     def writeToDirectory(self, dirpath):
0544         for e in self.entries:
0545             p = os.path.join(dirpath, e.header.filename)
0546             if os.path.commonprefix([p, dirpath]) != dirpath:
0547                 # error, zip file would lie outside of parentdir
0548                 return
0549             if p.endswith('/'):
0550                 try:
0551                     os.makedirs(p)
0552                 except:
0553                     None
0554                 continue
0555             try:
0556                 os.makedirs(os.path.dirname(p))
0557             except:
0558                 None
0559             f = open(p, 'wb')
0560             f.write(e.data)
0561             f.close()
0562         None
0563     def writeToDataFile(self, zipdatafile):
0564         f = open(zipdatafile, 'w')
0565         # write file specific line with 9 fields first
0566         for i in range(8):
0567             f.write(str(self.fileinfo[i]) + '\t')
0568         f.write(base64.b64encode(self.fileinfo[8]) + '\n')
0569         # write one line with 37 fields per entry
0570         for e in self.entries:
0571             f.write('\t'.join(e.getFields()) + '\n')
0572         f.close()
0573     def readFromDataFile(self, zipdatafile):
0574         self.reset()
0575         f = open(zipdatafile, 'r')
0576         line = f.readline()
0577         fields = line.split('\t')
0578         for i in range(8):
0579             self.fileinfo[i] = int(fields[i])
0580         self.fileinfo[8] = base64.b64decode(fields[8])
0581         if (len(fields) != 9):
0582             self.error = 'First line does not have 9 entries.'
0583         for line in f:
0584             fields = line.split('\t')
0585             if (len(fields) != 37):
0586                 self.error = 'Entry line does not have 37 entries.'
0587             e = ZipEntry()
0588             e.setFields(fields)
0589             self.entries.append(e)
0590         f.close()
0591         self.filedata = len(self.entries)*['']
0592 
0593 def filenameToDirname(filename, extensions):
0594     ext = filter(lambda e: filename.endswith('.' + e), extensions)
0595     if len(ext) == 1:
0596         l = len(ext[0])
0597         return filename[:-l-1] + '_' + ext[0]
0598     return None
0599 
0600 def dirnameToFilename(dirname, extensions):
0601     ext = filter(lambda e: dirname.endswith('_' + e), extensions)
0602     if len(ext) == 1:
0603         l = len(ext[0])
0604         return dirname[:-l-1] + '.' + ext[0]
0605     return None
0606 
0607 """
0608     List all files and directories that are potentially supported.
0609     The list is created on the extension of the file and trailing part of the
0610     name of the directory
0611 """
0612 def scanDirectory(rootdir, extensions):
0613     if os.path.isfile(rootdir):
0614         return [rootdir]
0615 
0616     filext = map(lambda e: "." + e, extensions)
0617     list = []
0618     for root, directories, files in os.walk(rootdir):
0619         for file in files:
0620             if file.startswith('.'):
0621                 continue
0622             if any(map(lambda e: file.endswith(e), filext)):
0623                 list.append(os.path.join(root, file))
0624         for dir in directories:
0625             file = dirnameToFilename(dir, extensions)
0626             if file:
0627                 list.append(os.path.join(root, file))
0628 
0629     # remove duplicates by converting to a set
0630     return frozenset(list)
0631 
0632 def readZipData(filepath):
0633     if not os.path.exists(filepath):
0634         return
0635 
0636     try:
0637         fd = open(filepath, "rb")
0638         magic = fd.read(4)
0639         if magic != 'PK\3\4':
0640             return
0641         fd.seek(0)
0642         data = fd.read()
0643         fd.close()
0644     except:
0645         return
0646     return data
0647 
0648 def writeZipped(data, filepath):
0649     fd = open(filepath, "wb")
0650     fd.write(data)
0651     fd.close()
0652 
0653 def writeUnzipped(data, dirpath, descriptionfile):
0654     zipdata = ZipData()
0655     zipdata.setFromFileContents(data)
0656     if not zipdata.valid:
0657         return
0658     zipdata.writeToDirectory(dirpath)
0659     zipdata.writeToDataFile(descriptionfile)
0660 
0661 def listzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
0662     # if there is a problem reading, simply do not list the file
0663     data = readZipData(filepath)
0664     if not data:
0665         return
0666     zipdata = ZipData()
0667     zipdata.setFromFileContents(data)
0668     if zipdata.valid:
0669         print filepath
0670 
0671 def createzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
0672     # check that no file exists yet
0673     if os.path.isfile(filepath) or os.path.isfile(hiddenfile):
0674         return
0675 
0676     zipdata = ZipData()
0677     try:
0678         zipdata.setFromDirectory(dirpath, descriptionfile)
0679     except:
0680         raise
0681         return
0682     data = zipdata.recreate()
0683     writeZipped(data, filepath)
0684     shutil.copy(filepath, hiddenfile)
0685 
0686 def createunzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
0687     # check that no directory exists yet
0688     if os.path.isdir(dirpath) or os.path.isfile(hiddenfile) \
0689             or os.path.isfile(descriptionfile):
0690         return
0691 
0692     # if there is a problem reading, simply do not unzip the file
0693     data = readZipData(filepath)
0694     if not data:
0695         return
0696     writeUnzipped(data, dirpath, descriptionfile)
0697     shutil.copy(filepath, hiddenfile)
0698 
0699 """ Find which file is the newest, that is which is different from the other
0700     two. Returns None if all are equal, 'Error' when it cannot be determined,
0701     e.g. because one version does not exist. 'unzipped' when the unzipped
0702     version is different, 'zipped' when the zipped version is different and
0703     'both' when no version resembles the hidden file. """
0704 def findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile):
0705     d = dict(source='error', data='')
0706     # check that an unzipped version and a hidden file exist
0707     if not os.path.isdir(dirpath) or not os.path.isfile(hiddenfile) \
0708             or not os.path.isfile(filepath):
0709         return d
0710 
0711     # check that the files are in sync
0712     hidden = readZipData(hiddenfile)
0713     zipped = readZipData(filepath)
0714     zipdata = ZipData()
0715     try:
0716         zipdata.setFromDirectory(dirpath, descriptionfile)
0717     except:
0718         return d
0719     unzipped = zipdata.recreate()
0720     if hidden == zipped:
0721         d['data'] = unzipped
0722         if hidden == unzipped:
0723             d['source'] = None
0724             return d
0725         d['source'] = 'unzipped'
0726         return d
0727     if hidden == unzipped:
0728         d['data'] = unzipped
0729         d['source'] = 'zipped'
0730         return d
0731     d['source'] = 'both'
0732     return d
0733 
0734 def syncFunction(filepath, dirpath, descriptionfile, hiddenfile):
0735     d = findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile)
0736     if d['source'] == 'both':
0737         print 'Conflict for ' + filepath
0738     elif d['source'] == 'zipped':
0739         writeUnzipped(d['data'], dirpath, descriptionfile)
0740         shutil.copy(filepath, hiddenfile)
0741     elif d['source'] == 'unzipped' or d['source'] == None:
0742         writeZipped(d['data'], filepath)
0743         shutil.copy(filepath, hiddenfile)
0744 
0745 def removezippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
0746     # only delete a version of there is no different version
0747     d = findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile)
0748     if d['source'] != None:
0749         return
0750     os.remove(filepath)
0751     os.remove(hiddenfile)
0752 
0753 def removeunzippedFunction(filepath, dirpath, descriptionfile, hiddenfile):
0754     # only delete a version of there is no different version
0755     d = findDifferentVersion(filepath, dirpath, descriptionfile, hiddenfile)
0756     if d['source'] != None:
0757         return
0758     os.remove(hiddenfile)
0759     if os.path.isfile(descriptionfile):
0760         os.remove(descriptionfile)
0761     shutil.rmtree(dirpath)
0762 
0763 if __name__ == '__main__':
0764     import sys
0765 
0766     if len(sys.argv) < 2:
0767         print 'Bad usage'
0768         exit(1)
0769 
0770     command = sys.argv[1]
0771     if len(sys.argv) == 2:
0772         directories = ['.']
0773     else:
0774             directories = sys.argv[2:]
0775 
0776     commands = {'listzipped': listzippedFunction,
0777         'createzipped': createzippedFunction,
0778         'createunzipped': createunzippedFunction,
0779         'sync': syncFunction,
0780         'removezipped': removezippedFunction,
0781         'removeunzipped': removeunzippedFunction}
0782 
0783         if not command in commands:
0784         print 'invalid command "' + command + '"'
0785         exit(1)
0786 
0787     commandFunction = commands[command]
0788 
0789     extensions = ["odt", "odp", "ods", "odg", "jar", "zip"]
0790 
0791     for directory in directories:
0792         fileList = scanDirectory(directory, extensions)
0793         for file in fileList:
0794             dir = filenameToDirname(file, extensions)
0795             descriptionfile = dir + '.cd'
0796             if file.find('/') == -1:
0797                 hiddenfile = '.' + file
0798             else:
0799                 hiddenfile = '/.'.join(os.path.split(file))
0800             commandFunction(file, dir, descriptionfile, hiddenfile)