app/models/AssetFolderFileEntryDAO.scala (134 lines of code) (raw):

package models import akka.stream.scaladsl.Source import drivers.StorageDriver import org.slf4j.LoggerFactory import play.api.db.slick.DatabaseConfigProvider import play.api.inject.Injector import slick.jdbc.PostgresProfile import slick.jdbc.PostgresProfile.api._ import slick.lifted.TableQuery import java.nio.file.{Path, Paths} import javax.inject.{Inject, Singleton} import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} @Singleton class AssetFolderFileEntryDAO @Inject()(dbConfigProvider:DatabaseConfigProvider)(implicit ec:ExecutionContext, injector:Injector) { private final val db = dbConfigProvider.get[PostgresProfile].db private final val logger = LoggerFactory.getLogger(getClass) /** * Writes this model into the database, inserting if id is None and returning a fresh object with id set. If an id * was set, then returns the same object. */ def save(entry:AssetFolderFileEntry):Future[Try[AssetFolderFileEntry]] = entry.id match { case None=> val insertQuery = TableQuery[AssetFolderFileEntryRow] returning TableQuery[AssetFolderFileEntryRow].map(_.id) into ((item,id)=>item.copy(id=Some(id))) db.run( (insertQuery+=entry).asTry ).map({ case Success(insertResult)=>Success(insertResult) case Failure(error)=>Failure(error) }) case Some(realEntityId)=> db.run( TableQuery[AssetFolderFileEntryRow].filter(_.id===realEntityId).update(entry).asTry ).map({ case Success(_)=>Success(entry) case Failure(error)=>Failure(error) }) } def saveSimple(entry:AssetFolderFileEntry):Future[AssetFolderFileEntry] = save(entry).flatMap({ case Success(e)=>Future(e) case Failure(err)=>Future.failed(err) }) def storage(entry:AssetFolderFileEntry):Future[Option[StorageEntry]] = { db.run( TableQuery[StorageEntryRow].filter(_.id===entry.storageId).result ).map(_.headOption) } def getFullPath(entry:AssetFolderFileEntry):Future[String] = storage(entry).map({ case Some(storage)=> Paths.get(storage.rootpath.getOrElse(""), entry.filepath).toString case None=> entry.filepath }) /** * This attempts to delete the file from disk, using the configured storage driver * @param entry AssetFolderFileEntry to delete * @return A future containing either a Right() containing a Boolean indicating whether the delete happened, or a Left with an error string */ def deleteFromDisk(entry:AssetFolderFileEntry):Future[Either[String,Boolean]] = { val maybeStorageDriverFuture = storage(entry).map({ case Some(storageRef)=> storageRef.getStorageDriver case None=> None }) maybeStorageDriverFuture.flatMap({ case Some(storagedriver)=> getFullPath(entry).map(fullpath=>Right(storagedriver.deleteFileAtPath(fullpath, entry.version))) case None=> Future(Left("No storage driver configured for storage")) }) } /** * Attempt to delete the underlying record from the database * @param entry FileEntry to delete * @return A Future with no value on success. On failure, the future fails; pick this up with .recover() or .onComplete */ def deleteRecord(entry:AssetFolderFileEntry):Future[Unit] = entry.id match { case Some(databaseId)=> logger.info(s"Deleting database record for file $databaseId (${entry.filepath})") db.run( DBIO.seq( TableQuery[AssetFolderFileEntryRow].filter(_.id===databaseId).delete ) ) case None=> Future.failed(new RuntimeException("Cannot delete a record that has not been saved to the database")) } /** * Get an [[AssetFolderFileEntry]] instance for the given database id. * @param entryId Database id. to look up * @return A Future, containing an Option that may contain a [[AssetFolderFileEntry]] instance */ def entryFor(entryId: Int):Future[Option[AssetFolderFileEntry]] = db.run( TableQuery[AssetFolderFileEntryRow].filter(_.id===entryId).result.asTry ).map({ case Success(result)=> result.headOption case Failure(error)=>throw error }) /** * Get an AssetFolderFileEntry instance for the given filename and version * @param fileName File name to search for * @param version id. to search for * @return A Future, containing a Try that contains a sequence of zero or more AssetFolderFileEntry instances */ def entryFor(fileName: String, version:Int):Future[Try[Seq[AssetFolderFileEntry]]] = db.run( TableQuery[AssetFolderFileEntryRow] .filter(_.filepath===fileName) .filter(_.version===version) .result .asTry ) /** * Improved version of entryFor that returns either one or no entries in a more composable way. * This should be all that is needed because of table constraints * @param fileName The file name to search for (exact match) * @param version Version number to search for * @return A Future containing either an AssetFolderFileEntry or None. The future fails if there is a problem. */ def singleEntryFor(fileName: String, storageId:Int, version:Int):Future[Option[AssetFolderFileEntry]] = db.run( TableQuery[AssetFolderFileEntryRow].filter(_.filepath===fileName).filter(_.storage===storageId).filter(_.version===version).result ).map(_.headOption) def allVersionsFor(fileName: String):Future[Try[Seq[AssetFolderFileEntry]]] = db.run( TableQuery[AssetFolderFileEntryRow].filter(_.filepath===fileName).sortBy(_.version.desc.nullsLast).result.asTry ) /** * Returns a list of matching records for the given file name, ordered by most recent first (if versioning is enabled) * @param target File path to query. this should be a relative filepath for the given storage. * @return A Future containing a sequence of results */ def findByFilename(target:Path, drop:Int=0, take:Int=100) = { val baseQuery = TableQuery[AssetFolderFileEntryRow].filter(_.filepath===target.toString) db.run { baseQuery.sortBy(_.version.desc.nullsLast).drop(drop).take(take).result } } /** * Returns a streaming source that lists out all files in the database, optionally limiting to a given storage id. * @return An Akka Source, that yields AssetFolderFileEntry objects */ def scanAllFiles() = { val baseQuery = TableQuery[AssetFolderFileEntryRow] Source.fromPublisher(db.stream(baseQuery.sortBy(_.mtime.asc).result)) } def validatePathExists(entry:AssetFolderFileEntry) = for { filePath <- getFullPath(entry) maybeStorage <- storage(entry) result <- Future( maybeStorage .map(_.validatePathExists(filePath, entry.version)) match { case Some(result)=>result case None=>Left(s"No storage could be found for id. ${entry.storageId} on file ${entry.id}") } ) } yield result def validatePathExistsDirect(entry:AssetFolderFileEntry)(implicit driver:StorageDriver) = { getFullPath(entry).map(path=>driver.pathExists(path, entry.version)) } def entryForLatestVersionByProject(projectId: Int, storageId: Int, filePath: String):Future[Option[AssetFolderFileEntry]] = db.run( TableQuery[AssetFolderFileEntryRow].filter(_.storage===storageId).filter(_.project===projectId).filter(_.filepath===filePath).sortBy(_.version.desc.nullsLast).result.asTry ).map({ case Success(result)=> result.headOption case Failure(error)=>throw error }) }