From 3e9957d419616768438692ae476c72a8be8a1649 Mon Sep 17 00:00:00 2001 From: Arsen Musayelyan Date: Mon, 29 Aug 2022 14:43:16 -0700 Subject: [PATCH] Implement resource loading feature --- blefs/error.go | 1 + blefs/iofs.go | 2 +- go.sum | 2 + resources.go | 180 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 resources.go diff --git a/blefs/error.go b/blefs/error.go index 8441929..7328e2d 100644 --- a/blefs/error.go +++ b/blefs/error.go @@ -13,6 +13,7 @@ var ( ErrOffsetChanged = errors.New("offset has already been changed") ErrReadOpen = errors.New("only one file can be opened for reading at a time") ErrWriteOpen = errors.New("only one file can be opened for writing at a time") + ErrNoRemoveRoot = errors.New("refusing to remove root directory") ) // FSError represents an error returned by BLE FS diff --git a/blefs/iofs.go b/blefs/iofs.go index 05d1c1c..c602926 100644 --- a/blefs/iofs.go +++ b/blefs/iofs.go @@ -12,4 +12,4 @@ func (iofs goFS) Open(path string) (fs.File, error) { func (blefs *FS) GoFS() fs.FS { return goFS{blefs} -} \ No newline at end of file +} diff --git a/go.sum b/go.sum index aa0b3aa..feac569 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 h1:kOnq7TfaAO2Vc/MHxPqFIXe00y1qBxJAvhctXdko6vo= +github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/resources.go b/resources.go new file mode 100644 index 0000000..92e9ddb --- /dev/null +++ b/resources.go @@ -0,0 +1,180 @@ +package infinitime + +import ( + "archive/zip" + "encoding/json" + "io" + "os" + "path/filepath" + + "go.arsenm.dev/infinitime/blefs" +) + +// ResourceOperation represents an operation performed during +// resource loading +type ResourceOperation uint8 + +const ( + // ResourceOperationUpload represents the upload phase + // of resource loading + ResourceOperationUpload = iota + // ResourceOperationRemoveObsolete represents the obsolete + // file removal phase of resource loading + ResourceOperationRemoveObsolete +) + +// ResourceManifest is the structure of the resource manifest file +type ResourceManifest struct { + Resources []Resource `json:"resources"` + Obsolete []ObsoleteResource `json:"obsolete_files"` +} + +// Resource represents a resource entry in the manifest +type Resource struct { + Name string `json:"filename"` + Path string `json:"path"` +} + +// ObsoleteResource represents an obsolete file entry in the manifest +type ObsoleteResource struct { + Path string `json:"path"` + Since string `json:"since"` +} + +// ResourceLoadProgress contains information on the progress of +// a resource load +type ResourceLoadProgress struct { + Operation ResourceOperation + Name string + Total int64 + Sent int64 + Err error +} + +// LoadResources accepts a resources zip file and a BLE FS. +// It loads the resources from the zip onto the FS. +func LoadResources(file *os.File, fs *blefs.FS) (<-chan ResourceLoadProgress, error) { + out := make(chan ResourceLoadProgress, 10) + + fi, err := file.Stat() + if err != nil { + return nil, err + } + + r, err := zip.NewReader(file, fi.Size()) + if err != nil { + return nil, err + } + + m, err := r.Open("resources.json") + if err != nil { + return nil, err + } + + var manifest ResourceManifest + err = json.NewDecoder(m).Decode(&manifest) + if err != nil { + return nil, err + } + + go func() { + defer close(out) + + for _, file := range manifest.Obsolete { + name := filepath.Base(file.Path) + + err := fs.RemoveAll(file.Path) + if err != nil { + out <- ResourceLoadProgress{ + Name: name, + Operation: ResourceOperationRemoveObsolete, + Err: err, + } + return + } + + out <- ResourceLoadProgress{ + Name: name, + Operation: ResourceOperationRemoveObsolete, + } + } + + for _, file := range manifest.Resources { + src, err := r.Open(file.Name) + if err != nil { + out <- ResourceLoadProgress{ + Name: file.Name, + Operation: ResourceOperationUpload, + Err: err, + } + return + } + + srcFi, err := src.Stat() + if err != nil { + out <- ResourceLoadProgress{ + Name: file.Name, + Operation: ResourceOperationUpload, + Total: srcFi.Size(), + Err: err, + } + return + } + + err = fs.MkdirAll(filepath.Dir(file.Path)) + if err != nil { + out <- ResourceLoadProgress{ + Name: file.Name, + Operation: ResourceOperationUpload, + Total: srcFi.Size(), + Err: err, + } + return + } + + dst, err := fs.Create(file.Path, uint32(srcFi.Size())) + if err != nil { + out <- ResourceLoadProgress{ + Name: file.Name, + Operation: ResourceOperationUpload, + Total: srcFi.Size(), + Err: err, + } + return + } + + progCh := dst.Progress() + go func() { + for sent := range progCh { + out <- ResourceLoadProgress{ + Name: file.Name, + Operation: ResourceOperationUpload, + Total: srcFi.Size(), + Sent: int64(sent), + } + + if sent == uint32(srcFi.Size()) { + return + } + } + }() + + n, err := io.Copy(dst, src) + if err != nil { + out <- ResourceLoadProgress{ + Name: file.Name, + Operation: ResourceOperationUpload, + Total: srcFi.Size(), + Sent: n, + Err: err, + } + return + } + + src.Close() + dst.Close() + } + }() + + return out, nil +}