From 27a0195146488af2becc04bcfd1e55dfe71605fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Mon, 5 Oct 2026 15:49:23 +0200 Subject: [PATCH 1/3] Set the guest_link capability according to the config --- services/frontend/pkg/revaconfig/config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/frontend/pkg/revaconfig/config.go b/services/frontend/pkg/revaconfig/config.go index 44eb91418a..afa7018f8b 100644 --- a/services/frontend/pkg/revaconfig/config.go +++ b/services/frontend/pkg/revaconfig/config.go @@ -290,6 +290,9 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string "outgoing": cfg.EnableFederatedSharingOutgoing, "incoming": cfg.EnableFederatedSharingIncoming, }, + "guest_links": map[string]any{ + "enabled": cfg.Commons.EnableGuestLinks, + }, }, "spaces": map[string]any{ "version": "1.0.0", From ec7f3f68a456fc0da831830823ca04232c5cfc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Mon, 5 Oct 2026 16:06:26 +0200 Subject: [PATCH 2/3] Add email notifications when a guest link has been created --- services/notifications/pkg/command/server.go | 1 + services/notifications/pkg/email/templates.go | 18 +++ .../notifications/pkg/service/guestlinks.go | 84 +++++++++++++ services/notifications/pkg/service/service.go | 6 + .../notifications/pkg/service/service_test.go | 115 ++++++++++++++++-- 5 files changed, 211 insertions(+), 13 deletions(-) create mode 100644 services/notifications/pkg/service/guestlinks.go diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go index f1fbfa4d78..b2d4df6a64 100644 --- a/services/notifications/pkg/command/server.go +++ b/services/notifications/pkg/command/server.go @@ -91,6 +91,7 @@ func Server(cfg *config.Config) *cobra.Command { events.ScienceMeshInviteTokenGenerated{}, events.SendEmailsEvent{}, ocEvents.ResourceMention{}, + ocEvents.GuestTokenCreated{}, } registeredEvents := make(map[string]events.Unmarshaller) for _, e := range evs { diff --git a/services/notifications/pkg/email/templates.go b/services/notifications/pkg/email/templates.go index 5ab5d813c1..e4432171b1 100644 --- a/services/notifications/pkg/email/templates.go +++ b/services/notifications/pkg/email/templates.go @@ -49,6 +49,24 @@ Even though this share has been revoked you still might have access through othe Even though this share has been revoked you still might have access through other shares and/or space memberships.`), } + // Guest link templates + GuestLinkShareCreated = MessageTemplate{ + textTemplate: _textTemplate, + htmlTemplate: _htmlTemplate, + // GuestLinkShareCreated email template, Subject field (resolves directly) + Subject: l10n.Template(`{ShareSharer} shared '{ShareFolder}' with you`), + // GuestLinkShareCreated email template, resolves via {{ .Greeting }} + Greeting: l10n.Template(`Hello,`), + // GuestLinkShareCreated email template, resolves via {{ .MessageBody }} + MessageBody: l10n.Template(`{ShareSharer} has shared "{ShareFolder}" with you. + +The link below is personal and can only be used once. Please do not forward it. + +The link is only valid for 30 minutes.`), + // GuestLinkShareCreated email template, resolves via {{ .CallToAction }} + CallToAction: l10n.Template(`Click here to view it: {ShareLink}`), + } + // Spaces templates SharedSpace = MessageTemplate{ textTemplate: _textTemplate, diff --git a/services/notifications/pkg/service/guestlinks.go b/services/notifications/pkg/service/guestlinks.go new file mode 100644 index 0000000000..7f719fc17f --- /dev/null +++ b/services/notifications/pkg/service/guestlinks.go @@ -0,0 +1,84 @@ +// Copyright 2026 OpenCloud GmbH +// SPDX-License-Identifier: Apache-2.0 + +package service + +import ( + "context" + + "github.com/opencloud-eu/reva/v2/pkg/utils" + "google.golang.org/protobuf/types/known/fieldmaskpb" + + ocevents "github.com/opencloud-eu/opencloud/pkg/events" + "github.com/opencloud-eu/opencloud/services/notifications/pkg/channels" + "github.com/opencloud-eu/opencloud/services/notifications/pkg/email" +) + +// handleGuestTokenCreated sends the guest link to the guest's email address. +// Guests have no account, settings or locale, so the mail is always sent +// instantly (the token is short-lived) in the default language. +func (s eventsNotifier) handleGuestTokenCreated(e ocevents.GuestTokenCreated) { + logger := s.logger.With(). + Str("event", "GuestTokenCreated"). + Str("itemid", e.ItemID.GetOpaqueId()). + Logger() + + if err := validate.Var(e.GranteeEmail, "required,email"); err != nil { + logger.Error().Err(err).Msg("invalid guest email address") + return + } + if e.Token == "" { + logger.Error().Msg("guest token is empty") + return + } + + gatewayClient, err := s.gatewaySelector.Next() + if err != nil { + logger.Error().Err(err).Msg("could not select next gateway client") + return + } + + ctx, err := utils.GetServiceUserContextWithContext(context.Background(), gatewayClient, s.serviceAccountID, s.serviceAccountSecret) + if err != nil { + logger.Error().Err(err).Msg("could not get service user context") + return + } + + owner, err := utils.GetUserNoGroups(ctx, e.Sharer, gatewayClient) + if err != nil { + logger.Error().Err(err).Msg("could not get user") + return + } + + shareFolder := e.ResourceName + if shareFolder == "" { + resourceInfo, err := s.getResourceInfo(ctx, e.ItemID, &fieldmaskpb.FieldMask{Paths: []string{"name"}}) + if err != nil { + logger.Error().Err(err).Msg("could not stat resource") + return + } + shareFolder = resourceInfo.GetName() + } + + shareLink, err := urlJoinPath(s.openCloudURL, "g", e.Token) + if err != nil { + logger.Error().Err(err).Msg("could not create guest link") + return + } + + msg, err := email.RenderEmailTemplate(email.GuestLinkShareCreated, s.defaultLanguage, s.defaultLanguage, + s.emailTemplatePath, s.translationPath, + map[string]string{ + "ShareSharer": owner.GetDisplayName(), + "ShareFolder": shareFolder, + "ShareLink": shareLink, + }) + if err != nil { + logger.Error().Err(err).Msg("could not render the email") + return + } + msg.Sender = owner.GetDisplayName() + msg.Recipient = []string{e.GranteeEmail} + + s.send(ctx, []*channels.Message{msg}) +} diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index 74b2e491e8..f4c916d587 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -131,6 +131,8 @@ EventLoop: s.handleShareExpired(e, evt.ID) case events.ShareRemoved: s.handleShareRemoved(e, evt.ID) + case ocEvents.GuestTokenCreated: + s.handleGuestTokenCreated(e) case events.ScienceMeshInviteTokenGenerated: s.handleScienceMeshInviteTokenGenerated(e) case events.SendEmailsEvent: @@ -202,6 +204,10 @@ func (s eventsNotifier) ensureGranteeList(ctx context.Context, executant, u *use func (s eventsNotifier) getGranteeList(ctx context.Context, executant, u *user.UserId, g *group.GroupId) ([]*user.User, error) { switch { case u != nil: + // guests aren't users, they are notified via GuestTokenCreated + if u.GetType() == user.UserType_USER_TYPE_GUEST { + return nil, nil + } if s.disableEmails(ctx, u) { return nil, nil } diff --git a/services/notifications/pkg/service/service_test.go b/services/notifications/pkg/service/service_test.go index 0605b9ba76..54f0221284 100644 --- a/services/notifications/pkg/service/service_test.go +++ b/services/notifications/pkg/service/service_test.go @@ -13,6 +13,7 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + ocEvents "github.com/opencloud-eu/opencloud/pkg/events" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/shared" settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0" @@ -98,7 +99,7 @@ var _ = Describe("Notifications", func() { cfg.GRPCClientTLS = &shared.GRPCClientTLS{} ch := make(chan events.Event) evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", - "", "", "", "", "", "", + "", "", "", testOpenCloudURL, "", "", store.Create(), nil, nil) go evts.Run() @@ -118,7 +119,7 @@ var _ = Describe("Notifications", func() { Dr. S. Harer has shared "secrets of the board" with you. -Click here to view it: files/shares/with-me +Click here to view it: https://cloud.example.com/files/shares/with-me --- @@ -126,6 +127,7 @@ OpenCloud - a safe home for all your data https://opencloud.eu `, expectedSender: sharer.GetDisplayName(), + expectedLink: testOpenCloudURL + "/files/shares/with-me", done: make(chan struct{}), }, events.Event{ Event: events.ShareCreated{ @@ -192,7 +194,7 @@ https://opencloud.eu Dr. S. Harer has invited you to join "secret space". -Click here to view it: f/spaceid +Click here to view it: https://cloud.example.com/f/spaceid --- @@ -200,6 +202,7 @@ OpenCloud - a safe home for all your data https://opencloud.eu `, expectedSender: sharer.GetDisplayName(), + expectedLink: testOpenCloudURL + "/f/spaceid", done: make(chan struct{}), }, events.Event{ Event: events.SpaceShared{ @@ -219,7 +222,7 @@ Dr. S. Harer has removed you from "secret space". You might still have access through your other groups or direct membership. -Click here to check it: f/spaceid +Click here to check it: https://cloud.example.com/f/spaceid --- @@ -227,6 +230,7 @@ OpenCloud - a safe home for all your data https://opencloud.eu `, expectedSender: sharer.GetDisplayName(), + expectedLink: testOpenCloudURL + "/f/spaceid", done: make(chan struct{}), }, events.Event{ Event: events.SpaceUnshared{ @@ -261,6 +265,79 @@ https://opencloud.eu ExpiredAt: time.Date(2023, 4, 17, 16, 42, 0, 0, time.UTC), }, }), + + Entry("Guest Token Created", testChannel{ + expectedReceipients: []string{"guest@example.com"}, + expectedSubject: "Dr. S. Harer shared 'guest folder' with you", + expectedTextBody: `Hello, + +Dr. S. Harer has shared "guest folder" with you. + +The link below is personal and can only be used once. Please do not forward it. + +The link is only valid for 30 minutes. + +Click here to view it: https://cloud.example.com/g/v1.sharehash.secret + + +--- +OpenCloud - a safe home for all your data +https://opencloud.eu +`, + expectedHTMLBody: ` + + + + + + +
+ + + + + + + + + + + + + + + +
  + Hello, +

+ Dr. S. Harer has shared "guest folder" with you.

The link below is personal and can only be used once. Please do not forward it.

The link is only valid for 30 minutes. +

+ Click here to view it: https://cloud.example.com/g/v1.sharehash.secret +
 
  + +
 
+
+ + +`, + expectedSender: sharer.GetDisplayName(), + expectedLink: testOpenCloudURL + "/g/v1.sharehash.secret", + done: make(chan struct{}), + }, events.Event{ + Event: ocEvents.GuestTokenCreated{ + Sharer: sharer.GetId(), + GranteeEmail: "guest@example.com", + ItemID: resourceid, + ResourceName: "guest folder", + Token: "v1.sharehash.secret", + }, + }), ) }) @@ -339,7 +416,7 @@ var _ = Describe("Notifications X-Site Scripting", func() { cfg.GRPCClientTLS = &shared.GRPCClientTLS{} ch := make(chan events.Event) evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", - "", "", "", "", "", "", + "", "", "", testOpenCloudURL, "", "", store.Create(), nil, nil) go evts.Run() @@ -359,7 +436,7 @@ var _ = Describe("Notifications X-Site Scripting", func() { Dr. O'reilly has shared "" with you. -Click here to view it: files/shares/with-me +Click here to view it: https://cloud.example.com/files/shares/with-me --- @@ -380,7 +457,7 @@ https://opencloud.eu

Dr. O'reilly has shared "<script>alert('secrets of the board');</script>" with you.

- Click here to view it: files/shares/with-me + Click here to view it: https://cloud.example.com/files/shares/with-me @@ -410,7 +487,8 @@ https://opencloud.eu `, expectedSender: sharer.GetDisplayName(), - done: make(chan struct{}), + expectedLink: testOpenCloudURL + "/files/shares/with-me", + done: make(chan struct{}), }, events.Event{ Event: events.ShareCreated{ Sharer: sharer.GetId(), @@ -427,7 +505,7 @@ https://opencloud.eu Dr. O'reilly has invited you to join "". -Click here to view it: f/spaceid +Click here to view it: https://cloud.example.com/f/spaceid --- @@ -449,7 +527,7 @@ https://opencloud.eu

Dr. O'reilly has invited you to join "<script>alert('secret space');</script>".

- Click here to view it: f/spaceid + Click here to view it: https://cloud.example.com/f/spaceid @@ -477,7 +555,8 @@ https://opencloud.eu `, - done: make(chan struct{}), + expectedLink: testOpenCloudURL + "/f/spaceid", + done: make(chan struct{}), }, events.Event{ Event: events.SpaceShared{ Executant: sharer.GetId(), @@ -489,14 +568,18 @@ https://opencloud.eu ) }) -// NOTE: This is explictitly not testing the message itself. Should we? +const testOpenCloudURL = "https://cloud.example.com" + type testChannel struct { expectedReceipients []string expectedSubject string expectedTextBody string expectedHTMLBody string expectedSender string - done chan struct{} + // expectedLink, if set, must be an absolute URL on the OpenCloud instance + // and must be linked in the HTML body. + expectedLink string + done chan struct{} } func (tc testChannel) SendMessage(ctx context.Context, m *channels.Message) error { @@ -506,9 +589,15 @@ func (tc testChannel) SendMessage(ctx context.Context, m *channels.Message) erro Expect(tc.expectedSubject).To(Equal(m.Subject)) Expect(tc.expectedTextBody).To(Equal(m.TextBody)) Expect(tc.expectedSender).To(Equal(m.Sender)) + Expect(m.HTMLBody).ToNot(BeEmpty()) if tc.expectedHTMLBody != "" { Expect(tc.expectedHTMLBody).To(Equal(m.HTMLBody)) } + if tc.expectedLink != "" { + Expect(tc.expectedLink).To(HavePrefix(testOpenCloudURL + "/")) + Expect(m.TextBody).To(ContainSubstring(tc.expectedLink)) + Expect(m.HTMLBody).To(ContainSubstring(``)) + } tc.done <- struct{}{} return nil } From 3858d7ec50c12ce524a9fdead11f922f54d43f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Duffeck?= Date: Tue, 6 Oct 2026 13:25:09 +0200 Subject: [PATCH 3/3] Bump reva to pull in the new capability --- go.mod | 4 ++-- go.sum | 4 ++-- .../cloud/capabilities/capabilities.go | 6 ++++++ .../reva/v2/pkg/owncloud/ocs/capabilities.go | 6 ++++++ .../fs/decomposed/blobstore/blobstore.go | 21 +++++++++++++++---- .../storage/fs/ocis/blobstore/blobstore.go | 21 +++++++++++++++---- .../storage/fs/posix/blobstore/blobstore.go | 4 ++-- vendor/modules.txt | 2 +- 8 files changed, 53 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index edf987e782..52c8fc7749 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/go-playground/validator/v10 v10.30.3 github.com/go-resty/resty/v2 v2.17.2 github.com/go-viper/mapstructure/v2 v2.5.0 + github.com/gofrs/flock v0.13.1 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang/protobuf v1.5.4 github.com/google/go-cmp v0.7.0 @@ -65,7 +66,7 @@ require ( github.com/open-policy-agent/opa v1.19.1 github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 github.com/opencloud-eu/libre-graph-api-go v1.0.8 - github.com/opencloud-eu/reva/v2 v2.51.0 + github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424 github.com/opensearch-project/opensearch-go/v4 v4.7.3 github.com/orcaman/concurrent-map v1.0.0 github.com/pkg/errors v0.9.1 @@ -229,7 +230,6 @@ require ( github.com/gobwas/ws v1.4.0 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/gofrs/flock v0.13.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v1.0.0 // indirect diff --git a/go.sum b/go.sum index c30cad60d3..086bba6d3e 100644 --- a/go.sum +++ b/go.sum @@ -761,8 +761,8 @@ github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+l github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI= github.com/opencloud-eu/libre-graph-api-go v1.0.8 h1:VTkFLkLNna+T4RN6JUZnVQ1bqVAfPtMeORXbFzEYGKs= github.com/opencloud-eu/libre-graph-api-go v1.0.8/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU= -github.com/opencloud-eu/reva/v2 v2.51.0 h1:PbvUtLlbCpS5em48cSGk2sHRdjVA7htRQVKjpckI2w4= -github.com/opencloud-eu/reva/v2 v2.51.0/go.mod h1:ngLsDakKnt8zCFCeULhuSZFESGy5NIuSq5/JGeOhPdU= +github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424 h1:rvopdGQTsI0PT7P3MRamrDsO1yYIKJBOxJAbbYmc4P8= +github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424/go.mod h1:ngLsDakKnt8zCFCeULhuSZFESGy5NIuSq5/JGeOhPdU= github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI= github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= diff --git a/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go b/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go index cdbb161a61..350fed869c 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go +++ b/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocs/handlers/cloud/capabilities/capabilities.go @@ -198,6 +198,12 @@ func (h *Handler) Init(c *config.Config) { // h.c.Capabilities.FilesSharing.Federation.Outgoing is boolean // h.c.Capabilities.FilesSharing.Federation.Incoming is boolean + if h.c.Capabilities.FilesSharing.GuestLinks == nil { + h.c.Capabilities.FilesSharing.GuestLinks = &ocs.CapabilitiesFilesSharingGuestLinks{} + } + + // h.c.Capabilities.FilesSharing.GuestLinks.Enabled is boolean + if h.c.Capabilities.FilesSharing.SearchMinLength == 0 { h.c.Capabilities.FilesSharing.SearchMinLength = 2 } diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/owncloud/ocs/capabilities.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/owncloud/ocs/capabilities.go index 603ec41536..4a643b8fb5 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/owncloud/ocs/capabilities.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/owncloud/ocs/capabilities.go @@ -225,6 +225,7 @@ type CapabilitiesFilesSharing struct { DefaultPermissions int `json:"default_permissions" xml:"default_permissions" mapstructure:"default_permissions"` UserEnumeration *CapabilitiesFilesSharingUserEnumeration `json:"user_enumeration" xml:"user_enumeration" mapstructure:"user_enumeration"` Federation *CapabilitiesFilesSharingFederation `json:"federation" xml:"federation"` + GuestLinks *CapabilitiesFilesSharingGuestLinks `json:"guest_links" xml:"guest_links" mapstructure:"guest_links"` Public *CapabilitiesFilesSharingPublic `json:"public" xml:"public"` User *CapabilitiesFilesSharingUser `json:"user" xml:"user"` // TODO: Remove next line once web defaults to resharing=false @@ -291,6 +292,11 @@ type CapabilitiesFilesSharingFederation struct { Incoming ocsBool `json:"incoming" xml:"incoming"` } +// CapabilitiesFilesSharingGuestLinks holds the guest links capabilities +type CapabilitiesFilesSharingGuestLinks struct { + Enabled ocsBool `json:"enabled" xml:"enabled" mapstructure:"enabled"` +} + // CapabilitiesNotifications holds a list of notification endpoints type CapabilitiesNotifications struct { Endpoints []string `json:"ocs-endpoints,omitempty" xml:"ocs-endpoints>element,omitempty" mapstructure:"endpoints"` diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go index 7c6edc0a8e..a4dbfde458 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore/blobstore.go @@ -53,7 +53,7 @@ func New(root string) (*Blobstore, error) { } // Upload stores some data in the blobstore under the given key -func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) error { +func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) (err error) { if node.BlobID == "" { return ErrBlobIDEmpty } @@ -80,14 +80,27 @@ func (bs *Blobstore) Upload(node *node.Node, source, _copyTarget string) error { if err != nil { return errors.Wrapf(err, "could not open blob '%s' for writing", dest) } + defer func() { + if cerr := f.Close(); cerr != nil && err == nil { + err = errors.Wrapf(cerr, "could not close blob '%s'", dest) + } + }() w := bufio.NewWriter(f) - _, err = w.ReadFrom(file) - if err != nil { + if _, err = w.ReadFrom(file); err != nil { return errors.Wrapf(err, "could not write blob '%s'", dest) } - return w.Flush() + if err = w.Flush(); err != nil { + return errors.Wrapf(err, "could not flush blob '%s'", dest) + } + + // Ensure data is synced to disk before returning + if err = f.Sync(); err != nil { + return errors.Wrapf(err, "could not sync blob '%s' to disk", dest) + } + + return nil } // Download retrieves a blob from the blobstore for reading diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/ocis/blobstore/blobstore.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/ocis/blobstore/blobstore.go index 41ed3a091c..07b820c220 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/ocis/blobstore/blobstore.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/ocis/blobstore/blobstore.go @@ -53,7 +53,7 @@ func New(root string) (*Blobstore, error) { } // Upload stores some data in the blobstore under the given key -func (bs *Blobstore) Upload(node *node.Node, source string) error { +func (bs *Blobstore) Upload(node *node.Node, source string) (err error) { if node.BlobID == "" { return ErrBlobIDEmpty } @@ -80,14 +80,27 @@ func (bs *Blobstore) Upload(node *node.Node, source string) error { if err != nil { return errors.Wrapf(err, "could not open blob '%s' for writing", dest) } + defer func() { + if cerr := f.Close(); cerr != nil && err == nil { + err = errors.Wrapf(cerr, "could not close blob '%s'", dest) + } + }() w := bufio.NewWriter(f) - _, err = w.ReadFrom(file) - if err != nil { + if _, err = w.ReadFrom(file); err != nil { return errors.Wrapf(err, "could not write blob '%s'", dest) } - return w.Flush() + if err = w.Flush(); err != nil { + return errors.Wrapf(err, "could not flush blob '%s'", dest) + } + + // Ensure data is synced to disk before returning + if err = f.Sync(); err != nil { + return errors.Wrapf(err, "could not sync blob '%s' to disk", dest) + } + + return nil } // Download retrieves a blob from the blobstore for reading diff --git a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go index bbf3397972..75f598c784 100644 --- a/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go +++ b/vendor/github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/blobstore/blobstore.go @@ -95,6 +95,7 @@ func (bs *Blobstore) Upload(n *node.Node, source, copyTarget string) error { } if err := copyWithPeriodicSync(tempFile, sourceFile); err != nil { + _ = tempFile.Close() return fmt.Errorf("failed to copy source file '%s' to temp file '%s' - %v", source, tempName, err) } @@ -204,8 +205,7 @@ func copyWithPeriodicSync(dst, src *os.File) error { } } if err == io.EOF { - _ = dst.Sync() - return nil + return dst.Sync() } if err != nil { return err diff --git a/vendor/modules.txt b/vendor/modules.txt index 909c2e43fd..94725c0862 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1362,7 +1362,7 @@ github.com/opencloud-eu/icap-client # github.com/opencloud-eu/libre-graph-api-go v1.0.8 ## explicit; go 1.23 github.com/opencloud-eu/libre-graph-api-go -# github.com/opencloud-eu/reva/v2 v2.51.0 +# github.com/opencloud-eu/reva/v2 v2.51.1-0.20261006112042-7d9dd4c07424 ## explicit; go 1.26.0 github.com/opencloud-eu/reva/v2/cmd/revad/internal/grace github.com/opencloud-eu/reva/v2/cmd/revad/runtime