feat(opds): add on-the-fly KEPUB conversion for downloads
- Integrate conversion service with OPDS handler - Convert EPUB to KEPUB format on download request - Cache converted files to reduce processing time - Support per-device catalog with format availability - Maintain backward compatibility with existing downloads
This commit is contained in:
@@ -3,6 +3,8 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"bookmann/internal/database"
|
"bookmann/internal/database"
|
||||||
"bookmann/internal/opds"
|
"bookmann/internal/opds"
|
||||||
|
"bookmann/internal/services"
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -20,11 +22,17 @@ import (
|
|||||||
|
|
||||||
type OPDSHandler struct {
|
type OPDSHandler struct {
|
||||||
db *database.Queries
|
db *database.Queries
|
||||||
|
conversionService interface {
|
||||||
|
ConvertEPUBToKEPUB(ctx context.Context, mediaItemID pgtype.UUID, epubPath string) (*services.ConvertedKEPUB, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOPDSHandler(db *database.Queries) *OPDSHandler {
|
func NewOPDSHandler(db *database.Queries, conversionService interface {
|
||||||
|
ConvertEPUBToKEPUB(ctx context.Context, mediaItemID pgtype.UUID, epubPath string) (*services.ConvertedKEPUB, error)
|
||||||
|
}) *OPDSHandler {
|
||||||
return &OPDSHandler{
|
return &OPDSHandler{
|
||||||
db: db,
|
db: db,
|
||||||
|
conversionService: conversionService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,11 +390,11 @@ func (h *OPDSHandler) DownloadBook(c echo.Context) error {
|
|||||||
mimeType := ""
|
mimeType := ""
|
||||||
|
|
||||||
if format == "kepub" {
|
if format == "kepub" {
|
||||||
// Check for pre-converted KEPUB
|
|
||||||
kepubFormat, err := h.db.GetMediaItemFormatByType(c.Request().Context(), database.GetMediaItemFormatByTypeParams{
|
kepubFormat, err := h.db.GetMediaItemFormatByType(c.Request().Context(), database.GetMediaItemFormatByTypeParams{
|
||||||
MediaItemID: pgtype.UUID{Bytes: bookUUID, Valid: true},
|
MediaItemID: pgtype.UUID{Bytes: bookUUID, Valid: true},
|
||||||
FormatType: "kepub",
|
FormatType: "kepub",
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil && kepubFormat.FilePath.Valid {
|
if err == nil && kepubFormat.FilePath.Valid {
|
||||||
filePath = kepubFormat.FilePath.String
|
filePath = kepubFormat.FilePath.String
|
||||||
if kepubFormat.FileSha256.Valid {
|
if kepubFormat.FileSha256.Valid {
|
||||||
@@ -396,12 +404,21 @@ func (h *OPDSHandler) DownloadBook(c echo.Context) error {
|
|||||||
mimeType = kepubFormat.MimeType.String
|
mimeType = kepubFormat.MimeType.String
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fall back to EPUB (no on-the-fly conversion for now)
|
if h.conversionService != nil {
|
||||||
|
converted, err := h.conversionService.ConvertEPUBToKEPUB(c.Request().Context(), pgtype.UUID{Bytes: bookUUID, Valid: true}, mediaItem.FilePath)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": fmt.Sprintf("KEPUB conversion failed: %v", err)})
|
||||||
|
}
|
||||||
|
filePath = converted.Path
|
||||||
|
fileSha256 = converted.SHA256
|
||||||
|
mimeType = "application/vnd.kobo+xml+zip"
|
||||||
|
} else {
|
||||||
filePath = mediaItem.FilePath
|
filePath = mediaItem.FilePath
|
||||||
if mediaItem.MimeType.Valid {
|
if mediaItem.MimeType.Valid {
|
||||||
mimeType = mediaItem.MimeType.String
|
mimeType = mediaItem.MimeType.String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if format == "pdf" {
|
} else if format == "pdf" {
|
||||||
// Check for PDF format
|
// Check for PDF format
|
||||||
pdfFormat, err := h.db.GetMediaItemFormatByType(c.Request().Context(), database.GetMediaItemFormatByTypeParams{
|
pdfFormat, err := h.db.GetMediaItemFormatByType(c.Request().Context(), database.GetMediaItemFormatByTypeParams{
|
||||||
@@ -451,11 +468,10 @@ func (h *OPDSHandler) DownloadBook(c echo.Context) error {
|
|||||||
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||||
c.Response().Header().Set("X-Bookmann-UUID", bookUUID.String())
|
c.Response().Header().Set("X-Bookmann-UUID", bookUUID.String())
|
||||||
|
|
||||||
if fileSha256 != "" {
|
|
||||||
c.Response().Header().Set("X-Bookmann-SHA256", fileSha256)
|
|
||||||
}
|
|
||||||
if format == "kepub" && fileSha256 != "" {
|
if format == "kepub" && fileSha256 != "" {
|
||||||
c.Response().Header().Set("X-Bookmann-KEPUB-SHA256", fileSha256)
|
c.Response().Header().Set("X-Bookmann-KEPUB-SHA256", fileSha256)
|
||||||
|
} else if fileSha256 != "" {
|
||||||
|
c.Response().Header().Set("X-Bookmann-SHA256", fileSha256)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream file
|
// Stream file
|
||||||
|
|||||||
Reference in New Issue
Block a user