diff --git a/webapi/src/main/scala/org/knora/webapi/messages/util/ValueUtilV1.scala b/webapi/src/main/scala/org/knora/webapi/messages/util/ValueUtilV1.scala index df326a3593..ba8312125a 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/util/ValueUtilV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/util/ValueUtilV1.scala @@ -81,6 +81,8 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) { makeVideoFileValue(valueProps, projectShortcode, responderManager, userProfile) case OntologyConstants.KnoraBase.DocumentFileValue => makeDocumentFileValue(valueProps, projectShortcode, responderManager, userProfile) + case OntologyConstants.KnoraBase.ArchiveFileValue => + makeArchiveFileValue(valueProps, projectShortcode, responderManager, userProfile) case OntologyConstants.KnoraBase.LinkValue => makeLinkValue(valueProps, responderManager, userProfile) } } @@ -106,6 +108,15 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) { def makeSipiDocumentGetUrlFromFilename(documentFileValueV1: DocumentFileValueV1): String = s"${settings.externalSipiIIIFGetUrl}/${documentFileValueV1.projectShortcode}/${documentFileValueV1.internalFilename}/file" + /** + * Creates a URL for accessing a archive file via Sipi. + * + * @param archiveFileValueV1 the archive file value. + * @return a Sipi URL. + */ + def makeSipiArchiveGetUrlFromFilename(archiveFileValueV1: ArchiveFileValueV1): String = + s"${settings.externalSipiIIIFGetUrl}/${archiveFileValueV1.projectShortcode}/${archiveFileValueV1.internalFilename}/file" + /** * Creates a URL for accessing a text file via Sipi. * @@ -197,6 +208,13 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) { path = makeSipiDocumentGetUrlFromFilename(documentFileValueV1) ) + case archiveFileValueV1: ArchiveFileValueV1 => + LocationV1( + format_name = mimeType2V1Format(archiveFileValueV1.internalMimeType), + origname = archiveFileValueV1.originalFilename, + path = makeSipiArchiveGetUrlFromFilename(archiveFileValueV1) + ) + case textFileValue: TextFileValueV1 => LocationV1( format_name = mimeType2V1Format(textFileValue.internalMimeType), @@ -398,6 +416,8 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) { case _: DocumentFileValueV1 => basicObjectResponse + case _: ArchiveFileValueV1 => basicObjectResponse + case _: AudioFileValueV1 => basicObjectResponse case _: MovingImageFileValueV1 => basicObjectResponse @@ -916,6 +936,30 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) { ) } + /** + * Converts a [[ValueProps]] into a [[ArchiveFileValueV1]]. + * + * @param valueProps a [[ValueProps]] representing the SPARQL query results to be converted. + * @return a [[ArchiveFileValueV1]]. + */ + private def makeArchiveFileValue( + valueProps: ValueProps, + projectShortcode: String, + responderManager: ActorRef, + userProfile: UserADM + )(implicit timeout: Timeout, executionContext: ExecutionContext): Future[ApiValueV1] = { + val predicates = valueProps.literalData + + Future( + ArchiveFileValueV1( + internalMimeType = predicates(OntologyConstants.KnoraBase.InternalMimeType).literals.head, + internalFilename = predicates(OntologyConstants.KnoraBase.InternalFilename).literals.head, + originalFilename = predicates.get(OntologyConstants.KnoraBase.OriginalFilename).map(_.literals.head), + projectShortcode = projectShortcode + ) + ) + } + /** * Converts a [[ValueProps]] into a [[AudioFileValueV1]]. * diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/valuemessages/ValueMessagesV1.scala b/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/valuemessages/ValueMessagesV1.scala index df65f0e87d..ac1fa87683 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/valuemessages/ValueMessagesV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v1/responder/valuemessages/ValueMessagesV1.scala @@ -1611,6 +1611,64 @@ case class DocumentFileValueV1( ) } +/** + * A representation of a compressed archive in a binary format. + * + * @param internalMimeType the MIME-type of the internal representation. + * @param internalFilename the internal filename of the object. + * @param originalFilename the original filename of the object at the time of the import. + */ +case class ArchiveFileValueV1( + internalMimeType: String, + internalFilename: String, + originalFilename: Option[String] = None, + originalMimeType: Option[String] = None, + projectShortcode: String +) extends FileValueV1 { + def valueTypeIri: IRI = OntologyConstants.KnoraBase.ArchiveFileValue + + def toJsValue: JsValue = ApiValueV1JsonProtocol.archiveFileValueV1Format.write(this) + + override def toString: String = internalFilename + + /** + * Checks if a new archive file value would duplicate an existing archive file value. + * + * @param other another [[ValueV1]]. + * @return `true` if `other` is a duplicate of `this`. + */ + override def isDuplicateOfOtherValue(other: ApiValueV1): Boolean = + other match { + case archiveFileValueV1: ArchiveFileValueV1 => archiveFileValueV1 == this + case otherValue => + throw InconsistentRepositoryDataException(s"Cannot compare a $valueTypeIri to a ${otherValue.valueTypeIri}") + } + + /** + * Checks if a new version of a archive file value would be redundant given the current version of the value. + * + * @param currentVersion the current version of the value. + * @return `true` if this [[UpdateValueV1]] is redundant given `currentVersion`. + */ + override def isRedundant(currentVersion: ApiValueV1): Boolean = + currentVersion match { + case archiveFileValueV1: ArchiveFileValueV1 => archiveFileValueV1 == this + case other => + throw InconsistentRepositoryDataException(s"Cannot compare a $valueTypeIri to a ${other.valueTypeIri}") + } + + override def toFileValueContentV2: FileValueContentV2 = + ArchiveFileValueContentV2( + ontologySchema = InternalSchema, + fileValue = FileValueV2( + internalFilename = internalFilename, + internalMimeType = internalMimeType, + originalFilename = originalFilename, + originalMimeType = Some(internalMimeType) + ) + ) +} + case class AudioFileValueV1( internalMimeType: String, internalFilename: String, @@ -1861,6 +1919,7 @@ object ApiValueV1JsonProtocol extends SprayJsonSupport with DefaultJsonProtocol implicit val dateValueV1Format: JsonFormat[DateValueV1] = jsonFormat5(DateValueV1) implicit val stillImageFileValueV1Format: JsonFormat[StillImageFileValueV1] = jsonFormat7(StillImageFileValueV1) implicit val documentFileValueV1Format: JsonFormat[DocumentFileValueV1] = jsonFormat8(DocumentFileValueV1) + implicit val archiveFileValueV1Format: JsonFormat[ArchiveFileValueV1] = jsonFormat5(ArchiveFileValueV1) implicit val textFileValueV1Format: JsonFormat[TextFileValueV1] = jsonFormat5(TextFileValueV1) implicit val audioFileValueV1Format: JsonFormat[AudioFileValueV1] = jsonFormat6(AudioFileValueV1) implicit val movingImageFileValueV1Format: JsonFormat[MovingImageFileValueV1] = jsonFormat9(MovingImageFileValueV1) diff --git a/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilV1.scala b/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilV1.scala index dafd767d4b..6534b5282f 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilV1.scala @@ -20,10 +20,11 @@ import org.knora.webapi.messages.store.sipimessages.GetFileMetadataResponse import org.knora.webapi.messages.util.standoff.StandoffTagUtilV2 import org.knora.webapi.messages.util.standoff.StandoffTagUtilV2.TextWithStandoffTagsV2 import org.knora.webapi.messages.v1.responder.valuemessages.{ - MovingImageFileValueV1, + ArchiveFileValueV1, AudioFileValueV1, DocumentFileValueV1, FileValueV1, + MovingImageFileValueV1, StillImageFileValueV1, TextFileValueV1 } @@ -258,11 +259,7 @@ object RouteUtilV1 { "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "application/zip", - "application/x-tar", - "application/x-iso9660-image", - "application/gzip" + "application/vnd.openxmlformats-officedocument.presentationml.presentation" ) /** @@ -283,6 +280,16 @@ object RouteUtilV1 { "video/mp4" ) + /** + * MIME types used in Sipi to store archive files. + */ + private val archiveMimeTypes: Set[String] = Set( + "application/zip", + "application/x-tar", + "application/gzip", + "application/x-7z-compressed" + ) + /** * Converts file metadata from Sipi into a [[FileValueV1]]. * @@ -348,6 +355,14 @@ object RouteUtilV1 { dimY = fileMetadataResponse.height.getOrElse(throw SipiException(s"Sipi did not return the height of the video")) ) + } else if (archiveMimeTypes.contains(fileMetadataResponse.internalMimeType)) { + ArchiveFileValueV1( + internalFilename = filename, + internalMimeType = fileMetadataResponse.internalMimeType, + originalFilename = fileMetadataResponse.originalFilename, + originalMimeType = fileMetadataResponse.originalMimeType, + projectShortcode = projectShortcode + ) } else { throw BadRequestException(s"MIME type ${fileMetadataResponse.internalMimeType} not supported in Knora API v1") } diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/addValueVersion.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/addValueVersion.scala.txt index ab418f944a..44fa0abfd5 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/addValueVersion.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/addValueVersion.scala.txt @@ -364,6 +364,27 @@ DELETE { } } + case archiveFileValue: ArchiveFileValueV1 => { + ?newValue knora-base:internalFilename """@archiveFileValue.internalFilename""" . + ?newValue knora-base:internalMimeType """@archiveFileValue.internalMimeType""" . + + @archiveFileValue.originalFilename match { + case Some(definedOriginalFilename) => { + ?newValue knora-base:originalFilename """@definedOriginalFilename""" . + } + + case None => {} + } + + @archiveFileValue.originalMimeType match { + case Some(definedOriginalMimeType) => { + ?newValue knora-base:originalMimeType """@definedOriginalMimeType""" . + } + + case None => {} + } + } + case listValue: HierarchicalListValueV1 => { ?newValue knora-base:valueHasListNode <@listValue.hierarchicalListIri> . diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/generateInsertStatementsForCreateValue.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/generateInsertStatementsForCreateValue.scala.txt index 37d3a262e5..bb910ea17d 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/generateInsertStatementsForCreateValue.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v1/generateInsertStatementsForCreateValue.scala.txt @@ -320,6 +320,27 @@ } } + case archiveFileValue: ArchiveFileValueV1 => { + <@newValueIri> knora-base:internalFilename """@archiveFileValue.internalFilename""" ; + knora-base:internalMimeType """@archiveFileValue.internalMimeType""" . + + @archiveFileValue.originalFilename match { + case Some(definedOriginalFilename) => { + <@newValueIri> knora-base:originalFilename """@definedOriginalFilename""" . + } + + case None => {} + } + + @archiveFileValue.originalMimeType match { + case Some(definedOriginalMimeType) => { + <@newValueIri> knora-base:originalMimeType """@definedOriginalMimeType""" . + } + + case None => {} + } + } + case listValue: HierarchicalListValueV1 => { <@newValueIri> knora-base:valueHasListNode <@listValue.hierarchicalListIri> . diff --git a/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala b/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala index a4866b283e..ce2c84ee1c 100644 --- a/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala @@ -850,9 +850,9 @@ class KnoraSipiIntegrationV1ITSpec uploadedZipFile.originalFilename should ===(minimalZipOriginalFilename) // Create a resource for the Zip file. - val createDocumentResourceParams = JsObject( + val archiveResourceParams = JsObject( Map( - "restype_id" -> JsString("http://www.knora.org/ontology/0001/anything#ThingDocument"), + "restype_id" -> JsString("http://www.knora.org/ontology/knora-base#ArchiveRepresentation"), "label" -> JsString("Zip file"), "project_id" -> JsString("http://rdfh.ch/projects/0001"), "properties" -> JsObject(), @@ -863,7 +863,7 @@ class KnoraSipiIntegrationV1ITSpec // Send the JSON in a POST request to the Knora API server. val createDocumentResourceRequest: HttpRequest = Post( baseApiUrl + "/v1/resources", - HttpEntity(ContentTypes.`application/json`, createDocumentResourceParams.compactPrint) + HttpEntity(ContentTypes.`application/json`, archiveResourceParams.compactPrint) ) ~> addCredentials(BasicHttpCredentials(userEmail, password)) val createDocumentResourceResponseJson: JsObject = getResponseJson(createDocumentResourceRequest)