diff --git a/source/dcp.py b/source/dcp.py index 2ca655e5b24f229cab49aeef92fdc9c3eca14516..428728a841b046b00022f5a636685270f89212db 100755 --- a/source/dcp.py +++ b/source/dcp.py @@ -1,23 +1,25 @@ #!/usr/bin/env python3 from hashlib import sha1 from base64 import b64encode -from os import stat, listdir +from os import stat, listdir, mkdir, statvfs +from os.path import isdir, basename +from shutil import copyfile from xml.dom.minidom import parse import sys import vlc -class Asset(object): +class Asset (object): '''A simple asset that's part of whole DCP package.''' - def verifySize(self): + def verifySize (self): '''verify that the size of the file is correct, if present.''' try: - return stat(self.fullpath).st_size == self.size + return stat (self.fullpath).st_size == self.size except AttributeError: return True - def verifyHash(self): + def verifyHash (self): '''verify that the hash is correct, if present.''' - if hasattr(self, 'hash') == False: + if hasattr (self, 'hash') == False: return True #from os import stat #try: @@ -25,13 +27,38 @@ class Asset(object): #except: #buffersize = 2**16 buffersize = 2 ** 16 - fh = open(self.fullpath, 'rb') - buffer = fh.read(buffersize) - hash = sha1() + fh = open (self.fullpath, 'rb') + buffer = fh.read (buffersize) + hash = sha1 () while buffer: - hash.update(buffer) - buffer = fh.read(buffersize) - return(self.hash == b64encode (hash.digest()).decode()) + hash.update (buffer) + buffer = fh.read (buffersize) + fh.close () + return (self.hash == b64encode (hash.digest ()).decode ()) + + def copyAndVerify (self, destination): + '''Copies the file to the destination directory and verifies the hash + during.''' + newfilepath = destination + '/' + self.filename + if hasattr (self, 'hash') == False: + copyfile (self.fullpath, newfilepath) + self.fullpath = newfilepath + self.rootpath = destination + return True + buffersize = 2 ** 16 + in_fh = open (self.fullpath, 'rb') + out_fh = open (newfullpath, 'wb') + buffer = in_fh.read (buffersize) + hash = sha1 () + while buffer: + hash.update (buffer) + out_fh.write (buffer) + buffer = in_fh.read (buffersize) + in_fh.close () + out_fh.close () + self.rootpath = destination + self.fullpath = newfullpath + return (self.hash == b64encode (hash.digest ()).decode ()) def add_duration (self): if hasattr (self, 'type') and self.type.find ('mxf') > -1: @@ -40,7 +67,8 @@ class Asset(object): media.parse () self.duration = media.get_duration () - def __init__(self, rootpath, filename, id=None, hash=None, size=None, packinglist=False, type=None): + def __init__(self, rootpath, filename, id=None, hash=None, size=None,\ + packinglist=False, type=None): '''Initialize an asset, has to have a filename and path.''' self.rootpath = rootpath if filename[0:8] == 'file:///': @@ -60,15 +88,15 @@ class Asset(object): class DCP: '''A complete DCP package.''' - def verify(self): + def verify (self): '''Verifies that all assets are of the correct size and have the correct hash.''' for asset in self.assets: try: - if asset.verifySize() == False: + if asset.verifySize () == False: return False except BaseException as e: - raise RuntimeError ('Failed size comparisement for ' + + raise RuntimeError ('Failed size comparisement for ' +\ asset.filename) from e #Sort the assets by size before calculating hashes, for performance. def sortkey(x): @@ -76,71 +104,75 @@ class DCP: return x.size except AttributeError: return 0 - self.assets.sort(key=sortkey) + self.assets.sort (key=sortkey) for asset in self.assets: try: - if asset.verifyHash() == False: + if asset.verifyHash () == False: return False except BaseException as e: - raise RuntimeError ('Failed hash calculation for ' + + raise RuntimeError ('Failed hash calculation for ' +\ asset.filename) from e return True def _parse_assetmap (self): '''Adds the asset map file to the list of assets and parses it.''' - if 'ASSETMAP.xml' in listdir(self.directory): + if 'ASSETMAP.xml' in listdir (self.directory): filename = 'ASSETMAP.xml' - elif 'ASSETMAP' in listdir(self.directory): + elif 'ASSETMAP' in listdir (self.directory): filename = 'ASSETMAP' else: raise RuntimeError ('Couldn\'t find assetmap file') - self.assets = [Asset(self.directory, filename, type='text/xml')] + self.assets = [Asset (self.directory, filename, type='text/xml')] assetmap = self.assets[0].fullpath try: - assetmap = parse(assetmap).getElementsByTagName('Asset') - self.assets.append(Asset(self.directory, filename)) + assetmap = parse (assetmap).getElementsByTagName ('Asset') + self.assets.append (Asset (self.directory, filename)) for element in assetmap: - id = element.getElementsByTagName('Id')[0].firstChild.data - id = id.split(':')[-1] - filename = element.getElementsByTagName('Path')[0].firstChild.data - packinglist = len(element.getElementsByTagName('PackingList')) > 0 + id = element.getElementsByTagName ('Id')[0].firstChild.data + id = id.split (':')[-1] + filename = element.getElementsByTagName ('Path')[0].firstChild.data + packinglist = len (element.getElementsByTagName ('PackingList')) > 0 if packinglist: - self.assets.append(Asset(self.directory, filename, id=id, packinglist=packinglist, type='text/xml')) + self.assets.append (Asset (self.directory, filename,\ + id=id, packinglist=packinglist, type='text/xml')) else: - self.assets.append(Asset(self.directory, filename, id=id, packinglist=packinglist)) + self.assets.append (Asset (self.directory, filename,\ + id=id, packinglist=packinglist)) except BaseException as e: raise RuntimeError ('Failed to parse assetmap file') from e def _add_volindex (self): '''Adds the volume index file to the list of assets.''' - if 'VOLINDEX.xml' in listdir(self.directory): + if 'VOLINDEX.xml' in listdir (self.directory): filename = 'VOLINDEX.xml' - elif 'VOLINDEX' in listdir(self.directory): + elif 'VOLINDEX' in listdir (self.directory): filename = 'VOLINDEX' else: #raise RuntimeError ('Couldn\'t find volindex file') return - self.assets.append(Asset(self.directory, filename, type='text/xml')) + self.assets.append (Asset (self.directory, filename, type='text/xml')) def _parse_packinglist (self): '''Parses the packing list.''' try: - pkls = (parse(x.fullpath) for x in self.assets if x.packinglist) + pkls = (parse (x.fullpath) for x in self.assets if x.packinglist) for pkl in pkls: - if hasattr(self, 'signed') == False: - self.signed = len(pkl.getElementsByTagName('Signer')) > 0 + if hasattr (self, 'signed') == False: + self.signed = len (pkl.getElementsByTagName ('Signer')) > 0 try: - if hasattr(self, 'name') == False: - self.name = pkl.getElementsByTagName('AnnotationText')[0].firstChild.data.strip() + if hasattr (self, 'name') == False: + self.name = pkl.getElementsByTagName\ + ('AnnotationText')[0].firstChild.data.strip() except: pass - for element in pkl.getElementsByTagName('Asset'): - id = element.getElementsByTagName('Id')[0].firstChild.data - id = id.split(':')[-1] - hash = element.getElementsByTagName('Hash')[0].firstChild.data - type = element.getElementsByTagName('Type')[0].firstChild.data - size = int(element.getElementsByTagName('Size')[0].firstChild.data) - asset = [x for x in self.assets if hasattr(x, 'id') and x.id == id][0] + for element in pkl.getElementsByTagName ('Asset'): + id = element.getElementsByTagName ('Id')[0].firstChild.data + id = id.split (':')[-1] + hash = element.getElementsByTagName ('Hash')[0].firstChild.data + type = element.getElementsByTagName ('Type')[0].firstChild.data + size = int(element.getElementsByTagName ('Size')[0].firstChild.data) + asset = [x for x in self.assets if hasattr (x, 'id') and\ + x.id == id][0] asset.hash = hash asset.size = size asset.type = type @@ -148,23 +180,57 @@ class DCP: except BaseException as e: raise RuntimeError ('Failed to parse packinglist file') from e - def __init__(self, directory): + def __init__ (self, directory): '''Parses the DCP in the directory specified.''' self.directory = directory self._parse_assetmap () self._add_volindex () self._parse_packinglist () try: - self.duration = max ([x.duration for x in self.assets if hasattr (x, 'duration')]) + self.duration = max ([x.duration for x in self.assets if hasattr\ + (x, 'duration')]) except: self.duration = 'Unknown' + def copyAndVerify (self, destination): + '''Copies the DCP to the destination directory and verifies during.''' + for asset in self.assets: + totalsize = stat (asset.fullpath).st_size + try: + if asset.verifySize () == False: + return False + except BaseException as e: + raise RuntimeError ('Failed size comparisement for ' +\ + asset.filename) from e + freespace = statvfs (destination).f_bavail * statvfs\ + (destination).f_bsize + if freespace < totalsize: + return False + #Sort the assets by size before calculating hashes, for performance. + def sortkey(x): + try: + return x.size + except AttributeError: + return 0 + self.assets.sort (key=sortkey) + newdirectory = destination + '/' + basename (self.directory) + mkdir (newdirectory) + for asset in self.assets: + try: + if asset.copyAndVerify (newdirectory) == False: + return False + except BaseException as e: + raise RuntimeError ('Failed hash calculation for ' +\ + asset.filename) from e + self.directory = newdirectory + return True + if __name__ == '__main__': - if (len(sys.argv) == 2): - dcp = DCP(sys.argv[1]) + if len (sys.argv) == 2: + dcp = DCP (sys.argv[1]) else: - dcp = DCP('./') + dcp = DCP ('./') try: print ('Name:', dcp.name) except: @@ -174,8 +240,8 @@ if __name__ == '__main__': else: print ('DCP is unsigned') print ('Duration:', dcp.duration) - if (dcp.verify()): + if dcp.verify (): print ('Verification succeeded.') else: print ('Verification failed.') - exit(0) + exit (0) diff --git a/source/manager.py b/source/manager.py index 0aedaa821d9054ceed67796b285fc6dd4e8aaeb8..db37f276fcbf1410d5fa39d6e07c3ae598d1904f 100755 --- a/source/manager.py +++ b/source/manager.py @@ -6,6 +6,8 @@ from PyQt4 import QtCore from dcp import DCP import ui import sys +import syslog +import time class verifyThread(QtCore.QThread): '''A seperate thread to verify the DCP (IO intensive). @@ -20,8 +22,12 @@ class verifyThread(QtCore.QThread): result = dcp.verify() if result: window.verifyLine.setText('OK') + syslog.syslog (syslog.LOG_INFO, time.ctime () + dcp.name +\ + ' verification succeeded.') else: window.verifyLine.setText('Corrupted!') + syslog.syslog (syslog.LOG_INFO, time.ctime () + dcp.name +\ + ' verification failed.') except BaseException as exception: window.verifyLine.setText(str(exception)) @@ -38,6 +44,7 @@ def verify_in_thread(): if __name__ == '__main__': + syslog.openlog (ident = 'dcpman', facility = syslog.LOG_USER) app = QtGui.QApplication(sys.argv) icon = QtGui.QIcon('/usr/share/icons/oxygen/16x16/apps/kmplayer.png') app.setWindowIcon(icon) @@ -51,8 +58,18 @@ if __name__ == '__main__': dcp = DCP(directory) try: window.nameLine.setText(dcp.name) + syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\ + 'parsed.') except AttributeError: window.nameLine.setText(directory.split('/')[-1]) + if dcp.signed: + syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\ + ' is signed.') + else: + syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\ + ' is not sigend.') + syslog.syslog (syslog.INFO, time.ctime () + dcp.name +\ + ' duration is ' + dcp.duration) if dcp.signed and dcp.duration == 0: window.encryptedLine.setText('Most likely') elif dcp.signed or dcp.duration == 0: