Skip to content

Commit

Permalink
Windows support
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanBaulch committed Nov 17, 2024
1 parent b7d1a07 commit 86ae881
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 71 deletions.
8 changes: 2 additions & 6 deletions backend/azure/fileSystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/url"
"os"
"path"
"regexp"
"strings"
Expand Down Expand Up @@ -133,11 +132,8 @@ func ParsePath(p string) (host, pth string, err error) {
if p == "/" {
return "", "", errors.New("no container specified for Azure path")
}
var isLocation bool
if p[len(p)-1:] == string(os.PathSeparator) {
isLocation = true
}
l := strings.Split(p, string(os.PathSeparator))
isLocation := strings.HasSuffix(p, "/")
l := strings.Split(p, "/")
p = utils.EnsureLeadingSlash(path.Join(l[2:]...))
if isLocation {
p = utils.EnsureTrailingSlash(p)
Expand Down
7 changes: 4 additions & 3 deletions backend/mem/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ func (s *memFileTest) SetupTest() {
s.NoError(s.testFile.Touch(), "unexpected error touching file")
}

func (s *memFileTest) TeardownTest() {
func (s *memFileTest) TearDownTest() {
err := s.testFile.Close()
s.NoError(err, "close error not expected")
s.NoError(s.testFile.Delete(), "delete failed unexpectedly")
_ = s.testFile.Delete()
}

// TestZBR ensures that we can always read zero bytes
Expand Down Expand Up @@ -382,14 +382,15 @@ func (s *memFileTest) TestCopyToLocationOS() {

s.NotNil(copiedFile)
s.Equal("/test_files/test.txt", s.testFile.Path()) // testFile's path should be unchanged
s.Equal(filepath.Join(dir, "test.txt"), copiedFile.Path()) // new path should be that
s.Equal(path.Join(osFile.Location().Path(), "test.txt"), copiedFile.Path()) // new path should be that

_, err = copiedFile.Read(readSlice)
s.NoError(err, "unexpected read error")

_, err = s.testFile.Read(readSlice2)
s.NoError(err, "unexpected read error")
s.Equal(readSlice2, readSlice) // both reads should be the same
s.Require().NoError(copiedFile.Close())
cleanErr := os.RemoveAll(dir) // clean up
s.NoError(cleanErr, "unexpected error cleaning up osFiles")
}
Expand Down
59 changes: 38 additions & 21 deletions backend/os/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path"
"path/filepath"
"runtime"
"time"

"github.com/c2fo/vfs/v6"
Expand All @@ -22,6 +23,7 @@ type opener func(filePath string) (*os.File, error)
// File implements vfs.File interface for os fs.
type File struct {
file *os.File
volume string
name string
filesystem *FileSystem
cursorPos int64
Expand All @@ -34,7 +36,7 @@ type File struct {

// Delete unlinks the file returning any error or nil.
func (f *File) Delete(_ ...options.DeleteOption) error {
err := os.Remove(f.Path())
err := os.Remove(osFilePath(f))
if err == nil {
f.file = nil
}
Expand All @@ -43,7 +45,7 @@ func (f *File) Delete(_ ...options.DeleteOption) error {

// LastModified returns the timestamp of the file's mtime or error, if any.
func (f *File) LastModified() (*time.Time, error) {
stats, err := os.Stat(f.Path())
stats, err := os.Stat(osFilePath(f))
if err != nil {
return nil, err
}
Expand All @@ -66,12 +68,12 @@ func (f *File) Name() string {
//
// someFile.Location().Path()
func (f *File) Path() string {
return filepath.Join(f.Location().Path(), f.Name())
return path.Join(f.Location().Path(), f.Name())
}

// Size returns the size (in bytes) of the File or any error.
func (f *File) Size() (uint64, error) {
stats, err := os.Stat(f.Path())
stats, err := os.Stat(osFilePath(f))
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -181,7 +183,7 @@ func (f *File) Seek(offset int64, whence int) (int64, error) {

// Exists true if the file exists on the file system, otherwise false, and an error, if any.
func (f *File) Exists() (bool, error) {
_, err := os.Stat(f.Path())
_, err := os.Stat(osFilePath(f))
if err != nil {
// file does not exist
if os.IsNotExist(err) {
Expand Down Expand Up @@ -217,19 +219,22 @@ func (f *File) Write(p []byte) (n int, err error) {
func (f *File) Location() vfs.Location {
return &Location{
fileSystem: f.filesystem,
volume: f.volume,
name: utils.EnsureTrailingSlash(path.Dir(f.name)),
}
}

// MoveToFile move a file. It accepts a target vfs.File and returns an error, if any.
func (f *File) MoveToFile(file vfs.File) error {
// validate seek is at 0,0 before doing copy
if err := backend.ValidateCopySeekPosition(f); err != nil {
return err
if f.file != nil {
// validate seek is at 0,0 before doing copy
if err := backend.ValidateCopySeekPosition(f); err != nil {
return err
}
}
// handle native os move/rename
if file.Location().FileSystem().Scheme() == Scheme {
return safeOsRename(f.Path(), file.Path())
return safeOsRename(osFilePath(f), osFilePath(file))
}

// do copy/delete move for non-native os moves
Expand All @@ -245,7 +250,7 @@ func safeOsRename(srcName, dstName string) error {
err := os.Rename(srcName, dstName)
if err != nil {
e, ok := err.(*os.LinkError)
if ok && e.Err.Error() == osCrossDeviceLinkError {
if ok && (e.Err.Error() == osCrossDeviceLinkError || (runtime.GOOS == "windows" && e.Err.Error() == "Access is denied.")) {
// do cross-device renaming
if err := osCopy(srcName, dstName); err != nil {
return err
Expand Down Expand Up @@ -306,9 +311,11 @@ func (f *File) MoveToLocation(location vfs.Location) (vfs.File, error) {

// CopyToFile copies the file to a new File. It accepts a vfs.File and returns an error, if any.
func (f *File) CopyToFile(file vfs.File) error {
// validate seek is at 0,0 before doing copy
if err := backend.ValidateCopySeekPosition(f); err != nil {
return err
if f.file != nil {
// validate seek is at 0,0 before doing copy
if err := backend.ValidateCopySeekPosition(f); err != nil {
return err
}
}
_, err := f.copyWithName(file.Name(), file.Location())
return err
Expand All @@ -317,9 +324,11 @@ func (f *File) CopyToFile(file vfs.File) error {
// CopyToLocation copies existing File to new Location with the same name.
// It accepts a vfs.Location and returns a vfs.File and error, if any.
func (f *File) CopyToLocation(location vfs.Location) (vfs.File, error) {
// validate seek is at 0,0 before doing copy
if err := backend.ValidateCopySeekPosition(f); err != nil {
return nil, err
if f.file != nil {
// validate seek is at 0,0 before doing copy
if err := backend.ValidateCopySeekPosition(f); err != nil {
return nil, err
}
}
return f.copyWithName(f.Name(), location)
}
Expand Down Expand Up @@ -351,7 +360,7 @@ func (f *File) Touch() error {
return f.Close()
}
now := time.Now()
return os.Chtimes(f.Path(), now, now)
return os.Chtimes(osFilePath(f), now, now)
}

func (f *File) copyWithName(name string, location vfs.Location) (vfs.File, error) {
Expand Down Expand Up @@ -386,7 +395,7 @@ func (f *File) openFile() (*os.File, error) {
openFunc = f.fileOpener
}

file, err := openFunc(f.Path())
file, err := openFunc(osFilePath(f))
if err != nil {
return nil, err
}
Expand All @@ -398,7 +407,7 @@ func (f *File) openFile() (*os.File, error) {
func openOSFile(filePath string) (*os.File, error) {
// Ensure the path exists before opening the file, NoOp if dir already exists.
var fileMode os.FileMode = 0666
if err := os.MkdirAll(path.Dir(filePath), os.ModeDir|0750); err != nil {
if err := os.MkdirAll(filepath.Dir(filePath), os.ModeDir|0750); err != nil {
return nil, err
}

Expand Down Expand Up @@ -431,7 +440,7 @@ func (f *File) getInternalFile() (*os.File, error) {
openFunc = f.fileOpener
}

finalFile, err := openFunc(f.Path())
finalFile, err := openFunc(osFilePath(f))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -475,10 +484,11 @@ func (f *File) copyToLocalTempReader() (*os.File, error) {
openFunc = f.fileOpener
}

actualFile, err := openFunc(f.Path())
actualFile, err := openFunc(osFilePath(f))
if err != nil {
return nil, err
}
defer func() { _ = actualFile.Close() }()
if _, err := io.Copy(tmpFile, actualFile); err != nil {
return nil, err
}
Expand All @@ -493,3 +503,10 @@ func (f *File) copyToLocalTempReader() (*os.File, error) {

return tmpFile, nil
}

func osFilePath(f vfs.File) string {
if runtime.GOOS == "windows" {
return f.Location().Volume() + filepath.FromSlash(f.Path())
}
return f.Path()
}
21 changes: 20 additions & 1 deletion backend/os/fileSystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package os

import (
"path"
"path/filepath"
"runtime"

"github.com/c2fo/vfs/v6"
"github.com/c2fo/vfs/v6/backend"
Expand All @@ -22,22 +24,39 @@ func (fs *FileSystem) Retry() vfs.Retry {

// NewFile function returns the os implementation of vfs.File.
func (fs *FileSystem) NewFile(volume, name string) (vfs.File, error) {
if runtime.GOOS == "windows" && filepath.IsAbs(name) {
if v := filepath.VolumeName(name); v != "" {
volume = v
name = name[len(v):]
}
}

name = filepath.ToSlash(name)
err := utils.ValidateAbsoluteFilePath(name)
if err != nil {
return nil, err
}
return &File{name: name, filesystem: fs}, nil
return &File{volume: volume, name: name, filesystem: fs}, nil
}

// NewLocation function returns the os implementation of vfs.Location.
func (fs *FileSystem) NewLocation(volume, name string) (vfs.Location, error) {
if runtime.GOOS == "windows" && filepath.IsAbs(name) {
if v := filepath.VolumeName(name); v != "" {
volume = v
name = name[len(v):]
}
}

name = filepath.ToSlash(name)
err := utils.ValidateAbsoluteLocationPath(name)
if err != nil {
return nil, err
}

return &Location{
fileSystem: fs,
volume: volume,
name: utils.EnsureTrailingSlash(path.Clean(name)),
}, nil
}
Expand Down
Loading

0 comments on commit 86ae881

Please sign in to comment.