class Group (object) : # Shared values xmlConfFile = 'group.xml' def __init__(self, project, cfg, parent = None) : '''Initialize this class.''' self.project = project self.cfg = cfg self.parent = parent or project self.managers = {} self.tools = Tools() def render(self) : '''Render a group.''' self.tools.terminal("Warning: Calling dummy rendering in the group class.")
class Template (object) : def __init__(self, pid) : '''Intitate the whole class and create the object.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig self.local = ProjLocal(pid) self.log = ProjLog(pid) self.projData = ProjData(pid) def projectToTemplate (self, tid = None) : '''Preserve critical project information in a template. The pid is the project that the template will be bassed from. The template will go in the template lib.''' # Set needed vals if not tid : tid = self.pid tempDir = os.path.join(tempfile.mkdtemp(), tid) target = os.path.join(self.local.userLibTemplate, tid + '.zip') # Make a temp copy of the project that we can manipulate shutil.copytree(self.local.projHome, tempDir) # Now make the config files generic for use with any project tc = ConfigObj(os.path.join(tempDir, 'Config', 'project.conf'), encoding='utf-8') tc['ProjectInfo']['projectTitle'] = '' tc['ProjectInfo']['projectIDCode'] = '' tc['ProjectInfo']['projectCreateDate'] = '' tc['ProjectInfo']['projectCreateDate'] = '' # Remove unnecessary folders needNot = ['Component', 'Deliverable', 'Illustration'] for f in needNot : fld = os.path.join(tempDir, f) if os.path.exists(fld) : shutil.rmtree(fld) # Remove unnecessary config files needNot = ['adjustment', 'illustration'] for f in needNot : fl = os.path.join(tempDir, 'Config', f + '.conf') if os.path.exists(fl) : os.remove(fl) # Remove unnecessary project config stuff needNot = ['Groups', 'Backup'] for s in needNot : if tc.has_key(s) : del tc[s] # Write out the new template project config file tc.filename = os.path.join(tempDir, 'Config', 'project.conf') tc.write() # Kill the log file os.remove(os.path.join(tempDir, 'rapuma.log')) # Exclude files excludeFiles = self.projData.makeExcludeFileList(source) # Zip it up using the above params root_len = len(tempDir) with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as myzip : sys.stdout.write('Creating template') sys.stdout.flush() for root, dirs, files in os.walk(tempDir): # 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 f[-1] == '~' : continue elif f in excludeFiles : continue elif f.rfind('.') != -1 : 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 space for next message sys.stdout.write('\n') # Remove the temp project dir we made self.tools.terminal('\nCompleted creating template: ' + target + '\n') def templateToProject (self, targetDir = None, source = None) : '''Create a new project based on the provided template ID. If a path to the template is not provided it will look in the users template lib. A PID must be provided. That is checked with the system. If the same PID is found in the system, it must be removed before reruning this function. If a non-default location is needed, a target path must be provided.''' # import pdb; pdb.set_trace() # Set a default target path projHome = os.path.join(self.local.projParentDefaultFolder, self.pid) # See if we can build a better target path if targetDir != '' : projHome = os.path.join(targetDir, self.pid) elif self.local.projHome : projHome = os.path.join(self.local.projHome, self.pid) # self.tools.dieNow() # Test to see if the project already exists if self.pid in self.projList : self.tools.terminal('\nError: Project ID [' + self.pid + '] is already exists on this system. Use the remove command to remove it.') self.tools.dieNow() # Test for source template file if not source : source = os.path.join(self.local.userLibTemplate, self.pid + '.zip') if not os.path.exists(source) : self.tools.terminal('\nError: No template can be found for [' + self.pid + ']\n') self.tools.dieNow() # Unzip the template in place to start the new project with zipfile.ZipFile(source, 'r') as myzip : myzip.extractall(projHome) # Peek into the project pc = ConfigObj(os.path.join(projHome, 'Config', 'project.conf'), encoding='utf-8') pc['ProjectInfo']['projectCreateDate'] = self.tools.tStamp() pc['ProjectInfo']['projectIDCode'] = self.pid pc.filename = os.path.join(projHome, 'Config', 'project.conf') pc.write() # Get the media type from the newly placed project for registration projectMediaIDCode = pc['ProjectInfo']['projectMediaIDCode'] # Reset the local settings self.local = ProjLocal(self.pid) # Create any folders that might be needed for fld in self.local.projFolders : folder = os.path.join(self.local.projHome, fld) if not os.path.exists(folder) : os.makedirs(folder) # Report what happened self.tools.terminal('A new project [' + self.pid + '] has been created based on the [' + self.tools.fName(source) + '] template.')
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 UserConfig (object) : def __init__(self) : '''Intitate the whole class and create the object.''' # import pdb; pdb.set_trace() self.rapumaHome = os.environ.get('RAPUMA_BASE') self.defaultUserHome = os.environ.get('RAPUMA_USER') self.userConfFileName = 'rapuma.conf' self.tools = Tools() # Point to the right user config either server or desktop # The RAPUMA_USER setting should have the right path from # the mother script (rapuma) self.userConfFile = os.path.join(self.defaultUserHome, self.userConfFileName) # Check to see if the file is there, then read it in and break it into # sections. If it fails, scream really loud! rapumaXMLDefaults = os.path.join(self.rapumaHome, 'config', 'rapuma.xml') if os.path.exists(rapumaXMLDefaults) : self.tools.sysXmlConfig = self.tools.xml_to_section(rapumaXMLDefaults) else : raise IOError, "Can't open " + rapumaXMLDefaults # Now make the users local rapuma.conf file if it isn't there if not os.path.isfile(self.userConfFile) : self.initUserHome() # Load the system.conf file into an object self.userConfig = ConfigObj(self.userConfFile, encoding='utf-8') # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], } ############################################################################### ############################ User Config Functions ############################ ############################################################################### def initUserHome (self) : '''Initialize a user config file on a new install or system re-init.''' # Create home folders if not os.path.isdir(self.defaultUserHome) : os.mkdir(self.defaultUserHome) # Make the default global rapuma.conf for custom environment settings if not os.path.isfile(self.userConfFile) : self.userConfig = ConfigObj(self.tools.sysXmlConfig.dict(), encoding='utf-8') self.userConfig.filename = self.userConfFile self.userConfig['System']['initDate'] = self.tools.tStamp() self.userConfig.write() def setSystemSettings (self, section, key, value) : '''Function to make system settings.''' oldValue = self.userConfig[section][key] if oldValue != value : self.userConfig[section][key] = value # Write out the results self.userConfig.write() self.tools.terminal('\nRapuma user name setting changed from [' + oldValue + '] to [' + value + '].\n\n') else : self.tools.terminal('\nSame value given, nothing to changed.\n\n')
class ProjCommander (object) : def __init__(self, pid) : '''Intitate the whole class and create the object.''' self.pid = pid self.tools = Tools() self.user = UserConfig() self.userConfig = self.user.userConfig self.projHome = os.path.join(os.environ['RAPUMA_PROJECTS'], self.pid) self.local = ProjLocal(self.pid) self.proj_config = Config(pid) self.proj_config.getProjectConfig() self.projectConfig = self.proj_config.projectConfig self.projectMediaIDCode = self.projectConfig['ProjectInfo']['projectMediaIDCode'] # Log messages for this module self.errorCodes = { '0000' : ['MSG', 'Placeholder message'], } ############################################################################### ########################## Command Creation Functions ######################### ############################################################################### def removeScripts (self) : '''Remove any unnecessary group control scripts from the project.''' self.tools.dieNow('removeScripts() not implemented yet.') def updateScripts (self) : '''Update all the helper command scripts in a project.''' self.makeStaticScripts() self.makeGrpScripts() def makeGrpScripts (self) : '''Create scripts that process specific group components.''' if not os.path.isdir(self.local.projHelpScriptFolder) : os.mkdir(self.local.projHelpScriptFolder) # Output the scripts (If this is a new project we need to pass) if self.projectConfig.has_key('Groups') : for gid in self.projectConfig['Groups'].keys() : allScripts = self.getGrpScripInfo(gid) for key in allScripts.keys() : fullFile = os.path.join(self.local.projHelpScriptFolder, key) + gid with codecs.open(fullFile, "w", encoding='utf_8') as writeObject : writeObject.write(self.makeScriptHeader(allScripts[key][0], allScripts[key][1])) # Strip out extra spaces from command cmd = re.sub(ur'\s+', ur' ', allScripts[key][1]) writeObject.write(cmd + '\n\n') # Make the script executable self.tools.makeExecutable(fullFile) self.tools.terminal('\nCompleted creating/recreating group helper scripts.\n') else : pass def makeStaticScripts (self) : '''Create helper scripts for a project to help with repetitive tasks. If any scripts are present with the same name they will be overwritten. Note: This is only for temporary use due to the lack of an interface at this time (20130306140636). It assumes the cType is usfm which, at some point may not be the case.''' if not os.path.isdir(self.local.projHelpScriptFolder) : os.mkdir(self.local.projHelpScriptFolder) # Output the scripts allScripts = self.getStaticScripInfo() for key in allScripts.keys() : fullFile = os.path.join(self.local.projHelpScriptFolder, key) with codecs.open(fullFile, "w", encoding='utf_8') as writeObject : writeObject.write(self.makeScriptHeader(allScripts[key][0], allScripts[key][1])) writeObject.write(allScripts[key][1] + '\n\n') # Make the script executable self.tools.makeExecutable(fullFile) self.tools.terminal('\nCompleted creating/recreating static helper scripts.\n') def makeScriptHeader (self, desc, cmd) : '''Make a helper script header.''' return '#!/bin/sh\n\n# Description: ' + desc + '\n\necho \necho Rapuma helper script: ' + desc + '\n\necho \necho command: ' + self.echoClean(cmd) + '\n\n' def echoClean (self, cmdStr) : '''Clean up a string for an echo statement in a shell script.''' clean = re.sub(ur'\;', ur'\\;', cmdStr) clean = re.sub(ur'\s+', ur' ', clean) return clean def getStaticScripInfo (self) : '''Create a dictionary of all the static auxillary script information used in most projects.''' pid = self.pid mid = self.projectMediaIDCode return { 'addBible' : ['Add Scripture components for a Bible group.', 'rapuma group ' + pid + ' BIBLE group add --source_path $1 '], 'addNT' : ['Add Scripture components for an NT group.', 'rapuma group ' + pid + ' NT group add --source_path $1 '], 'addOT' : ['Add Scripture components for an OT group.', 'rapuma group ' + pid + ' OT group add --source_path $1 '], 'archive' : ['Archive this project', 'rapuma project ' + pid + ' archive save '], 'backup' : ['Backup this project', 'rapuma project ' + pid + ' backup save '], 'cloudPull' : ['Pull data for this project from the cloud', 'rapuma project ' + pid + ' cloud restore '], 'cloudPush' : ['Push data from this project to the cloud', 'rapuma project ' + pid + ' cloud save $1 '], 'restore' : ['Restore a backup.', 'rapuma project ' + pid + ' backup restore '], 'template' : ['Create a template of the project.', 'rapuma project ' + pid + ' template save --id $1 '], 'updateScripts' : ['Update the project scripts.', 'rapuma project ' + pid + ' project update --update_type helper '], 'bind' : ['Create the binding PDF file', 'if [ "$1" ]; then CMD=" $1"; fi; if [ "$2" ]; then CMD=" $1 $2"; fi; rapuma project ' + pid + ' project bind $CMD '], 'placeholdOff' : ['Turn off illustration placeholders.', 'rapuma settings ' + pid + ' ' + mid + '_layout Illustrations useFigurePlaceHolders False '], 'placeholdOn' : ['Turn on illustration placeholders.', 'rapuma settings ' + pid + ' ' + mid + '_layout Illustrations useFigurePlaceHolders True '] } def getGrpScripInfo (self, gid) : '''Create a dictionary of the auxillary group script information used in most projects.''' # import pdb; pdb.set_trace() # Set the vars for this function pid = self.pid cType = self.projectConfig['Groups'][gid]['cType'] Ctype = cType.capitalize() renderer = self.projectConfig['CompTypes'][Ctype]['renderer'] self.proj_macro = Macro(self.pid, gid) macroConfig = self.proj_macro.macroConfig font = '' if macroConfig and macroConfig['FontSettings'].has_key('primaryFont') : font = macroConfig['FontSettings']['primaryFont'] macro = self.projectConfig['CompTypes'][Ctype]['macroPackage'] mid = self.projectMediaIDCode # Return a dictionary of all the commands we generate return { 'compare' : ['Compare component working text with backup.', 'if [ "$1" ]; then CMD="--cid_list $1"; fi; rapuma group ' + pid + ' ' + gid + ' group compare --compare_type backup $CMD '], 'render' : ['Render ' + gid + ' group PDF file.', 'if [ "$1" ]; then CMD="--cid_list $1"; fi; if [ "$2" ]; then CMD="--cid_list $1 $2"; fi; if [ "$3" ]; then CMD="--cid_list $1 $2 $3"; fi; rapuma group ' + pid + ' ' + gid + ' group render $CMD '], 'update' : ['Update the ' + gid + ' group from its source.', 'if [ "$2" ]; then CMD="--cid_list $2"; fi; rapuma group ' + pid + ' ' + gid + ' group update --source_path $1 $CMD '], 'background' : ['Re/Create the project background.', 'rapuma project ' + pid + ' project update --update_type background '], 'transparency' : ['Re/Create the project diagnostic layer.', 'rapuma project ' + pid + ' project update --update_type diagnostic '], 'addFont' : ['Add a font to the ' + gid + ' group.', 'rapuma package ' + pid + ' ' + gid + ' $1 font add -f '], 'removeFont' : ['Remove a font from the ' + gid + ' group.', 'rapuma package ' + pid + ' ' + gid + ' $1 font remove -f '], 'primaryFont' : ['Make font primary for the ' + gid + ' group.', 'rapuma package ' + pid + ' ' + gid + ' $1 font primary -f '], 'updateFont' : ['Update the ' + gid + ' font.', 'rapuma package ' + pid + ' ' + gid + ' \"' + font + '\" font update '], 'updateMacro' : ['Update the ' + gid + ' macro package.', 'rapuma package ' + pid + ' ' + gid + ' \"' + macro + '\" macro update '] }
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
tools = Tools() source = sys.argv[1] tempFile = source + '.tmp' bakFile = source + '.bak' # Make backup and temp file shutil.copy(source, bakFile) # Read in the source file contents = codecs.open(source, "rt", encoding="utf_8_sig").read() # Do stuff here # Please delete this warning message when you start modifying this script. tools.terminal('\nThe group preprocess script has not been modified yet. It is in its default form. Nothing has been done to the component data.\n(This anoying little message can be found in a file in your project Script folder.)\n') # Examples: # Change cross reference into footnotes #contents = re.sub(ur'\\x(\s.+?)\\xo(\s\d+:\d+)(.+?)\\x\*', ur'\\f\1\\fr\2 \\ft\3\\f*', contents) # Insert horizontal rule after intro section #contents = re.sub(ur'(\\c\s1\r\n)', ur'\skipline\n\hrule\r\n\1', contents) # Insert a chapter label marker with zwsp to center the chapter number #contents = re.sub(ur'(\\c\s1\r\n)', ur'\cl \u200b\r\n\1', contents) # Write out a temp file so we can do some checks codecs.open(tempFile, "wt", encoding="utf_8_sig").write(contents) # Finish by copying the tempFile to the source
class ProjLog (object) : def __init__(self, pid) : '''Do the primary initialization for this manager.''' self.tools = Tools() self.pid = pid self.rapumaHome = os.environ.get('RAPUMA_BASE') self.userHome = os.environ.get('RAPUMA_USER') self.user = UserConfig() self.userConfig = self.user.userConfig self.local = ProjLocal(pid) ############################################################################### ############################### Logging Functions ############################# ############################################################################### # These have to do with keeping a running project log file. Everything done is # recorded in the log file and that file is trimmed to a length that is # specified in the system settings. Everything is channeled to the log file but # depending on what has happened, they are classed in three levels: # 1) [MSG] - Common event going to log and terminal # 2) [WRN] - Warning event going to log and terminal if debugging is turned on # 3) [ERR] - Error event going to the log and terminal and kills the process # 4) [LOG] - Messages that go only to the log file to help with debugging # FIXME: Following not implemented yet # 5) [TOD] - To do list. Output to a file that helps guide the user. def writeToLog (self, errCode, args=None, location=None) : '''Send an event to one of the log files or the terminal if specified. Everything gets written to a log. Where a message gets written to depends on what type code it is. The type code is in with the error code data. There are five type codes: MSG = General messages go to both the terminal and log file LOG = Messages that go only to the log file WRN = Warnings that go to the terminal and log file ERR = Errors that go to both the terminal and log file TOD = Messages that will go to a special todo file to guide the user The errCode points to a specific message that will be sent to a log file. The args parameter can contain extra information like file names to help the user better figure out what happened.''' # Get the message from the errorCode list the module sent if type(errCode) == list : if location : msg = errCode[1] + ' : (' + location + ')' else : msg = errCode[1] code = errCode[0] else : self.tools.terminal('\nThe code: [' + errCode + '] is not recognized by the Rapuma system.') return # If args were given, do s/r on them and add # args info that needs to be added to msg. # Look for a <<#>> pattern replace it with # the corresponding position in the args list. if args : for count, arg in enumerate(args) : msg = msg.replace('<<' + str(count+1) + '>>', arg) # Write out everything but LOG messages to the terminal if code != 'LOG' and code != 'TOD' : self.tools.terminal('\n' + code + ' - ' + msg) # Test to see if this is a live project by seeing if the project conf is # there. If it is, we can write out log files. Otherwise, why bother? if self.local.projectConfFile and os.path.exists(self.local.projectConfFile) : # Build the event line eventLine = '\"' + self.tools.tStamp() + '\", \"' + code + '\", \"' + msg + '\"' # Do we need a log file made? try : if not os.path.isfile(self.local.projLogFile) or os.path.getsize(self.local.projLogFile) == 0 : writeObject = codecs.open(self.local.projLogFile, "w", encoding='utf_8') writeObject.write('Rapuma event log file created: ' + self.tools.tStamp() + '\n') writeObject.close() # Now log the event to the top of the log file using preAppend(). self.preAppend(eventLine, self.local.projLogFile) # FIXME: Add the TOD list output here, also, output any TODs # to the error log as well as these are bad errors. # Write errors and warnings to the error log file if code == 'WRN' and self.userConfig['System']['debugging'] == 'True': self.writeToErrorLog(self.local.projErrorLogFile, eventLine) if code == 'ERR' : self.writeToErrorLog(self.local.projErrorLogFile, eventLine) except Exception as e : # If we don't succeed, we should probably quite here self.tools.terminal("Failed to write message to log file: " + msg) self.tools.terminal('Internal error: [' + str(e) + ']') self.tools.dieNow() # Halt the process if this was an 'ERR' level type code if code == 'ERR' : self.tools.dieNow('Sorry, I have to stop.') else : return def writeToErrorLog (self, errorLog, eventLine) : '''In a perfect world there would be no errors, but alas there are and we need to put them in a special file that can be accessed after the process is run. The error file from the previous session is deleted at the beginning of each new run.''' try : # Because we want to read errors from top to bottom, we don't pre append # them to the error log file. if not os.path.isfile(errorLog) : writeObject = codecs.open(errorLog, "w", encoding='utf_8') else : writeObject = codecs.open(errorLog, "a", encoding='utf_8') # Write and close writeObject.write(eventLine + '\n') writeObject.close() except : self.tools.terminal('Error writing this event to error log: ' + eventLine) return def preAppend (self, line, file_name) : '''Got the following code out of a Python forum. This will pre-append a line to the beginning of a file.''' # import pdb; pdb.set_trace() fobj = fileinput.FileInput(file_name, inplace=1) first_line = fobj.readline() sys.stdout.write("%s\n%s" % (line, first_line)) for line in fobj: sys.stdout.write("%s" % line) fobj.close()
class UserConfig(object): def __init__(self): """Intitate the whole class and create the object.""" self.rapumaHome = os.environ.get("RAPUMA_BASE") self.defaultUserHome = os.environ.get("RAPUMA_USER") self.userConfFileName = "rapuma.conf" self.tools = Tools() # Point to the right user config # Look for a web installation first, if not go to default # Note that a slash is put before var as it is off of root # That kind of stops this from being cross-platform rapumaWebConfig = os.path.join("/var", "lib", "rapuma", "config", self.userConfFileName) defaultConfig = os.path.join(self.defaultUserHome, self.userConfFileName) if os.path.exists(rapumaWebConfig): self.userConfFile = rapumaWebConfig else: self.userConfFile = defaultConfig # Check to see if the file is there, then read it in and break it into # sections. If it fails, scream really loud! rapumaXMLDefaults = os.path.join(self.rapumaHome, "config", "rapuma.xml") if os.path.exists(rapumaXMLDefaults): self.tools.sysXmlConfig = self.tools.xml_to_section(rapumaXMLDefaults) else: raise IOError, "Can't open " + rapumaXMLDefaults # import pdb; pdb.set_trace() # Now make the users local rapuma.conf file if it isn't there if not os.path.exists(self.userConfFile): self.initUserHome() # Load the Rapuma conf file into an object self.userConfig = ConfigObj(self.userConfFile, encoding="utf-8") # Initialize the user's home folders, like resources, etc self.makeHomeFolders() # Log messages for this module self.errorCodes = {"0000": ["MSG", "Placeholder message"]} ############################################################################### ############################ User Config Functions ############################ ############################################################################### def initUserHome(self): """Initialize a user config file on a new install or system re-init.""" # Create home folders if not os.path.isdir(self.defaultUserHome): os.mkdir(self.defaultUserHome) # Make the default global rapuma.conf for custom environment settings if not os.path.isfile(self.userConfFile): self.userConfig = ConfigObj(self.tools.sysXmlConfig.dict(), encoding="utf-8") self.userConfig.filename = self.userConfFile self.userConfig["System"]["initDate"] = self.tools.tStamp() self.userConfig.write() def setSystemSettings(self, section, key, value): """Function to make system settings.""" oldValue = self.userConfig[section][key] if oldValue != value: self.userConfig[section][key] = value # Write out the results self.userConfig.write() self.tools.terminal("\nRapuma user name setting changed from [" + oldValue + "] to [" + value + "].\n\n") else: self.tools.terminal("\nSame value given, nothing to changed.\n\n") def makeHomeFolders(self): """Setup the default Rapuma resource folders.""" # import pdb; pdb.set_trace() # We do not write out unless this flag is set confWriteFlag = False # Setup Resources section if needed if not self.userConfig.has_key("Resources"): self.tools.buildConfSection(self.userConfig, "Resources") # Get the user config project folder location (or set a default) if not self.userConfig["Resources"].has_key("projects") or not self.userConfig["Resources"]["projects"]: projects = os.path.join(os.environ.get("HOME"), "Publishing") if not os.path.exists(projects): os.makedirs(projects) self.userConfig["Resources"]["projects"] = projects confWriteFlag = True elif not os.path.exists(self.tools.resolvePath(self.userConfig["Resources"]["projects"])): sys.exit( "\nERROR: Invalid projects folder path: " + self.userConfig["Resources"]["projects"] + "\n\nProcess halted.\n" ) else: projects = self.tools.resolvePath(self.userConfig["Resources"]["projects"]) # Note: The following was commented because it no longer is necessary to load in locations # for various resouces. That will be dynamically. If the program ever reaches the point # where this would be good to have, it can be handled by a GUI. (djd - 20160223) # # Get the user config Rapuma resource folder location # if not self.userConfig['Resources'].has_key('rapumaResource') : # # Check for pre-typo-fix value ('rapumaResouce') before creating new section # if self.userConfig['Resources'].has_key('rapumaResouce') : # self.userConfig['Resources'].rename('rapumaResouce', 'rapumaResource') # confWriteFlag = True # else: # self.tools.buildConfSection(self.userConfig['Resources'], 'rapumaResource') # confWriteFlag = True # if len(self.userConfig['Resources']['rapumaResource']) > 0 : # rapumaResource = self.userConfig['Resources']['rapumaResource'] # else : # # This is the default location # rapumaResource = os.path.join(site.USER_BASE, 'share', 'rapuma') # self.userConfig['Resources']['rapumaResource'] = rapumaResource # confWriteFlag = True # # Make a list of sub-folders to make in the Rapuma resourcs folder # resourceFolders = ['archive', 'backup', 'font', 'illustration', \ # 'macro','script', 'template'] # for r in resourceFolders : # # Build the path and check if it can be made # thisPath = os.path.join(rapumaResource, r) # if not os.path.isdir(thisPath) : # os.makedirs(thisPath) # self.userConfig['Resources'][r] = thisPath # confWriteFlag = True # Write out if needed if confWriteFlag: self.userConfig.write() return True