class ProjData (object) : def __init__(self, pid, gid = None) : '''Intitate the whole class and create the object.''' self.pid = pid self.gid = gid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig self.local = ProjLocal(pid) self.log = ProjLog(pid) self.projHome = os.path.join(os.path.expanduser(self.userConfig['Resources']['projects']), self.pid) self.projList = self.tools.getProjIdList(os.path.expanduser(self.userConfig['Resources']['projects'])) # Log messages for this module self.errorCodes = { '1220' : ['LOG', 'Project [<<1>>] already registered in the system.'], '1240' : ['ERR', 'Could not find/open the Project configuration file for [<<1>>]. Project could not be registered!'], '3410' : ['LOG', 'Backup file cull skipping: [<<1>>] Not a recognized Rapuma backup file name format.'], '3510' : ['ERR', 'The path (or name) given is not valid: [<<1>>].'], '3530' : ['MSG', 'Project backup: [<<1>>] has been restored to: [<<2>>]. A backup of the orginal project remains and must be manually removed.'], '3550' : ['ERR', 'Project backup version request: [<<1>>] exceeds the maxium number which could be in storage which is: [<<2>>]. Request an earlier (lesser) version.'], '3610' : ['ERR', 'The [<<1>>]. project is not registered. No backup was done.'], '3620' : ['ERR', 'The path to the backup folder is not valid [<<1>>]. Please try again.'], '3622' : ['ERR', 'The path to the backup folder is not set. Please set it and try again.'], '3625' : ['ERR', 'The path given to the backup folder is not valid [<<1>>]. Please set the system backup path.'], '3630' : ['MSG', 'Backup for [<<1>>] created and saved to: [<<2>>]'], '4110' : ['MSG', 'Completed merging data.'], '4120' : ['MSG', 'No files updated.'], '4130' : ['MSG', 'Added: <<1>> file(s).'], '4140' : ['MSG', 'Updated: <<1>> file(s)'], '4150' : ['WRN', 'The project data in: [<<1>>] will be replaced with the data from: [<<2>>].'], '4210' : ['MSG', 'Completed pulling/restoring data from the cloud.'], '4220' : ['ERR', 'Cannot resolve path: [<<1>>]'], '4250' : ['ERR', 'The cloud project [<<1>>] you want to pull from is owned by [<<2>>]. Use force (-f) to pull the project and change the local owner ID.'], '4260' : ['ERR', 'The local project [<<1>>] is newer than the cloud copy. If you seriously want to overwrite it, use force (-f) to do so.'], '4270' : ['MSG', 'Restored the project [<<1>>] from the cloud copy. Local copy is owned by [<<2>>].'], } ############################################################################### ############################## General Functions ############################## ############################################################################### ####################### Error Code Block Series = 1000 ######################## ############################################################################### ############################################################################### ########################## Archive Project Functions ########################## ############################################################################### ####################### Error Code Block Series = 2000 ######################## ############################################################################### def makeExcludeFileList (self, source) : '''Return a list of files that are not necessary to be included in a backup template or an archive. These will be all auto-generated files that containe system- specific paths, etc.''' excludeFiles = [] excludeTypes = ['delayed', 'log', 'notepages', 'parlocs', 'pdf', 'tex', 'piclist', 'adj', 'zip'] excludeFolders = ['Draft', 'Final', 'HelperScript', 'Proof'] # Process the excluded folders for root, dirs, files in os.walk(source) : for fileName in files : if os.path.basename(root) in excludeFolders : excludeFiles.append(os.path.join(root, fileName)) else : # Get rid of edited backup files if fileName[-1] == '~' : excludeFiles.append(os.path.join(root, fileName)) continue ext = os.path.splitext(fileName)[1][1:] if ext in excludeTypes : # A special indicator for file we want to keep if fileName.find('-ext.') > 0 : continue return excludeFiles # FIXME: Should archiveProject() use self.pid instead of explicitly passing in a pid? def archiveProject (self, pid, path = None) : '''Archive a project. Send the compressed archive file to the user-specified archive folder. If none is specified, put the archive in cwd. If a valid path is specified, send it to that location. Like backup, this too will overwrite any existing file of the same name. The difference is that this will also disable the project so it cannot be accesses by Rapuma. When a project is archived, all work should cease on the project.''' # Make a private project object just for archiving aProject = Project(pid, self.gid) # Set some paths and file names archName = aProject.projectIDCode + '.rapuma' userArchives = self.userConfig['Resources']['archive'] archTarget = '' if path : path = self.tools.resolvePath(path) if os.path.isdir(path) : archTarget = os.path.join(path, archName) else : self.tools.terminal('\nError: The path given is not valid: [' + path + ']\n') self.tools.dieNow() elif os.path.isdir(userArchives) : archTarget = os.path.join(userArchives, archName) elif os.path.isdir(os.path.dirname(aProject.local.projHome)) : # Default to the dir just above the project archTarget = os.path.dirname(aProject.local.projHome) else : self.tools.terminal('\nError: Cannot resolve a path to create the archive file!\n') self.tools.dieNow() # Get a list of files we don't want excludeFiles = self.makeExcludeFileList(source) self.zipUpProject(archTarget, excludeFiles) # Rename the source dir to indicate it was archived bakArchProjDir = aProject.local.projHome + '(archived)' if os.path.isdir(bakArchProjDir) : self.tools.terminal('\nError: Cannot complete archival process!\n') self.tools.terminal('\nAnother archived version of this project exsits with the folder name of: ' + self.tools.fName(bakArchProjDir) + '\n') self.tools.terminal('\nPlease remove or rename it and then repete the process.\n') self.tools.dieNow() else : os.rename(aProject.local.projHome, bakArchProjDir) # Finish here self.tools.terminal('Archive for [' + pid + '] created and saved to: ' + archTarget + '\n') def zipUpProject (self, target, excludeFiles = None) : '''Zip up a project and deposit it to target location. Be sure to strip out all all auto-created, user-specific files that could mess up a transfer to another system. This goes for archives and backups''' # import pdb; pdb.set_trace() # In case an exclude list is not given if not excludeFiles : excludeFiles = [] # Do the zip magic here root_len = len(self.local.projHome) with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as myzip : sys.stdout.write('Backing up files') sys.stdout.flush() for root, dirs, files in os.walk(self.local.projHome) : # Chop off the part of the path we do not need to store zip_root = os.path.abspath(root)[root_len:] for f in files : if os.path.join(root, f) in excludeFiles : continue if not f[-1] == '~' : fn, fx = os.path.splitext(f) fullpath = os.path.join(root, f) zip_name = os.path.join(zip_root, f) sys.stdout.write('.') sys.stdout.flush() myzip.write(fullpath, zip_name, zipfile.ZIP_DEFLATED) # Add a space before the next message print '\n' # FIXME: Should restoreArchive() use self.pid instead of explicitly passing in a pid? def restoreArchive (self, pid, targetPath, sourcePath = None) : '''Restore a project from the user specified storage area or sourcePath if specified. Use targetPath to specify where the project will be restored. Rapuma will register the project there.''' # Check to see if the user included the extension try : pid.split('.')[1] == 'rapuma' archName = pid pid = pid.split('.')[0] except : archName = pid + '.rapuma' archSource = '' archTarget = '' userArchives = '' # First look for the archive that is to be restored if sourcePath : if os.path.isdir(sourcePath) : archSource = os.path.join(sourcePath, archName) elif os.path.isdir(self.userConfig['Resources']['archive']) : userArchives = self.userConfig['Resources']['archive'] archSource = os.path.join(userArchives, archName) else : self.tools.terminal('\nError: The path (or name) given is not valid: [' + archSource + ']\n') self.tools.dieNow() # Now set the target params if targetPath : if not os.path.isdir(targetPath) : self.tools.terminal('\nError: The path given is not valid: [' + targetPath + ']\n') self.tools.dieNow() else : archTarget = os.path.join(targetPath, pid) # If we made it this far, extract the archive with zipfile.ZipFile(archSource, 'r') as myzip : myzip.extractall(archTarget) # Permission for executables is lost in the zip, fix it here for folder in ['Scripts', os.path.join('Macros', 'User')] : self.tools.fixExecutables(os.path.join(archTarget, folder)) # FIXME: This will need some work # Add project to local Rapuma project registry # To do this we need to open up the restored project config file # and pull out some settings. local = ProjLocal(pid) pc = Config(pid) log = ProjLog(pid) aProject = Project(pid, self.gid) # import pdb; pdb.set_trace() # Finish here self.tools.terminal('\nRapuma archive [' + pid + '] has been restored to: ' + archTarget + '\n') ############################################################################### ########################### Backup Project Functions ########################## ############################################################################### ####################### Error Code Block Series = 3000 ######################## ############################################################################### def cullBackups (self, maxBak, bakDir) : '''Remove any excess backups from the backup folder in this project.''' # Get number of maximum backups to store maxStoreBackups = int(maxBak) if not maxStoreBackups or maxStoreBackups == 0 : maxStoreBackups = 1 # Build the cullList cullList = [] files = os.listdir(bakDir) for f in files : try : cullList.append(int(f.split('.')[0])) except : self.log.writeToLog(self.errorCodes['3410'], [f]) # Remove oldest file(s) while len(cullList) > maxStoreBackups : fn = min(cullList) cullList.remove(min(cullList)) os.remove(os.path.join(bakDir, str(fn) + '.zip')) def backupProject (self, targetPath=None) : '''Backup a project. Send the compressed backup file with a date-stamp file name to the user-specified backup folder. If a target path is specified, put the archive there but use the PID in the name. If other backups with the same name exist there, increment with a number.''' # First see if this is even a valid project if self.pid not in self.projList : self.log.writeToLog(self.errorCodes['3610'], [self.pid]) # Set some paths and file names if not targetPath : # Now check for a valid location to backup to if self.local.userLibBackup == '' : self.log.writeToLog(self.errorCodes['3622']) elif not os.path.exists(self.local.userLibBackup) : self.log.writeToLog(self.errorCodes['3620'], [self.local.userLibBackup]) projBackupFolder = os.path.join(self.local.userLibBackup, self.pid) backupTarget = os.path.join(projBackupFolder, self.tools.fullFileTimeStamp() + '.zip') else : projBackupFolder = self.tools.resolvePath(targetPath) # Now check for a valid target path if not os.path.exists(projBackupFolder) : self.log.writeToLog(self.errorCodes['3625'], [targetPath]) backupTarget = self.tools.incrementFileName(os.path.join(projBackupFolder, self.pid + '.zip')) # Make sure the dir is there if not os.path.exists(projBackupFolder) : os.makedirs(projBackupFolder) # import pdb; pdb.set_trace() # Zip up but use a list of files we don't want self.zipUpProject(backupTarget, self.makeExcludeFileList(source)) # Cull out any excess backups if not targetPath : self.cullBackups(self.userConfig['System']['maxStoreBackups'], projBackupFolder) # Finish here pc = Config(self.pid) pc.getProjectConfig() pc.projectConfig['Backup']['lastBackup'] = self.tools.fullFileTimeStamp() self.tools.writeConfFile(pc.projectConfig) self.log.writeToLog(self.errorCodes['3630'], [self.pid,backupTarget]) return True def backupRestore (self, backup, target = None) : '''Restore a backup to the current or specified project.''' # import pdb; pdb.set_trace() if not target : target = self.local.projHome # Now remove the orginal if os.path.exists(target) : shutil.rmtree(target) # Create an empty folder to restore to os.makedirs(target) # If we made it this far, extract the archive with zipfile.ZipFile(backup, 'r') as myzip : myzip.extractall(target) return True # def restoreLocalBackup (self, bakFile) : # '''Restore from a project backup. As a project may have multiple backups in # its backup folder, the user will need to provide a number from 1 to n (n being # the number of backups in the folder, 1 being the most recent and n being the # oldest). If no number is provided, 1, (the most recent) will be restored.''' # # Adjust bNum if needed # maxBak = int(self.userConfig['System']['maxStoreBackups']) # if not bNum : # bNum = 0 # else : # bNum = int(bNum) # if bNum <= 0 : # bNum = 0 # elif bNum > maxBak : # self.log.writeToLog(self.errorCodes['3550'], [str(bNum), str(maxBak)]) # else : # bNum = bNum-1 # # Get vals we need # projHome = self.getProjHome() # projBackupFolder = self.tools.resolvePath(os.path.join(self.userConfig['Resources']['backup'], self.pid)) # # Get the archive file name # files = os.listdir(projBackupFolder) # fns = [] # for f in files : # fns.append(int(f.split('.')[0])) # # Sort the list, last (latest) first # fns.sort(reverse=True) # # Make file path/name # backup = os.path.join(projBackupFolder, str(fns[bNum]) + '.zip') # if not os.path.exists(backup) : # self.log.writeToLog(self.errorCodes['3510'], [backup]) # # Restore the backup # self.backupRestore(backup, projHome) # # Permission for executables is lost in the zip, fix them here # self.tools.fixExecutables(projHome) # # Add helper scripts if needed # if self.tools.str2bool(self.userConfig['System']['autoHelperScripts']) : # ProjCommander(self.pid).updateScripts() # # Finish here (We will leave the project backup in place) # self.log.writeToLog(self.errorCodes['3530'], [self.tools.fName(backup),projHome]) def restoreExternalBackup (self, source, target = None, force = False) : '''Restore a non-existant project from an external backup to a target folder. If no target is provided the project will be installed in the default project folder. The source path and ZIP file must be valid''' # Get/make the (localized) project home reference projHome = self.getProjHome(target) # import pdb; pdb.set_trace() # Create the source backup file name # source = os.path.join(source, self.pid + '.zip') # FIXME: This needs some review and rework # Restore the backup if self.backupRestore(source, projHome) : # Permission for executables is lost in the zip, fix them here self.tools.fixExecutables(projHome) # If this is a new project we will need to register it now self.registerProject(projHome) # Add helper scripts if needed if self.tools.str2bool(self.userConfig['System']['autoHelperScripts']) : ProjCommander(self.pid).updateScripts() # Finish here (We will leave the backup-backup in place) self.tools.terminal('\nRapuma backup [' + self.pid + '] has been restored to: ' + projHome + '\n') return True ############################################################################### ############################ Cloud Backup Functions ########################### ############################################################################### ####################### Error Code Block Series = 4000 ######################## ############################################################################### def isNewerThanCloud (self, cloud, projectConfig) : '''Compare time stamps between the cloud and the local project. Return True if the local project is newer or the same age as the copy in the cloud. Return True if the project does not exist in the local copy of the cloud.''' # First see if it exists cConfig = self.getConfig(cloud) if not cConfig : return True elif not cConfig.has_key('Backup') : return True elif not cConfig['Backup'].has_key('lastCloudPush') : return True # Check local for key if not projectConfig.has_key('Backup') : return False elif not projectConfig['Backup'].has_key('lastCloudPush') : return False # Compare if we made it this far cStamp = cConfig['Backup']['lastCloudPush'] lStamp = projectConfig['Backup']['lastCloudPush'] if lStamp >= cStamp : return True def isNewerThanLocal (self, cloud, projectConfig) : '''Compare time stamps between the cloud and the local project. Return True if the cloud project is newer or the same age as the local copy. Return True if the project does not exist in as a local copy.''' # First see if the local exists if not projectConfig : return True # See if cloud is there and up-to-date cloudConfig = self.getConfig(cloud) if not cloudConfig : return False # Compare if we made it this far cStamp = cloudConfig['Backup']['lastCloudPush'] # It is possible the local has never been pushed # If that is the case, local is assumed older try : pStamp = projectConfig['Backup']['lastCloudPush'] except : return False if cStamp >= pStamp : return True def getConfig (self, projHome) : '''Return a valid config object from cloud project.''' # import pdb; pdb.set_trace() projectConfigFile = os.path.join(projHome, 'Config', 'project.conf') if os.path.exists(projectConfigFile) : return ConfigObj(projectConfigFile, encoding='utf-8') def getCloudOwner (self, cloud) : '''Return the owner of a specified cloud project.''' try : return self.getConfig(cloud)['Backup']['ownerID'] except : return None def getLocalOwner (self) : '''Return the owner of a specified cloud project.''' return self.userConfig['System']['userID'] def sameOwner (self, cloud) : '''Return True if the owner of a given cloud is the same as the system user. Also return True if the cloud owner is not present.''' # First check for existence if not self.getCloudOwner(cloud) : return True # Compare if we made it to this point if self.getCloudOwner(cloud) == self.getLocalOwner() : return True def setCloudPushTime (self, projectConfig) : '''Set/reset the lastPush time stamp setting.''' projectConfig['Backup']['lastCloudPush'] = self.tools.fullFileTimeStamp() self.tools.writeConfFile(projectConfig) def buyCloud (self, projectConfig) : '''Change the ownership on a project in the cloud by assigning your userID to the local project cloudOwnerID. Then, using force the next time the project is pushed to the cloud, you will own it.''' projOwnerID = self.userConfig['System']['userID'] projectConfig['Backup']['ownerID'] = projOwnerID self.tools.writeConfFile(projectConfig) def buyLocal (self, projectConfig) : '''Change the ownership on a local project by assigning your userID to it.''' projOwnerID = self.userConfig['System']['userID'] projectConfig['Backup']['ownerID'] = projOwnerID self.tools.writeConfFile(projectConfig) def replaceProject (self, source, target) : '''This will completly replace an existing project (target) with data from another project (source). This assumes source and target are valid.''' # We simply just get rid of the target before doing a merge shutil.rmtree(target) self.log.writeToLog(self.errorCodes['4150'], [target, source]) self.mergeProjects(source, target) def mergeProjects(self, source, target) : '''This will merge two Rapuma projects and try to preserve data in the target that is newer than the source. This assumes target and source are valid.''' # Get a list of files we do not want excludeFiles = self.makeExcludeFileList(source) # Get a total list of files from the project cn = 0 cr = 0 # Add space for output message sys.stdout.write('\n') sys.stdout.write('Merging files from: ' + source + ' to: ' + target) sys.stdout.flush() for folder, subs, files in os.walk(source): for fileName in files: # Do not include any backup files we find if fileName[-1] == '~' : continue if os.path.join(folder, fileName) not in excludeFiles : if not os.path.isdir(folder.replace(source, target)) : os.makedirs(folder.replace(source, target)) targetFile = os.path.join(folder, fileName).replace(source, target) sourceFile = os.path.join(folder, fileName) if not os.path.isfile(targetFile) : sys.stdout.write('.') sys.stdout.flush() shutil.copy(sourceFile, targetFile) cn +=1 # Otherwise if the cloud file is older than # the project file, refresh it elif self.tools.isOlder(targetFile, sourceFile) : if os.path.isfile(targetFile) : os.remove(targetFile) sys.stdout.write('.') sys.stdout.flush() shutil.copy(sourceFile, targetFile) cr +=1 # Add space for next message sys.stdout.write('\n') # Report what happened self.log.writeToLog(self.errorCodes['4110']) if cn == 0 and cr == 0 : self.log.writeToLog(self.errorCodes['4120']) else : if cn > 0 : self.log.writeToLog(self.errorCodes['4130'], [str(cn)]) if cr > 0 : self.log.writeToLog(self.errorCodes['4140'], [str(cr)]) return True def getProjHome (self, tPath = None) : '''Return a project home path by checking to see what the best path might be. Provided path gets first dibs, then ''' if tPath : if os.path.isfile(tPath) : return self.local.projHome elif self.tools.resolvePath(tPath) : tPath = self.tools.resolvePath(tPath) lastFolder = os.path.basename(tPath) if lastFolder == self.pid : return tPath else : return os.path.join(tPath, self.pid) else : self.log.writeToLog(self.errorCodes['4220'], [tPath]) elif self.local.projHome : return self.local.projHome else : return self.tools.resolvePath(os.path.join(self.userConfig['Resources']['projects'], self.pid))
class ProjBackground (object) : def __init__(self, pid, gid = None) : '''Intitate the whole class and create the object.''' # import pdb; pdb.set_trace() self.pid = pid self.gid = gid self.local = ProjLocal(pid, gid) self.tools = Tools() self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.proj_config.getLayoutConfig() self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.log = ProjLog(pid) self.user = UserConfig() self.userConfig = self.user.userConfig self.projHome = os.path.join(os.environ['RAPUMA_PROJECTS'], self.pid) self.mmToPx = 72 / 25.4 # For debugging purposes a switch can be set here for verbose # message output via the terminal self.debugMode = self.tools.str2bool(self.userConfig['System']['debugging']) # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '1110' : ['MSG', 'File exsits: [<<1>>]. Use \"force\" to remove it.'], '1280' : ['ERR', 'Failed to merge background file with command: [<<1>>]. This is the error: [<<2>>]'], '1290' : ['ERR', 'Failed to convert background file [<<1>>]. Error: [<<2>>] The command was: [<<3>>]'], '1300' : ['MSG', 'Background merge operation in process, please wait...'], '1305' : ['MSG', 'Adding document information, please wait...'], '1310' : ['WRN', 'Failed to add background component: [<<1>>] with error: [<<2>>]'], '1320' : ['MSG', 'New background created.'] } ############################################################################### ############################### Basic Functions ############################### ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def turnOnBackground (self) : '''Change the layout config settings to turn on the background.''' if not self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) : self.layoutConfig['DocumentFeatures']['useBackground'] = True self.tools.writeConfFile(self.layoutConfig) def turnOffBackground (self) : '''Change the layout config settings to turn off the background.''' if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) : self.layoutConfig['DocumentFeatures']['useBackground'] = False self.tools.writeConfFile(self.layoutConfig) def turnOnDocInfo (self) : '''Change the layout config settings to turn on the doc info in the background.''' if not self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) : self.layoutConfig['DocumentFeatures']['useDocInfo'] = True self.tools.writeConfFile(self.layoutConfig) def turnOffDocInfo (self) : '''Change the layout config settings to turn off the doc info in the background.''' if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) : self.layoutConfig['DocumentFeatures']['useDocInfo'] = False self.tools.writeConfFile(self.layoutConfig) def addBackground (self, target) : '''Add a background (watermark) to a rendered PDF file. This will figure out what the background is to be composed of and create a master background page. Using force will cause it to be remade.''' # Do a quick check if the background needs to be remade # The background normally is not remade if one already exists. # If one is there, it can be remade if regenerate is set to # to True. Obviously, if one is not there, it will be made. if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['regenerateBackground']) : self.createBackground() else : # If there isn't one, make it if not os.path.exists(self.local.backgroundFile) : self.createBackground() # Merge target with the project's background file in the Illustraton folder self.log.writeToLog(self.errorCodes['1300']) # Create a special name for the file with the background # Then merge and save it viewFile = self.tools.alterFileName(target, 'view') shutil.copy(self.tools.mergePdfFilesPdftk(self.centerOnPrintPage(target), self.local.backgroundFile), viewFile) # Not returning a file name would mean it failed if os.path.exists(viewFile) : return viewFile def addDocInfo (self, target) : '''Add (merge) document information to the rendered target doc.''' # Initialize the process docInfoText = self.layoutConfig['DocumentFeatures']['docInfoText'] timestamp = self.tools.tStamp() if self.gid : headerLine = self.pid + ' / ' + self.gid + ' / ' + timestamp else : headerLine = self.pid + ' / ' + timestamp svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # This can be determined with the pyPdf element # "pdf.getPage(0).mediaBox", which returns a # RectangleObject([0, 0, Width, Height]). The # width and height are in points. Hopefully we will # always be safe by measuring the gidPdfFile size. #pdf = PdfFileReader(open(self.local.gidPdfFile,'rb')) #var2 = pdf.getPage(0).mediaBox #trimWidth = float(var2.getWidth()) #trimHeight = float(var2.getHeight()) trimWidth = float(self.layoutConfig['PageLayout']['pageWidth']) trimHeight = float(self.layoutConfig['PageLayout']['pageHeight']) # Printer page size pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] ppsCenter = ppsWidth/2 # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''"> <g><text x = "''' + str(ppsCenter) + '''" y = "''' + str(20) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:8;text-anchor:middle;fill:#000000;fill-opacity:1">''' + headerLine + '''</text></g> <g><text x = "''' + str(ppsCenter) + '''" y = "''' + str(ppsHeight-30) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:8;text-anchor:middle;fill:#000000;fill-opacity:1">''' + self.tools.fName(target) + '''</text></g> <g><text x = "''' + str(ppsCenter) + '''" y = "''' + str(ppsHeight-20) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:8;text-anchor:middle;fill:#000000;fill-opacity:1">''' + docInfoText + '''</text></g> </svg>''') # Merge target with the background self.log.writeToLog(self.errorCodes['1305']) # Create a special name for the file with the background viewFile = self.tools.alterFileName(target, 'view') # Compare the new file name with the target to see if we are # already working with a background file if viewFile == target : # If the target is a BG file all we need to do is merge it self.tools.mergePdfFilesPdftk(viewFile, self.tools.convertSvgToPdfRsvg(svgFile)) else : # If not a BG file, we need to be sure the target is the same # size as the print page to merge with pdftk shutil.copy(self.tools.mergePdfFilesPdftk(self.centerOnPrintPage(target), self.tools.convertSvgToPdfRsvg(svgFile)), viewFile) # Not returning a file name would mean it failed if os.path.exists(viewFile) : return viewFile ##### Background Creation Functions ##### def createBackground (self) : '''Create a background file. This will overwrite any existing background file and will add each recognized background type found in the bacgroundComponents config setting.''' self.createBlankBackground() # Add each component to the blank background file for comp in self.layoutConfig['DocumentFeatures']['backgroundComponents'] : try : getattr(self, 'merge' + comp.capitalize())() except Exception as e : self.log.writeToLog(self.errorCodes['1310'],[comp,str(e)]) pass self.log.writeToLog(self.errorCodes['1320']) return True def createBlankBackground (self) : '''Create a blank background page according to the print page size specified.''' # Set the temp svg file name svgFile = tempfile.NamedTemporaryFile().name # Printer page size pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] # Be sure there is an illustrations folder in place if not os.path.isdir(self.local.projIllustrationFolder) : os.mkdir(self.local.projIllustrationFolder) # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight)+ '''"> </svg>''') shutil.copy(self.tools.convertSvgToPdfRsvg(svgFile), self.local.backgroundFile) def mergeWatermark (self) : '''Create a watermark file and return the file name. Using force will cause any exsiting versions to be recreated.''' # import pdb; pdb.set_trace() # Initialize the process pubProg = "Rapuma" watermarkText = self.layoutConfig['DocumentFeatures']['watermarkText'] svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # Trim page width [px] trimWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # Trim page height [px] trimHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Printer page size [px] (ppsWidth, ppsHeight) = self.printerPageSize() pageX = ppsWidth/2 pageY = ppsHeight/2 # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''"> <g><text x = "''' + str(pageX-trimWidth*.42) + '''" y = "''' + str(pageY-trimHeight*0.39) + '''" style="font-family:DejaVu Sans;font-style:regular;font-size:32;text-anchor:start;fill:#d5d5f5;fill-opacity:1">''' + str(pubProg) + ''' <tspan x = "''' + str(pageX) + '''" y = "''' + str(pageY - trimHeight*0.22) + '''" style="text-anchor:middle">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX+trimWidth*0.42)+ '''" y = "''' + str(pageY-trimHeight*0.05) + '''" style="text-anchor:end">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX) + '''" y = "''' + str(pageY+trimHeight*0.12) + '''" style="text-anchor:middle">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX-trimWidth*0.42) + '''" y = "''' + str(pageY+trimHeight*0.29) + '''" style="text-anchor:start">''' + str(pubProg) + '''</tspan> <tspan x = "''' + str(pageX+trimWidth*0.49) + '''" y = "''' + str(pageY+trimHeight*0.455) + '''" style="font-weight:bold;font-size:68;text-anchor:end">''' + watermarkText + ''' </tspan> </text></g></svg>''') # Convert the temp svg to pdf and merge into backgroundFile results = self.tools.mergePdfFilesPdftk(self.local.backgroundFile, self.tools.convertSvgToPdfRsvg(svgFile)) if os.path.isfile(results) : return True def mergeCropmarks (self) : '''Merge cropmarks on to the background page.''' # Initialize the process svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # Trim page width [px] trimWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # Trim page height [px] trimHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Printer page size [px] pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] with codecs.open(svgFile, 'wb') as fbackgr : # starting lines of SVG xml fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''">\n''') # vertical top left fbackgr.write( '''<path d = "m''' + str((ppsWidth - trimWidth)/2) + ''',''' + str((ppsHeight - trimHeight)/2 - 32.0) + ''',v27," style="stroke:#000000;stroke-width:.2"/>\n''') # vertical bottom left fbackgr.write( '''<path d = "m''' + str((ppsWidth - trimWidth)/2) + ''',''' + str((ppsHeight + trimHeight)/2 + 5.0) + ''',v27" style="stroke:#000000;stroke-width:.2" />\n''') # vertical bottom right fbackgr.write( '''<path d = "m''' + str((ppsWidth + trimWidth)/2) + ''',''' + str((ppsHeight - trimHeight)/2 - 32.0) + ''',v27" style="stroke:#000000;stroke-width:.2"/>\n''') # vertical top right fbackgr.write( '''<path d = "m''' + str((ppsWidth + trimWidth)/2) + ''',''' + str((ppsHeight + trimHeight)/2 + 5.0) + ''',v27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal top left fbackgr.write( '''<path d =" m''' + str((ppsWidth - trimWidth)/2 - 32.0) + ''',''' + str((ppsHeight - trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal top right fbackgr.write( '''<path d =" m''' + str((ppsWidth + trimWidth)/2 + 5.0) + ''',''' + str((ppsHeight - trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal bottom right fbackgr.write( '''<path d =" m''' + str((ppsWidth - trimWidth)/2 - 32.0) + ''',''' + str((ppsHeight + trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') # horzontal bottom left fbackgr.write( '''<path d =" m''' + str((ppsWidth + trimWidth)/2 +5.0) + ''',''' + str((ppsHeight + trimHeight)/2) + ''',h27" style="stroke:#000000;stroke-width:.2" />\n''') fbackgr.write( '''</svg>''') # Convert the temp svg to pdf and merge into backgroundFile results = self.tools.mergePdfFilesPdftk(self.local.backgroundFile, self.tools.convertSvgToPdfRsvg(svgFile)) if os.path.isfile(results) : return True def mergePagebox (self) : '''Merge a page box with the background page to be used for proof reading.''' # Initialize the process svgFile = tempfile.NamedTemporaryFile().name ## RENDERED PAGE DIMENSIONS (body) # Trim page width [px] trimWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # Trim page height [px] trimHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Printer page size [px] pps = self.printerPageSize() ppsWidth = pps[0] ppsHeight = pps[1] with codecs.open(svgFile, 'wb') as fbackgr : # starting lines of SVG xml fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(ppsWidth) + '''" height = "''' + str(ppsHeight) + '''">''') # rectangle fbackgr.write( '''<rect x = "''' + str((ppsWidth - trimWidth)/2) + '''" y= "''' + str((ppsHeight - trimHeight)/2) + '''" height = "''' + str(trimHeight) + '''" width = "''' + str(trimWidth) + '''" style = "fill:none;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:.2"/> </svg>''') # Convert the temp svg to pdf and merge into backgroundFile results = self.tools.mergePdfFilesPdftk(self.local.backgroundFile, self.tools.convertSvgToPdfRsvg(svgFile)) if os.path.isfile(results) : return True # Depricated #def makeBgFileName (self, orgName) : #'''Alter the file name to reflect the fact it has a background #added to it. This assumes the file only has a single extention. #If that's not the case we're hosed.''' #name = orgName.split('.')[0] #ext = orgName.split('.')[1] ## Just in case this is the second pass #name = name.replace('-bg', '') #return name + '-bg.' + ext def centerOnPrintPage (self, contents) : '''Center a PDF file on the printerPageSize page. GhostScript is the only way to do this at this point.''' # import pdb; pdb.set_trace() tmpFile = tempfile.NamedTemporaryFile().name # Get the size of our printer page (ppsWidth, ppsHeight) = self.printerPageSize() (wo, ho) = self.getPageOffset() pageOffset = str(wo) + ' ' + str(ho) # Assemble the GhostScript command cmd = [ 'gs', '-o', tmpFile, '-sDEVICE=pdfwrite', '-dQUIET', '-dDEVICEWIDTHPOINTS=' + str(ppsWidth), '-dDEVICEHEIGHTPOINTS=' + str(ppsHeight), '-dFIXEDMEDIA', '-c', '<</PageOffset [' + str(pageOffset) + ']>>', 'setpagedevice', '-f', contents] if self.debugMode : self.tools.terminal('Debug Mode On: \centerOnPrintPage() command: ' + str(cmd)) # Run the process try: subprocess.call(cmd) # Return the name of the temp PDF return tmpFile except Exception as e : self.log.writeToLog(self.errorCodes['1280'], [str(cmd), str(e)]) # Return the temp file name for further processing def printerPageSize (self) : '''Return the width and height of the printer page size in points. Only US Letter and A4 are supported. If not specified return the page trim size.''' printerPageSizeCode = self.layoutConfig['PageLayout']['printerPageSizeCode'].lower() ## STANDARD PRINTER PAGE SIZES # The output page (printer page) is what the typeset page will be placed on # with a watermark behind it. There are only two page sizes supported. # They are A4 and US Letter. We will determine the size by the ID code # found in the layout.conf file. However, in some cases, for example # during the layout process, the trim size is what is needed. If # one of the two supported pages sizes are not used, this will defult # to the trim size. if printerPageSizeCode == 'a4' : return float(595), float(842) elif printerPageSizeCode == 'letter' : return float(612), float(792) else : # Just default to the page trim size (assumed mm coming in) factor mmToPx = 72 / 25.4 return float(int(self.layoutConfig['PageLayout']['pageWidth']) * self.mmToPx), float(int(self.layoutConfig['PageLayout']['pageHeight']) * self.mmToPx) def getPageOffset (self) : '''Return the amount of horizontal and vertical offset that will enable the page trim size to be centered on the printer page. If something other than A4 or Letter is being used, the offset returned will be zero as this only supports those two sizes. The offset is based on the starting point being the lower-left side corner of the page. ''' # Get the printer page size printerPageSizeCode = self.layoutConfig['PageLayout']['printerPageSizeCode'].lower() # Get the page trim size (assuming mm input)factor mmToPx = 72 / 25.4 trimWidth = int(self.layoutConfig['PageLayout']['pageWidth']) * self.mmToPx trimHeight = int(self.layoutConfig['PageLayout']['pageHeight']) * self.mmToPx # The trim size of the content page can never be bigger than # the printer page size. If so, the offset is 0 if printerPageSizeCode == 'a4' or printerPageSizeCode == 'letter' : (bw, bh) = self.printerPageSize() wo = float((bw/2)-(trimWidth/2)) ho = float((bh/2)-(trimHeight/2)) return wo, ho else : return 0, 0
class ProjScript (object) : def __init__(self, pid, gid) : '''Do the primary initialization for this class.''' self.pid = pid self.gid = gid self.tools = Tools() self.local = ProjLocal(pid) self.user = UserConfig() self.userConfig = self.user.userConfig self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.cType = self.projectConfig['Groups'][gid]['cType'] self.Ctype = self.cType.capitalize() self.log = ProjLog(pid) # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '1010' : ['MSG', 'Script install process for [<<1>>] Succeeded.'], '1020' : ['ERR', 'Script install process for [<<1>>] failed.'], '1030' : ['ERR', 'Script type [<<1>>] not supported.'], '1040' : ['ERR', 'Script install cannot proceed for [<<1>>] because this script already exists in the project. You must remove it first before you can add another script.'], '2010' : ['MSG', 'Script remove process for [<<1>>] Succeeded.'], '2020' : ['ERR', 'Script remove process for [<<1>>] failed.'], '4210' : ['MSG', 'Processes completed successfully on: [<<1>>] by [<<2>>]'], '4220' : ['ERR', 'Processes for [<<1>>] failed. Script [<<2>>] returned this error: [<<3>>]'], '4260' : ['ERR', 'Installed the default component preprocessing script. Editing will be required for it to work with your project.'], '4265' : ['LOG', 'Component preprocessing script is already installed.'], '4310' : ['ERR', 'Script is an unrecognized type: [<<1>>] Cannot continue with installation.'] } ############################################################################### ############################# Script Add Functions ############################ ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def addScriptFiles (self, path, scriptType) : '''Import/add processing script files for a group that are found in the given path. Assumes valid path. Will fail if a copy doesn't succeed. If the file is already there, give a warning and will not copy.''' # import pdb; pdb.set_trace() source = path fileName = self.tools.fName(path) target = os.path.join(self.local.projScriptFolder, fileName) # Do an initial check to see if the script is already there # Never copy over the top of an existing script if os.path.isfile(target) : self.log.writeToLog(self.errorCodes['1040'], [fileName]) # Make script folder if needed if not os.path.isdir(self.local.projScriptFolder) : os.makedirs(self.local.projScriptFolder) # Copy in the script if self.scriptInstall(source, target) : # Record the script file name if scriptType == 'preprocess' : self.projectConfig['Groups'][self.gid]['preprocessScript'] = fileName elif scriptType == 'postprocess' : self.projectConfig['Groups'][self.gid]['postprocessScript'] = fileName else : self.log.writeToLog(self.errorCodes['1030'], [scriptType]) self.tools.writeConfFile(self.projectConfig) self.log.writeToLog(self.errorCodes['1010'], [fileName]) return True else : self.log.writeToLog(self.errorCodes['1020'], [fileName]) ############################################################################### ############################ Script Remove Functions ########################## ############################################################################### ######################## Error Code Block Series = 2000 ####################### ############################################################################### def removeScriptFiles (self, scriptType) : '''Remove processing script files for a group.''' if scriptType == 'preprocess' : fileName = self.projectConfig['Groups'][self.gid]['preprocessScript'] elif scriptType == 'postprocess' : fileName = self.projectConfig['Groups'][self.gid]['postprocessScript'] else : self.log.writeToLog(self.errorCodes['1030'], [scriptType]) target = os.path.join(self.local.projScriptFolder, fileName) # Remove the script (if it's not there, we don't care) try : os.remove(target) except : pass if not os.path.isfile(target) : if scriptType == 'preprocess' : self.projectConfig['Groups'][self.gid]['preprocessScript'] = '' elif scriptType == 'postprocess' : self.projectConfig['Groups'][self.gid]['postprocessScript'] = '' else : self.log.writeToLog(self.errorCodes['1030'], [scriptType]) self.tools.writeConfFile(self.projectConfig) self.log.writeToLog(self.errorCodes['2010'], [fileName]) return True else : self.log.writeToLog(self.errorCodes['2020'], [fileName]) ############################################################################### ########################## General Script Functions ########################### ############################################################################### ######################## Error Code Block Series = 4000 ####################### ############################################################################### def runProcessScript (self, target, scriptFile) : '''Run a text processing script on a component. This assumes the component and the script are valid and the component lock is turned off. If not, you cannot expect any good to come of this.''' # subprocess will fail if permissions are not set on the # script we want to run. The correct permission should have # been set when we did the installation. err = subprocess.call([scriptFile, target]) if err == 0 : self.log.writeToLog(self.errorCodes['4210'], [self.tools.fName(target), self.tools.fName(scriptFile)]) else : self.log.writeToLog(self.errorCodes['4220'], [self.tools.fName(target), self.tools.fName(scriptFile), str(err)]) return False return True def scriptInstall (self, source, target) : '''Install a script. A script can be a collection of items in a zip file or a single .py script file.''' scriptTargetFolder, fileName = os.path.split(target) if self.tools.isExecutable(source) : if not shutil.copy(source, target) : self.tools.makeExecutable(target) return True elif self.tools.fName(source).split('.')[1].lower() == 'zip' : myZip = zipfile.ZipFile(source, 'r') for f in myZip.namelist() : data = myZip.read(f, source) # Pretty sure zip represents directory separator char as "/" regardless of OS myPath = os.path.join(scriptTargetFolder, f.split("/")[-1]) try : myFile = open(myPath, "wb") myFile.write(data) myFile.close() except : pass myZip.close() return True else : self.log.writeToLog(self.errorCodes['4310'], [self.tools.fName(source)])
class ProjProcess (object) : def __init__(self, pid, gid = None, projectConfig = None) : '''Intitate the whole class and create the object.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig if projectConfig : self.projectConfig = projectConfig else : self.proj_config = Config(self.pid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.local = ProjLocal(pid, gid, self.projectConfig) self.log = ProjLog(pid) # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], 'XPRT-000' : ['MSG', 'Messages for export issues (probably only in project.py)'], 'XPRT-005' : ['MSG', 'Unassigned error message ID.'], 'XPRT-010' : ['ERR', 'Export file name could not be formed with available configuration information.'], 'XPRT-020' : ['ERR', 'Unable to export: [<<1>>].'], 'XPRT-030' : ['MSG', 'Files exported to [<<1>>].'], 'XPRT-040' : ['MSG', 'Beginning export, please wait...'], 'XPRT-050' : ['MSG', 'Unassigned error message ID.'], '1210' : ['MSG', 'Processes completed successfully on: [<<1>>] by [<<2>>]'], '1220' : ['ERR', 'Processes for [<<1>>] failed. Script [<<2>>] returned this error: [<<3>>]'], '1240' : ['MSG', 'Component group preprocessing [<<1>>] for group [<<2>>].'], '1260' : ['ERR', 'Installed the default component preprocessing script. Editing will be required for it to work with your project.'], '1265' : ['LOG', 'Component preprocessing script is already installed.'], } ############################################################################### ############################### Export Functions ############################## ############################################################################### ####################### Error Code Block Series = 0200 ######################## ############################################################################### # FIXME: This needs to be rewritten #def export (self, cType, cName, path = None, script = None, bundle = False, force = False) : #'''Facilitate the exporting of project text. It is assumed that the #text is clean and ready to go and if any extraneous publishing info #has been injected into the text, it will be removed by an appropreate #post-process that can be applied by this function. No validation #will be initiated by this function.''' ## FIXME - Todo: add post processing script feature ## Probably need to create the component object now #self.createComponent(cName) ## Figure out target path #if path : #path = self.tools.resolvePath(path) #else : #parentFolder = os.path.dirname(self.local.projHome) #path = os.path.join(parentFolder, 'Export') ## Make target folder if needed #if not os.path.isdir(path) : #os.makedirs(path) ## Start a list for one or more files we will process #fList = [] ## Will need the stylesheet for copy #projSty = self.projectConfig['Managers'][cType + '_Style']['mainStyleFile'] #projSty = os.path.join(self.local.projStyleFolder, projSty) ## Process as list of components #self.log.writeToLog('XPRT-040') #for cid in self.components[cName].getSubcomponentList(cName) : #cidCName = self.components[cName].getRapumaCName(cid) #ptName = PT_Tools(self).formPTName(cName, cid) ## Test, no name = no success #if not ptName : #self.log.writeToLog('XPRT-010') #self.tools.dieNow() #target = os.path.join(path, ptName) #source = os.path.join(self.local.projComponentFolder, cidCName, cid + '.' + cType) ## If shutil.copy() spits anything back its bad news #if shutil.copy(source, target) : #self.log.writeToLog('XPRT-020', [self.tools.fName(target)]) #else : #fList.append(target) ## Start the main process here #if bundle : #archFile = os.path.join(path, cName + '_' + self.tools.ymd() + '.zip') ## Hopefully, this is a one time operation but if force is not True, ## we will expand the file name so nothing is lost. #if not force : #if os.path.isfile(archFile) : #archFile = os.path.join(path, cName + '_' + self.tools.fullFileTimeStamp() + '.zip') #myzip = zipfile.ZipFile(archFile, 'w', zipfile.ZIP_DEFLATED) #for f in fList : ## Create a string object from the contents of the file #strObj = StringIO.StringIO() #for l in open(f, "rb") : #strObj.write(l) ## Write out string object to zip #myzip.writestr(self.tools.fName(f), strObj.getvalue()) #strObj.close() ## Close out the zip and report #myzip.close() ## Clean out the folder #for f in fList : #os.remove(f) #self.log.writeToLog('XPRT-030', [self.tools.fName(archFile)]) #else : #self.log.writeToLog('XPRT-030', [path]) #return True ############################################################################### ########################## Text Processing Functions ########################## ############################################################################### ######################## Error Code Block Series = 1200 ####################### ############################################################################### def turnOnOffPreprocess (self, gid, onOff) : '''Turn on or off preprocessing on incoming component text.''' self.projectConfig['Groups'][gid]['usePreprocessScript'] = onOff self.tools.writeConfFile(self.projectConfig) self.log.writeToLog(self.errorCodes['1240'], [str(onOff), gid]) def checkForPreprocessScript (self, gid) : '''Check to see if a preprocess script is installed. If not, install the default script and give a warning that the script is not complete.''' # First make sure the Scripts folder is there if not os.path.isdir(self.local.projScriptFolder) : os.makedirs(self.local.projScriptFolder) # Check and copy if needed if not os.path.isfile(self.local.groupPreprocessFile) : shutil.copy(self.local.rpmPreprocessFile, self.local.groupPreprocessFile) self.tools.makeExecutable(self.local.groupPreprocessFile) self.log.writeToLog(self.errorCodes['1260']) else : self.log.writeToLog(self.errorCodes['1265']) def runProcessScript (self, target, scriptFile) : '''Run a text processing script on a component. This assumes the component and the script are valid and the component lock is turned off. If not, you cannot expect any good to come of this.''' # subprocess will fail if permissions are not set on the # script we want to run. The correct permission should have # been set when we did the installation. err = subprocess.call([scriptFile, target]) if err == 0 : self.log.writeToLog(self.errorCodes['1210'], [self.tools.fName(target), self.tools.fName(scriptFile)]) else : self.log.writeToLog(self.errorCodes['1220'], [self.tools.fName(target), self.tools.fName(scriptFile), str(err)]) return False return True def scriptInstall (self, source, target) : '''Install a script. A script can be a collection of items in a zip file or a single .py script file.''' scriptTargetFolder, fileName = os.path.split(target) if self.tools.isExecutable(source) : shutil.copy(source, target) self.tools.makeExecutable(target) elif self.tools.fName(source).split('.')[1].lower() == 'zip' : myZip = zipfile.ZipFile(source, 'r') for f in myZip.namelist() : data = myZip.read(f, source) # Pretty sure zip represents directory separator char as "/" regardless of OS myPath = os.path.join(scriptTargetFolder, f.split("/")[-1]) try : myFile = open(myPath, "wb") myFile.write(data) myFile.close() except : pass myZip.close() return True else : self.tools.dieNow('Script is an unrecognized type: ' + self.tools.fName(source) + ' Cannot continue with installation.') def installPostProcess (self, cType, script, force = None) : '''Install a post process script into the main components processing folder for a specified component type. This script will be run on every file of that type that is imported into the project. Some projects will have their own specially developed post process script. Use the "script" var to specify a process (which should be bundled in a system compatable way). If "script" is not specified we will copy in a default script that the user can modify. This is currently limited to Python scripts only which do in-place processes on the target files. The script needs to have the same name as the zip file it is bundled in, except the extention is .py instead of the bundle .zip extention.''' # Define some internal vars Ctype = cType.capitalize() oldScript = '' scriptName = os.path.split(script)[1] scriptSourceFolder = os.path.split(script)[0] scriptTarget = os.path.join(self.local.projScriptFolder, self.tools.fName(script).split('.')[0] + '.py') if scriptName in self.projectConfig['CompTypes'][Ctype]['postprocessScripts'] : oldScript = scriptName # First check for prexsisting script record if not force : if oldScript : self.log.writeToLog('POST-080', [oldScript]) return False # In case this is a new project we may need to install a component # type and make a process (components) folder if not self.components[cType] : self.tools.addComponentType(self.projectConfig, self.local, cType) # Make the target folder if needed if not os.path.isdir(self.local.projScriptFolder) : os.makedirs(self.local.projScriptFolder) # First check to see if there already is a script file, return if there is if os.path.isfile(scriptTarget) and not force : self.log.writeToLog('POST-082', [self.tools.fName(scriptTarget)]) return False # No script found, we can proceed if not os.path.isfile(scriptTarget) : self.scriptInstall(script, scriptTarget) if not os.path.isfile(scriptTarget) : self.tools.dieNow('Failed to install script!: ' + self.tools.fName(scriptTarget)) self.log.writeToLog('POST-110', [self.tools.fName(scriptTarget)]) elif force : self.scriptInstall(script, scriptTarget) if not os.path.isfile(scriptTarget) : self.tools.dieNow('Failed to install script!: ' + self.tools.fName(scriptTarget)) self.log.writeToLog('POST-115', [self.tools.fName(scriptTarget)]) # Record the script with the cType post process scripts list scriptList = self.projectConfig['CompTypes'][Ctype]['postprocessScripts'] if self.tools.fName(scriptTarget) not in scriptList : self.projectConfig['CompTypes'][Ctype]['postprocessScripts'] = self.tools.addToList(scriptList, self.tools.fName(scriptTarget)) self.tools.writeConfFile(self.projectConfig) return True def removePostProcess (self, cType) : '''Remove (actually disconnect) a preprocess script from a component type. This will not actually remove the script. That would need to be done manually. Rather, this will remove the script name entry from the component type so the process cannot be accessed for this specific component type.''' Ctype = cType.capitalize() # Get old setting old = self.projectConfig['CompTypes'][Ctype]['postprocessScripts'] # Reset the field to '' if old != '' : self.projectConfig['CompTypes'][Ctype]['postprocessScripts'] = '' self.tools.writeConfFile(self.projectConfig) self.log.writeToLog('POST-130', [old,Ctype]) else : self.log.writeToLog('POST-135', [cType.capitalize()]) return True
class ProjBinding (object) : def __init__(self, pid) : '''Do the primary initialization for this manager.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig self.config = Config(pid) self.pg_back = ProjBackground(self.pid) self.config.getProjectConfig() self.config.getLayoutConfig() self.projectConfig = self.config.projectConfig self.layoutConfig = self.config.layoutConfig self.useBackground = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) self.useDocInfo = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) self.projHome = os.path.join(os.environ['RAPUMA_PROJECTS'], self.pid) self.local = ProjLocal(self.pid) self.log = ProjLog(self.pid) self.pdfViewerCmd = self.tools.getPdfViewerCommand(self.userConfig, self.projectConfig) # Log messages for this module self.errorCodes = { '0205' : ['MSG', 'Unassigned message.'], '0210' : ['MSG', 'No contents are specified for binding.'], '0215' : ['ERR', 'Failed to bind contents into the [<<1>>] fille. Got error: [<<2>>]'], '0220' : ['ERR', 'Could not copy [<<1>>] temp file to [<<2>>] saved binding file.'], '0230' : ['MSG', 'Completed proccessing on the [<<1>>] binding file.'], '0235' : ['ERR', 'Failed to complete proccessing on the [<<1>>] binding file.'], '0240' : ['LOG', 'Recorded [<<1>>] rendered pages in the [<<2>>] binding file.'], '0260' : ['ERR', 'PDF viewer failed with this error: [<<1>>]'], '0265' : ['ERR', 'Rendered file not found: <<1>>'], '0270' : ['WRN', 'PDF viewing is disabled.'], '0280' : ['ERR', 'GS PDF file merge failed with this error: [<<1>>]'], '0300' : ['MSG', 'File binding operation in process, please wait...'] } ############################################################################### ############################## Binding Functions ############################## ############################################################################### ######################## Error Code Block Series = 200 ######################## ############################################################################### # import pdb; pdb.set_trace() def bind (self, save = False) : '''Bind all groups in the order they are indicated by group bindOrder settings. Note, because binding spans groups and the main body (project.py) mainly just works on one group at a time, this has to be called from outside project and project needs to be reinitialized each time a group is rendered from here.''' # import pdb; pdb.set_trace() # Get the order of the groups to be bound. bindOrder = {} # Put a safty in here in case there are no groups yet if not self.projectConfig.has_key('Groups') : return False # Build the bindOrder dict with ord num as key and file name as value for gid in self.projectConfig['Groups'].keys() : if not self.projectConfig['Groups'][gid].has_key('bindingOrder') : self.projectConfig['Groups'][gid]['bindingOrder'] = 0 self.tools.writeConfFile(self.projectConfig) if int(self.projectConfig['Groups'][gid]['bindingOrder']) > 0 : gidPdfFile = os.path.join(self.local.projComponentFolder, gid, gid + '.pdf') # bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = self.projectConfig['Groups'][gid]['bindingFile'] bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = gidPdfFile bindGrpNum = len(bindOrder) # Need not keep going if nothing was found if bindGrpNum == 0 : self.log.writeToLog(self.errorCodes['0210']) return False # Make an ordered key list keyList = bindOrder.keys() keyList.sort() # Output the bind files in order according to the list we made fileList = [] for key in keyList : fileList.append(bindOrder[key]) # First merge the master pages together tempFile = self.mergePdfFilesGs(fileList) # Now add background and doc info if requested bgFile = '' if self.useBackground : bgFile = self.pg_back.addBackground(tempFile) if self.useDocInfo : if bgFile : bgFile = self.pg_back.addDocInfo(bgFile) else : bgFile = self.pg_back.addDocInfo(tempFile) # If we are saving this make a name for it if save : bindFileName = self.pid + '_contents_' + self.tools.ymd() # Save this to the Deliverable folder (Make sure there is one) if not os.path.isdir(self.local.projDeliverableFolder) : os.makedirs(self.local.projDeliverableFolder) bindFile = os.path.join(self.local.projDeliverableFolder, bindFileName + '.pdf') if os.path.exists(bgFile) : if shutil.copy(bgFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [bgFile,bindFile]) else : if shutil.copy(tempFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [tempFile,bindFile]) # Direct to viewFile viewFile = bindFile else : if os.path.exists(bgFile) : viewFile = bgFile else : viewFile = tempFile # Binding should have been successful, report it now self.log.writeToLog(self.errorCodes['0230'], [viewFile]) # View the file if os.path.isfile(viewFile) : if self.pdfViewerCmd : # Add the file to the viewer command self.pdfViewerCmd.append(viewFile) # Run the viewer try : subprocess.Popen(self.pdfViewerCmd) return True except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['0260'], [str(e)]) else : self.log.writeToLog(self.errorCodes['0270']) else : self.log.writeToLog(self.errorCodes['0265'], [self.tools.fName(viewFile)]) def mergePdfFilesGs (self, sourceList) : '''Using GS, merge multiple PDF files into one. The advantage of using Gs is that the index will be maintained. pdftk strips out the index, which is bad...''' # This is our working file for this operation tempFile = tempfile.NamedTemporaryFile().name + '.pdf' # FIXME: Note/Warning, the use "-dPDFSETTINGS=/prepress" can cause an issue when # rendering. A segmentation fault may occur. This will cause the bind feature # to fail because gs failed. The segmentation fault seems to occure from graphics # embedded in the PDF that are either too big or not a true grayscale, but rather # RGB. The /prepress setting will then fail which means the doc will fail to test # out right in PDF x1a tests. # There does not seem to be any good way around this at this time. With more # development time and perhaps better options, we might someday be able to get # the bind process to be completely automated and be able to pass all Adobe tests # for x1a and other issues. As there is not any real demand for Rapuma, it isn't # worth putting any more effort into this section of code. Therefore, the # /prepress setting will be dropped from here and the user will be required to # Run any output through an Adobe product to get output that will be acceptable # to Adobe. Adobe wins, I quite. :-( # This command structure was in production until the segmenation fault issue was found: # cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-dPDFSETTINGS=/prepress', '-sOutputFile=' + tempFile] # For issues with segmentation faults, the following should help # with debugging. Adjust as needed. for more info go to: # http://ghostscript.com/doc/8.54/Use.htm#Debugging # cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-sOutputFile=' + tempFile, '-dPDFWRDEBUG', '-E']; print cmd # This is the non-x1a compatable command that will normally be used cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-sOutputFile=' + tempFile] # Continueing on, now add the files we want to bind together cmd = cmd + sourceList # Now bind the files try : self.log.writeToLog(self.errorCodes['0300']) subprocess.call(cmd) # Return our file name for further processing return tempFile except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['0280'], [str(e)])
class ProjIllustration (object) : def __init__(self, pid, gid) : '''Do the primary initialization for this class.''' self.pid = pid self.gid = gid self.tools = Tools() self.local = ProjLocal(pid) self.user = UserConfig() self.userConfig = self.user.userConfig self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.proj_config.getLayoutConfig() self.proj_config.getIllustrationConfig() self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.illustrationConfig = self.proj_config.illustrationConfig self.cType = self.projectConfig['Groups'][gid]['cType'] self.Ctype = self.cType.capitalize() self.log = ProjLog(pid) self.backgroundTypes = ['watermark', 'lines'] # Folder paths self.projComponentFolder = self.local.projComponentFolder self.projIllustrationFolder = self.local.projIllustrationFolder self.projConfFolder = self.local.projConfFolder # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '0010' : ['ERR', 'Component type [<<1>>] not recognized!'], '0210' : ['WRN', 'Cannot copy [<<1>>] into the project Illustration folder. File already exists. Please use remove or update commands if the file needs to be replaced.'], '0220' : ['MSG', 'Copied [<<1>>] into the project Illustration folder.'], '0230' : ['ERR', 'Failed to Copy [<<1>>] into the project Illustration folder.'], '0240' : ['MSG', 'Illustration add operation complete!'], '0265' : ['LOG', 'Piclist file for [<<1>>] has been created.'], '0270' : ['WRN', 'Illustration file [<<1>>] not found in Illustration folder.'], '0280' : ['ERR', 'There was a problem trying to import the illustration file(s). A possible cause could be that there was no \\fig markers in the source imported into the project. I\'m just say\'n.'], '1010' : ['MSG', 'Removed illustration file [<<1>>] from project Illustration folder.'], '1020' : ['LOG', 'Request to removed illustration file [<<1>>] from project Illustration folder. File not found. Operation not complete'], '1030' : ['MSG', 'Illustration remove operation complete!'], '2010' : ['MSG', 'Updated illustration file [<<1>>] in project Illustration folder.'], '2020' : ['ERR', 'Update failed, file [<<1>>] not found in project Illustration folder. Use add illustration command to install the illustration.'], '2030' : ['ERR', 'Update failed on file [<<1>>]. Copy proceedure failed.'], '2040' : ['MSG', 'Illustration update operation complete!'] } ############################################################################### ############################ Illustration Functions ########################### ############################################################################### ######################## Error Code Block Series = 0200 ####################### ############################################################################### def addIllustrationFiles (self, path) : '''Import all the illustrations for a group that are found in the given path. This assumes illustrations are being used in one or more components in this group. Assumes valid path. Will fail if a copy doesn't succeed. If the file is already there, give a warning and do not copy.''' # import pdb; pdb.set_trace() try : for i in self.illustrationConfig[self.gid].keys() : cid = self.illustrationConfig[self.gid][i]['bid'] fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.projIllustrationFolder, fileName) # Check to see if the target exists if not os.path.isfile(target) : source = os.path.join(path, fileName) if os.path.isfile(source) : # Make sure we have a target dir if not os.path.isdir(self.projIllustrationFolder) : os.makedirs(self.projIllustrationFolder) # Copy in the source to the project if not shutil.copy(source, target) : self.log.writeToLog(self.errorCodes['0220'], [self.tools.fName(source)]) # Double check that it happened if not os.path.isfile(target) : self.log.writeToLog(self.errorCodes['0230'], [self.tools.fName(source)]) else : self.log.writeToLog(self.errorCodes['0210'], [self.tools.fName(target)]) # If nothing above failed, we can return True now self.log.writeToLog(self.errorCodes['0240']) return True except : self.log.writeToLog(self.errorCodes['0280']) return False def missingIllustrations (self, bid) : '''Check for any missing illustration files for this component and report them. The assumption is that this component is supposed to have one or more illustrations in it so it should be used under hasIllustrations().''' missing = 0 for i in self.illustrationConfig[self.gid].keys() : if self.illustrationConfig[self.gid][i]['bid'] == bid : fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.local.projIllustrationFolder, fileName) if not os.path.exists(target) : self.log.writeToLog(self.errorCodes['0270'], [fileName]) missing +=1 # Returning True if something was missing if missing > 0 : return True def hasIllustrations (self, bid) : '''Return True if this component as any illustration associated with it.''' # Adjustment for Map cType if self.cType == 'map' : bid = 'map' # If we are missing the bid we fail (gracefully) try : for i in self.illustrationConfig[self.gid].keys() : if self.illustrationConfig[self.gid][i]['bid'] == bid : return True except : return False def getCidPiclistFile (self, cid) : '''Return the full path of the cName working text illustration file. This assumes the cName is valid.''' return os.path.join(self.local.projComponentFolder, cid, cid + '_base.' + self.cType + '.piclist') def createPiclistFile (self, cid) : '''Look in the cid for \fig data. Extract it from the cid and use it to create a piclist file for this specific cid. If there is no \fig data no piclist file will be made.''' # import pdb; pdb.set_trace() # Right now, this is the only place we need the macroConfig so # we will just make it local for now self.proj_config.getMacroConfig() macroConfig = self.proj_config.macroConfig cType = self.projectConfig['Groups'][self.gid]['cType'] if cType == 'usfm' : piclistFile = self.getCidPiclistFile(cid) elif cType == 'map' : piclistFile = self.getCidPiclistFile(self.gid) else : self.log.writeToLog(self.errorCodes['0010'], [cType]) macPackId = self.projectConfig['CompTypes'][cType.capitalize()]['macroPackage'] cvSep = macroConfig['Macros'][macPackId]['Illustrations']['chapterVerseSeperator'] thisRef = '' trueCid = cid obj = {} # Change cid for map cType # Note: The piclist IDs must be three characters long but need not be recognized USFM # file IDs. As such, we adjust the code to recognize 'map' as our ID for map rendering # operations. This seems to work for now. # if self.cType == 'map' : # cid = 'map' with codecs.open(piclistFile, "w", encoding='utf_8') as writeObject : writeObject.write('% This is an auto-generated usfmTex piclist file for this project.\n') writeObject.write('% Do not bother editing this file.\n\n') for i in self.illustrationConfig[self.gid].keys() : obj = self.illustrationConfig[self.gid][i] thisRef = '' # Filter out if needed with this if not self.tools.str2bool(obj['useThisIllustration']) : continue # Is a caption going to be used on this illustration? caption = '' if self.illustrationConfig[self.gid][i]['bid'] == cid : if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useCaptions']) \ and self.tools.str2bool(self.illustrationConfig[self.gid][i]['useThisCaption']) : if obj['caption'] : caption = obj['caption'] # Work out if we want a caption reference or not for this illustration if self.illustrationConfig[self.gid][i]['bid'] == cid : if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useCaptionReferences']) \ and self.tools.str2bool(self.illustrationConfig[self.gid][i]['useThisCaptionRef']) : if obj['location'] : thisRef = obj['location'] else : thisRef = obj['chapter'] + cvSep + obj['verse'] # If we made it this far we can output the line writeObject.write(obj['bid'] + ' ' + obj['chapter'] + '.' + obj['verse'] + \ ' |' + obj['fileName'] + '|' + obj['width'] + '|' + obj['position'] + \ '|' + obj['scale'] + '|' + obj['copyright'] + '|' + caption + '|' + thisRef + ' \n') # Report to log self.log.writeToLog(self.errorCodes['0265'], [trueCid]) return True ############################################################################### ######################## Illustration Remove Functions ######################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def removeIllustrationFiles (self) : '''Remove all the illustration files from the illustration folder for a group. This does not affect the illustration.conf file.''' for i in self.illustrationConfig[self.gid].keys() : cid = self.illustrationConfig[self.gid][i]['bid'] fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.projIllustrationFolder, fileName) # Check to see if the target exists, then delete, skip if not there if os.path.exists(target) : os.remove(target) self.log.writeToLog(self.errorCodes['1010'], [self.tools.fName(target)]) else : self.log.writeToLog(self.errorCodes['1020'], [self.tools.fName(target)]) # Report and return self.log.writeToLog(self.errorCodes['1030']) return True ############################################################################### ######################## Illustration Update Functions ######################## ############################################################################### ######################## Error Code Block Series = 2000 ####################### ############################################################################### def updateIllustrationFiles (self, path) : '''Update all the illustrations in a group from path as referenced in the illustration.conf file. Skip any referenced but not in path.''' for i in self.illustrationConfig[self.gid].keys() : cid = self.illustrationConfig[self.gid][i]['bid'] fileName = self.illustrationConfig[self.gid][i]['fileName'] target = os.path.join(self.projIllustrationFolder, fileName) source = os.path.join(path, fileName) # Check to see if the source exists, proceed if it does if os.path.isfile(source) : if os.path.isfile(target) : # Copy the source to the project if not shutil.copy(source, target) : self.log.writeToLog(self.errorCodes['2010'], [self.tools.fName(source)]) else : self.log.writeToLog(self.errorCodes['2030'], [self.tools.fName(source)]) else : self.log.writeToLog(self.errorCodes['2020'], [self.tools.fName(source)]) # Report and return self.log.writeToLog(self.errorCodes['2040']) return True
class Project (object) : def __init__(self, pid, gid) : '''Instantiate this class.''' self.pid = pid self.gid = gid self.user = UserConfig() self.userConfig = self.user.userConfig self.projHome = os.path.join(os.environ['RAPUMA_PROJECTS'], self.pid) self.config = Config(self.pid) self.config.getProjectConfig() self.projectConfig = self.config.projectConfig self.cType = self.projectConfig['Groups'][self.gid]['cType'] self.Ctype = self.cType.capitalize() self.local = ProjLocal(pid, gid, self.cType) self.log = ProjLog(self.pid) self.tools = Tools() self.groups = {} self.components = {} self.managers = {} self.projectMediaIDCode = self.projectConfig['ProjectInfo']['projectMediaIDCode'] self.projectIDCode = self.projectConfig['ProjectInfo']['projectIDCode'] self.projectName = self.projectConfig['ProjectInfo']['projectTitle'] self.plid = self.projectConfig['ProjectInfo']['languageCode'] self.psid = self.projectConfig['ProjectInfo']['scriptCode'] # The gid cannot generally be set yet but we will make a placeholder # for it here and the functions below will set it. (I'm just say'n) # import pdb; pdb.set_trace() m = import_module('rapuma.manager.' + self.projectMediaIDCode) self.__class__ = getattr(m, self.projectMediaIDCode[0].upper() + self.projectMediaIDCode[1:]) # Update the existing config file with the project type XML file # if needed newXmlDefaults = os.path.join(self.local.rapumaConfigFolder, self.projectMediaIDCode + '.xml') xmlConfig = self.tools.getXMLSettings(newXmlDefaults) newConf = ConfigObj(xmlConfig.dict(), encoding='utf-8').override(self.projectConfig) for s,v in self.projectConfig.items() : if s not in newConf : newConf[s] = v # Replace with new conf if new is different from old # Rem new conf doesn't have a filename, give it one if self.projectConfig != newConf : self.projectConfig = newConf self.projectConfig.filename = self.local.projectConfFile # Log messages for this module self.errorCodes = { 'PROJ-000' : ['MSG', 'Project module messages'], 'PROJ-030' : ['MSG', 'Changed [<<1>>][<<2>>][<<3>>] setting from \"<<4>>\" to \"<<5>>\".'], 'PROJ-040' : ['ERR', 'Problem making setting change. Section [<<1>>] missing from configuration file.'], 'PROJ-050' : ['ERR', 'Component [<<1>>] working text file was not found in the project configuration.'], 'PROJ-060' : ['ERR', 'Component [<<1>>] was not found in the project configuration.'], 'PROJ-070' : ['ERR', 'Source file not found: [<<1>>].'], 'PROJ-080' : ['MSG', 'Successful copy of [<<1>>] to [<<2>>].'], 'PROJ-090' : ['ERR', 'Target file [<<1>>] already exists. Use force (-f) to overwrite.'], '0205' : ['LOG', 'Created the [<<1>>] manager object.'], '0210' : ['LOG', 'Wrote out [<<1>>] settings to the project configuration file.'], '0211' : ['ERR', 'Failed to write out project [<<1>>] settings to the project configuration file.'], '0660' : ['ERR', 'Invalid component ID: [<<1>>].'], } ############################################################################### ############################ Manager Level Functions ########################## ############################################################################### ######################## Error Code Block Series = 200 ######################## ############################################################################### def createManager (self, mType) : '''Check to see if a manager is listed in the config and load it if it is not already.''' fullName = self.cType + '_' + mType.capitalize() if fullName not in self.managers : self.addManager(mType) self.loadManager(mType) self.log.writeToLog(self.errorCodes['0205'], [fullName]) def loadManager (self, mType) : '''Do basic load on a manager.''' fullName = self.cType + '_' + mType.capitalize() cfg = self.projectConfig['Managers'][fullName] module = import_module('rapuma.manager.' + mType) ManagerClass = getattr(module, mType.capitalize()) manobj = ManagerClass(self, cfg, self.cType) self.managers[fullName] = manobj def addManager (self, mType) : '''Create a manager reference in the project config that components will point to.''' # import pdb; pdb.set_trace() fullName = self.cType + '_' + mType.capitalize() managerDefaults = None # Insert the Manager section if it is not already there self.tools.buildConfSection(self.projectConfig, 'Managers') if not self.projectConfig['Managers'].has_key(fullName) : self.tools.buildConfSection(self.projectConfig['Managers'], fullName) # Update settings if needed update = False managerDefaults = self.tools.getXMLSettings(os.path.join(self.local.rapumaConfigFolder, mType + '.xml')) for k, v, in managerDefaults.iteritems() : # Do not overwrite if a value is already there if not self.projectConfig['Managers'][fullName].has_key(k) : self.projectConfig['Managers'][fullName][k] = v # If we are dealing with an empty string, don't bother writing out # Trying to avoid needless conf updating here. Just in case we are # working with a list, we'll use len() if len(v) > 0 : update = True # Update the conf if one or more settings were changed if update : if self.tools.writeConfFile(self.projectConfig) : self.log.writeToLog(self.errorCodes['0210'],[fullName]) else : self.log.writeToLog(self.errorCodes['0211'],[fullName]) ############################################################################### ############################ Group Level Functions ############################ ############################################################################### ####################### Error Code Block Series = 0600 ######################## ############################################################################### def renderGroup (self, cidList = '', pages = '', override = '', save = False) : '''Render a group of subcomponents or any number of components in the group specified in the cidList.''' # import pdb; pdb.set_trace() # If there are any cids listed we need to test them if cidList : self.isValidCidList(cidList) # Otherwise, do a basic test for exsistance and move on if self.projectConfig['Groups'].has_key(self.gid) : # Now create the group and pass the params on self.createGroup().render(self.gid, cidList, pages, override, save) return True def createGroup (self) : '''Create a group object that can be acted on. It is assumed this only happens for one group per session. This group will contain one or more compoenents. The information for each one will be contained in the group object.''' # import pdb; pdb.set_trace() # If the object already exists just return it if self.gid in self.groups: return self.groups[self.gid] cType = self.projectConfig['Groups'][self.gid]['cType'] # Create a special component object if called cfg = self.projectConfig['Groups'][self.gid] module = import_module('rapuma.group.' + cType) ManagerClass = getattr(module, cType.capitalize()) groupObj = ManagerClass(self, cfg) self.groups[self.gid] = groupObj return groupObj def isValidCidList (self, thisCidlist) : '''Check to see if all the components in the list are in the group.''' cidList = self.projectConfig['Groups'][self.gid]['cidList'] for cid in thisCidlist : # If this is not a usfm type we do not need to validate if self.cType == 'usfm' : if not cid in cidList : self.log.writeToLog(self.errorCodes['0660'],[cid]) def listAllComponents (self, cType) : '''Generate a list of valid component IDs and cNames for this cType.''' # Create the component object now with a special component caller ID self.createComponent('usfm_internal_caller') # Get the component info dictionary comps = self.components['usfm_internal_caller'].usfmCidInfo() # List and sort cList = list(comps.keys()) cList.sort() # For now we'll output to terminal but may want to change this later. for c in cList : if c != '_z_' : print c, comps[c][1] ############################################################################### ############################ System Level Functions ########################### ############################################################################### def run (self, command, opts, userConfig) : '''Run a command''' if command in self.commands : self.commands[command].run(opts, self, userConfig) else : self.tools.terminalError('The command: [' + command + '] failed to run with these options: ' + str(opts))
class Macro (object) : def __init__(self, pid, cType, gid=None) : '''Do the primary initialization for this class.''' self.pid = pid self.gid = gid self.cType = cType self.user = UserConfig() self.userConfig = self.user.userConfig self.projHome = os.path.join(os.path.expanduser(os.environ['RAPUMA_PROJECTS']), self.pid) self.local = ProjLocal(pid, gid, cType) # import pdb; pdb.set_trace() self.proj_config = Config(pid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.tools = Tools() self.log = ProjLog(pid) # Create config placeholders self.layoutConfig = None self.illustrationConfig = None self.macroConfig = self.tools.loadConfig(self.local.macroConfFile, self.local.macroConfXmlFile) # Log messages for this module self.errorCodes = { '3010' : ['ERR', 'No macro package is registered for the [<<1>>] component type.'], '3020' : ['ERR', 'Cannot update! No macro package is registered for the [<<1>>] component type.'], '3050' : ['ERR', 'Macro package file not found: [<<1>>]'], '3100' : ['ERR', 'Macro package: [<<1>>] already exists in the project. I am not allowed to copy over an existing package.'], '3200' : ['ERR', 'Failed to install macro package: [<<1>>]'], '3300' : ['MSG', 'Installed macro package: [<<1>>], Reinitialized [<<2>>]'], '3310' : ['ERR', 'Failed to copy [<<1>>] to folder [<<2>>].'], '3400' : ['MSG', 'Removed macro package configuration settings for: [<<1>>] from the macro.conf file.'], '3500' : ['MSG', 'Removed macro package [<<1>>] folder and all files contained.'], '3600' : ['MSG', 'Updated component type [<<1>>] with macro package [<<2>>]'], '3650' : ['ERR', 'Failed to updated macro package [<<1>>]'] } ############################################################################### ###################### Macro Package Handling Functions ####################### ############################################################################### ######################## Error Code Block Series = 3000 ####################### ############################################################################### def createMacroFiles (self, macPackId) : '''Create all the necessary macro file names with their assigned paths.''' self.projMacPackFolder = os.path.join(self.local.projMacPackFolder, macPackId) texFileIds = {'preStyTexExtFile':'preSty-ext.tex', 'macSettingsFile':'settings.tex', 'extTexFile':'extension.tex', 'grpExtTexFile': self.gid + '-extension.tex', '':'', '':'', '':'', '':'', '':'', } styFileIds = {'glbExtStyFile':'extension.sty', 'grpExtStyFile': self.gid + '-extension.sty', '':'', '':'', '':'', '':'', '':''} def getMacPackIdFromFileName (self, fileName) : '''Return the macPack ID based on the file name''' # File name less ext is the ID parts = len(fileName.split('.')) return '.'.join(fileName.split('.')[:parts-1]) def getMacPackIdFromSource (self, source) : '''Return the macPack ID based on the complete path and file name.''' # Get the file name from the path fileName = self.tools.fName(source) # Return the ID return self.getMacPackIdFromFileName(fileName) def addMacPack (self, source) : '''Add a macro package to the project. It will not work if the same package is already present. Remove must be used to get rid of the existing one first.''' # import pdb; pdb.set_trace() macPackId = self.getMacPackIdFromSource(source) confXml = os.path.join(self.local.projMacroFolder, macPackId, macPackId + '.xml') if not os.path.isfile(source) : self.log.writeToLog(self.errorCodes['3050'], [source]) # Do not add/install if there seems to be a macro package there already if self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] and os.path.exists(self.local.macroConfFile) : self.log.writeToLog(self.errorCodes['3100'], [macPackId]) return False # Set the projectConf to the new/same package self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] = macPackId self.tools.writeConfFile(self.projectConfig) # If we got this far, install the a fresh copy of the macPack self.installMacPackOnly(source) # Move the style files and custom TeX files out of the macPack self.moveMacStyles(macPackId) self.moveMacTex(macPackId) # Create a fresh macro.conf file if it dosn't exist if not os.path.isfile(self.local.macroConfFile) : self.macroConfig = self.tools.initNewConfig(self.local.macroConfFile, self.local.macroConfXmlFile) # Inject information from this particular macro package mInfo = self.tools.getXMLSettings(confXml) self.macroConfig['Macros'][macPackId] = mInfo.dict() # Save the settings now self.tools.writeConfFile(self.macroConfig) self.log.writeToLog(self.errorCodes['3300'], [macPackId, self.local.macroConfFileName]) return True def moveMacStyles (self, macPackId) : '''Move the default macro package styles out of the freshly installed project macro package folder to the project Style folder.''' # import pdb; pdb.set_trace() # Collect the style files to copy for f in self.getMacStyExtFiles(macPackId) : source = os.path.join(os.path.join(self.local.projMacroFolder, macPackId, f)) target = os.path.join(self.local.projStyleFolder, f) self.tools.makedirs(self.local.projStyleFolder) # Do not overwrite existing files unless force is used if not os.path.exists(target) : shutil.copy(source, target) # Look for default and set to read-only defaultStyFile = os.path.join(self.local.projStyleFolder, macPackId + '.sty') if target == defaultStyFile : self.tools.makeReadOnly(defaultStyFile) # Remove the source to avoid confusion if os.path.exists(target) : os.remove(source) else : self.log.writeToLog(self.errorCodes['3310'], [source,self.local.projStyleFolder]) def getMacStyExtFiles (self, macPackId) : '''Return a list of macro package style extention files.''' sFiles = [] macPackFiles = os.listdir(os.path.join(self.local.projMacroFolder, macPackId)) for f in macPackFiles : if f.split('.')[1].lower() == 'sty' : sFiles.append(f) return sFiles def moveMacTex (self, macPackId) : '''Move the custom macro package TeX out of the freshly installed project macro package folder to the project TeX folder.''' # Collect the TeX extention files to copy for f in self.getMacTexExtFiles(macPackId) : source = os.path.join(os.path.join(self.local.projMacroFolder, macPackId, f)) target = os.path.join(self.local.projTexFolder, f) self.tools.makedirs(self.local.projTexFolder) # Do not overwrite existing files if not os.path.exists(target) : shutil.copy(source, target) # Remove the source to avoid confusion if os.path.exists(target) : os.remove(source) else : self.log.writeToLog(self.errorCodes['3310'], [source,self.local.projTexFolder]) def getMacTexExtFiles (self, macPackId) : '''Return a list of macro package TeX extention files.''' tFiles = [] macPackFiles = os.listdir(os.path.join(self.local.projMacroFolder, macPackId)) for f in macPackFiles : if f.find('ext.tex') > 0 : tFiles.append(f) return tFiles def removeMacPack (self) : '''Remove the macro package from a component type''' # import pdb; pdb.set_trace() if self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] != '' : macPackId = self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] else : self.log.writeToLog(self.errorCodes['3010'], [self.cType]) # Aquire target to delete target = os.path.join(self.local.projMacroFolder, macPackId) # Remove the macPack settings from the config file if it is there try : del self.macroConfig['Macros'][macPackId] # Save the settings now self.tools.writeConfFile(self.macroConfig) self.log.writeToLog(self.errorCodes['3400'], [macPackId]) except : pass # Now remove the macro folder (with all its contents) if os.path.exists(target) : shutil.rmtree(target) self.log.writeToLog(self.errorCodes['3500'], [macPackId]) # Remove any style files associated with the macro styTarget = os.path.join(self.local.projStyleFolder, macPackId + '.sty') self.tools.makeExecutable(styTarget) self.tools.removeFile(styTarget) # Remove the reference for this macro package from the component type # that uses it. Normally that would probably be just be one of them. self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] = '' self.tools.writeConfFile(self.projectConfig) def updateMacPack (self, source) : '''Update a macro package with the latest version but do not touch the config file.''' # Do not update if no macro package is registered in the projectConfig if not self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] : self.log.writeToLog(self.errorCodes['3020'], [self.cType.capitalize()]) return False macPackId = self.getMacPackIdFromSource(source) confXml = os.path.join(self.local.projMacroFolder, macPackId, macPackId + '.xml') oldMacPackId = self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] oldMacDir = os.path.join(self.local.projMacroFolder, oldMacPackId) newMacDir = os.path.join(self.local.projMacroFolder, macPackId) # Install the new macPack (but use the old settings) if self.installMacPackOnly(source) : # The current macro system must have a "master" style file. # This is a version of the ParaText style file. Because # an update might include an update to the style file, # we need to copy that from the macro folder to the Style # folder. We will do that now. The name of the file should # be the macPackId + ".sty", in theory. srcStyleFile = os.path.join(self.local.projMacroFolder, macPackId, macPackId + '.sty') oldStyleFile = os.path.join(self.local.projStyleFolder, oldMacPackId + '.sty') newStyleFile = os.path.join(self.local.projStyleFolder, macPackId + '.sty') # Unlock the old one so it can be deleted. self.tools.makeExecutable(oldStyleFile) self.tools.removeFile(oldStyleFile) # Now copy in the new one. shutil.copy(srcStyleFile, newStyleFile) # The style file should never be edited by the user, relock it self.tools.makeReadOnly(newStyleFile) # Update exsisting extention file names. We never want to loose # any settings that the user may have added to the extention # files so we will rename the existing files to have the # new ID otherwise the system will not find them at render time. oldStyExtFile = os.path.join(self.local.projStyleFolder, oldMacPackId + '_ext.sty') oldTexExtFile = os.path.join(self.local.projTexFolder, oldMacPackId + '_ext.tex') oldTexPreStyExtFile = os.path.join(self.local.projTexFolder, oldMacPackId + '_preSty-ext.tex') newStyExtFile = os.path.join(self.local.projStyleFolder, macPackId + '_ext.sty') newTexExtFile = os.path.join(self.local.projTexFolder, macPackId + '_ext.tex') newTexPreStyExtFile = os.path.join(self.local.projTexFolder, macPackId + '_preSty-ext.tex') # By default, we protect any existing versions if os.path.exists(newStyExtFile) : os.remove(oldStyExtFile) else : self.tools.renameFile(oldStyExtFile, newStyExtFile) if os.path.exists(newTexExtFile) : os.remove(oldTexExtFile) else : self.tools.renameFile(oldTexExtFile, newTexExtFile) if os.path.exists(newTexPreStyExtFile) : os.remove(oldTexPreStyExtFile) else : self.tools.renameFile(oldTexPreStyExtFile, newTexPreStyExtFile) # Remove un-needed sty and tex files from the newMacDir to # avoid confusion. The ext files never are updated because # they could contain custom project code that we don't want # to loose in an update. for f in self.getMacStyExtFiles(macPackId) : source = os.path.join(newMacDir, f) if os.path.exists(source) : os.remove(source) for f in self.getMacTexExtFiles(macPackId) : source = os.path.join(newMacDir, f) if os.path.exists(source) : os.remove(source) # Remove the old macPack folder shutil.rmtree(oldMacDir) # Merge new settings into old section (change name to new ID) # When updating, we are assuming the new macro is in the same # family as the old one. As such, settings should be almost # identical, but in case new settings are being added, we will # merge them in now. oldConfSettings = self.macroConfig['Macros'][oldMacPackId] newConfSettings = self.tools.getXMLSettings(confXml) # Now merge newConfSettings.merge(oldConfSettings) # Inject the new section # self.tools.buildConfSection(self.macroConfig, macPackId) self.macroConfig['Macros'][macPackId] = newConfSettings.dict() # Delete the old section del self.macroConfig['Macros'][oldMacPackId] # Save the changes self.tools.writeConfFile(self.macroConfig) # Assuming everything went well we will change the macPackID on the cType self.projectConfig['CompTypes'][self.cType.capitalize()]['macroPackage'] = macPackId self.tools.writeConfFile(self.projectConfig) # Report success self.log.writeToLog(self.errorCodes['3600'], [self.cType.capitalize(), macPackId]) return True # But if the install fails everything stays the same and we report else : self.log.writeToLog(self.errorCodes['3650'], [macPackId]) return False def installMacPackOnly (self, source) : '''Install the new macro package but only that.''' # import pdb; pdb.set_trace() if self.tools.pkgExtract(source, self.local.projMacroFolder, self.local.macroConfXmlFile) : return True else : self.log.writeToLog(self.errorCodes['3200'], [self.tools.fName(source)]) return False
class ProjDiagnose (object) : def __init__(self, pid, gid = None) : '''Intitate the whole class and create the object.''' # import pdb; pdb.set_trace() self.pid = pid self.gid = gid self.local = ProjLocal(pid, gid) self.tools = Tools() self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.proj_config.getLayoutConfig() self.layoutConfig = self.proj_config.layoutConfig self.user = UserConfig() self.userConfig = self.user.userConfig self.log = ProjLog(pid) # to [px] is 72/25.4 self.mmToPx = 72 / 25.4 # page width [px] self.paperPxWidth = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageWidth']),1) # page height [px] self.paperPxHeight = round(self.mmToPx * float(self.layoutConfig['PageLayout']['pageHeight']),1) # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '1310' : ['WRN', 'Failed to add diagnostic component: [<<1>>] with error: [<<2>>]'] } ############################################################################### ############################### Create Functions ############################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def turnOnDiagnostic (self) : '''Change the layout config settings to turn on the diagnostic layer.''' if not self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDiagnostic']) : self.layoutConfig['DocumentFeatures']['useDiagnostic'] = True self.tools.writeConfFile(self.layoutConfig) def turnOffDiagnostic (self) : '''Change the layout config settings to turn off the diagnostic layer.''' if self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDiagnostic']) : self.layoutConfig['DocumentFeatures']['useDiagnostic'] = False self.tools.writeConfFile(self.layoutConfig) def addTransparency (self, target, force = False) : '''Add a transparent layer to a rendered PDF file. This will add in diagnosing format issues. Using force will cause any existing layer file to be remade.''' # Do a quick check if the transparency needs to be remade # The transparency normally is not remade if one already exists. # If one is there, it can be remade in two ways, with a force # or a regenerate command. if force : self.createDiagnostic() elif self.tools.str2bool(self.layoutConfig['DocumentFeatures']['regenerateTransparency']) : self.createDiagnostic() else : # If there isn't one, make it if not os.path.exists(self.local.diagnosticFile) : self.createDiagnostic() # Create a special temp named file for the target tmpTarget = tempfile.NamedTemporaryFile().name # Copy the target to the tmpTarget shutil.copy(target, tmpTarget) # Overlay the transparency diagnostic file over the tmpTarget self.tools.mergePdfFilesPdftk(tmpTarget, self.local.diagnosticFile) # Create a special name for the file with the background # Then merge and save it viewFile = self.tools.alterFileName(target, 'view') # Copy the results back to the target (should be done now) shutil.copy(tmpTarget, viewFile) # Not returning a file name would mean it failed if os.path.exists(viewFile) : return viewFile def createDiagnostic (self) : '''Create a diagnostic transparency (file) that will be superimposed over the page contents to help diagnose format issues. This will overwrite any existing transparency file and will add each recognoized diagnostic type found in the diagnosticComponents config setting.''' # import pdb; pdb.set_trace() self.createBlankTransparency() # Add each component to the blank transparency file for comp in self.layoutConfig['DocumentFeatures']['diagnosticComponents'] : try : getattr(self, 'merge' + comp.capitalize())() except Exception as e : self.log.writeToLog(self.errorCodes['1310'],[comp,str(e)]) pass return True def createBlankTransparency (self) : '''Create a blank background page according to the trim size specified.''' # Set the temp svg file name svgFile = tempfile.NamedTemporaryFile().name # Be sure there is an illustrations folder in place if not os.path.isdir(self.local.projIllustrationFolder) : os.mkdir(self.local.projIllustrationFolder) # Write out SVG document text with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(self.paperPxWidth) + '''" height = "''' + str(self.paperPxHeight)+ '''"> </svg>''') shutil.copy(self.tools.convertSvgToPdfRsvg(svgFile), self.local.diagnosticFile) ############################################################################### ############################# Component Functions ############################# ############################################################################### ######################## Error Code Block Series = 2000 ####################### ############################################################################### def mergeLeading (self) : '''Create a diagnostic page component that has lines to indicate the text leading. This will be superimposed over the contents of the trim page, not in the background like a watermark, etc.''' # Initialize the process svgFile = tempfile.NamedTemporaryFile().name # PAGE DIMENSIONS # The page dimensions extracted from layoutConfig are in [mm] and # must be converted to pixels [px], the conversion factor for [mm] # bodyFontSize [px] bodyFontSize = self.layoutConfig['TextElements']['bodyFontSize'] bodyFontPxSize = round(float(bodyFontSize) * 72/72.27,3) # bodyTextLeading [px] bodyTextLeading = self.layoutConfig['TextElements']['bodyTextLeading'] bodyTextPxLeading = round(float(bodyTextLeading) * 72/72.27,3) # top margin [px] topMargin = self.layoutConfig['PageLayout']['topMargin'] topPxMargin = round(self.mmToPx * float(topMargin),1) # outside margin [px] outsideMargin = self.layoutConfig['PageLayout']['outsideMargin'] outsidePxMargin = round(self.mmToPx * float(outsideMargin),1) #inside margin [px] insideMargin = self.layoutConfig['PageLayout']['insideMargin'] insidePxMargin = round(self.mmToPx * float(outsideMargin),1) # bottom margin [px] bottomMargin = self.layoutConfig['PageLayout']['bottomMargin'] bottomPxMargin = round(self.mmToPx * float(bottomMargin),1) # width of the body text textPxWidth = self.paperPxWidth - (outsidePxMargin + insidePxMargin) # Create the svg file with codecs.open(svgFile, 'wb') as fbackgr : # open file for writing # starting lines of SVG xml fbackgr.write( '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width = "''' + str(self.paperPxWidth)+ '''" height = "'''+str(self.paperPxHeight) + '''"> \n <!--RECTANGLE OF MARGINS-->\n''') fbackgr.write( '''<rect x = "''' + str(outsidePxMargin) + '''" y= "''' + str(topPxMargin) + '''" height = "''' + str(self.paperPxHeight - topPxMargin - bottomPxMargin) + '''" width = "''' + str(textPxWidth) + '''" style = "fill:none;fill-opacity:1;stroke:#ffc800;stroke-opacity:1;stroke-width:.2"/> \n <!--START OF LINEGRID-->\n''') fbackgr.write( '''<path d= "m ''' + str(outsidePxMargin * 0.75-1) + "," + str(topPxMargin + bodyFontPxSize) + " " + str(textPxWidth + outsidePxMargin * 0.25)+ ''',0''') # filling the space between the top line and bottom margin, starting at distance # counter num = 0 up to the but not including the total of num x leading # equals the distance between top and bottom margin num = 0 while (num < int(round(self.paperPxHeight - bottomPxMargin - topPxMargin)/bodyTextPxLeading)): # lines are drawn in zigzag pattern: RTL when num is even and LTR when odd if num%2 == 0: fbackgr.write( ''' m 0, ''' + str(bodyTextPxLeading) + " -" + str(textPxWidth + outsidePxMargin * 0.25)+ ''',0''') else: fbackgr.write( ''' m 0, ''' + str(bodyTextPxLeading) + " " + str(textPxWidth + outsidePxMargin * 0.25)+ ''',0''') num = num +1 # draw all lines with following style fbackgr.write( '''" style="stroke-width:0.2px;stroke:#ffc800;stroke-opacity:1"/> \n <!--LINE NUMBERS-->\n''') # add line number '1' to top line just left of margin fbackgr.write( '''<text x="''' + str(outsidePxMargin * 0.75-2) + '''" y="''' + str(topPxMargin + bodyFontPxSize-3) + '''" style="font-family: Charis SIL;font-style:italic;font-size:7;fill:#760076"> 1''') # add line numbers to all lines down to bottom margin, starting with line number # counter linecount = 2, the distance counter runs from '0' till one short of # the quotient (distance between top and bottom margin)/bodyTextPxLeading num = 0 # line counter linenumber = 2 # line number while (num < int(round(self.paperPxHeight - bottomPxMargin - topPxMargin)/bodyTextPxLeading)): fbackgr.write( '''<tspan x="''' + str(outsidePxMargin * 0.75-2) + '''" dy="''' + str(bodyTextPxLeading) + '''">''' + str(linenumber) + '''</tspan>''') linenumber = linenumber +1 num = num +1 fbackgr.write('''</text> \n <!--LINEGRID CAPTION--> <text x="36" y="''' + str(self.paperPxHeight - bottomPxMargin+10) + '''" style="font-family: Charis SIL;font-style:italic;font-size:7;fill:#ffc800">page size: ''' + str(int(self.paperPxWidth/72*25.4+.5)) + ''' x ''' + str(int(self.paperPxHeight/72*25.4+.5)) + ''' mm ; font size: ''' + str(bodyFontSize) + ''' pt; leading: ''' + str(bodyTextLeading) + ''' pt</text> \n <!--PURPLE LINES TOP AND BOTTOM MARGINS--> <path d="M ''' + str(outsidePxMargin) + "," + str(topPxMargin) + " " + str(textPxWidth + outsidePxMargin) + "," + str(topPxMargin) + '''" style="fill:#ffffff;fill-opacity:1;stroke-width:0.4px;stroke:#760076;stroke-opacity:1"/> <path d="M ''' + str(outsidePxMargin) + "," + str(self.paperPxHeight - bottomPxMargin) + " " + str(textPxWidth + outsidePxMargin) + "," + str(self.paperPxHeight - bottomPxMargin) + '''" style="fill:#ffffff;fill-opacity:1;stroke-width:0.4px;stroke:#760076;stroke-opacity:1"/> </svg>''') # Convert the lines background component to PDF leadingPdf = self.tools.convertSvgToPdfRsvg(svgFile) # Merge leadingPdf with existing transparency results = self.tools.mergePdfFilesPdftk(self.local.diagnosticFile, leadingPdf) # Test and return if good if os.path.isfile(results) : return True
class Macro(object): def __init__(self, pid, cType, gid=None): """Do the primary initialization for this class.""" self.pid = pid self.gid = gid self.cType = cType self.user = UserConfig() self.userConfig = self.user.userConfig self.projHome = os.path.join(os.path.expanduser(self.userConfig["Resources"]["projects"]), self.pid) self.local = ProjLocal(pid, gid, cType) # import pdb; pdb.set_trace() self.proj_config = Config(pid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.layoutConfig = self.proj_config.layoutConfig self.tools = Tools() self.log = ProjLog(pid) # Create config placeholders self.layoutConfig = None self.illustrationConfig = None self.macroConfig = self.tools.loadConfig(self.local.macroConfFile, self.local.macroConfXmlFile) # Log messages for this module self.errorCodes = { "3010": ["ERR", "No macro package is registered for the [<<1>>] component type."], "3020": ["ERR", "Cannot update! No macro package is registered for the [<<1>>] component type."], "3050": ["ERR", "Macro package file not found: [<<1>>]"], "3100": [ "ERR", "Macro package: [<<1>>] already exists in the project. I am not allowed to copy over an existing package.", ], "3200": ["ERR", "Failed to install macro package: [<<1>>]"], "3300": ["MSG", "Installed macro package: [<<1>>], Reinitialized [<<2>>]"], "3310": ["ERR", "Failed to copy [<<1>>] to folder [<<2>>]."], "3400": ["MSG", "Removed macro package configuration settings for: [<<1>>] from the macro.conf file."], "3500": ["MSG", "Removed macro package [<<1>>] folder and all files contained."], "3600": ["MSG", "Updated component type [<<1>>] with macro package [<<2>>]"], "3650": ["ERR", "Failed to updated macro package [<<1>>]"], } ############################################################################### ###################### Macro Package Handling Functions ####################### ############################################################################### ######################## Error Code Block Series = 3000 ####################### ############################################################################### def createMacroFiles(self, macPackId): """Create all the necessary macro file names with their assigned paths.""" self.projMacPackFolder = os.path.join(self.local.projMacPackFolder, macPackId) texFileIds = { "preStyTexExtFile": "preSty-ext.tex", "macSettingsFile": "settings.tex", "extTexFile": "extension.tex", "grpExtTexFile": self.gid + "-extension.tex", "": "", "": "", "": "", "": "", "": "", } styFileIds = { "glbExtStyFile": "extension.sty", "grpExtStyFile": self.gid + "-extension.sty", "": "", "": "", "": "", "": "", "": "", } # <file> # <name>TeX lccode Definition File</name> # <description>The TeX file that contains lccode definitions and is linked with the hypenation exclusions file.</description> # <fileID>lccodeTexFile</fileID> # <fileName>[self:gid]-lccode.tex</fileName> # <filePath>[self:projGidFolder]</filePath> # <depends></depends> # <relies></relies> # <note>This file is located in the component group folder to allow more segregated processing.</note> # </file> # <file> # <name>TeX Group Hyphenation Exclusions File</name> # <description>The file that contains the hypenation words exclusions list for the current group that TeX will use to render the text.</description> # <fileID>grpHyphExcTexFile</fileID> # <fileName>[self:gid]-hyphenation.tex</fileName> # <filePath>[self:projGidFolder]</filePath> # <depends></depends> # <relies></relies> # <note>This file is located in the component group folder to allow more segregated processing.</note> # </file> def getMacPackIdFromFileName(self, fileName): """Return the macPack ID based on the file name""" # File name less ext is the ID parts = len(fileName.split(".")) return ".".join(fileName.split(".")[: parts - 1]) def getMacPackIdFromSource(self, source): """Return the macPack ID based on the complete path and file name.""" # Get the file name from the path fileName = self.tools.fName(source) # Return the ID return self.getMacPackIdFromFileName(fileName) def addMacPack(self, source): """Add a macro package to the project. It will not work if the same package is already present. Remove must be used to get rid of the existing one first.""" # import pdb; pdb.set_trace() macPackId = self.getMacPackIdFromSource(source) confXml = os.path.join(self.local.projMacroFolder, macPackId, macPackId + ".xml") if not os.path.isfile(source): self.log.writeToLog(self.errorCodes["3050"], [source]) # Do not add/install if there seems to be a macro package there already if self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"] and os.path.exists( self.local.macroConfFile ): self.log.writeToLog(self.errorCodes["3100"], [macPackId]) return False # Set the projectConf to the new/same package self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"] = macPackId self.tools.writeConfFile(self.projectConfig) # If we got this far, install the a fresh copy of the macPack self.installMacPackOnly(source) # Move the style files and custom TeX files out of the macPack self.moveMacStyles(macPackId) self.moveMacTex(macPackId) # Create a fresh macro.conf file if it dosn't exist if not os.path.isfile(self.local.macroConfFile): self.macroConfig = self.tools.initNewConfig(self.local.macroConfFile, self.local.macroConfXmlFile) # Inject information from this particular macro package mInfo = self.tools.getXMLSettings(confXml) self.macroConfig["Macros"][macPackId] = mInfo.dict() # Save the settings now self.tools.writeConfFile(self.macroConfig) self.log.writeToLog(self.errorCodes["3300"], [macPackId, self.local.macroConfFileName]) return True def moveMacStyles(self, macPackId): """Move the default macro package styles out of the freshly installed project macro package folder to the project Style folder.""" # import pdb; pdb.set_trace() # Collect the style files to copy for f in self.getMacStyExtFiles(macPackId): source = os.path.join(os.path.join(self.local.projMacroFolder, macPackId, f)) target = os.path.join(self.local.projStyleFolder, f) self.tools.makedirs(self.local.projStyleFolder) # Do not overwrite existing files unless force is used if not os.path.exists(target): shutil.copy(source, target) # Look for default and set to read-only defaultStyFile = os.path.join(self.local.projStyleFolder, macPackId + ".sty") if target == defaultStyFile: self.tools.makeReadOnly(defaultStyFile) # Remove the source to avoid confusion if os.path.exists(target): os.remove(source) else: self.log.writeToLog(self.errorCodes["3310"], [source, self.local.projStyleFolder]) def getMacStyExtFiles(self, macPackId): """Return a list of macro package style extention files.""" sFiles = [] macPackFiles = os.listdir(os.path.join(self.local.projMacroFolder, macPackId)) for f in macPackFiles: if f.split(".")[1].lower() == "sty": sFiles.append(f) return sFiles def moveMacTex(self, macPackId): """Move the custom macro package TeX out of the freshly installed project macro package folder to the project TeX folder.""" # Collect the TeX extention files to copy for f in self.getMacTexExtFiles(macPackId): source = os.path.join(os.path.join(self.local.projMacroFolder, macPackId, f)) target = os.path.join(self.local.projTexFolder, f) self.tools.makedirs(self.local.projTexFolder) # Do not overwrite existing files if not os.path.exists(target): shutil.copy(source, target) # Remove the source to avoid confusion if os.path.exists(target): os.remove(source) else: self.log.writeToLog(self.errorCodes["3310"], [source, self.local.projTexFolder]) def getMacTexExtFiles(self, macPackId): """Return a list of macro package TeX extention files.""" tFiles = [] macPackFiles = os.listdir(os.path.join(self.local.projMacroFolder, macPackId)) for f in macPackFiles: if f.find("ext.tex") > 0: tFiles.append(f) return tFiles def removeMacPack(self): """Remove the macro package from a component type""" # import pdb; pdb.set_trace() if self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"] != "": macPackId = self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"] else: self.log.writeToLog(self.errorCodes["3010"], [self.cType]) # Aquire target to delete target = os.path.join(self.local.projMacroFolder, macPackId) # Remove the macPack settings from the config file if it is there try: del self.macroConfig["Macros"][macPackId] # Save the settings now self.tools.writeConfFile(self.macroConfig) self.log.writeToLog(self.errorCodes["3400"], [macPackId]) except: pass # Now remove the macro folder (with all its contents) if os.path.exists(target): shutil.rmtree(target) self.log.writeToLog(self.errorCodes["3500"], [macPackId]) # Remove any style files associated with the macro styTarget = os.path.join(self.local.projStyleFolder, macPackId + ".sty") self.tools.makeExecutable(styTarget) self.tools.removeFile(styTarget) # Remove the reference for this macro package from the component type # that uses it. Normally that would probably be just be one of them. self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"] = "" self.tools.writeConfFile(self.projectConfig) def updateMacPack(self, source): """Update a macro package with the latest version but do not touch the config file.""" # Do not update if no macro package is registered in the projectConfig if not self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"]: self.log.writeToLog(self.errorCodes["3020"], [self.cType.capitalize()]) return False macPackId = self.getMacPackIdFromSource(source) confXml = os.path.join(self.local.projMacroFolder, macPackId, macPackId + ".xml") oldMacPackId = self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"] oldMacDir = os.path.join(self.local.projMacroFolder, oldMacPackId) newMacDir = os.path.join(self.local.projMacroFolder, macPackId) # Install the new macPack (but use the old settings) if self.installMacPackOnly(source): # The current macro system must have a "master" style file. # This is a version of the ParaText style file. Because # an update might include an update to the style file, # we need to copy that from the macro folder to the Style # folder. We will do that now. The name of the file should # be the macPackId + ".sty", in theory. srcStyleFile = os.path.join(self.local.projMacroFolder, macPackId, macPackId + ".sty") oldStyleFile = os.path.join(self.local.projStyleFolder, oldMacPackId + ".sty") newStyleFile = os.path.join(self.local.projStyleFolder, macPackId + ".sty") # Unlock the old one so it can be deleted. self.tools.makeExecutable(oldStyleFile) self.tools.removeFile(oldStyleFile) # Now copy in the new one. shutil.copy(srcStyleFile, newStyleFile) # The style file should never be edited by the user, relock it self.tools.makeReadOnly(newStyleFile) # Update exsisting extention file names. We never want to loose # any settings that the user may have added to the extention # files so we will rename the existing files to have the # new ID otherwise the system will not find them at render time. oldStyExtFile = os.path.join(self.local.projStyleFolder, oldMacPackId + "_ext.sty") oldTexExtFile = os.path.join(self.local.projTexFolder, oldMacPackId + "_ext.tex") oldTexPreStyExtFile = os.path.join(self.local.projTexFolder, oldMacPackId + "_preSty-ext.tex") newStyExtFile = os.path.join(self.local.projStyleFolder, macPackId + "_ext.sty") newTexExtFile = os.path.join(self.local.projTexFolder, macPackId + "_ext.tex") newTexPreStyExtFile = os.path.join(self.local.projTexFolder, macPackId + "_preSty-ext.tex") # By default, we protect any existing versions if os.path.exists(newStyExtFile): os.remove(oldStyExtFile) else: self.tools.renameFile(oldStyExtFile, newStyExtFile) if os.path.exists(newTexExtFile): os.remove(oldTexExtFile) else: self.tools.renameFile(oldTexExtFile, newTexExtFile) if os.path.exists(newTexPreStyExtFile): os.remove(oldTexPreStyExtFile) else: self.tools.renameFile(oldTexPreStyExtFile, newTexPreStyExtFile) # Remove un-needed sty and tex files from the newMacDir to # avoid confusion. The ext files never are updated because # they could contain custom project code that we don't want # to loose in an update. for f in self.getMacStyExtFiles(macPackId): source = os.path.join(newMacDir, f) if os.path.exists(source): os.remove(source) for f in self.getMacTexExtFiles(macPackId): source = os.path.join(newMacDir, f) if os.path.exists(source): os.remove(source) # Remove the old macPack folder shutil.rmtree(oldMacDir) # Merge new settings into old section (change name to new ID) # When updating, we are assuming the new macro is in the same # family as the old one. As such, settings should be almost # identical, but in case new settings are being added, we will # merge them in now. oldConfSettings = self.macroConfig["Macros"][oldMacPackId] newConfSettings = self.tools.getXMLSettings(confXml) # Now merge newConfSettings.merge(oldConfSettings) # Inject the new section # self.tools.buildConfSection(self.macroConfig, macPackId) self.macroConfig["Macros"][macPackId] = newConfSettings.dict() # Delete the old section del self.macroConfig["Macros"][oldMacPackId] # Save the changes self.tools.writeConfFile(self.macroConfig) # Assuming everything went well we will change the macPackID on the cType self.projectConfig["CompTypes"][self.cType.capitalize()]["macroPackage"] = macPackId self.tools.writeConfFile(self.projectConfig) # Report success self.log.writeToLog(self.errorCodes["3600"], [self.cType.capitalize(), macPackId]) return True # But if the install fails everything stays the same and we report else: self.log.writeToLog(self.errorCodes["3650"], [macPackId]) return False def installMacPackOnly(self, source): """Install the new macro package but only that.""" # import pdb; pdb.set_trace() if self.tools.pkgExtract(source, self.local.projMacroFolder, self.local.macroConfXmlFile): return True else: self.log.writeToLog(self.errorCodes["3200"], [self.tools.fName(source)]) return False
class ProjBinding (object) : def __init__(self, pid) : '''Do the primary initialization for this manager.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig self.config = Config(pid) self.pg_back = ProjBackground(self.pid) self.config.getProjectConfig() self.config.getLayoutConfig() self.projectConfig = self.config.projectConfig self.layoutConfig = self.config.layoutConfig self.useBackground = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useBackground']) self.useDocInfo = self.tools.str2bool(self.layoutConfig['DocumentFeatures']['useDocInfo']) self.projHome = os.path.join(self.userConfig['Resources']['projects'], self.pid) self.local = ProjLocal(self.pid) self.log = ProjLog(self.pid) self.pdfViewerCmd = self.userConfig['System']['pdfViewerCommand'] # Log messages for this module self.errorCodes = { '0205' : ['MSG', 'Unassigned message.'], '0210' : ['MSG', 'No contents are specified for binding.'], '0215' : ['ERR', 'Failed to bind contents into the [<<1>>] fille. Got error: [<<2>>]'], '0220' : ['ERR', 'Could not copy [<<1>>] temp file to [<<2>>] saved binding file.'], '0230' : ['MSG', 'Completed proccessing on the [<<1>>] binding file.'], '0235' : ['ERR', 'Failed to complete proccessing on the [<<1>>] binding file.'], '0240' : ['LOG', 'Recorded [<<1>>] rendered pages in the [<<2>>] binding file.'], '0260' : ['ERR', 'PDF viewer failed with this error: [<<1>>]'], '0265' : ['ERR', 'Rendered file not found: <<1>>'], '0270' : ['WRN', 'PDF viewing is disabled.'], '0280' : ['ERR', 'GS PDF file merge failed with this error: [<<1>>]'], '0300' : ['MSG', 'File binding operation in process, please wait...'] } ############################################################################### ############################## Binding Functions ############################## ############################################################################### ######################## Error Code Block Series = 200 ######################## ############################################################################### # import pdb; pdb.set_trace() def bind (self, save = False) : '''Bind all groups in the order they are indicated by group bindOrder settings. Note, because binding spans groups and the main body (project.py) mainly just works on one group at a time, this has to be called from outside project and project needs to be reinitialized each time a group is rendered from here.''' # import pdb; pdb.set_trace() # Get the order of the groups to be bound. bindOrder = {} # Put a safty in here in case there are no groups yet if not self.projectConfig.has_key('Groups') : return False # Build the bindOrder dict with ord num as key and file name as value for gid in self.projectConfig['Groups'].keys() : if not self.projectConfig['Groups'][gid].has_key('bindingOrder') : self.projectConfig['Groups'][gid]['bindingOrder'] = 0 self.tools.writeConfFile(self.projectConfig) if int(self.projectConfig['Groups'][gid]['bindingOrder']) > 0 : gidPdfFile = os.path.join(self.local.projComponentFolder, gid, gid + '.pdf') # bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = self.projectConfig['Groups'][gid]['bindingFile'] bindOrder[self.projectConfig['Groups'][gid]['bindingOrder']] = gidPdfFile bindGrpNum = len(bindOrder) # Need not keep going if nothing was found if bindGrpNum == 0 : self.log.writeToLog(self.errorCodes['0210']) return False # Make an ordered key list keyList = bindOrder.keys() keyList.sort() # Output the bind files in order according to the list we made fileList = [] for key in keyList : fileList.append(bindOrder[key]) # First merge the master pages together tempFile = self.mergePdfFilesGs(fileList) # Now add background and doc info if requested bgFile = '' if self.useBackground : bgFile = self.pg_back.addBackground(tempFile) if self.useDocInfo : if bgFile : bgFile = self.pg_back.addDocInfo(bgFile) else : bgFile = self.pg_back.addDocInfo(tempFile) # If we are saving this make a name for it if save : bindFileName = self.pid + '_contents_' + self.tools.ymd() # Save this to the Deliverable folder (Make sure there is one) if not os.path.isdir(self.local.projDeliverableFolder) : os.makedirs(self.local.projDeliverableFolder) bindFile = os.path.join(self.local.projDeliverableFolder, bindFileName + '.pdf') if os.path.exists(bgFile) : if shutil.copy(bgFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [bgFile,bindFile]) else : if shutil.copy(tempFile, bindFile) : self.log.writeToLog(self.errorCodes['0220'], [tempFile,bindFile]) # Direct to viewFile viewFile = bindFile else : if os.path.exists(bgFile) : viewFile = bgFile else : viewFile = tempFile # Binding should have been successful, report it now self.log.writeToLog(self.errorCodes['0230'], [viewFile]) # View the file if os.path.isfile(viewFile) : if not len(self.pdfViewerCmd[0]) == 0 : # Add the file to the viewer command self.pdfViewerCmd.append(viewFile) # Run the viewer try : subprocess.Popen(self.pdfViewerCmd) return True except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['0260'], [str(e)]) else : self.log.writeToLog(self.errorCodes['0270']) else : self.log.writeToLog(self.errorCodes['0265'], [self.tools.fName(viewFile)]) def mergePdfFilesGs (self, sourceList) : '''Using GS, merge multiple PDF files into one. The advantage of using Gs is that the index will be maintained. pdftk strips out the index, which is bad...''' # This is our working file for this operation tempFile = tempfile.NamedTemporaryFile().name + '.pdf' # FIXME: Note/Warning, the use "-dPDFSETTINGS=/prepress" can cause an issue when # rendering. A segmentation fault may occur. This will cause the bind feature # to fail and gs will fail. There does not seem to be any way around this # as getting feedback from gs while processing is not possible, or at least # not very easy. It will have to stay this way for now. cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-dPDFSETTINGS=/prepress', '-sOutputFile=' + tempFile] # If there issues with segmentation faults, the following should help with debugging # cmd = ['gs', '-dBATCH', '-dNOPAUSE', '-q', '-sDEVICE=pdfwrite', '-sOutputFile=' + tempFile] # Now add the files we want to bind together cmd = cmd + sourceList # Now bind the files try : self.log.writeToLog(self.errorCodes['0300']) subprocess.call(cmd) # Return our file name for further processing return tempFile except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['0280'], [str(e)])
class ProjFont (object) : def __init__(self, pid) : '''Do the primary initialization for this class.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.log = ProjLog(pid) self.userConfig = self.user.userConfig self.proj_config = Config(pid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.proj_config.getFontConfig() self.fontConfig = self.proj_config.fontConfig self.local = ProjLocal(pid, self.projectConfig) # Load all font settings for use in this module if self.fontConfig : for k, v in self.fontConfig['GeneralSettings'].iteritems() : setattr(self, k, v) # Log messages for this module self.errorCodes = { 'FONT-000' : ['MSG', 'Font module messages'], 'FONT-005' : ['MSG', 'FONT-005 - Unassigned error message ID.'], 'FONT-015' : ['MSG', 'FONT-015 - Unassigned error message ID.'], 'FONT-020' : ['ERR', 'Failed to find font setting in ParaTExt project (.ssf file). A primary font must be set before this component can be successfully rendered.'], 'FONT-025' : ['ERR', 'No source editor was found for this project. Please enter this setting before continuing.'], 'FONT-042' : ['MSG', 'The [<<1>>] font setup information was added to project config'], 'FONT-050' : ['ERR', 'Halt! [<<1>>] not found. - font.copyInFont()'], 'FONT-070' : ['LOG', 'Copied the [<<1>>] font file into the project. - proj_font.copyInFont()'], 'FONT-100' : ['ERR', 'This function has not been implemented yet!. - proj_font.setGlyphMap()'], '0010' : ['LOG', 'Wrote out new font configuration (font.__init__())'], '1220' : ['ERR', 'The Font bundle file [<<1>>] could not be found. Process halted.'], '1235' : ['MSG', 'Font [<<1>>] has been installed into the project.'], '1237' : ['MSG', 'Font [<<1>>] has been updated.'], '1240' : ['ERR', 'Font bundle file [<<1>>] not found.'], '1241' : ['ERR', 'Font bundle [<<1>>] not found.'], '1245' : ['LOG', '<<1>> font setup information added to project config'], '1250' : ['ERR', 'The [<<1>>] font is apparently part of this project. Please remove before trying to re-add this font.'], '1260' : ['MSG', 'The <<1>> font bundle has been copied into the project font folder.'], '1262' : ['LOG', 'The <<1>> font bundle already exsits in the font folder.'], '1265' : ['ERR', 'Failed to extract the [<<1>>] font bundle into the project. Font install process failed.'], '1267' : ['LOG', 'The <<1>> font bundle has been copied into the project font folder.'], '1280' : ['MSG', 'Failed to install the font: [<<1>>] into the project.'], '1370' : ['LOG', 'Removed [<<1>>] font name from project component type: [<<2>>].'], '1380' : ['MSG', 'Removed the [<<1>>] font from the project.'], '1382' : ['MSG', 'Force switch was set (-f). This process has completely removed the [<<1>>] font and settings from the project. - proj_font.removeFont()'], '1390' : ['LOG', 'Removed the [<<1>>] font package.'], '1395' : ['MSG', 'Could not remove the [<<1>>] font package. It may be used by another group. Use force (-f) to remove the package from the font folder.'] } ############################################################################### ############################ Project Level Functions ########################## ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def setGlyphMap (self, cType, font) : '''If needed, set the glyph map used for this component type font.''' self.log.writeToLog('FONT-100') def getFontIdFromFileName (self, fileName) : '''Return the font ID based on the file name''' # File name less ext is the font ID parts = len(fileName.split('.')) return '.'.join(fileName.split('.')[:parts-1]) def getFontIdFromSource (self, source) : '''Return the font ID based on the complete path and file name.''' # Get the file name from the path fileName = self.tools.fName(source) # Return the font ID return self.getFontIdFromFileName(fileName) def recordFont (self, fontId, cType=None) : '''Check for the exsitance of the specified font in the font folder. Then extract the meta data into the appropreate configurations.''' # import pdb; pdb.set_trace() # Set vars do initial checks metaDataSource = os.path.join(self.local.projFontFolder, fontId, fontId + '.xml') if not os.path.isfile(metaDataSource) : self.log.writeToLog(self.errorCodes['1240'], [fontId + '.xml', 'proj_font.recordFont():1240']) # Build the Fonts section in the config (if needed) self.tools.buildConfSection(self.fontConfig, 'Fonts') # (Re)Inject the font info into the macPack config file. fInfo = self.tools.getXMLSettings(metaDataSource) self.fontConfig['Fonts'][fontId] = fInfo.dict() # Save the settings now self.tools.writeConfFile(self.fontConfig) # If a component type was specified, record that as well if cType : self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] = fontId self.tools.writeConfFile(self.projectConfig) return True def copyInFont (self, source) : '''Copy a font into a project. The font is bundled with other necessary components in a .zip file. If the font folder is already there we assume there is a font there and we do not proceed. The user will be prompted to remove the old one first.''' fontId = self.getFontIdFromSource(source) confXml = os.path.join(self.local.projFontFolder, fontId, fontId + '.xml') if not os.path.isfile(source) : self.log.writeToLog(self.errorCodes['1220'], [source]) # Install new copy if self.tools.pkgExtract(source, self.local.projFontFolder, confXml) : self.log.writeToLog(self.errorCodes['1260'], [self.tools.fName(source)]) return True else : self.log.writeToLog(self.errorCodes['1265'], [fontId]) return False def addFont (self, source, cType=None) : '''It is a three step process to install a font. This will both copy in a font and record it in one call. Do not try to install a substitute font. Path is assumed to exsist and contains the file name too.''' # import pdb; pdb.set_trace() fontId = self.getFontIdFromSource(source) # Check for existance, never copy over if self.isFont(fontId) : self.log.writeToLog(self.errorCodes['1250'], [fontId]) # Now install and record if self.copyInFont(source) and self.recordFont(fontId, cType) : self.log.writeToLog(self.errorCodes['1235'], [fontId]) return True else : self.log.writeToLog(self.errorCodes['1280'], [fontId]) return False def updateFontPack (self, source, cType=None) : '''Update a font package but do not change any of the existing settings. If there are setting issues (changes) it would be best to remove, then reinstall. A potential confusion point is that updating a font normally means changing the ID. Then updating actually becomes just installing a font.''' # import pdb; pdb.set_trace() # Get the font ID fontId = self.getFontIdFromSource(source) # Be sure the font is in the project if self.isFont(fontId) : # Remove old copy if os.path.exists(os.path.join(self.local.projFontFolder, fontId)) : shutil.rmtree(os.path.join(self.local.projFontFolder, fontId)) # Bring in a fresh copy if self.copyInFont(source) : # If a component type was specified, record that as well if cType : self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] = fontId self.tools.writeConfFile(self.projectConfig) self.log.writeToLog(self.errorCodes['1237'], [fontId]) return True def removeFontSettings (self, fontId) : '''Remove the font settings from the project.''' # import pdb; pdb.set_trace() if self.fontConfig.has_key('Fonts') : if self.fontConfig['Fonts'].has_key(fontId) : del self.fontConfig['Fonts'][fontId] # Write out the new settings files self.tools.writeConfFile(self.fontConfig) self.log.writeToLog(self.errorCodes['1380'], [fontId]) # Check to see if this font is listed in any of the cTypes # If so, remove it. found = 0 for cType in self.projectConfig['CompTypes'].keys() : # It might be possible that the cType has no font settings, so we check if self.projectConfig['CompTypes'][cType.capitalize()].has_key('fontName') : if self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] == fontId : self.projectConfig['CompTypes'][cType.capitalize()]['fontName'] = '' self.log.writeToLog(self.errorCodes['1370'], [fontId, cType]) found +=1 if found > 0 : self.tools.writeConfFile(self.projectConfig) return True def removeFontPack (self, fontId) : '''Remove a font from a component type which will virtually disconnect it from the calling component type. However, if the force switch is set, then remove the font, regardless as to if it is used by another component or not. This is useful for purging a font from a project but should be used with care.''' # Purge the font files and folder fontDir = os.path.join(self.local.projFontFolder, fontId) if os.path.exists(fontDir) : shutil.rmtree(fontDir) self.log.writeToLog(self.errorCodes['1390'], [fontId]) if self.removeFontSettings(fontId) : return True ############################################################################### ############################ Font Settings Functions ########################## ############################################################################### ######################## Error Code Block Series = 2000 ####################### ############################################################################### def isFont (self, fontId) : '''Return if it is varified that this font is part of the project.''' # First do a cusory check to see if at least the font folder is there fontDir = os.path.join(self.local.projFontFolder, fontId) if not os.path.exists(fontDir) : return False # If the above passed, check in the font config to see if it is listed if not self.fontConfig['Fonts'].has_key(fontId) : return False # If the two tests above passed it is probably there return True
class ProjCompare (object) : def __init__(self, pid) : '''Intitate the whole class and create the object.''' self.pid = pid self.log = ProjLog(self.pid) self.tools = Tools() self.user = UserConfig() self.config = Config(pid) self.local = ProjLocal(self.pid) self.userConfig = self.user.userConfig self.config.getProjectConfig() self.projectConfig = self.config.projectConfig if self.userConfig['System']['textDifferentialViewerCommand'] == '' : self.diffViewCmd = None else : self.diffViewCmd = self.userConfig['System']['textDifferentialViewerCommand'] # Make sure the diff view command is a list if type(self.diffViewCmd) != list : self.diffViewCmd = [self.userConfig['System']['textDifferentialViewerCommand']] # Log messages for this module self.errorCodes = { '0210' : ['WRN', 'File does not exist: [<<1>>] compare cannot be done.'], '0280' : ['ERR', 'Failed to compare files with error: [<<1>>]'], '0285' : ['ERR', 'Cannot compare component [<<1>>] because a coresponding subcomponent could not be found.'], '0290' : ['ERR', 'Compare test type: [<<1>>] is not valid.'], '0295' : ['MSG', 'Comparing: [<<1>>] with parent: [<<2>>] Close the viewer to return to the terminal prompt.'], '0220' : ['MSG', 'Comparison not needed, files seem to be the same.'], '0300' : ['WRN', 'Files are different but visual compare is not enabled.'] } ############################################################################### ############################## Compare Functions ############################## ############################################################################### ######################## Error Code Block Series = 0200 ######################## ############################################################################### def compareComponent (self, gid, cid, parent='backup') : '''Compare a component with its source which was copied into the project when the component was created. This will pull up the user's differential viewer and compare the two files.''' cType = self.projectConfig['Groups'][gid]['cType'] pFile = self.local.getComponentFiles(gid, cid, cType)[parent.lower()] wFile = self.local.getComponentFiles(gid, cid, cType)['working'] # Do a quick check between the current and the source # Run the compare app if a difference is found if self.isDifferent(wFile, pFile) : self.compare(wFile, pFile) else : self.log.writeToLog(self.errorCodes['0220']) def isDifferent (self, new, old) : '''Return True if the contents of the files are different.''' # If one file is missing, return True if not os.path.exists(new) or not os.path.exists(old) : return True # Inside of diffl() open both files with universial line endings then # check each line for differences. diff = difflib.ndiff(open(new, 'rU').readlines(), open(old, 'rU').readlines()) for d in diff : if d[:1] == '+' or d[:1] == '-' : return True # FIXME: Is returning False better than None? return False def compare (self, old, new) : '''Run a compare on two files.''' # import pdb; pdb.set_trace() # If there are any differences, open the diff viewer if self.isDifferent(old, new) : # If no diffViewCmd is found this may be running headless # in that case just report that the file is different and # and leave the function if not self.diffViewCmd : self.log.writeToLog(self.errorCodes['0300']) else : # To prevent file names being pushed back to the list ref # we need to use extend() rather than append() cmd = [] cmd.extend(self.diffViewCmd) cmd.extend([old, new]) try : self.log.writeToLog(self.errorCodes['0295'], [self.tools.fName(old),self.tools.fName(new)]) subprocess.call(cmd) except Exception as e : # If we don't succeed, we should probably quite here self.log.writeToLog(self.errorCodes['0280'], [str(e)]) else : self.log.writeToLog(self.errorCodes['0220'])
class ProjHyphenation (object) : def __init__(self, pid, gid=None) : '''Do the primary initialization for this class.''' # import pdb; pdb.set_trace() self.pid = pid self.gid = gid self.tools = Tools() self.local = ProjLocal(pid) self.user = UserConfig() self.userConfig = self.user.userConfig self.proj_config = Config(pid, gid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.log = ProjLog(pid) self.hyphenationOn = self.tools.str2bool(self.projectConfig['ProjectInfo']['hyphenationOn']) # Get lid and sid, if these do not exist, we need to catch it during processing self.plid = self.projectConfig['ProjectInfo']['languageCode'] self.psid = self.projectConfig['ProjectInfo']['scriptCode'] if gid : self.glid = self.projectConfig['Groups'][gid]['groupLanguageCode'] self.gsid = self.projectConfig['Groups'][gid]['groupScriptCode'] # Determine the prefix for the target files, this should match the # system for everthing to work. if gid : if self.glid and self.gsid : self.prefix = self.glid + '-' + self.gsid + '_' else : self.prefix = self.plid + '-' + self.psid + '_' else : self.prefix = self.plid + '-' + self.psid + '_' # File names self.rpmHyphCharTexFile = self.local.rpmHyphCharTexFile self.rpmHyphExcTexFile = self.local.rpmHyphExcTexFile self.rpmHyphSetTexFile = self.local.rpmHyphSetTexFile # Folder paths self.projHyphenationFolder = self.local.projHyphenationFolder self.rapumaHyphenationFolder = self.local.rapumaHyphenationFolder # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], '1100' : ['ERR', 'Language and/or writing system codes for the project have not been properly set. Please set these values before continuing. Check Hyphenation documentation for more information.'], '1200' : ['ERR', 'The path and/or file name given does not appear to be valid: [<<1>>]'], '1300' : ['ERR', 'Rapuma is looking for a file named: [<<1>>], it cannot be found. Are all the files correctly named? Check Hyphenation documentation for more information.'], '1400' : ['ERR', 'One or more hyphenation files were found in this project. Please use "remove" to get them out first, then "add" to replace with the files you want.'], '1500' : ['MSG', 'Copied file: [<<1>>] to folder: [<<2>>]'], '1550' : ['ERR', 'Copied file failed for file: [<<1>>]'], '1600' : ['MSG', 'Added hyphenation files to the project.'], '2000' : ['LOG', 'When trying to remove hyphenation files, this file was not found: [<<1>>]'], '2100' : ['LOG', 'Set ProjectInfo/hyphenationOn to False, turning off hyphenation for the project.'], '2600' : ['MSG', 'Removed all hyphenation files from the project.'], '3500' : ['MSG', 'Updated file: [<<1>>]'], '3600' : ['MSG', 'The updating of hyphenation files is complete.'] } # Build some file names that are shared by other functions and modules # Note these names will not be any good if the lid and sid are not there. self.projHyphExcTexFile = os.path.join(self.projHyphenationFolder, self.prefix + self.tools.fName(self.rpmHyphExcTexFile)) self.projHyphCharTexFile = os.path.join(self.projHyphenationFolder, self.prefix + self.tools.fName(self.rpmHyphCharTexFile)) self.projHyphSetTexFile = os.path.join(self.projHyphenationFolder, self.prefix + self.tools.fName(self.rpmHyphSetTexFile)) self.projHyphFiles = [self.projHyphCharTexFile, self.projHyphExcTexFile, self.projHyphSetTexFile] self.baseFileNames = [self.tools.fName(self.rpmHyphCharTexFile), self.tools.fName(self.rpmHyphExcTexFile), self.tools.fName(self.rpmHyphSetTexFile)] ############################################################################### ############################ Hyphenation Functions ############################ ############################################################################### ######################## Error Code Block Series = 1000 ####################### ############################################################################### def crossCheckSettings (self) : '''Cross check settings and die here if we are missing anything.''' # May need to add more things to check if not self.plid or not self.psid : self.log.writeToLog(self.errorCodes['1100']) def addHyphenationFiles (self, path = None) : '''Import necessary hyphenation files from the specified path. If no path is given and a file is not found in the project, create place holder file(s) that the user will modify.''' self.crossCheckSettings() # If needed, logically assemble the target file names. This is what # Rapuma is expecting to see. if path : # Check to see if the path given is valid. It should only be a folder, not a file. if not os.path.isdir(path) : self.log.writeToLog(self.errorCodes['1200'], [path]) # Check to see if the files we need are at the specified location for f in self.baseFileNames : if not os.path.isfile(os.path.join(path, self.prefix + f)) : self.log.writeToLog(self.errorCodes['1300'], [self.prefix + f]) # Check to see if any of the hyphenation files are present # in the project. If there are any, hault the process and advise # the user to remove them first. for f in self.baseFileNames : if os.path.isfile(os.path.join(self.projHyphenationFolder, self.prefix, f)) : self.log.writeToLog(self.errorCodes['1400'], [self.prefix + f]) # If we make it this far we should be good to go if not os.path.isdir(self.projHyphenationFolder) : os.makedirs(self.projHyphenationFolder) if path : # Copy user provided files for f in self.baseFileNames : if not shutil.copy(os.path.join(path, self.prefix + f), self.projHyphenationFolder) : self.log.writeToLog(self.errorCodes['1500'], [f, self.projHyphenationFolder]) # Double check that it happened if not os.path.isfile(os.path.join(path, self.prefix + f)) : self.log.writeToLog(self.errorCodes['1550'], [f]) else : # Copy system model files for f in self.baseFileNames : if not shutil.copy(os.path.join(self.rapumaHyphenationFolder, f), os.path.join(self.projHyphenationFolder, self.prefix + f)) : self.log.writeToLog(self.errorCodes['1500'], [self.prefix + f, self.projHyphenationFolder]) # Double check that it happened if not os.path.isfile(os.path.join(self.projHyphenationFolder, self.prefix + f)) : self.log.writeToLog(self.errorCodes['1550'], [f]) # Report and return self.log.writeToLog(self.errorCodes['1600']) return True def removeHyphenationFiles (self) : '''Remove all the hyphenation files from the hyphenation folder. This is currently not smart enough to turn off hyphenation at the group level but will switch off hyphenation at the project level to avoid problems at rendering time.''' self.crossCheckSettings() for f in self.projHyphFiles : if os.path.isfile(f) : os.remove(f) else : self.log.writeToLog(self.errorCodes['2000'], [self.tools.fName(f)]) # Just to be safe, turn off hyphenation for the project if needed if self.hyphenationOn == True : self.projectConfig['ProjectInfo']['hyphenationOn'] = 'False' self.tools.writeConfFile(self.projectConfig) self.log.writeToLog(self.errorCodes['2100']) # Report and return self.log.writeToLog(self.errorCodes['2600']) return True def updateHyphenationFiles (self, path) : '''Update all the hyphenation files for a language in a project. Add any that are not in the target folder. Overwrite any that are in the target folder. Skip any that are not in the source path. A path must be specified.''' self.crossCheckSettings() # Bulk update using all valid files found in a valid folder if os.path.isdir(path) : # Look for and copy the files to be updated in this folder. for f in self.baseFileNames : if os.path.isfile(os.path.join(path, self.prefix + f)) : if not shutil.copy(os.path.join(path, self.prefix + f), os.path.join(self.projHyphenationFolder, self.prefix + f)) : self.log.writeToLog(self.errorCodes['1500'], [self.prefix + f, self.projHyphenationFolder]) # Double check that it happened if not os.path.isfile(os.path.join(self.projHyphenationFolder, self.prefix + f)) : self.log.writeToLog(self.errorCodes['1550'], [f]) # Update using a single valid file elif os.path.isfile(path) : copied = False # Validate file name for f in self.baseFileNames : if self.prefix + f == self.tools.fName(path) : if not shutil.copy(path, self.prefix + f) : self.log.writeToLog(self.errorCodes['1500'], [self.prefix + f, self.projHyphenationFolder]) # Double check that it happened if not os.path.isfile(os.path.join(self.projHyphenationFolder, self.prefix + f)) : self.log.writeToLog(self.errorCodes['1550'], [f]) # Neither file or folder found, throw error else : self.log.writeToLog(self.errorCodes['1200'], [path]) # Report and return self.log.writeToLog(self.errorCodes['3600']) return True