Skip to content

Commit 524f0ed

Browse files
committed
ipn,ipnlocal,taildrop: use SAF for Android files
Create FileOps for calling platform-specific file operations such as SAF APIs in Taildrop Update taildrop.PutFile to support both traditional and SAF modes Updates #15263 Signed-off-by: kari-ts <[email protected]>
1 parent 70b6e8c commit 524f0ed

File tree

5 files changed

+371
-84
lines changed

5 files changed

+371
-84
lines changed

feature/taildrop/ext.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ type Extension struct {
7373
// *.partial file to its final name on completion.
7474
directFileRoot string
7575

76+
// FileOps abstracts platform-specific file operations needed for file transfers.
77+
// This is currently being used for Android to use the Storage Access Framework.
78+
FileOps FileOps
79+
7680
nodeBackendForTest ipnext.NodeBackend // if non-nil, pretend we're this node state for tests
7781

7882
mu sync.Mutex // Lock order: lb.mu > e.mu
@@ -85,6 +89,30 @@ type Extension struct {
8589
outgoingFiles map[string]*ipn.OutgoingFile
8690
}
8791

92+
// safDirectoryPrefix is used to determine if the directory is managed via SAF.
93+
const SafDirectoryPrefix = "content://"
94+
95+
// PutMode controls how Manager.PutFile writes files to storage.
96+
//
97+
// PutModeDirect – write files directly to a filesystem path (default).
98+
// PutModeAndroidSAF – use Android’s Storage Access Framework (SAF), where
99+
// the OS manages the underlying directory permissions.
100+
type PutMode int
101+
102+
const (
103+
PutModeDirect PutMode = iota
104+
PutModeAndroidSAF
105+
)
106+
107+
// FileOps defines platform-specific file operations.
108+
type FileOps interface {
109+
OpenFileWriter(filename string) (io.WriteCloser, string, error)
110+
111+
// RenamePartialFile finalizes a partial file.
112+
// It returns the new SAF URI as a string and an error.
113+
RenamePartialFile(partialUri, targetDirUri, targetName string) (string, error)
114+
}
115+
88116
func (e *Extension) Name() string {
89117
return "taildrop"
90118
}
@@ -153,12 +181,18 @@ func (e *Extension) onChangeProfile(profile ipn.LoginProfileView, _ ipn.PrefsVie
153181
if fileRoot == "" {
154182
e.logf("no Taildrop directory configured")
155183
}
184+
mode := PutModeDirect
185+
if e.directFileRoot != "" && strings.HasPrefix(e.directFileRoot, SafDirectoryPrefix) {
186+
mode = PutModeAndroidSAF
187+
}
156188
e.setMgrLocked(managerOptions{
157189
Logf: e.logf,
158190
Clock: tstime.DefaultClock{Clock: e.sb.Clock()},
159191
State: e.stateStore,
160192
Dir: fileRoot,
161193
DirectFileMode: isDirectFileMode,
194+
FileOps: e.FileOps,
195+
Mode: mode,
162196
SendFileNotify: e.sendFileNotify,
163197
}.New())
164198
}

feature/taildrop/paths.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ func (e *Extension) SetDirectFileRoot(root string) {
1818
e.directFileRoot = root
1919
}
2020

21+
// SetFileOps sets the platform specific file operations. This is used
22+
// to call Android's Storage Access Framework APIs.
23+
func (e *Extension) SetFileOps(fileOps FileOps) {
24+
e.FileOps = fileOps
25+
}
26+
2127
func (e *Extension) setPlatformDefaultDirectFileRoot() {
2228
dg := distro.Get()
2329

0 commit comments

Comments
 (0)