infinitime/resources.go
2023-04-20 19:53:34 -07:00

210 lines
4.4 KiB
Go

package infinitime
import (
"archive/zip"
"encoding/json"
"io"
"os"
"path/filepath"
"go.elara.ws/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
}
defer m.Close()
var manifest ResourceManifest
err = json.NewDecoder(m).Decode(&manifest)
if err != nil {
return nil, err
}
m.Close()
log.Debug("Decoded manifest file").Send()
go func() {
defer close(out)
for _, file := range manifest.Obsolete {
name := filepath.Base(file.Path)
log.Debug("Removing file").Str("file", file.Path).Send()
err := fs.RemoveAll(file.Path)
if err != nil {
out <- ResourceLoadProgress{
Name: name,
Operation: ResourceOperationRemoveObsolete,
Err: err,
}
return
}
log.Debug("Removed file").Str("file", file.Path).Send()
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,
}
src.Close()
return
}
log.Debug("Making directories").Str("file", file.Path).Send()
err = fs.MkdirAll(filepath.Dir(file.Path))
if err != nil {
log.Debug("Error making directories").Err(err).Send()
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Err: err,
}
src.Close()
return
}
log.Debug("Creating file").
Str("file", file.Path).
Int64("size", srcFi.Size()).
Send()
dst, err := fs.Create(file.Path, uint32(srcFi.Size()))
if err != nil {
log.Debug("Error creating file").Err(err).Send()
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Err: err,
}
src.Close()
return
}
progCh := dst.Progress()
go func() {
for sent := range progCh {
log.Debug("Progress event sent").
Int64("total", srcFi.Size()).
Uint32("sent", sent).
Send()
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 {
log.Debug("Error writing to file").Err(err).Send()
out <- ResourceLoadProgress{
Name: file.Name,
Operation: ResourceOperationUpload,
Total: srcFi.Size(),
Sent: n,
Err: err,
}
src.Close()
dst.Close()
return
}
src.Close()
dst.Close()
}
}()
return out, nil
}