- Update modules version - Add cmd to start fuse mount - Update Write and Read logic and add ReadAt and WriteAt - Clean cache function and add cache to readdir Signed-off-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
289 lines
7.5 KiB
Go
289 lines
7.5 KiB
Go
package drivefs
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"google.golang.org/api/drive/v3"
|
|
"google.golang.org/api/googleapi"
|
|
)
|
|
|
|
var (
|
|
_ fs.FileInfo = (*NodeStat)(nil)
|
|
_ File = (*DirNode)(nil)
|
|
_ File = (*FileNode)(nil)
|
|
|
|
_ fs.FS = (*Gdrive)(nil)
|
|
_ fs.StatFS = (*Gdrive)(nil)
|
|
_ fs.ReadDirFS = (*Gdrive)(nil)
|
|
_ fs.ReadFileFS = (*Gdrive)(nil)
|
|
_ fs.SubFS = (*Gdrive)(nil)
|
|
)
|
|
|
|
// Extends [*google.golang.org/api/drive/v3.File]
|
|
type NodeStat struct{ File *drive.File }
|
|
|
|
func (node *NodeStat) Sys() any { return node.File }
|
|
func (node NodeStat) Name() string { return pathManipulate(path.Clean(node.File.Name)).EscapeName() }
|
|
func (node NodeStat) Size() int64 { return node.File.Size }
|
|
func (node NodeStat) IsDir() bool { return node.File.MimeType == GoogleDriveMimeFolder }
|
|
func (node NodeStat) Mode() fs.FileMode {
|
|
if mode, ok := node.File.Properties[UnixModeProperties]; ok {
|
|
if mod, err := strconv.ParseUint(mode, 10, 64); err == nil {
|
|
return fs.FileMode(mod)
|
|
}
|
|
}
|
|
|
|
switch node.File.MimeType {
|
|
case GoogleDriveMimeFolder:
|
|
return fs.ModeDir | 0666
|
|
case GoogleDriveMimeSyslink:
|
|
return fs.ModeSymlink | 0777
|
|
}
|
|
|
|
return 0777 | fs.ModeType
|
|
}
|
|
|
|
func (node NodeStat) ModTime() time.Time {
|
|
for _, fileTime := range []string{node.File.ModifiedTime, node.File.CreatedTime} {
|
|
if fileTime != "" {
|
|
if t, err := time.ParseInLocation(time.RFC3339, fileTime, time.UTC); err == nil {
|
|
return t
|
|
}
|
|
}
|
|
}
|
|
return time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)
|
|
}
|
|
|
|
func convertDriveToDir(input []*drive.File) (out []fs.DirEntry) {
|
|
out = make([]fs.DirEntry, 0)
|
|
for _, data := range input {
|
|
out = append(out, fs.FileInfoToDirEntry(&NodeStat{File: data}))
|
|
}
|
|
return
|
|
}
|
|
|
|
// Representation to Dir entrys and non regular file
|
|
type DirNode struct {
|
|
Node *drive.File
|
|
|
|
Offset int // current count
|
|
Files []*drive.File // files node
|
|
}
|
|
|
|
// Remote File Read and Write directly without with basic At Read and write
|
|
type FileNode struct {
|
|
Node *drive.File
|
|
NodeUpdate *drive.FilesUpdateCall
|
|
|
|
Writer io.WriteCloser
|
|
Reader io.ReadCloser
|
|
Client *Gdrive
|
|
Locker *sync.Mutex
|
|
|
|
Offset int64 // Offset
|
|
}
|
|
|
|
type LocalFile struct {
|
|
*os.File // Local file append to struct
|
|
}
|
|
|
|
func (*DirNode) Sync() error { return nil }
|
|
func (*DirNode) Truncate(size int64) error { return io.EOF }
|
|
func (*DirNode) Read([]byte) (int, error) { return 0, io.EOF }
|
|
func (*DirNode) ReadAt(p []byte, off int64) (n int, err error) { return 0, io.EOF }
|
|
func (*DirNode) Write(p []byte) (n int, err error) { return 0, io.EOF }
|
|
func (*DirNode) WriteAt(p []byte, off int64) (n int, err error) { return 0, io.EOF }
|
|
|
|
func (dir *DirNode) Close() error { dir.Offset = -1; return nil }
|
|
func (dir *DirNode) Stat() (fs.FileInfo, error) { return &NodeStat{File: dir.Node}, nil }
|
|
|
|
func (dir *DirNode) Seek(offset int64, whence int) (int64, error) {
|
|
if dir.Offset < 0 || len(dir.Files) >= dir.Offset {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
switch whence {
|
|
case io.SeekStart:
|
|
if offset < 0 {
|
|
return 0, fs.ErrInvalid
|
|
} else if offset > int64(len(dir.Files)) {
|
|
return 0, io.ErrUnexpectedEOF
|
|
}
|
|
dir.Offset = int(offset)
|
|
case io.SeekCurrent:
|
|
offset = int64(dir.Offset) + offset
|
|
if offset > int64(len(dir.Files)) || offset < 0 {
|
|
return 0, io.ErrUnexpectedEOF
|
|
}
|
|
dir.Offset += int(offset)
|
|
case io.SeekEnd:
|
|
if offset > 0 {
|
|
offset = ^offset
|
|
}
|
|
dir.Offset = len(dir.Files) + int(offset)
|
|
}
|
|
|
|
return int64(dir.Offset), nil
|
|
}
|
|
|
|
func (dir *DirNode) ReadDir(count int) ([]fs.DirEntry, error) {
|
|
if dir.Offset < 0 || len(dir.Files) >= dir.Offset {
|
|
return nil, io.EOF
|
|
} else if count < 0 {
|
|
dir.Offset = -1
|
|
return convertDriveToDir(dir.Files), nil
|
|
}
|
|
min := min(count, len(dir.Files[dir.Offset:]))
|
|
dir.Offset += min
|
|
return convertDriveToDir(dir.Files[dir.Offset : dir.Offset+min]), nil
|
|
}
|
|
|
|
func (*FileNode) Sync() error { return nil }
|
|
func (*FileNode) ReadDir(count int) ([]fs.DirEntry, error) { return nil, fs.ErrInvalid }
|
|
func (*FileNode) Truncate(size int64) error { return syscall.ECONNREFUSED }
|
|
|
|
func (file *FileNode) Stat() (fs.FileInfo, error) { return &NodeStat{File: file.Node}, nil }
|
|
func (file *FileNode) Close() error {
|
|
var closed io.Closer
|
|
switch {
|
|
case file.Reader != nil:
|
|
closed = file.Reader
|
|
case file.Writer != nil:
|
|
closed = file.Writer
|
|
default:
|
|
return nil // nothing to close
|
|
}
|
|
file.Offset = -1
|
|
file.Reader = nil
|
|
file.Writer = nil
|
|
file.Client = nil
|
|
return closed.Close()
|
|
}
|
|
|
|
func (file *FileNode) Read(p []byte) (int, error) { return file.ReadAt(p, file.Offset) }
|
|
func (file *FileNode) Write(p []byte) (n int, err error) { return file.WriteAt(p, file.Offset) }
|
|
|
|
// Update offset
|
|
func (file *FileNode) Seek(offset int64, whence int) (int64, error) {
|
|
if file.Reader == nil || file.Writer == nil || file.Client == nil || file.Offset < 0 {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
switch whence {
|
|
case io.SeekStart:
|
|
if offset < 0 {
|
|
return 0, fs.ErrInvalid
|
|
} else if offset > file.Node.Size {
|
|
return 0, io.ErrUnexpectedEOF
|
|
}
|
|
file.Offset = offset
|
|
case io.SeekCurrent:
|
|
offset = file.Offset + offset
|
|
if offset > file.Node.Size || offset < 0 {
|
|
return 0, io.ErrUnexpectedEOF
|
|
}
|
|
file.Offset += offset
|
|
case io.SeekEnd:
|
|
if offset > 0 {
|
|
offset = ^offset
|
|
}
|
|
file.Offset = file.Node.Size + offset
|
|
default:
|
|
return 0, &fs.PathError{Op: "seek", Path: file.Node.Id, Err: fs.ErrInvalid}
|
|
}
|
|
|
|
return file.Offset, nil
|
|
}
|
|
|
|
// Read is Seek and Read in same function
|
|
func (file *FileNode) ReadAt(p []byte, off int64) (n int, err error) {
|
|
if file.Writer == nil && file.Reader == nil {
|
|
return 0, io.EOF
|
|
} else if file.Writer != nil && file.Reader == nil {
|
|
return 0, fs.ErrInvalid
|
|
} else if off < 0 || off > file.Node.Size {
|
|
return 0, io.ErrUnexpectedEOF
|
|
}
|
|
|
|
switch min(1, max(-1, file.Offset-off)) {
|
|
case 1: // Discart next reader
|
|
file.Locker.Lock()
|
|
defer file.Locker.Unlock()
|
|
|
|
if _, err = io.CopyN(io.Discard, file.Reader, file.Offset-off); err != nil {
|
|
return 0, ProcessErr(nil, err)
|
|
}
|
|
file.Offset = off
|
|
case -1: // Restart body reader
|
|
file.Locker.Lock()
|
|
defer file.Locker.Unlock()
|
|
|
|
// Set current offset off file
|
|
reopenFile := file.Client.driveService.Files.Get(file.Node.Id)
|
|
reopenFile.Header().Set("Range", fmt.Sprintf("bytes=%d-", off))
|
|
|
|
res, err := openFileAPI(reopenFile)
|
|
if err != nil {
|
|
return 0, ProcessErr(httpRes(res), err)
|
|
}
|
|
|
|
// Close if opened header
|
|
if file.Reader != nil {
|
|
file.Reader.Close()
|
|
}
|
|
|
|
// Add to Reader field
|
|
file.Reader = res.Body
|
|
file.Offset = off
|
|
}
|
|
|
|
n, err = file.Reader.Read(p)
|
|
file.Offset += int64(n)
|
|
if err == io.EOF {
|
|
file.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (file *FileNode) WriteAt(p []byte, off int64) (n int, err error) {
|
|
if file.Writer == nil && file.Reader == nil {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
if file.Writer != nil && file.Reader != nil {
|
|
go file.Client.driveService.Files.Update(file.Node.Id, nil).Media(file.Reader, googleapi.ContentType("application/octet-stream")).Do()
|
|
file.Reader = nil // Remove from struct
|
|
}
|
|
|
|
if file.Writer == nil && file.Reader != nil {
|
|
return 0, syscall.EINVAL
|
|
}
|
|
|
|
// Start media writer
|
|
if updater := file.NodeUpdate; updater != nil {
|
|
file.NodeUpdate = nil
|
|
go updater.Do()
|
|
}
|
|
|
|
if file.Offset == off { // direct write
|
|
n, err = file.Writer.Write(p)
|
|
file.Offset += int64(n)
|
|
if err == io.EOF {
|
|
file.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
// return invalid error
|
|
return 0, errors.Join(errors.New("WriteAt not support to change offset"), fs.ErrInvalid)
|
|
}
|