app/services/actors/creation/CreateFileEntry.scala (107 lines of code) (raw):

package services.actors.creation import java.sql.Timestamp import java.time.LocalDateTime import javax.inject.Inject import akka.actor.Props import drivers.StorageDriver import akka.pattern.ask import exceptions.ProjectCreationError import models.{FileEntry, FileEntryDAO, ProjectRequestFull, ProjectType} import org.slf4j.MDC import play.api.db.slick.DatabaseConfigProvider import slick.jdbc.JdbcProfile import scala.concurrent.Future import scala.util.{Failure, Success, Try} object CreateFileEntry { case class DidCreateFileEntry(entry: FileEntry) extends CreationMessage } import scala.concurrent.ExecutionContext.Implicits.global class CreateFileEntry @Inject() (dbConfigProvider:DatabaseConfigProvider) (implicit fileEntryDAO:FileEntryDAO) extends GenericCreationActor { override val persistenceId = "create-file-entry-" + self.path.name import GenericCreationActor._ import CreateFileEntry._ implicit val db = dbConfigProvider.get[JdbcProfile].db /** * Combines the provided filename with a (possibly) provided extension * @param filename filename * @param extension Option possibly containing a string of the file extension * @return Combined filename and extension. If no extension, filename returned unchanged; if the extension does not start with a * dot then a dot is inserted between name and extension */ private def makeFileName(filename:String,extension:Option[String]):String = { if(extension.isDefined){ if(extension.get.startsWith(".")) s"$filename${extension.get}" else s"$filename.${extension.get}" } else filename } /** * Either create a new file entry for the required destination file or retrieve a pre-exisiting one * @param rq ProjectRequestFull instance describing the project to be created * @param recordTimestamp time to record for creation * @param db implicitly provided database instance * @return a Future, containing a Try, containing a saved FileEntry instance if successful */ def getDestFileFor(rq:ProjectRequestFull, recordTimestamp:Timestamp)(implicit db: slick.jdbc.PostgresProfile#Backend#Database): Future[FileEntry] = ProjectType.entryFor(rq.projectTemplate.projectTypeId).flatMap(projectType=>{ fileEntryDAO.entryFor(rq.filename, rq.destinationStorage.id.get, 1).map({ case Success(filesList) => if (filesList.isEmpty) { //no file entries exist already, create one (at version 1) and proceed //note that the premiere version field, if required, is set dynamically by a postrun FileEntry(None, makeFileName(rq.filename, projectType.fileExtension), rq.destinationStorage.id.get, "system", 1, recordTimestamp, recordTimestamp, recordTimestamp, hasContent = false, hasLink = false, backupOf = None, maybePremiereVersion = None) } else { //a file entry does already exist, but may not have data on it if (filesList.length > 1) throw new ProjectCreationError(s"Multiple files exist for ${rq.filename} on ${rq.destinationStorage.repr}") else if (filesList.head.hasContent) throw new ProjectCreationError(s"File ${rq.filename} on ${rq.destinationStorage.repr} already has data") else filesList.head } case Failure(error) => throw error }) }) def removeDestFileFor(rq: ProjectRequestFull)(implicit db: slick.jdbc.PostgresProfile#Backend#Database): Future[Try[Boolean]] = ProjectType.entryFor(rq.projectTemplate.projectTypeId).flatMap(projectType=>{ fileEntryDAO.entryFor(makeFileName(rq.filename, projectType.fileExtension), rq.destinationStorage.id.get, 1).flatMap({ case Success(filesList) => filesList.headOption match { case None => //no file entries exist, so nothing to remove logger.warn(s"No file entry exists for ${rq.filename} so I can't remove it in rollback") Future(Success(false)) case Some(file) => logger.info(s"Found file $file to delete") file.deleteSelf .map(_=>Success(true)) .recover({ case err:Throwable=> logger.error(s"Could not remove invalid dastination file: ${err.getMessage}") Failure(err) }) } case Failure(err) => Future(Failure(err)) }) }) override def receive: Receive = { case entryRequest:NewProjectRequest=> val originalSender = sender() val recordTimestamp = Timestamp.valueOf(entryRequest.createTime.getOrElse(LocalDateTime.now())) val projectCreateData = entryRequest.data getDestFileFor(entryRequest.rq, recordTimestamp).flatMap(fileEntry=> { logger.info(s"Creating file $fileEntry") fileEntry.save.map({ case Success(savedFileEntry) => originalSender ! StepSucceded(projectCreateData.copy(destFileEntry = Some(savedFileEntry))) case Failure(error) => MDC.put("fileEntry", fileEntry.toString) logger.error("Failed to create file", error) originalSender ! StepFailed(projectCreateData, error) }) }).recover({ case error:Throwable=> MDC.put("entryRequest", entryRequest.toString) logger.error("Could not create destination file record", error) originalSender ! StepFailed(projectCreateData, error) }) case rollbackRequest:NewProjectRollback=> val originalSender = sender() val projectCreateData = rollbackRequest.data removeDestFileFor(rollbackRequest.rq).map({ case Success(_)=> originalSender ! StepSucceded(projectCreateData.copy(destFileEntry = None)) case Failure(error)=> logger.error("Could not remove destination file record in rollback", error) originalSender ! StepFailed(projectCreateData.copy(destFileEntry = None), error) }) case _=> super.receive } }