You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale-android/libtailscale/fileops.go

103 lines
2.7 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package libtailscale
import (
"encoding/json"
"fmt"
"io"
"os"
"time"
"tailscale.com/feature/taildrop"
)
// AndroidFileOps implements the FileOps interface using the Android ShareFileHelper.
type AndroidFileOps struct {
helper ShareFileHelper
}
var _ taildrop.FileOps = (*AndroidFileOps)(nil)
func NewAndroidFileOps(helper ShareFileHelper) *AndroidFileOps {
return &AndroidFileOps{helper: helper}
}
func (ops *AndroidFileOps) OpenWriter(name string, offset int64, perm os.FileMode) (io.WriteCloser, string, error) {
var wc OutputStream
if offset == 0 {
wc = ops.helper.OpenFileWriter(name)
} else {
wc = ops.helper.OpenFileWriterAt(name, offset)
}
if wc == nil {
return nil, "", fmt.Errorf("OpenFileWriter returned nil for %q", name)
}
uri := ops.helper.OpenFileURI(name)
return wc, uri, nil
}
func (ops *AndroidFileOps) Remove(baseName string) error {
uri := ops.helper.OpenFileURI(baseName)
return ops.helper.DeleteFile(uri)
}
func (ops *AndroidFileOps) Rename(oldPath, newName string) (string, error) {
tree := ops.helper.TreeURI()
newURI := ops.helper.RenamePartialFile(oldPath, tree, newName)
if newURI == "" {
return "", fmt.Errorf("failed to rename partial file via SAF")
}
return newURI, nil
}
func (ops *AndroidFileOps) ListFiles() ([]string, error) {
namesJSON := ops.helper.ListPartialFilesJSON("")
var names []string
if err := json.Unmarshal([]byte(namesJSON), &names); err != nil {
return nil, err
}
return names, nil
}
func (ops *AndroidFileOps) OpenReader(name string) (io.ReadCloser, error) {
in := ops.helper.OpenPartialFileReader(name)
if in == nil {
return nil, fmt.Errorf("OpenPartialFileReader returned nil for %q", name)
}
return adaptInputStream(in), nil
}
func (ops *AndroidFileOps) Stat(name string) (os.FileInfo, error) {
infoJSON := ops.helper.GetFileInfo(name)
if infoJSON == "" {
return nil, os.ErrNotExist
}
var info struct {
Name string `json:"name"`
Size int64 `json:"size"`
ModTime int64 `json:"modTime"` // Unix millis
}
if err := json.Unmarshal([]byte(infoJSON), &info); err != nil {
return nil, err
}
return &androidFileInfo{
name: info.Name,
size: info.Size,
modTime: time.UnixMilli(info.ModTime),
}, nil
}
type androidFileInfo struct {
name string
size int64
modTime time.Time
}
func (fi *androidFileInfo) Name() string { return fi.name }
func (fi *androidFileInfo) Size() int64 { return fi.size }
func (fi *androidFileInfo) Mode() os.FileMode { return 0o600 }
func (fi *androidFileInfo) ModTime() time.Time { return fi.modTime }
func (fi *androidFileInfo) IsDir() bool { return false }
func (fi *androidFileInfo) Sys() interface{} { return nil }