Add filemanager api

This commit is contained in:
世界 2023-04-21 16:43:06 +08:00
parent 2fc9c6028c
commit a39dcdba79
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
5 changed files with 249 additions and 1 deletions

View file

@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/sagernet/sing/common/service"
"github.com/sagernet/sing/service"
)
func TimeFuncFromContext(ctx context.Context) func() time.Time {

View file

@ -0,0 +1,164 @@
package filemanager
import (
"context"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/service"
)
var _ Manager = (*defaultManager)(nil)
type defaultManager struct {
basePath string
tempPath string
chown bool
userID int
groupID int
}
func WithDefault(ctx context.Context, basePath string, tempPath string, userID int, groupID int) context.Context {
chown := userID != os.Getuid() || groupID != os.Getgid()
if tempPath == "" {
tempPath = os.TempDir()
}
return service.ContextWith[Manager](ctx, &defaultManager{
basePath: basePath,
tempPath: tempPath,
chown: chown,
userID: userID,
groupID: groupID,
})
}
func (m *defaultManager) BasePath(name string) string {
if m.basePath == "" || strings.HasPrefix(name, "/") {
return name
}
return filepath.Join(m.basePath, name)
}
func (m *defaultManager) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
name = m.BasePath(name)
willCreate := flag&os.O_CREATE != 0 && !rw.FileExists(name)
file, err := os.OpenFile(name, flag, perm)
if err != nil {
return nil, err
}
if m.chown && willCreate {
err = file.Chown(m.userID, m.groupID)
if err != nil {
file.Close()
os.Remove(file.Name())
return nil, err
}
}
return file, nil
}
func (m *defaultManager) Create(name string) (*os.File, error) {
name = m.BasePath(name)
file, err := os.Create(name)
if err != nil {
return nil, err
}
if m.chown {
err = file.Chown(m.userID, m.groupID)
if err != nil {
file.Close()
os.Remove(file.Name())
return nil, err
}
}
return file, nil
}
func (m *defaultManager) CreateTemp(pattern string) (*os.File, error) {
file, err := os.CreateTemp(m.tempPath, pattern)
if err != nil {
return nil, err
}
if m.chown {
err = file.Chown(m.userID, m.groupID)
if err != nil {
file.Close()
os.Remove(file.Name())
return nil, err
}
}
return file, nil
}
func (m *defaultManager) Mkdir(path string, perm os.FileMode) error {
path = m.BasePath(path)
err := os.Mkdir(path, perm)
if err != nil {
return err
}
if m.chown {
err = os.Chown(path, m.userID, m.groupID)
if err != nil {
os.Remove(path)
return err
}
}
return nil
}
func (m *defaultManager) MkdirAll(path string, perm os.FileMode) error {
path = m.BasePath(path)
dir, err := os.Stat(path)
if err == nil {
if dir.IsDir() {
return nil
}
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
i := len(path)
for i > 0 && os.IsPathSeparator(path[i-1]) {
i--
}
j := i
for j > 0 && !os.IsPathSeparator(path[j-1]) {
j--
}
if j > 1 {
err = m.MkdirAll(fixRootDirectory(path[:j-1]), perm)
if err != nil {
return err
}
}
err = os.Mkdir(path, perm)
if err != nil {
dir, err1 := os.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
if m.chown {
err = os.Chown(path, m.userID, m.groupID)
if err != nil {
os.Remove(path)
return err
}
}
return nil
}
func fixRootDirectory(p string) string {
if len(p) == len(`\\?\c:`) {
if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' {
return p + `\`
}
}
return p
}

View file

@ -0,0 +1,84 @@
package filemanager
import (
"context"
"os"
"github.com/sagernet/sing/service"
)
type Manager interface {
BasePath(name string) string
OpenFile(name string, flag int, perm os.FileMode) (*os.File, error)
Create(name string) (*os.File, error)
CreateTemp(pattern string) (*os.File, error)
Mkdir(path string, perm os.FileMode) error
MkdirAll(path string, perm os.FileMode) error
}
func BasePath(ctx context.Context, name string) string {
manager := service.FromContext[Manager](ctx)
if manager == nil {
return name
}
return manager.BasePath(name)
}
func OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (*os.File, error) {
manager := service.FromContext[Manager](ctx)
if manager == nil {
return os.OpenFile(name, flag, perm)
}
return manager.OpenFile(name, flag, perm)
}
func Create(ctx context.Context, name string) (*os.File, error) {
manager := service.FromContext[Manager](ctx)
if manager == nil {
return os.Create(name)
}
return manager.Create(name)
}
func CreateTemp(ctx context.Context, pattern string) (*os.File, error) {
manager := service.FromContext[Manager](ctx)
if manager == nil {
return os.CreateTemp("", pattern)
}
return manager.CreateTemp(pattern)
}
func Mkdir(ctx context.Context, path string, perm os.FileMode) error {
manager := service.FromContext[Manager](ctx)
if manager == nil {
return os.Mkdir(path, perm)
}
return manager.Mkdir(path, perm)
}
func MkdirAll(ctx context.Context, path string, perm os.FileMode) error {
manager := service.FromContext[Manager](ctx)
if manager == nil {
return os.MkdirAll(path, perm)
}
return manager.MkdirAll(path, perm)
}
func WriteFile(ctx context.Context, name string, data []byte, perm os.FileMode) error {
manager := service.FromContext[Manager](ctx)
if manager == nil {
return os.WriteFile(name, data, perm)
}
file, err := manager.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
if err != nil {
return err
}
_, err = file.Write(data)
if err1 := file.Close(); err1 != nil && err == nil {
err = err1
}
return err
}