Skip to content

Commit b5648f2

Browse files
authored
Services improvements (#270)
1 parent f3816c5 commit b5648f2

File tree

9 files changed

+102
-30
lines changed

9 files changed

+102
-30
lines changed

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
44

55
**Warning:** Features marked as *alpha* may change or be removed in a future release without notice. Use with caution.
66

7+
## [0.12.0] - 2025-10-10
8+
9+
All services are now hidden by default. This mainly affects implementers creating webservers, but you also shouldn't need to manually remove services just to produce clean manifest output. To restore previous functionality, set the `streamer.Config`'s `AddServiceLinks` to `true`
10+
11+
### Added
12+
13+
- ServicesBuilder now has a convenience function `Services` to get the names of all services currently in the builder
14+
- ServicesBuilder now has a `ExposeLinks` and `HideLinks` function to toggle the exposure of a service via the links that get added to the WebPub manifest, as well as access to the service via its well-known link path. By default, services are **private**
15+
- `streamer.Config` has a new property, `AddServiceLinks`. Setting this property is equivalent to calling the aforementioned `ExposeLinks` function for every service
16+
17+
### Changed
18+
19+
- `ServiceFactory` now has a required `public` property. This lets a service expose itself via the `Get` and `Links` function if set to true. This also means that by default, all services are now "private". That means they will not be added to manifests as links, or callable by said link's path. They are still directly accessible in Go code using e.g. `Publication.FindService`, and then directly calling their functions by casting them to the correct service type (see e.g. `Publication.Positions`)
20+
721
## [0.11.0] - 2025-07-30
822

923
The WebPub data the toolkit parses and provides has been updated to more closely match the latest WebPub spec. Pay close attention to these changes if you depend on the WebPub output in reading systems/libraries that use an older version of the spec!
@@ -77,7 +91,7 @@ The WebPub data the toolkit parses and provides has been updated to more closely
7791

7892
### Changed
7993

80-
- In order to support remote streaming, a lot of APIs have been altered to accept a `context.Context` as the first parameter, to provide implementors with the ability to e.g. cancel a request to fetch a resource.
94+
- In order to support remote streaming, a lot of APIs have been altered to accept a `context.Context` as the first parameter, to provide implementers with the ability to e.g. cancel a request to fetch a resource.
8195
- `ReadAsString`, `ReadAsJSON`, and `ReadAsXML` functions have been removed from `Resource` and are instead available as helper functions.
8296

8397
## [0.8.1] - 2025-02-24

pkg/parser/epub/media_overlay_service.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func MediaOverlayFactory() pub.ServiceFactory {
14-
return func(context pub.Context) pub.Service {
14+
return func(context pub.Context, public bool) pub.Service {
1515
// Process reading order to find and replace SMIL alternates
1616
smilMap := make(map[string]manifest.Link)
1717
var smilIndexes []string
@@ -49,11 +49,13 @@ func MediaOverlayFactory() pub.ServiceFactory {
4949
fetcher: context.Fetcher,
5050
originalSmilAlternates: smilMap,
5151
originalSmilIndexes: smilIndexes,
52+
public: public,
5253
}
5354
}
5455
}
5556

5657
type MediaOverlayService struct {
58+
public bool
5759
fetcher fetcher.Fetcher
5860
originalSmilAlternates map[string]manifest.Link
5961
originalSmilIndexes []string
@@ -66,6 +68,9 @@ func (s *MediaOverlayService) Close() {
6668
}
6769

6870
func (s *MediaOverlayService) Links() manifest.LinkList {
71+
if !s.public {
72+
return nil
73+
}
6974
return manifest.LinkList{pub.GuidedNavigationLink}
7075
}
7176

@@ -120,5 +125,8 @@ func (s *MediaOverlayService) GuideForResource(ctx context.Context, href string)
120125
}
121126

122127
func (s *MediaOverlayService) Get(ctx context.Context, link manifest.Link) (fetcher.Resource, bool) {
128+
if !s.public {
129+
return nil, false
130+
}
123131
return pub.GetForGuidedNavigationService(ctx, s, link)
124132
}

pkg/parser/epub/positions_service.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,27 @@ import (
1919
// https://github.com/readium/architecture/blob/master/models/locators/best-practices/format.md#epub
2020
// https://github.com/readium/architecture/issues/101
2121
type PositionsService struct {
22-
readingOrder manifest.LinkList
23-
layout manifest.Layout
24-
fetcher fetcher.Fetcher
25-
reflowableStrategy ReflowableStrategy
26-
positions [][]manifest.Locator
22+
public bool // Whether the service exposes itself via Links() and Get()
23+
readingOrder manifest.LinkList // The reading order of the publication
24+
layout manifest.Layout // The publication's layout
25+
fetcher fetcher.Fetcher // The publication's fetcher
26+
reflowableStrategy ReflowableStrategy // How to compute positions in reflowable resources
27+
positions [][]manifest.Locator // Cached calculated positions
2728
}
2829

2930
func (s *PositionsService) Close() {}
3031

3132
func (s *PositionsService) Links() manifest.LinkList {
33+
if !s.public {
34+
return nil
35+
}
3236
return manifest.LinkList{pub.PositionsLink}
3337
}
3438

3539
func (s *PositionsService) Get(ctx context.Context, link manifest.Link) (fetcher.Resource, bool) {
40+
if !s.public {
41+
return nil, false
42+
}
3643
return pub.GetForPositionsService(ctx, s, link)
3744
}
3845

@@ -132,8 +139,9 @@ func PositionsServiceFactory(reflowableStrategy ReflowableStrategy) pub.ServiceF
132139
reflowableStrategy = RecommendedReflowableStrategy
133140
}
134141

135-
return func(context pub.Context) pub.Service {
142+
return func(context pub.Context, public bool) pub.Service {
136143
return &PositionsService{
144+
public: public,
137145
readingOrder: context.Manifest.ReadingOrder,
138146
layout: context.Manifest.Metadata.Layout,
139147
fetcher: context.Fetcher,

pkg/parser/pdf/positions_service.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
// Positions Service for an PDF.
1515
type PositionsService struct {
16+
public bool // Whether the service exposes itself via Links() and Get()
1617
link manifest.Link // The [Link] to the PDF document in the [Publication].
1718
pageCount uint // Total page count in the PDF document.
1819
tableOfContents manifest.LinkList // Table of contents used to compute the position titles.
@@ -22,10 +23,16 @@ type PositionsService struct {
2223
func (s *PositionsService) Close() {}
2324

2425
func (s *PositionsService) Links() manifest.LinkList {
26+
if !s.public {
27+
return nil
28+
}
2529
return manifest.LinkList{pub.PositionsLink}
2630
}
2731

2832
func (s *PositionsService) Get(ctx context.Context, link manifest.Link) (fetcher.Resource, bool) {
33+
if !s.public {
34+
return nil, false
35+
}
2936
return pub.GetForPositionsService(ctx, s, link)
3037
}
3138

@@ -86,7 +93,7 @@ func (s *PositionsService) computePositions() [][]manifest.Locator {
8693
}
8794

8895
func PositionsServiceFactory() pub.ServiceFactory {
89-
return func(context pub.Context) pub.Service {
96+
return func(context pub.Context, public bool) pub.Service {
9097
if len(context.Manifest.ReadingOrder) == 0 {
9198
return nil
9299
}
@@ -97,6 +104,7 @@ func PositionsServiceFactory() pub.ServiceFactory {
97104
}
98105

99106
return &PositionsService{
107+
public: public,
100108
link: context.Manifest.ReadingOrder[0],
101109
pageCount: count,
102110
tableOfContents: context.Manifest.TableOfContents,

pkg/pub/publication.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,27 +104,15 @@ func (p Publication) BaseURL() url.URL {
104104
return nil
105105
}
106106

107+
// Find a service in the publication by its name.
107108
func (p Publication) FindService(serviceName ServiceName) Service {
108-
for k, v := range p.services {
109-
if k != serviceName {
110-
continue
111-
}
112-
return v
109+
svc, ok := p.services[serviceName]
110+
if ok {
111+
return svc
113112
}
114113
return nil
115114
}
116115

117-
func (p Publication) FindServices(serviceName ServiceName) []Service {
118-
var services []Service
119-
for k, v := range p.services {
120-
if k != serviceName {
121-
continue
122-
}
123-
services = append(services, v)
124-
}
125-
return services
126-
}
127-
128116
// Returns the resource targeted by the given non-templated [link].
129117
func (p Publication) Get(ctx context.Context, link manifest.Link) fetcher.Resource {
130118
for _, service := range p.services {

pkg/pub/service.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ func NewContext(manifest manifest.Manifest, fetcher fetcher.Fetcher) Context {
3939
}
4040
}
4141

42-
type ServiceFactory func(context Context) Service
42+
type ServiceFactory func(context Context, public bool) Service
4343

4444
// Builds a list of [Service] from a collection of service factories.
4545
// Provides helpers to manipulate the list of services of a [pub.Publication].
4646
type ServicesBuilder struct {
4747
serviceFactories map[ServiceName]ServiceFactory
48+
publicFlags map[ServiceName]bool
4849
}
4950

5051
/*
@@ -62,6 +63,7 @@ func NewServicesBuilder(fcs map[ServiceName]ServiceFactory) *ServicesBuilder {
6263

6364
return &ServicesBuilder{
6465
serviceFactories: fcs,
66+
publicFlags: map[ServiceName]bool{},
6567
}
6668
}
6769

@@ -71,15 +73,26 @@ func (s *ServicesBuilder) Build(context Context) map[ServiceName]Service {
7173
for k, v := range s.serviceFactories {
7274
// Allow service factories to be nil
7375
if v != nil {
76+
public := s.publicFlags[k]
77+
7478
// Allow service factories to return nil
75-
if service := v(context); service != nil {
79+
if service := v(context, public); service != nil {
7680
services[k] = service
7781
}
7882
}
7983
}
8084
return services
8185
}
8286

87+
// Gets the names of all services currently in the builder
88+
func (s *ServicesBuilder) Services() []ServiceName {
89+
keys := make([]ServiceName, 0, len(s.serviceFactories))
90+
for k := range s.serviceFactories {
91+
keys = append(keys, k)
92+
}
93+
return keys
94+
}
95+
8396
// Gets the publication service factory for the given service type.
8497
func (s *ServicesBuilder) Get(name ServiceName) *ServiceFactory {
8598
if v, ok := s.serviceFactories[name]; ok {
@@ -138,3 +151,11 @@ func (s *ServicesBuilder) Decorate(name ServiceName, transform func(*ServiceFact
138151
s.serviceFactories[name] = transform(nil)
139152
}
140153
}
154+
155+
func (s *ServicesBuilder) ExposeLinks(name ServiceName) {
156+
s.publicFlags[name] = true
157+
}
158+
159+
func (s *ServicesBuilder) HideLinks(name ServiceName) {
160+
delete(s.publicFlags, name)
161+
}

pkg/pub/service_content.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ type ContentService interface {
3333
type DefaultContentService struct {
3434
context Context
3535
resourceContentIteratorFactories []iterator.ResourceContentIteratorFactory
36+
public bool
3637
}
3738

3839
func GetForContentService(ctx context.Context, service ContentService, link manifest.Link) (fetcher.Resource, bool) {
39-
if link.Href != ContentLink.Href {
40+
if !link.URL(nil, nil).Equivalent(ContentLink.URL(nil, nil)) {
4041
return nil, false
4142
}
4243

@@ -55,10 +56,16 @@ func GetForContentService(ctx context.Context, service ContentService, link mani
5556
func (s DefaultContentService) Close() {}
5657

5758
func (s DefaultContentService) Links() manifest.LinkList {
59+
if !s.public {
60+
return nil
61+
}
5862
return manifest.LinkList{ContentLink}
5963
}
6064

6165
func (s DefaultContentService) Get(ctx context.Context, link manifest.Link) (fetcher.Resource, bool) {
66+
if !s.public {
67+
return nil, false
68+
}
6269
return GetForContentService(ctx, s, link)
6370
}
6471

@@ -94,10 +101,11 @@ func (c contentImplementation) Text(ctx context.Context, separator *string) (str
94101
}
95102

96103
func DefaultContentServiceFactory(resourceContentIteratorFactories []iterator.ResourceContentIteratorFactory) ServiceFactory {
97-
return func(context Context) Service {
104+
return func(context Context, public bool) Service {
98105
return DefaultContentService{
99106
context: context,
100107
resourceContentIteratorFactories: resourceContentIteratorFactories,
108+
public: public,
101109
}
102110
}
103111
}

pkg/pub/service_positions.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type PositionsService interface {
2828
type PerResourcePositionsService struct {
2929
readingOrder manifest.LinkList
3030
fallbackMediaType mediatype.MediaType
31+
public bool
3132
}
3233

3334
func GetForPositionsService(ctx context.Context, service PositionsService, link manifest.Link) (fetcher.Resource, bool) {
@@ -48,10 +49,16 @@ func GetForPositionsService(ctx context.Context, service PositionsService, link
4849
func (s PerResourcePositionsService) Close() {}
4950

5051
func (s PerResourcePositionsService) Links() manifest.LinkList {
52+
if !s.public {
53+
return nil
54+
}
5155
return manifest.LinkList{PositionsLink}
5256
}
5357

5458
func (s PerResourcePositionsService) Get(ctx context.Context, link manifest.Link) (fetcher.Resource, bool) {
59+
if !s.public {
60+
return nil, false
61+
}
5562
return GetForPositionsService(ctx, s, link)
5663
}
5764

@@ -86,10 +93,11 @@ func (s PerResourcePositionsService) PositionsByReadingOrder(ctx context.Context
8693
}
8794

8895
func PerResourcePositionsServiceFactory(fallbackMediaType mediatype.MediaType) ServiceFactory {
89-
return func(context Context) Service {
96+
return func(context Context, public bool) Service {
9097
return PerResourcePositionsService{
9198
readingOrder: context.Manifest.ReadingOrder,
9299
fallbackMediaType: fallbackMediaType,
100+
public: public,
93101
}
94102
}
95103
}

pkg/streamer/streamer.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Streamer struct {
3030
// TODO pdfFactory
3131
httpClient *http.Client
3232
onCreatePublication OnCreatePublicationFunc
33+
addServicelinks bool
3334
}
3435

3536
type OnCreatePublicationFunc func(builder *pub.Builder) error
@@ -43,6 +44,7 @@ type Config struct {
4344
ArchiveFactory archive.ArchiveFactory // Opens an archive (e.g. ZIP, RAR), optionally protected by credentials.
4445
HttpClient *http.Client // Service performing HTTP requests.
4546
OnCreatePublication OnCreatePublicationFunc // Called on every parsed [pub.Builder]. It can be used to modify the manifest, the root container or the list of service factories of a [pub.Publication]
47+
AddServiceLinks bool // When true, services will add their links to the publication manifest. This can also be adjusted for individual services in `OnCreatePublication`
4648
}
4749

4850
type InferA11yMetadata uint8
@@ -86,6 +88,7 @@ func New(config Config) Streamer { // TODO contentProtections
8688
archiveFactory: config.ArchiveFactory,
8789
httpClient: config.HttpClient,
8890
onCreatePublication: config.OnCreatePublication,
91+
addServicelinks: config.AddServiceLinks,
8992
}
9093
}
9194

@@ -117,6 +120,12 @@ func (s Streamer) Open(ctx context.Context, a asset.PublicationAsset, credential
117120
return nil, errors.New("cannot find a parser for this asset")
118121
}
119122

123+
if s.addServicelinks {
124+
for _, name := range builder.ServicesBuilder.Services() {
125+
builder.ServicesBuilder.ExposeLinks(name)
126+
}
127+
}
128+
120129
if s.onCreatePublication != nil {
121130
if err := s.onCreatePublication(builder); err != nil {
122131
return nil, errors.Wrap(err, "failed creating publication")

0 commit comments

Comments
 (0)