mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-15 03:09:06 +02:00
[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:
@@ -113,8 +113,9 @@ type Client struct {
|
||||
|
||||
// The file drop handle survives engine restarts so the UI keeps one listener
|
||||
// registration and one history view across reconnects. See fileDropFor.
|
||||
fileDropMu sync.Mutex
|
||||
fileDrop *FileDrop
|
||||
fileDropMu sync.Mutex
|
||||
fileDrop *FileDrop
|
||||
fileDropSink FileDropSink
|
||||
}
|
||||
|
||||
func (c *Client) setState(cfg *profilemanager.Config, cacheDir string, cfgPath string, cc *internal.ConnectClient) {
|
||||
|
||||
@@ -10,6 +10,15 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
)
|
||||
|
||||
// SetFileDropSink installs the platform sink incoming payloads are staged
|
||||
// through. It takes effect on the next handle the client opens, so the platform
|
||||
// sets it before asking for one.
|
||||
func (c *Client) SetFileDropSink(sink FileDropSink) {
|
||||
c.fileDropMu.Lock()
|
||||
defer c.fileDropMu.Unlock()
|
||||
c.fileDropSink = sink
|
||||
}
|
||||
|
||||
// FileDrop returns the handle of the active profile, creating it on first use.
|
||||
// The UI calls this to list transfers and change settings while disconnected.
|
||||
func (c *Client) FileDrop(configDir string) (*FileDrop, error) {
|
||||
@@ -32,7 +41,7 @@ func (c *Client) fileDropFor(configDir, profileID string) (*FileDrop, error) {
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
fd, err := NewFileDrop(configDir, profileID)
|
||||
fd, err := NewFileDrop(configDir, profileID, c.fileDropSink)
|
||||
if err != nil {
|
||||
c.fileDropMu.Unlock()
|
||||
return nil, err
|
||||
|
||||
@@ -28,8 +28,10 @@ type FileDrop struct {
|
||||
listener FileDropListener
|
||||
}
|
||||
|
||||
// NewFileDrop opens the file drop state of the given profile.
|
||||
func NewFileDrop(configDir, profileID string) (*FileDrop, error) {
|
||||
// NewFileDrop opens the file drop state of the given profile. A nil sink leaves
|
||||
// payloads staged and delivered on the filesystem, under the destination
|
||||
// directory the policy names.
|
||||
func NewFileDrop(configDir, profileID string, sink FileDropSink) (*FileDrop, error) {
|
||||
if configDir == "" || profileID == "" {
|
||||
return nil, errors.New("file drop requires a config dir and profile ID")
|
||||
}
|
||||
@@ -39,12 +41,21 @@ func NewFileDrop(configDir, profileID string) (*FileDrop, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var platformSink filedrop.Sink
|
||||
if sink != nil {
|
||||
platformSink, err = filedrop.NewPlatformSink(newPlatformSinkAdapter(sink))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrap file drop sink: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
fd := &FileDrop{configDir: configDir, profileID: profileID}
|
||||
manager, err := filedrop.NewManager(filedrop.ManagerConfig{
|
||||
Profile: profilemanager.ID(profileID),
|
||||
DataDir: filepath.Join(configDir, filedropDataSubdir, profileID),
|
||||
Store: filedrop.NewProfileStore(prefs.prefs),
|
||||
Events: fd.publish,
|
||||
Sink: platformSink,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create file drop manager: %w", err)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
//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)
|
||||
}
|
||||
Reference in New Issue
Block a user