[client] Stage incoming file drop payloads through a platform sink

Android cannot address the user's shared storage by path, so a received
file had to land in app-private storage and be copied out afterwards,
needing twice the space of the transfer.

Put the staging area behind a Sink interface the receiver writes every
payload through. The filesystem spool implements it unchanged and stays
the default; a platform that cannot be addressed by path implements the
gomobile-bound half instead and stages payloads wherever it can reach.

The writer reports its own total rather than returning a written count:
gomobile copies a []byte argument into a fresh Java array and carries no
count back out.

A failed delivery now drops the staged payloads. The filesystem spool
swept them up on its next pass, but a sink holding entries the engine
cannot address by path has no such fallback.
This commit is contained in:
Zoltán Papp
2026-08-25 18:45:38 +02:00
parent b165e0126e
commit 861e0d5609
11 changed files with 518 additions and 26 deletions
+19 -5
View File
@@ -35,9 +35,12 @@ type Notifier interface {
OnWithdrawn(offer Offer)
}
// ServerConfig configures the receiving side.
// ServerConfig configures the receiving side. Sink overrides the filesystem
// spool, for a platform that stages payloads somewhere the engine cannot
// address; SpoolDir is then unused.
type ServerConfig struct {
SpoolDir string
Sink Sink
Policy *PolicyStore
Resolver PeerResolver
Notifier Notifier
@@ -70,9 +73,13 @@ func NewServer(cfg ServerConfig) (*Server, error) {
return nil, errors.New("receiving policy is required")
}
spool, err := NewSpool(cfg.SpoolDir)
if err != nil {
return nil, fmt.Errorf("create spool: %w", err)
spool := cfg.Sink
if spool == nil {
fsSpool, err := NewSpool(cfg.SpoolDir)
if err != nil {
return nil, fmt.Errorf("create spool: %w", err)
}
spool = fsSpool
}
maxAge := cfg.SpoolMaxAge
@@ -96,10 +103,17 @@ func (s *Server) Offers() *OfferStore {
}
// Spool returns the staging area, so the platform layer can deliver completed payloads.
func (s *Server) Spool() *Spool {
func (s *Server) Spool() Sink {
return s.recv.spool
}
// FileSpool returns the staging area as a filesystem spool, nil when the
// platform staged payloads elsewhere.
func (s *Server) FileSpool() *Spool {
spool, _ := s.recv.spool.(*Spool)
return spool
}
// Policy returns the active profile's receiving policy store.
func (s *Server) Policy() *PolicyStore {
return s.recv.policy