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 }