11package org.nypl.simplified.books.core
22
3+ import com.google.common.base.Preconditions
34import com.io7m.jfunctional.None
45import com.io7m.jfunctional.Option
56import com.io7m.jfunctional.OptionType
@@ -205,7 +206,9 @@ internal class BooksControllerBorrowTask(
205206 }
206207
207208 @Throws(IOException ::class )
208- private fun runFulfillSimplifiedBearerToken (file : File ) {
209+ private fun runFulfillSimplifiedBearerToken (
210+ acquisition : OPDSAcquisition ,
211+ file : File ) {
209212 LOG .debug(" [{}]: fulfilling Simplified bearer token file" , this .shortID)
210213
211214 /*
@@ -231,14 +234,15 @@ internal class BooksControllerBorrowTask(
231234 return
232235 }
233236
234- val acquisition = OPDSAcquisition (
235- ACQUISITION_GENERIC ,
236- token.location,
237- Option .some(BooksControllerBorrowTask .SIMPLIFIED_BEARER_TOKEN_CONTENT_TYPE ),
238- emptyList())
237+ val nextAcquisition =
238+ OPDSAcquisition (
239+ ACQUISITION_GENERIC ,
240+ token.location,
241+ acquisition.type,
242+ acquisition.indirectAcquisitions)
239243
240244 val auth = HTTPAuthOAuth (token.accessToken)
241- this .runAcquisitionFulfillDoDownload(acquisition , Option .some(auth))
245+ this .runAcquisitionFulfillDoDownload(nextAcquisition , Option .some(auth))
242246 }
243247
244248 private fun downloadDataReceived (
@@ -609,7 +613,7 @@ internal class BooksControllerBorrowTask(
609613 }
610614
611615 private fun runAcquisitionFulfillDoDownload (
612- a : OPDSAcquisition ,
616+ acquisition : OPDSAcquisition ,
613617 predeterminedAuth : OptionType <HTTPAuthType >): DownloadType {
614618
615619 /*
@@ -622,8 +626,9 @@ internal class BooksControllerBorrowTask(
622626 }
623627
624628 val sid = this .shortID
625- this .fulfillURI = a.uri
626- LOG .debug(" [{}]: starting download {}" , sid, a.uri)
629+ this .fulfillURI = acquisition.uri
630+ LOG .debug(" [{}]: starting download {}" , sid, acquisition.uri)
631+ LOG .debug(" [{}]: expecting content type {}" , sid, acquisition.type)
627632
628633 /*
629634 * Point the downloader at the acquisition link. The result will be an
@@ -634,7 +639,7 @@ internal class BooksControllerBorrowTask(
634639 */
635640
636641 val task = this
637- return this .downloader.download(a .uri, auth, object : DownloadListenerType {
642+ return this .downloader.download(acquisition .uri, auth, object : DownloadListenerType {
638643 override fun onDownloadStarted (
639644 download : DownloadType ,
640645 expectedTotal : Long ) {
@@ -689,20 +694,15 @@ internal class BooksControllerBorrowTask(
689694 if (BooksControllerBorrowTask .ACSM_CONTENT_TYPE == contentType) {
690695 task.runFulfillACSM(file)
691696 } else if (BooksControllerBorrowTask .SIMPLIFIED_BEARER_TOKEN_CONTENT_TYPE == contentType) {
692- task.runFulfillSimplifiedBearerToken(file)
697+ task.runFulfillSimplifiedBearerToken(acquisition, file)
693698 } else {
694699 task.saveFinalContent(
695700 file = file,
696- contentType = contentType)
701+ expectedContentTypes = acquisition.availableFinalContentTypes(),
702+ receivedContentType = contentType)
697703 }
698- } catch (e: IOException ) {
699- LOG .error(" onDownloadCompleted: i/o exception: " , e)
700- task.downloadFailed(Option .some(e))
701- } catch (e: BookUnsupportedTypeException ) {
702- LOG .error(" onDownloadCompleted: unsupported book exception: " , e)
703- task.downloadFailed(Option .some(e))
704- } catch (e: AdobeAdeptACSMException ) {
705- LOG .error(" onDownloadCompleted: acsm exception: " , e)
704+ } catch (e: Exception ) {
705+ LOG .error(" onDownloadCompleted: exception: " , e)
706706 task.downloadFailed(Option .some(e))
707707 }
708708 }
@@ -711,14 +711,23 @@ internal class BooksControllerBorrowTask(
711711
712712 private fun saveFinalContent (
713713 file : File ,
714- contentType : String ) {
714+ expectedContentTypes : Set <String >,
715+ receivedContentType : String ) {
716+
717+ LOG .debug(
718+ " [{}]: saving content {} (expected one of {}, received {})" ,
719+ this .shortID,
720+ file,
721+ expectedContentTypes,
722+ receivedContentType)
715723
716- LOG .debug(" [{}]: saving content {} ({})" , this .shortID, file, contentType)
717724 LOG .debug(" [{}]: saving adobe rights {}" , this .shortID, this .adobeRights)
718725 LOG .debug(" [{}]: saving fulfill URI {}" , this .shortID, this .fulfillURI)
719726
727+ val handleContentType =
728+ checkExpectedContentType(expectedContentTypes, receivedContentType)
720729 val formatHandleOpt: OptionType <BookDatabaseEntryFormatHandle > =
721- this .databaseEntry.entryFindFormatHandleForContentType(contentType )
730+ this .databaseEntry.entryFindFormatHandleForContentType(handleContentType )
722731
723732 fun updateStatus () {
724733 val downloadedSnap = this .databaseEntry.entryGetSnapshot()
@@ -747,8 +756,55 @@ internal class BooksControllerBorrowTask(
747756 }
748757 } else {
749758 LOG .error(" [{}]: database entry does not have a format handle for {}" ,
750- this .shortID, contentType)
751- throw BookUnsupportedTypeException (contentType)
759+ this .shortID, handleContentType)
760+ throw BookUnsupportedTypeException (handleContentType)
761+ }
762+ }
763+
764+ /*
765+ * If we expect a specific content type, but the server actually delivers application/octet-stream,
766+ * then assume that the server delivered the expected type. Otherwise, check that the received
767+ * type matches the expected type.
768+ */
769+
770+ private fun checkExpectedContentType (
771+ expectedContentTypes : Set <String >,
772+ receivedContentType : String ): String {
773+
774+ Preconditions .checkArgument(
775+ ! expectedContentTypes.isEmpty(),
776+ " At least one expected content type" )
777+
778+ return when (receivedContentType) {
779+ " application/octet-stream" -> {
780+ LOG .debug(" [{}]: expected one of {} but received {} (acceptable)" ,
781+ this .shortID,
782+ expectedContentTypes,
783+ receivedContentType)
784+ expectedContentTypes.first()
785+ }
786+
787+ else -> {
788+ if (expectedContentTypes.contains(receivedContentType)) {
789+ return receivedContentType
790+ }
791+
792+ LOG .debug(" [{}]: expected {} but received {} (unacceptable)" ,
793+ this .shortID, expectedContentTypes, receivedContentType)
794+
795+ throw BookUnexpectedTypeException (
796+ message =
797+ StringBuilder (" Unexpected content type\n " )
798+ .append(" Expected: One of " )
799+ .append(expectedContentTypes)
800+ .append(' \n ' )
801+ .append(" Received: " )
802+ .append(receivedContentType)
803+ .append(' \n ' )
804+ .toString(),
805+ expected = expectedContentTypes,
806+ received = receivedContentType)
807+ }
752808 }
753809 }
754810
@@ -808,7 +864,10 @@ internal class BooksControllerBorrowTask(
808864 override fun onFulfillmentSuccess (file : File , loan : AdobeAdeptLoan ) {
809865 try {
810866 this .task.adobeRights = Option .some(loan)
811- this .task.saveFinalContent(file, this .contentType)
867+ this .task.saveFinalContent(
868+ file,
869+ expectedContentTypes = setOf (this .contentType),
870+ receivedContentType = this .contentType)
812871 } catch (x: Throwable ) {
813872 LOG .error(" failure saving content/rights: " , x)
814873 this .task.downloadFailed(Option .some(x))
0 commit comments