Backend changes: - Update import paths: bookmann/internal → bookhoard/internal - Rename struct fields: BookmannUUID → BookhoardUUID - Update handler function names: mapContentIdToBookmannUUID → mapContentIdToBookhoardUUID - Update HTTP response headers: X-Bookmann-* → X-Bookhoard-* - Update service and middleware references - Update main.go imports and references This is part 2 of the project rename to Bookhoard.
186 lines
4.1 KiB
Go
186 lines
4.1 KiB
Go
package opds
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// OPDS 1.2 Feed Structures
|
|
|
|
type Feed struct {
|
|
XMLName xml.Name `xml:"feed"`
|
|
Xmlns string `xml:"xmlns,attr"`
|
|
OpdsNS string `xml:"xmlns:opds,attr"`
|
|
DcNS string `xml:"xmlns:dc,attr"`
|
|
ID string `xml:"id"`
|
|
Title string `xml:"title"`
|
|
Updated string `xml:"updated"`
|
|
Links []Link `xml:"link"`
|
|
Entries []Entry `xml:"entry"`
|
|
}
|
|
|
|
type Entry struct {
|
|
ID string `xml:"id"`
|
|
Title string `xml:"dc:title"`
|
|
Creator string `xml:"dc:creator,omitempty"`
|
|
Updated string `xml:"updated"`
|
|
Summary string `xml:"summary,omitempty"`
|
|
Links []Link `xml:"link"`
|
|
Identifier *Identifier `xml:"dc:identifier,omitempty"`
|
|
Metadata []Meta `xml:"meta"`
|
|
Categories []Category `xml:"category,omitempty"`
|
|
}
|
|
|
|
type Link struct {
|
|
Href string `xml:"href,attr"`
|
|
Type string `xml:"type,attr"`
|
|
Rel string `xml:"rel,attr,omitempty"`
|
|
}
|
|
|
|
type Identifier struct {
|
|
XMLName xml.Name `xml:"dc:identifier"`
|
|
ID string `xml:"id,attr"`
|
|
Content string `xml:",chardata"`
|
|
}
|
|
|
|
type Meta struct {
|
|
Property string `xml:"property,attr"`
|
|
Content string `xml:",chardata"`
|
|
}
|
|
|
|
type Category struct {
|
|
Scheme string `xml:"scheme,attr"`
|
|
Term string `xml:"term,attr"`
|
|
}
|
|
|
|
// NewFeed creates a new OPDS feed
|
|
func NewFeed(feedID, title string) *Feed {
|
|
now := time.Now().Format(time.RFC3339)
|
|
return &Feed{
|
|
Xmlns: "http://www.w3.org/2005/Atom",
|
|
OpdsNS: "http://opds-spec.org/2010/",
|
|
DcNS: "http://purl.org/dc/elements/1.1/",
|
|
ID: feedID,
|
|
Title: title,
|
|
Updated: now,
|
|
Links: []Link{},
|
|
Entries: []Entry{},
|
|
}
|
|
}
|
|
|
|
// AddLink adds a link to the feed
|
|
func (f *Feed) AddLink(href, linkType, rel string) {
|
|
f.Links = append(f.Links, Link{
|
|
Href: href,
|
|
Type: linkType,
|
|
Rel: rel,
|
|
})
|
|
}
|
|
|
|
// AddEntry adds an entry to the feed
|
|
func (f *Feed) AddEntry(entry Entry) {
|
|
f.Entries = append(f.Entries, entry)
|
|
}
|
|
|
|
// NewEntry creates a new OPDS entry
|
|
func NewEntry(id, title, creator, updated string) Entry {
|
|
return Entry{
|
|
ID: id,
|
|
Title: title,
|
|
Creator: creator,
|
|
Updated: updated,
|
|
Links: []Link{},
|
|
Metadata: []Meta{},
|
|
}
|
|
}
|
|
|
|
// AddAcquisitionLink adds an acquisition link to the entry
|
|
func (e *Entry) AddAcquisitionLink(href, linkType string) {
|
|
e.Links = append(e.Links, Link{
|
|
Href: href,
|
|
Type: linkType,
|
|
Rel: "http://opds-spec.org/acquisition/open-access",
|
|
})
|
|
}
|
|
|
|
// AddAlternateLink adds an alternate link to the entry
|
|
func (e *Entry) AddAlternateLink(href, linkType string) {
|
|
e.Links = append(e.Links, Link{
|
|
Href: href,
|
|
Type: linkType,
|
|
Rel: "alternate",
|
|
})
|
|
}
|
|
|
|
// AddLink adds a generic link to the entry
|
|
func (e *Entry) AddLink(href, linkType, rel string) {
|
|
e.Links = append(e.Links, Link{
|
|
Href: href,
|
|
Type: linkType,
|
|
Rel: rel,
|
|
})
|
|
}
|
|
|
|
// SetIdentifier sets the canonical identifier
|
|
func (e *Entry) SetIdentifier(id string) {
|
|
e.Identifier = &Identifier{
|
|
ID: "bookhoard",
|
|
Content: id,
|
|
}
|
|
}
|
|
|
|
// AddMetadata adds metadata to the entry
|
|
func (e *Entry) AddMetadata(property, content string) {
|
|
e.Metadata = append(e.Metadata, Meta{
|
|
Property: property,
|
|
Content: content,
|
|
})
|
|
}
|
|
|
|
// AddCategory adds a category to the entry
|
|
func (e *Entry) AddCategory(scheme, term string) {
|
|
e.Categories = append(e.Categories, Category{
|
|
Scheme: scheme,
|
|
Term: term,
|
|
})
|
|
}
|
|
|
|
// SetSummary sets the entry summary
|
|
func (e *Entry) SetSummary(summary string) {
|
|
e.Summary = summary
|
|
}
|
|
|
|
// GenerateXML generates the OPDS XML
|
|
func (f *Feed) GenerateXML() ([]byte, error) {
|
|
output, err := xml.MarshalIndent(f, "", " ")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal OPDS feed: %w", err)
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
// GenerateXMLString generates the OPDS XML as a string
|
|
func (f *Feed) GenerateXMLString() (string, error) {
|
|
output, err := f.GenerateXML()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return xml.Header + string(output), nil
|
|
}
|
|
|
|
// NewErrorFeed creates an error feed
|
|
func NewErrorFeed(message string) *Feed {
|
|
feed := NewFeed(
|
|
fmt.Sprintf("urn:uuid:error-%d", time.Now().Unix()),
|
|
"Error",
|
|
)
|
|
feed.AddEntry(Entry{
|
|
ID: "urn:uuid:error",
|
|
Title: "Error",
|
|
Summary: message,
|
|
Updated: feed.Updated,
|
|
})
|
|
return feed
|
|
}
|