@@ -73,6 +73,10 @@ type Extension struct {
73
73
// *.partial file to its final name on completion.
74
74
directFileRoot string
75
75
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
+
76
80
nodeBackendForTest ipnext.NodeBackend // if non-nil, pretend we're this node state for tests
77
81
78
82
mu sync.Mutex // Lock order: lb.mu > e.mu
@@ -85,6 +89,30 @@ type Extension struct {
85
89
outgoingFiles map [string ]* ipn.OutgoingFile
86
90
}
87
91
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
+
88
116
func (e * Extension ) Name () string {
89
117
return "taildrop"
90
118
}
@@ -153,12 +181,18 @@ func (e *Extension) onChangeProfile(profile ipn.LoginProfileView, _ ipn.PrefsVie
153
181
if fileRoot == "" {
154
182
e .logf ("no Taildrop directory configured" )
155
183
}
184
+ mode := PutModeDirect
185
+ if e .directFileRoot != "" && strings .HasPrefix (e .directFileRoot , SafDirectoryPrefix ) {
186
+ mode = PutModeAndroidSAF
187
+ }
156
188
e .setMgrLocked (managerOptions {
157
189
Logf : e .logf ,
158
190
Clock : tstime.DefaultClock {Clock : e .sb .Clock ()},
159
191
State : e .stateStore ,
160
192
Dir : fileRoot ,
161
193
DirectFileMode : isDirectFileMode ,
194
+ FileOps : e .FileOps ,
195
+ Mode : mode ,
162
196
SendFileNotify : e .sendFileNotify ,
163
197
}.New ())
164
198
}
0 commit comments