Add rewritten infinitime abstraction and integrate it into ITD
This commit is contained in:
61
internal/fsproto/errors.go
Normal file
61
internal/fsproto/errors.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package fsproto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFileNotExists = errors.New("file does not exist")
|
||||
ErrFileReadOnly = errors.New("file is read only")
|
||||
ErrFileWriteOnly = errors.New("file is write only")
|
||||
ErrInvalidOffset = errors.New("offset out of range")
|
||||
ErrNoRemoveRoot = errors.New("refusing to remove root directory")
|
||||
)
|
||||
|
||||
// Error represents an error returned by BLE FS
|
||||
type Error struct {
|
||||
Code int8
|
||||
}
|
||||
|
||||
// Error returns the string associated with the error code
|
||||
func (err Error) Error() string {
|
||||
switch err.Code {
|
||||
case 0x02:
|
||||
return "filesystem error"
|
||||
case 0x05:
|
||||
return "read-only filesystem"
|
||||
case 0x03:
|
||||
return "no such file"
|
||||
case 0x04:
|
||||
return "protocol error"
|
||||
case -5:
|
||||
return "input/output error"
|
||||
case -84:
|
||||
return "filesystem is corrupted"
|
||||
case -2:
|
||||
return "no such directory entry"
|
||||
case -17:
|
||||
return "entry already exists"
|
||||
case -20:
|
||||
return "entry is not a directory"
|
||||
case -39:
|
||||
return "directory is not empty"
|
||||
case -9:
|
||||
return "bad file number"
|
||||
case -27:
|
||||
return "file is too large"
|
||||
case -22:
|
||||
return "invalid parameter"
|
||||
case -28:
|
||||
return "no space left on device"
|
||||
case -12:
|
||||
return "no more memory available"
|
||||
case -61:
|
||||
return "no attr available"
|
||||
case -36:
|
||||
return "file name is too long"
|
||||
default:
|
||||
return fmt.Sprintf("unknown error (code %d)", err.Code)
|
||||
}
|
||||
}
|
||||
212
internal/fsproto/fsproto.go
Normal file
212
internal/fsproto/fsproto.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package fsproto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"tinygo.org/x/bluetooth"
|
||||
)
|
||||
|
||||
type FSReqOpcode uint8
|
||||
|
||||
const (
|
||||
ReadFileHeaderOpcode FSReqOpcode = 0x10
|
||||
ReadFileOpcode FSReqOpcode = 0x12
|
||||
WriteFileHeaderOpcode FSReqOpcode = 0x20
|
||||
WriteFileOpcode FSReqOpcode = 0x22
|
||||
DeleteFileOpcode FSReqOpcode = 0x30
|
||||
MakeDirectoryOpcode FSReqOpcode = 0x40
|
||||
ListDirectoryOpcode FSReqOpcode = 0x50
|
||||
MoveFileOpcode FSReqOpcode = 0x60
|
||||
)
|
||||
|
||||
type FSRespOpcode uint8
|
||||
|
||||
const (
|
||||
ReadFileResp FSRespOpcode = 0x11
|
||||
WriteFileResp FSRespOpcode = 0x21
|
||||
DeleteFileResp FSRespOpcode = 0x31
|
||||
MakeDirectoryResp FSRespOpcode = 0x41
|
||||
ListDirectoryResp FSRespOpcode = 0x51
|
||||
MoveFileResp FSRespOpcode = 0x61
|
||||
)
|
||||
|
||||
type ReadFileHeaderRequest struct {
|
||||
Padding byte
|
||||
PathLen uint16
|
||||
Offset uint32
|
||||
ReadLen uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
type ReadFileRequest struct {
|
||||
Status uint8
|
||||
Padding [2]byte
|
||||
Offset uint32
|
||||
ReadLen uint32
|
||||
}
|
||||
|
||||
type ReadFileResponse struct {
|
||||
Status int8
|
||||
Padding [2]byte
|
||||
Offset uint32
|
||||
FileSize uint32
|
||||
ChunkLen uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type WriteFileHeaderRequest struct {
|
||||
Padding byte
|
||||
PathLen uint16
|
||||
Offset uint32
|
||||
ModTime uint64
|
||||
FileSize uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
type WriteFileRequest struct {
|
||||
Status uint8
|
||||
Padding [2]byte
|
||||
Offset uint32
|
||||
ChunkLen uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type WriteFileResponse struct {
|
||||
Status int8
|
||||
Padding [2]byte
|
||||
Offset uint32
|
||||
ModTime uint64
|
||||
FreeSpace uint32
|
||||
}
|
||||
|
||||
type DeleteFileRequest struct {
|
||||
Padding byte
|
||||
PathLen uint16
|
||||
Path string
|
||||
}
|
||||
|
||||
type DeleteFileResponse struct {
|
||||
Status int8
|
||||
}
|
||||
|
||||
type MkdirRequest struct {
|
||||
Padding byte
|
||||
PathLen uint16
|
||||
Padding2 [4]byte
|
||||
Timestamp uint64
|
||||
Path string
|
||||
}
|
||||
|
||||
type MkdirResponse struct {
|
||||
Status int8
|
||||
Padding [6]byte
|
||||
ModTime uint64
|
||||
}
|
||||
|
||||
type ListDirRequest struct {
|
||||
Padding byte
|
||||
PathLen uint16
|
||||
Path string
|
||||
}
|
||||
|
||||
type ListDirResponse struct {
|
||||
Status int8
|
||||
PathLen uint16
|
||||
EntryNum uint32
|
||||
TotalEntries uint32
|
||||
Flags uint32
|
||||
ModTime uint64
|
||||
FileSize uint32
|
||||
Path []byte
|
||||
}
|
||||
|
||||
type MoveFileRequest struct {
|
||||
Padding byte
|
||||
OldPathLen uint16
|
||||
NewPathLen uint16
|
||||
OldPath string
|
||||
Padding2 byte
|
||||
NewPath string
|
||||
}
|
||||
|
||||
type MoveFileResponse struct {
|
||||
Status int8
|
||||
}
|
||||
|
||||
func WriteRequest(char *bluetooth.DeviceCharacteristic, opcode FSReqOpcode, req any) error {
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteByte(byte(opcode))
|
||||
|
||||
rv := reflect.ValueOf(req)
|
||||
for rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
switch field := rv.Field(i); field.Kind() {
|
||||
case reflect.String:
|
||||
io.WriteString(buf, field.String())
|
||||
case reflect.Slice:
|
||||
if field.Type().Elem().Kind() == reflect.Uint8 {
|
||||
buf.Write(field.Bytes())
|
||||
}
|
||||
default:
|
||||
binary.Write(buf, binary.LittleEndian, field.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
_, err := char.WriteWithoutResponse(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func ReadResponse(b []byte, expect FSRespOpcode, out interface{}) error {
|
||||
if len(b) == 0 {
|
||||
return errors.New("empty response packet")
|
||||
}
|
||||
if opcode := FSRespOpcode(b[0]); opcode != expect {
|
||||
return fmt.Errorf("unexpected response opcode: expected %x, got %x", expect, opcode)
|
||||
}
|
||||
|
||||
r := bytes.NewReader(b[1:])
|
||||
|
||||
ot := reflect.TypeOf(out)
|
||||
if ot.Kind() != reflect.Ptr || ot.Elem().Kind() != reflect.Struct {
|
||||
return errors.New("out parameter must be a pointer to a struct")
|
||||
}
|
||||
|
||||
ov := reflect.ValueOf(out).Elem()
|
||||
for i := 0; i < ot.Elem().NumField(); i++ {
|
||||
field := ot.Elem().Field(i)
|
||||
fieldValue := ov.Field(i)
|
||||
|
||||
// If the last field is a byte slice, just read the remaining data into it and return.
|
||||
if i == ot.Elem().NumField()-1 {
|
||||
if field.Type.Kind() == reflect.Slice && field.Type.Elem().Kind() == reflect.Uint8 {
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fieldValue.SetBytes(data)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := binary.Read(r, binary.LittleEndian, fieldValue.Addr().Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if statusField := ov.FieldByName("Status"); !statusField.IsZero() {
|
||||
code := statusField.Interface().(int8)
|
||||
if code != 0x01 {
|
||||
return Error{code}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,8 +9,7 @@ import (
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"go.elara.ws/infinitime"
|
||||
"go.elara.ws/infinitime/blefs"
|
||||
"go.elara.ws/itd/infinitime"
|
||||
"go.elara.ws/logger/log"
|
||||
)
|
||||
|
||||
@@ -47,14 +46,14 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
myfs *blefs.FS = nil
|
||||
myfs *infinitime.FS = nil
|
||||
inodemap map[string]uint64 = nil
|
||||
)
|
||||
|
||||
func BuildRootNode(dev *infinitime.Device) (*ITNode, error) {
|
||||
var err error
|
||||
inodemap = make(map[string]uint64)
|
||||
myfs, err = dev.FS()
|
||||
myfs = dev.FS()
|
||||
if err != nil {
|
||||
log.Error("FUSE Failed to get filesystem").Err(err).Send()
|
||||
return nil, err
|
||||
@@ -343,13 +342,10 @@ func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
go func() {
|
||||
// For every progress event
|
||||
for sent := range fp.Progress() {
|
||||
log.Debug("FUSE Flush progress").Int("bytes", int(sent)).Int("total", len(fh.content)).Send()
|
||||
}
|
||||
}()
|
||||
|
||||
fp.ProgressFunc = func(transferred, total uint32) {
|
||||
log.Debug("FUSE Read progress").Uint32("bytes", transferred).Uint32("total", total).Send()
|
||||
}
|
||||
|
||||
r := bytes.NewReader(fh.content)
|
||||
nread, err := io.Copy(fp, r)
|
||||
@@ -430,12 +426,9 @@ func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle,
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
|
||||
go func() {
|
||||
// For every progress event
|
||||
for sent := range fp.Progress() {
|
||||
log.Debug("FUSE Read progress").Int("bytes", int(sent)).Int("total", int(f.self.size)).Send()
|
||||
}
|
||||
}()
|
||||
fp.ProgressFunc = func(transferred, total uint32) {
|
||||
log.Debug("FUSE Read progress").Uint32("bytes", transferred).Uint32("total", total).Send()
|
||||
}
|
||||
|
||||
_, err = io.Copy(b, fp)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,7 @@ package fusefs
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"go.elara.ws/infinitime/blefs"
|
||||
"go.elara.ws/itd/internal/fsproto"
|
||||
)
|
||||
|
||||
func syscallErr(err error) syscall.Errno {
|
||||
@@ -12,10 +12,10 @@ func syscallErr(err error) syscall.Errno {
|
||||
}
|
||||
|
||||
switch err := err.(type) {
|
||||
case blefs.FSError:
|
||||
case fsproto.Error:
|
||||
switch err.Code {
|
||||
case 0x02: // filesystem error
|
||||
return syscall.EIO // TODO
|
||||
return syscall.EIO
|
||||
case 0x05: // read-only filesystem
|
||||
return syscall.EROFS
|
||||
case 0x03: // no such file
|
||||
@@ -25,7 +25,7 @@ func syscallErr(err error) syscall.Errno {
|
||||
case -5: // input/output error
|
||||
return syscall.EIO
|
||||
case -84: // filesystem is corrupted
|
||||
return syscall.ENOTRECOVERABLE // TODO
|
||||
return syscall.ENOTRECOVERABLE
|
||||
case -2: // no such directory entry
|
||||
return syscall.ENOENT
|
||||
case -17: // entry already exists
|
||||
@@ -45,28 +45,24 @@ func syscallErr(err error) syscall.Errno {
|
||||
case -12: // no more memory available
|
||||
return syscall.ENOMEM
|
||||
case -61: // no attr available
|
||||
return syscall.ENODATA // TODO
|
||||
return syscall.ENODATA
|
||||
case -36: // file name is too long
|
||||
return syscall.ENAMETOOLONG
|
||||
}
|
||||
default:
|
||||
switch err {
|
||||
case blefs.ErrFileNotExists: // file does not exist
|
||||
case fsproto.ErrFileNotExists: // file does not exist
|
||||
return syscall.ENOENT
|
||||
case blefs.ErrFileReadOnly: // file is read only
|
||||
case fsproto.ErrFileReadOnly: // file is read only
|
||||
return syscall.EACCES
|
||||
case blefs.ErrFileWriteOnly: // file is write only
|
||||
case fsproto.ErrFileWriteOnly: // file is write only
|
||||
return syscall.EACCES
|
||||
case blefs.ErrInvalidOffset: // invalid file offset
|
||||
case fsproto.ErrInvalidOffset: // invalid file offset
|
||||
return syscall.EINVAL
|
||||
case blefs.ErrOffsetChanged: // offset has already been changed
|
||||
return syscall.ESPIPE
|
||||
case blefs.ErrReadOpen: // only one file can be opened for reading at a time
|
||||
return syscall.ENFILE
|
||||
case blefs.ErrWriteOpen: // only one file can be opened for writing at a time
|
||||
return syscall.ENFILE
|
||||
case blefs.ErrNoRemoveRoot: // refusing to remove root directory
|
||||
case fsproto.ErrNoRemoveRoot: // refusing to remove root directory
|
||||
return syscall.EPERM
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user