Files
netbird/client/android/filedrop_sink.go
T
Zoltán Papp 861e0d5609 [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.
2026-08-25 18:45:38 +02:00

84 lines
2.5 KiB
Go

//go:build android
package android
import (
"fmt"
"github.com/netbirdio/netbird/client/internal/filedrop"
)
// FileDropSink stages incoming payloads on the platform's behalf. Android
// cannot address the user's shared storage by path, so the writing itself
// happens in Java and the engine only drives it.
//
// The methods mirror filedrop.PlatformSink; see that interface for the contract.
type FileDropSink interface {
// DestinationLabel names where payloads land, for the UI to show in place of
// a path the platform does not have.
DestinationLabel() string
Prepare(offerID string) error
Received(offerID string, index int) (int64, error)
OpenWriter(offerID string, index int, name string, offset int64, size int64) (FileDropWriter, error)
Deliver(offerID string) (string, error)
Remove(offerID string)
Cleanup(maxAgeSeconds int64)
}
// FileDropWriter is one payload's destination, opened by a FileDropSink.
//
// WriteChunk takes the bytes rather than filling a caller-supplied buffer, and
// the total is read back through Written: gomobile copies a []byte argument
// into a fresh Java array and never carries a written count back out.
type FileDropWriter interface {
WriteChunk(p []byte) error
Close() error
Written() int64
}
// platformSinkAdapter bridges the gomobile-bound FileDropSink onto the
// interface the engine consumes. The two differ only in the writer type, which
// gomobile cannot share between packages.
type platformSinkAdapter struct {
sink FileDropSink
}
func newPlatformSinkAdapter(sink FileDropSink) filedrop.PlatformSink {
return &platformSinkAdapter{sink: sink}
}
func (a *platformSinkAdapter) DestinationLabel() string {
return a.sink.DestinationLabel()
}
func (a *platformSinkAdapter) Prepare(offerID string) error {
return a.sink.Prepare(offerID)
}
func (a *platformSinkAdapter) Received(offerID string, index int) (int64, error) {
return a.sink.Received(offerID, index)
}
func (a *platformSinkAdapter) OpenWriter(offerID string, index int, name string, offset int64, size int64) (filedrop.PlatformWriter, error) {
w, err := a.sink.OpenWriter(offerID, index, name, offset, size)
if err != nil {
return nil, err
}
if w == nil {
return nil, fmt.Errorf("no destination for %s item %d", offerID, index)
}
return w, nil
}
func (a *platformSinkAdapter) Deliver(offerID string) (string, error) {
return a.sink.Deliver(offerID)
}
func (a *platformSinkAdapter) Remove(offerID string) {
a.sink.Remove(offerID)
}
func (a *platformSinkAdapter) Cleanup(maxAgeSeconds int64) {
a.sink.Cleanup(maxAgeSeconds)
}