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)