def makeFileThumbnail(srcFileId, mimeType, thumbnailFormat, fileStorageDirectory): """Given an image file, extract a thumbnail out of it.""" resizeMethod = imageMimeTypeToResizeMethodMap.get(mimeType) if resizeMethod == 'resize': newId = uuid4().hex if resizeToThumbnail( fileIdToPath(srcFileId, fileStorageDirectory=fileStorageDirectory), fileIdToPath(newId, fileStorageDirectory=fileStorageDirectory), thumbnailFormat=thumbnailFormat, ): return newId, mimeType else: return None, None elif resizeMethod == 'copy': newId = uuid4().hex shutil.copy( fileIdToPath(srcFileId, fileStorageDirectory=fileStorageDirectory), fileIdToPath(newId, fileStorageDirectory=fileStorageDirectory), ) return newId, mimeType elif resizeMethod is None: # if mime type not enumerated, fail and that's it return None, None else: raise NotImplementedError('resize method not supported: "%s"' % resizeMethod)
def collectArchivablePairs(tr, accPath=''): hereFiles = [{ 'type': 'file', 'path': fileIdToPath( file['file'].file_id, fileStorageDirectory, ), 'zip_path': os.path.join( accPath, file['file'].name, ), } for file in tr['contents']['files']] + [{ 'type': 'data', 'data': link['link'].formatAsString(), 'zip_path': os.path.join( accPath, '%s' % link['link'].name, ), } for link in tr['contents']['links']] subFiles = [ fp for subBox in tr['contents']['boxes'] for fp in collectArchivablePairs(subBox, accPath=os.path.join( accPath, subBox['box'].box_name, )) ] return hereFiles + subFiles
def updateBoxThumbnail( db, box, tId, tMT, user, fileStorageDirectory, accountDeletionInProgress=False, skipCommit=False): """ Update the thumbnail info for a box. Return a list of zero or one full paths for deletion.""" prevId = box.icon_file_id if prevId != '': delQueue = [ fileIdToPath(prevId, fileStorageDirectory=fileStorageDirectory) ] else: delQueue = [] # box.icon_file_id = tId if tId is not None else '' box.icon_mime_type = tMT if tMT is not None else '' box.icon_file_id_username = (user.username if not accountDeletionInProgress else '') dbUpdateRecordOnTable( db, 'boxes', box.asDict(), dbTablesDesc=dbSchema, ) # if not skipCommit: db.commit() return delQueue
def updateUserThumbnail( db, targetUser, tId, tMT, user, fileStorageDirectory, skipCommit=False): """ Update the thumbnail info for a user. Return a list of zero or one full paths for deletion.""" prevId = targetUser.icon_file_id if prevId != '': delQueue = [ fileIdToPath(prevId, fileStorageDirectory=fileStorageDirectory) ] else: delQueue = [] # targetUser.icon_file_id = tId if tId is not None else '' targetUser.icon_mime_type = tMT if tMT is not None else '' targetUser.icon_file_id_username = user.username dbUpdateRecordOnTable( db, 'users', targetUser.asDict(), dbTablesDesc=dbSchema, ) # if not skipCommit: db.commit() return delQueue
def deleteLink( db, parentBox, link, user, fileStorageDirectory, skipCommit=False, accountDeletionInProgress=False): """ Delete a link (either because permissions allow it or the caller is working as part of an account deletion, in which case the caller is responsible for lifting permission checks). Returns the filesystem delete queue. """ if (accountDeletionInProgress or userHasPermission(db, user, parentBox.permissions, 'w')): # fsDeleteQueue = ( [ fileIdToPath( link.icon_file_id, fileStorageDirectory=fileStorageDirectory, ) ] if link.icon_file_id != '' else [] ) dbDeleteRecordsByKey( db, 'links', {'link_id': link.link_id}, dbTablesDesc=dbSchema, ) if not skipCommit: db.commit() return fsDeleteQueue else: raise OstracionError('User has no write permission')
def makeTempFileIntoThumbnail(tmpFileFullPath, thumbnailFormat, fileStorageDirectory): """Given a temporarily saved file, prepare and store a thumbnail.""" mimeType = determineFileProperties(tmpFileFullPath).get('file_mime_type') resizeMethod = imageMimeTypeToResizeMethodMap.get(mimeType) if resizeMethod == 'resize': newId = uuid4().hex if resizeToThumbnail( tmpFileFullPath, fileIdToPath(newId, fileStorageDirectory=fileStorageDirectory), thumbnailFormat=thumbnailFormat, ): return newId, mimeType elif resizeMethod == 'copy': newId = uuid4().hex shutil.copy( tmpFileFullPath, fileIdToPath(newId, fileStorageDirectory=fileStorageDirectory), ) return newId, mimeType else: # if mime type not listed, fail and that's it return None, None
def _recursiveBoxDeletion( db, box, parentBox, user, fileStorageDirectory, accountDeletionInProgress=False): """ Take care of deleting a box, invoking itself on children boxes and erasing contained files, joining the results (the delete queue) accumulated in the sub-deletions. Used internaly by deleteBox. """ if box.box_id == '': raise OstracionError('Box cannot be deleted') else: if (accountDeletionInProgress or ( userHasPermission(db, user, box.permissions, 'w') and userHasPermission(db, user, parentBox.permissions, 'c') )): fsDeleteQueue = ([fileIdToPath( box.icon_file_id, fileStorageDirectory=fileStorageDirectory, )]) if box.icon_file_id != '' else [] allChildren = list(getBoxesFromParent(db, box, user)) if any(c is None for c in allChildren): raise OstracionError('User is not allowed to delete box') # for c in allChildren: if c is not None: fsDeleteQueue += _recursiveBoxDeletion( db, c, box, user, fileStorageDirectory=fileStorageDirectory, accountDeletionInProgress=accountDeletionInProgress) allChildFiles = getFilesFromBox(db, box) for f in allChildFiles: fsDeleteQueue += deleteFile( db, box, f, user, fileStorageDirectory=fileStorageDirectory, skipCommit=True, accountDeletionInProgress=accountDeletionInProgress) # actual deletion dbDeleteRecordsByKey( db, 'box_role_permissions', {'box_id': box.box_id}, dbTablesDesc=dbSchema) dbDeleteRecordsByKey( db, 'boxes', {'box_id': box.box_id}, dbTablesDesc=dbSchema) return fsDeleteQueue else: raise OstracionError('User is not allowed to delete box')
def updateSettingThumbnail(db, richSetting, tId, tMT, user, fileStorageDirectory, skipCommit=False): """ Update the image in a setting of type Image. This returns the (actual) filesystem deletion queue after taking care of the DB part. The new thumbnail ID is passed to this function ('' to reset). richSetting['setting'] is the DB object, at top-level we have enrichment info (such as the default/value resolved etc) """ prevId = richSetting['setting'].value if prevId != '': delQueue = [ fileIdToPath(prevId, fileStorageDirectory=fileStorageDirectory) ] else: delQueue = [] # newSettingDict = recursivelyMergeDictionaries( { 'value': tId if tId is not None else '', 'icon_mime_type': tMT if tMT is not None else '', }, defaultMap={ k: v for k, v in richSetting['setting'].asDict().items() if k in {'value', 'icon_mime_type', 'group_id', 'id'} }, ) dbUpdateRecordOnTable( db, 'settings', newSettingDict, dbTablesDesc=dbSchema, allowPartial=True, ) # if not skipCommit: db.commit() return delQueue
def calendarMakerGenerateCalendar(): """ Calendar actual generation view. Handles everything: building temporary image files driving the engine functions to make the pdf inserting the pdf in the box removing temporary files """ user = g.user db = dbGetDatabase() request._onErrorUrl = url_for('calendarMakerIndexView', ) # destBoxString = request.cookies.get('apps_calendarmaker_destbox') currentCalendar = cookiesToCurrentCalendar(request.cookies) coverImagePathString = currentCalendar.get('cover_image_path_string') calendarImagePaths = currentCalendar.get('image_path_strings', []) cProps = currentCalendar.get('properties', {}) # if destBoxString is None: destBox = None else: destBoxPath = splitPathString(destBoxString) destBox = getBoxFromPath(db, destBoxPath, user) if coverImagePathString is None: coverImageFileObject = None else: coverImageFileObject = pathToFileStructure( db, user, coverImagePathString, ) # calendarImages = [ pathToFileStructure(db, user, imgPath) for imgPath in calendarImagePaths ] numRequiredImages = countMonths( cProps.get('year0'), cProps.get('month0'), cProps.get('year1'), cProps.get('month1'), ) if (destBox is None or coverImageFileObject is None or any([ci is None for ci in calendarImages]) or numRequiredImages is None or numRequiredImages > len(calendarImages)): raise OstracionError('Cannot generate calendar') else: fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] # proceed with generation tempFileDirectory = g.settings['system']['system_directories'][ 'temp_directory']['value'] mkDirP(tempFileDirectory) texImageCoverPath = duplicateImageForCalendar( fileIdToPath( coverImageFileObject['file'].file_id, fileStorageDirectory=fileStorageDirectory, ), os.path.join( tempFileDirectory, '%s.%s' % ( uuid4().hex, admittedImageMimeTypeToExtension[ coverImageFileObject['file'].mime_type], )), ) texImagePaths = [ duplicateImageForCalendar( fileIdToPath( imageFile['file'].file_id, fileStorageDirectory=fileStorageDirectory, ), os.path.join( tempFileDirectory, '%s.%s' % ( uuid4().hex, admittedImageMimeTypeToExtension[ imageFile['file'].mime_type], )), ) for imageFile in calendarImages ] # fsDeletionQueue = [texImageCoverPath] + texImagePaths createdPdfTitle = uuid4().hex createdFile, creationToDelete = makeCalendarPdf( cProps, texImagePaths, texImageCoverPath, tempFileDirectory, createdPdfTitle, ) # if createdFile is not None: # name and description calDescription = 'Calendar %i/%i - %i/%i' % ( cProps['month0'], cProps['year0'], cProps['month1'], cProps['year1'], ) calFileName = findFirstAvailableObjectNameInBox( db, destBox, 'calendar_', '.pdf', ) # place the pdf in the box placeFSFileInBox( db, user, fileStorageDirectory, destBox, createdFile, calFileName, calDescription, ) # flushing the delete queue flushFsDeleteQueue(fsDeletionQueue + creationToDelete + [createdFile]) # messaging the user successMessage = ('Calendar generated. Please find the file ' '"%s" in the destination box.') % calFileName flashMessage('Success', 'Info', successMessage) # redirecting user to box return redirect(url_for('lsView', lsPathString=destBoxString)) else: # flushing the delete queue flushFsDeleteQueue( fsDeletionQueue + creationToDelete + [createdFile], ) # messaging the user flashMessage('Error', 'Error', 'Could not generate the calendar') return redirect(url_for('calendarMakerIndexView'))
def placeFSFileInBox(db, user, fileStorageDirectory, box, filePhysicalPath, fileName, fileDescription, fileTextualMode='', fileThumbnailFormat=None): """ Given an actual file at a physical location, insert it as a Ostracion file by handling the filesystem DB as well as the actual file copy into the storage directories. """ # can user write to box? if userHasPermission(db, user, box.permissions, 'w'): # is the name available? if not isNameUnderParentBox(db, box, fileName): protoFile = { 'box_id': box.box_id, 'name': fileName, 'description': fileDescription, 'date': datetime.datetime.now(), } userName = user.username newFile = File(**recursivelyMergeDictionaries( protoFile, defaultMap={ 'creator_username': userName, 'icon_file_id_username': userName, 'metadata_username': userName, 'editor_username': userName, 'textual_mode': fileTextualMode, }, )) filePath = fileIdToPath( newFile.file_id, fileStorageDirectory=fileStorageDirectory, ) shutil.copy2(filePhysicalPath, filePath) # fileProperties = determineFileProperties(filePath) newFile.mime_type = fileProperties['file_mime_type'] newFile.type = fileProperties['file_type'] newFile.size = fileProperties['file_size'] # if (fileThumbnailFormat is not None and isImageMimeType(newFile.mime_type)): # thumbnail preparation fileThumbnailId, fileThumbnailMimeType = makeFileThumbnail( newFile.file_id, newFile.mime_type, thumbnailFormat=fileThumbnailFormat, fileStorageDirectory=fileStorageDirectory, ) if fileThumbnailId is not None: newFile.icon_file_id = fileThumbnailId newFile.icon_mime_type = fileThumbnailMimeType # makeFileInParent(db, parentBox=box, newFile=newFile) db.commit() else: raise OstracionError('Cannot create a file with that name') else: raise OstracionError('User has no write permission on box')
def saveAndAnalyseFilesInBox(db, files, parentBox, user, thumbnailFormat, fileStorageDirectory, pastActionVerbForm='uploaded'): """ Save files and enrich them with type/mimetype, unless something fails - this handles overwrites (file-on-file) and blockades (file has same name as box) """ # # checking for name clashes with boxes if any( isBoxNameUnderParentBox(db, parentBox, fName) for fName in (fObj['name'] for fObj in files)): raise OstracionError('Files cannot have the name of existing boxes') else: if not userHasPermission(db, user, parentBox.permissions, 'w'): raise OstracionError('User has no write permission on this box') else: userName = user.username fsDeletionQueue = [] numReplacements = 0 # for file in files: newFile = File(**recursivelyMergeDictionaries( {k: v for k, v in file.items() if k != 'fileObject'}, defaultMap={ 'creator_username': userName, 'icon_file_id_username': userName, 'metadata_username': userName, 'editor_username': userName, 'textual_mode': 'plain', }, )) # are we overwriting a file? if isFileNameUnderParentBox(db, parentBox, newFile.name): # delete the old file entry # AND mark the file and its thumbnail # (if any) for later deletion prevFile = getFileFromParent( db, parentBox, newFile.name, user, ) fsDeletionQueue += deleteFile( db, parentBox, prevFile, user, fileStorageDirectory=fileStorageDirectory, skipCommit=True, ) numReplacements += 1 # filePath = fileIdToPath( newFile.file_id, fileStorageDirectory=fileStorageDirectory, ) file['fileObject'].save(filePath) # fileProperties = determineFileProperties(filePath) newFile.mime_type = fileProperties['file_mime_type'] newFile.type = fileProperties['file_type'] newFile.size = fileProperties['file_size'] # if (thumbnailFormat is not None and isImageMimeType(newFile.mime_type)): # thumbnail preparation fileThumbnailId, fileThumbnailMimeType = makeFileThumbnail( newFile.file_id, newFile.mime_type, thumbnailFormat=thumbnailFormat, fileStorageDirectory=fileStorageDirectory, ) if fileThumbnailId is not None: newFile.icon_file_id = fileThumbnailId newFile.icon_mime_type = fileThumbnailMimeType # makeFileInParent(db, parentBox=parentBox, newFile=newFile) flushFsDeleteQueue(fsDeletionQueue) db.commit() return '%i file%s %s successfully%s.' % ( len(files), '' if len(files) == 1 else 's', pastActionVerbForm, '' if numReplacements == 0 else (' (%i replaced)' % numReplacements))
def editTextFileView(fsPathString=''): """Edit-text-file route.""" user = g.user db = dbGetDatabase() lsPath = splitPathString(fsPathString) boxPath, fileName = lsPath[:-1], lsPath[-1] request._onErrorUrl = url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) fileStorageDirectory = g.settings['system']['system_directories'][ 'fs_directory']['value'] parentBox = getBoxFromPath(db, boxPath, user) file = getFileFromParent(db, parentBox, fileName, user) if file is None: raise OstracionError('File not found.') else: fileActions = prepareFileActions( db, file, boxPath[1:] + [file.name], parentBox, user, discardedActions={'text_edit'}, ) fileInfo = prepareFileInfo(db, file) form = EditTextFileForm() form.textformat.choices = [ (mId, mDesc['title']) for mId, mDesc in sorted( textFileViewingModes.items(), key=lambda kv: kv[1]['index'], ) ] ## if form.validate_on_submit(): newcontents = form.filecontents.data filePath = fileIdToPath( file.file_id, fileStorageDirectory=fileStorageDirectory, ) with open(filePath, 'w') as openFile: openFile.write('%s' % newcontents) # file properties fileProperties = determineFileProperties(filePath) newFile = File(**file.asDict()) newFile.mime_type = fileProperties['file_mime_type'] newFile.type = fileProperties['file_type'] newFile.size = fileProperties['file_size'] newFile.textual_mode = form.textformat.data newFile.editor_username = user.username updateFile(db, boxPath, file.name, newFile, user) # flashMessage('Info', 'Info', 'File "%s" saved.' % file.name) return redirect( url_for( 'lsView', lsPathString='/'.join(boxPath[1:]), ) ) else: form.filecontents.data = applyDefault( form.filecontents.data, open( fileIdToPath( file.file_id, fileStorageDirectory=fileStorageDirectory, ) ).read(), ) form.textformat.data = applyDefault( form.textformat.data, file.textual_mode, additionalNulls=['None'], ) pathBCrumbs = makeBreadCrumbs( boxPath, g, appendedItems=[ { 'kind': 'file', 'target': file, }, { 'kind': 'link', 'target': None, 'name': 'Edit text', } ], ) return render_template( 'edittextfile.html', user=user, form=form, fileActions=fileActions, fileInfo=fileInfo, pageTitle='"%s" file edit' % file.name, pageSubtitle='Click "Save" to commit the changes', breadCrumbs=pathBCrumbs, )
def dbDeleteUser(db, username, user, fileStorageDirectory): """ Delete a user altogether. Return a fsDeletionQueue for deletions.""" if username != '': if not userIsAdmin(db, dbGetUser(db, username)): try: if username == user.username or userIsAdmin(db, user): fsDeleteQueue = [] # 1. cleanup of file system rootBox = getRootBox(db) fsDeleteQueue += _traverseForAccountDeletion( db, rootBox, username, user, path=[''], fileStorageDirectory=fileStorageDirectory, ) # 2. deleting user-related data around dbDeleteRecordsByKey( db, 'user_roles', {'username': username}, dbTablesDesc=dbSchema, ) deleteeUser = dbGetUser(db, username) if deleteeUser.icon_file_id != '': fsDeleteQueue.append(fileIdToPath( deleteeUser.icon_file_id, fileStorageDirectory=fileStorageDirectory, )) dbDeleteRecordsByKey( db, 'tickets', {'username': username}, dbTablesDesc=dbSchema, ) for u in dbGetAllUsers(db, user, accountDeletionInProgress=True): if u.username != username: if u.icon_file_id_username == username: if u.icon_file_id != '': fsDeleteQueue.append(fileIdToPath( u.icon_file_id, fileStorageDirectory, )) dbUpdateUser( db, User(**recursivelyMergeDictionaries( { 'icon_file_id': '', 'icon_file_id_username': '', }, defaultMap=u.asDict(), )), user, skipCommit=True, ) # 3. delete user-specific role association from boxes dbDeleteRecordsByKey( db, 'box_role_permissions', {'role_class': 'user', 'role_id': username}, dbTablesDesc=dbSchema, ) # 4. delete user-specific role dbDeleteRecordsByKey( db, 'roles', {'role_class': 'user', 'role_id': username}, dbTablesDesc=dbSchema ) # 5. finally, delete the user dbDeleteRecordsByKey( db, 'users', {'username': username}, dbTablesDesc=dbSchema ) db.commit() return fsDeleteQueue else: raise OstracionError('Insufficient permissions') except Exception as e: db.rollback() raise e else: raise OstracionError('Cannot delete an admin') else: raise OstracionError('Cannot alter system user')