diff --git a/README.md b/README.md index 9a7c7fa..3494639 100644 --- a/README.md +++ b/README.md @@ -15,24 +15,24 @@ This library's import path is `go.arsenm.dev/infinitime`. ### Dependencies -This library requires `dbus`, `bluez`, and `pactl` to function. These allow the library to use bluetooth, control media, control volume, etc. +This library requires `dbus`, and `bluez` to function. These allow the library to use bluetooth, control media, control volume, etc. #### Arch ```shell -sudo pacman -S dbus bluez libpulse --needed +sudo pacman -S dbus bluez --needed ``` #### Debian/Ubuntu ```shell -sudo apt install dbus bluez pulseaudio-utils +sudo apt install dbus bluez ``` #### Fedora ```shell -sudo dnf install dbus bluez pulseaudio-utils +sudo dnf install dbus bluez ``` --- @@ -47,9 +47,10 @@ This library currently supports the following features: - Battery level - Music control - OTA firmware upgrades +- Navigation --- ### Mentions -The DFU process used in this library was created with the help of [siglo](https://github.com/alexr4535/siglo)'s source code. Specifically, this file: [ble_dfu.py](https://github.com/alexr4535/siglo/blob/main/src/ble_dfu.py) \ No newline at end of file +The DFU process used in this library was created with the help of [siglo](https://github.com/alexr4535/siglo)'s source code. Specifically, this file: [ble_dfu.py](https://github.com/alexr4535/siglo/blob/main/src/ble_dfu.py) diff --git a/blefs/all.go b/blefs/all.go new file mode 100644 index 0000000..5978de2 --- /dev/null +++ b/blefs/all.go @@ -0,0 +1,95 @@ +package blefs + +import ( + "path/filepath" + "strings" +) + +func (blefs *FS) RemoveAll(path string) error { + if path == "" { + return nil + } + + if filepath.Clean(path) == "/" { + return ErrNoRemoveRoot + } + + fi, err := blefs.Stat(path) + if err != nil { + return nil + } + + if fi.IsDir() { + return blefs.removeAllChildren(path) + } else { + err = blefs.Remove(path) + + var code int8 + if err, ok := err.(FSError); ok { + code = err.Code + } + + if err != nil && code != -2 { + return err + } + } + + return nil +} + +func (blefs *FS) removeAllChildren(path string) error { + list, err := blefs.ReadDir(path) + if err != nil { + return err + } + + for _, entry := range list { + name := entry.Name() + + if name == "." || name == ".." { + continue + } + entryPath := filepath.Join(path, name) + + if entry.IsDir() { + err = blefs.removeAllChildren(entryPath) + } else { + err = blefs.Remove(entryPath) + } + + var code int8 + if err, ok := err.(FSError); ok { + code = err.Code + } + + if err != nil && code != -2 { + return err + } + } + + return nil +} + +func (blefs *FS) MkdirAll(path string) error { + if path == "" || path == "/" { + return nil + } + + splitPath := strings.Split(path, "/") + for i := 1; i < len(splitPath); i++ { + curPath := strings.Join(splitPath[0:i+1], "/") + + err := blefs.Mkdir(curPath) + + var code int8 + if err, ok := err.(FSError); ok { + code = err.Code + } + + if err != nil && code != -17 { + return err + } + } + + return nil +} diff --git a/blefs/basic.go b/blefs/basic.go index 3097d9e..bfb5861 100644 --- a/blefs/basic.go +++ b/blefs/basic.go @@ -1,5 +1,29 @@ package blefs +import ( + "io/fs" + "path/filepath" +) + +// Stat does a ReadDir() and finds the current file in the output +func (blefs *FS) Stat(path string) (fs.FileInfo, error) { + // Get directory in filepath + dir := filepath.Dir(path) + // Read directory + dirEntries, err := blefs.ReadDir(dir) + if err != nil { + return nil, err + } + for _, entry := range dirEntries { + // If file name is base name of path + if entry.Name() == filepath.Base(path) { + // Return file info + return entry.Info() + } + } + return nil, ErrFileNotExists +} + // Rename moves or renames a file or directory func (blefs *FS) Rename(old, new string) error { // Create move request 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/file.go b/blefs/file.go index 4a4ee4e..bbe65da 100644 --- a/blefs/file.go +++ b/blefs/file.go @@ -3,7 +3,6 @@ package blefs import ( "io" "io/fs" - "path/filepath" "time" ) @@ -291,7 +290,7 @@ func (fl *File) Write(b []byte) (n int, err error) { } close(fl.offsetCh) - return int(fl.offset), nil + return int(fl.amtTferd), nil } // WriteString converts the string to []byte and calls Write() @@ -335,23 +334,9 @@ func (fl *File) Close() error { return nil } -// Stat does a RedDir() and finds the current file in the output +// Stat does a ReadDir() and finds the current file in the output func (fl *File) Stat() (fs.FileInfo, error) { - // Get directory in filepath - dir := filepath.Dir(fl.path) - // Read directory - dirEntries, err := fl.fs.ReadDir(dir) - if err != nil { - return nil, err - } - for _, entry := range dirEntries { - // If file name is base name of path - if entry.Name() == filepath.Base(fl.path) { - // Return file info - return entry.Info() - } - } - return nil, ErrFileNotExists + return fl.fs.Stat(fl.path) } // fileReadResponse represents a response for a read request diff --git a/blefs/fs.go b/blefs/fs.go index 6d9d06f..6f2ade9 100644 --- a/blefs/fs.go +++ b/blefs/fs.go @@ -199,7 +199,13 @@ func decode(data []byte, vals ...interface{}) error { // to send in a packet. Subtracting 20 ensures that the MTU // is never exceeded. func (blefs *FS) maxData() uint16 { - return blefs.transferChar.Properties.MTU - 20 + mtu := blefs.transferChar.Properties.MTU + // If MTU is zero, the current version of BlueZ likely + // doesn't support the MTU property, so assume 256. + if mtu == 0 { + mtu = 256 + } + return mtu - 20 } // padding returns a slice of len amount of 0x00. 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.mod b/go.mod index 58eed5a..9e885f7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/fxamacker/cbor/v2 v2.4.0 github.com/godbus/dbus/v5 v5.0.6 - github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a + github.com/muka/go-bluetooth v0.0.0-20220819140550-1d8857e3b268 github.com/rs/zerolog v1.26.1 golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect ) diff --git a/go.sum b/go.sum index 992599e..feac569 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +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-20220219050759-674a63b8741a h1:fnzS9RRQW8B5AgNCxkN0vJ/AoX+Xfqk3sAYon3iVrzA= -github.com/muka/go-bluetooth v0.0.0-20220219050759-674a63b8741a/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0= +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/infinitime.go b/infinitime.go index d3631b4..d1520f6 100644 --- a/infinitime.go +++ b/infinitime.go @@ -42,18 +42,22 @@ const ( ) var charNames = map[string]string{ - NewAlertChar: "New Alert", - NotifEventChar: "Notification Event", - StepCountChar: "Step Count", - MotionValChar: "Motion Values", - FirmwareVerChar: "Firmware Version", - CurrentTimeChar: "Current Time", - LocalTimeChar: "Local Time", - BatteryLvlChar: "Battery Level", - HeartRateChar: "Heart Rate", - FSTransferChar: "Filesystem Transfer", - FSVersionChar: "Filesystem Version", - WeatherDataChar: "Weather Data", + NewAlertChar: "New Alert", + NotifEventChar: "Notification Event", + StepCountChar: "Step Count", + MotionValChar: "Motion Values", + FirmwareVerChar: "Firmware Version", + CurrentTimeChar: "Current Time", + LocalTimeChar: "Local Time", + BatteryLvlChar: "Battery Level", + HeartRateChar: "Heart Rate", + FSTransferChar: "Filesystem Transfer", + FSVersionChar: "Filesystem Version", + WeatherDataChar: "Weather Data", + NavFlagsChar: "Navigation Icon", + NavNarrativeChar: "Navigation Instruction", + NavManDistChar: "Navigation Distance to next event", + NavProgressChar: "Navigation Progress", } type Device struct { @@ -70,9 +74,11 @@ type Device struct { fsVersionChar *gatt.GattCharacteristic1 fsTransferChar *gatt.GattCharacteristic1 weatherDataChar *gatt.GattCharacteristic1 + weatherdataChar *gatt.GattCharacteristic1 notifEventCh chan uint8 notifEventDone bool Music MusicCtrl + Navigation NavigationService DFU DFU } @@ -133,6 +139,7 @@ func Connect(ctx context.Context, opts *Options) (*Device, error) { // Create new device out := &Device{device: btDev} + out.Navigation = NavigationService{dev: out} // Resolve characteristics err = out.resolveChars() @@ -397,6 +404,14 @@ func (i *Device) resolveChars() error { charResolved := true // Set correct characteristics switch char.Properties.UUID { + case NavFlagsChar: + i.Navigation.flagsChar = char + case NavNarrativeChar: + i.Navigation.narrativeChar = char + case NavManDistChar: + i.Navigation.mandistChar = char + case NavProgressChar: + i.Navigation.progressChar = char case NewAlertChar: i.newAlertChar = char case NotifEventChar: diff --git a/navigation.go b/navigation.go new file mode 100644 index 0000000..ff44a36 --- /dev/null +++ b/navigation.go @@ -0,0 +1,151 @@ +package infinitime + +import ( + "errors" + "github.com/muka/go-bluetooth/bluez/profile/gatt" +) + +var ErrNavProgress = errors.New("progress needs to be between 0 and 100") + +const ( + NavFlagsChar = "00010001-78fc-48fe-8e23-433b3a1942d0" + NavNarrativeChar = "00010002-78fc-48fe-8e23-433b3a1942d0" + NavManDistChar = "00010003-78fc-48fe-8e23-433b3a1942d0" + NavProgressChar = "00010004-78fc-48fe-8e23-433b3a1942d0" +) + +type NavigationService struct { + dev *Device + flagsChar *gatt.GattCharacteristic1 + narrativeChar *gatt.GattCharacteristic1 + mandistChar *gatt.GattCharacteristic1 + progressChar *gatt.GattCharacteristic1 +} + +type NavFlag string + +const ( + NavFlagArrive NavFlag = "arrive" + NavFlagArriveLeft NavFlag = "arrive-left" + NavFlagArriveRight NavFlag = "arrive-right" + NavFlagArriveStraight NavFlag = "arrive-straight" + NavFlagClose NavFlag = "close" + NavFlagContinue NavFlag = "continue" + NavFlagContinueLeft NavFlag = "continue-left" + NavFlagContinueRight NavFlag = "continue-right" + NavFlagContinueSlightLeft NavFlag = "continue-slight-left" + NavFlagContinueSlightRight NavFlag = "continue-slight-right" + NavFlagContinueStraight NavFlag = "continue-straight" + NavFlagContinueUturn NavFlag = "continue-uturn" + NavFlagDepart NavFlag = "depart" + NavFlagDepartLeft NavFlag = "depart-left" + NavFlagDepartRight NavFlag = "depart-right" + NavFlagDepartStraight NavFlag = "depart-straight" + NavFlagEndOfRoadLeft NavFlag = "end-of-road-left" + NavFlagEndOfRoadRight NavFlag = "end-of-road-right" + NavFlagFerry NavFlag = "ferry" + NavFlagFlag NavFlag = "flag" + NavFlagFork NavFlag = "fork" + NavFlagForkLeft NavFlag = "fork-left" + NavFlagForkRight NavFlag = "fork-right" + NavFlagForkSlightLeft NavFlag = "fork-slight-left" + NavFlagForkSlightRight NavFlag = "fork-slight-right" + NavFlagForkStraight NavFlag = "fork-straight" + NavFlagInvalid NavFlag = "invalid" + NavFlagInvalidLeft NavFlag = "invalid-left" + NavFlagInvalidRight NavFlag = "invalid-right" + NavFlagInvalidSlightLeft NavFlag = "invalid-slight-left" + NavFlagInvalidSlightRight NavFlag = "invalid-slight-right" + NavFlagInvalidStraight NavFlag = "invalid-straight" + NavFlagInvalidUturn NavFlag = "invalid-uturn" + NavFlagMergeLeft NavFlag = "merge-left" + NavFlagMergeRight NavFlag = "merge-right" + NavFlagMergeSlightLeft NavFlag = "merge-slight-left" + NavFlagMergeSlightRight NavFlag = "merge-slight-right" + NavFlagMergeStraight NavFlag = "merge-straight" + NavFlagNewNameLeft NavFlag = "new-name-left" + NavFlagNewNameRight NavFlag = "new-name-right" + NavFlagNewNameSharpLeft NavFlag = "new-name-sharp-left" + NavFlagNewNameSharpRight NavFlag = "new-name-sharp-right" + NavFlagNewNameSlightLeft NavFlag = "new-name-slight-left" + NavFlagNewNameSlightRight NavFlag = "new-name-slight-right" + NavFlagNewNameStraight NavFlag = "new-name-straight" + NavFlagNotificationLeft NavFlag = "notification-left" + NavFlagNotificationRight NavFlag = "notification-right" + NavFlagNotificationSharpLeft NavFlag = "notification-sharp-left" + NavFlagNotificationSharpRight NavFlag = "notification-sharp-right" + NavFlagNotificationSlightLeft NavFlag = "notification-slight-left" + NavFlagNotificationSlightRight NavFlag = "notification-slight-right" + NavFlagNotificationStraight NavFlag = "notification-straight" + NavFlagOffRampLeft NavFlag = "off-ramp-left" + NavFlagOffRampRight NavFlag = "off-ramp-right" + NavFlagOffRampSharpLeft NavFlag = "off-ramp-sharp-left" + NavFlagOffRampSharpRight NavFlag = "off-ramp-sharp-right" + NavFlagOffRampSlightLeft NavFlag = "off-ramp-slight-left" + NavFlagOffRampSlightRight NavFlag = "off-ramp-slight-right" + NavFlagOffRampStraight NavFlag = "off-ramp-straight" + NavFlagOnRampLeft NavFlag = "on-ramp-left" + NavFlagOnRampRight NavFlag = "on-ramp-right" + NavFlagOnRampSharpLeft NavFlag = "on-ramp-sharp-left" + NavFlagOnRampSharpRight NavFlag = "on-ramp-sharp-right" + NavFlagOnRampSlightLeft NavFlag = "on-ramp-slight-left" + NavFlagOnRampSlightRight NavFlag = "on-ramp-slight-right" + NavFlagOnRampStraight NavFlag = "on-ramp-straight" + NavFlagRotary NavFlag = "rotary" + NavFlagRotaryLeft NavFlag = "rotary-left" + NavFlagRotaryRight NavFlag = "rotary-right" + NavFlagRotarySharpLeft NavFlag = "rotary-sharp-left" + NavFlagRotarySharpRight NavFlag = "rotary-sharp-right" + NavFlagRotarySlightLeft NavFlag = "rotary-slight-left" + NavFlagRotarySlightRight NavFlag = "rotary-slight-right" + NavFlagRotaryStraight NavFlag = "rotary-straight" + NavFlagRoundabout NavFlag = "roundabout" + NavFlagRoundaboutLeft NavFlag = "roundabout-left" + NavFlagRoundaboutRight NavFlag = "roundabout-right" + NavFlagRoundaboutSharpLeft NavFlag = "roundabout-sharp-left" + NavFlagRoundaboutSharpRight NavFlag = "roundabout-sharp-right" + NavFlagRoundaboutSlightLeft NavFlag = "roundabout-slight-left" + NavFlagRoundaboutSlightRight NavFlag = "roundabout-slight-right" + NavFlagRoundaboutStraight NavFlag = "roundabout-straight" + NavFlagTurnLeft NavFlag = "turn-left" + NavFlagTurnRight NavFlag = "turn-right" + NavFlagTurnSharpLeft NavFlag = "turn-sharp-left" + NavFlagTurnSharpRight NavFlag = "turn-sharp-right" + NavFlagTurnSlightLeft NavFlag = "turn-slight-left" + NavFlagTurnSlightRight NavFlag = "turn-slight-right" + NavFlagTurnStright NavFlag = "turn-stright" + NavFlagUpdown NavFlag = "updown" + NavFlagUturn NavFlag = "uturn" +) + +func (n *NavigationService) SetFlag(flag NavFlag) error { + log.Debug().Str("func", "SetFlag").Msg("Sending flag") + if err := n.dev.checkStatus(n.flagsChar, NavFlagsChar); err != nil { + return err + } + return n.flagsChar.WriteValue([]byte(flag), nil) +} + +func (n *NavigationService) SetNarrative(narrative string) error { + log.Debug().Str("func", "SetNarrative").Msg("Sending narrative") + if err := n.dev.checkStatus(n.narrativeChar, NavNarrativeChar); err != nil { + return err + } + return n.narrativeChar.WriteValue([]byte(narrative), nil) +} + +func (n *NavigationService) SetManDist(manDist string) error { + log.Debug().Str("func", "SetNarrative").Msg("Sending maneuver distance") + if err := n.dev.checkStatus(n.mandistChar, NavManDistChar); err != nil { + return err + } + return n.mandistChar.WriteValue([]byte(manDist), nil) +} + +func (n *NavigationService) SetProgress(progress uint8) error { + log.Debug().Str("func", "SetNarrative").Msg("Sending progress") + if err := n.dev.checkStatus(n.progressChar, NavProgressChar); err != nil { + return err + } + return n.progressChar.WriteValue([]byte{progress}, nil) +} diff --git a/pkg/player/pactl.go b/pkg/player/pactl.go deleted file mode 100644 index 3dc3441..0000000 --- a/pkg/player/pactl.go +++ /dev/null @@ -1,16 +0,0 @@ -package player - -import ( - "fmt" - "os/exec" -) - -// VolUp uses pactl to increase the volume of the default sink -func VolUp(percent uint) error { - return exec.Command("pactl", "set-sink-volume", "@DEFAULT_SINK@", fmt.Sprintf("+%d%%", percent)).Run() -} - -// VolDown uses pactl to decrease the volume of the default sink -func VolDown(percent uint) error { - return exec.Command("pactl", "set-sink-volume", "@DEFAULT_SINK@", fmt.Sprintf("-%d%%", percent)).Run() -} diff --git a/pkg/player/player.go b/pkg/player/player.go index 6f3e202..5358a80 100644 --- a/pkg/player/player.go +++ b/pkg/player/player.go @@ -107,6 +107,46 @@ func Prev() error { return nil } +func VolUp(percent uint) error { + + player, err := getPlayerObj() + if err != nil { + return err + } + if player != nil { + currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume") + if err != nil { + return err + } + newVal := currentVal.Value().(float64) + (float64(percent) / 100) + err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal) + if err != nil { + return err + } + } + return nil +} + +func VolDown(percent uint) error { + + player, err := getPlayerObj() + if err != nil { + return err + } + if player != nil { + currentVal, err := player.GetProperty("org.mpris.MediaPlayer2.Player.Volume") + if err != nil { + return err + } + newVal := currentVal.Value().(float64) - (float64(percent) / 100) + err = player.SetProperty("org.mpris.MediaPlayer2.Player.Volume", newVal) + if err != nil { + return err + } + } + return nil +} + type ChangeType int const ( diff --git a/resources.go b/resources.go new file mode 100644 index 0000000..4f753cf --- /dev/null +++ b/resources.go @@ -0,0 +1,209 @@ +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 + } + defer m.Close() + + var manifest ResourceManifest + err = json.NewDecoder(m).Decode(&manifest) + if err != nil { + return nil, err + } + + m.Close() + + log.Debug().Msg("Decoded manifest file") + + go func() { + defer close(out) + + for _, file := range manifest.Obsolete { + name := filepath.Base(file.Path) + + log.Debug().Str("file", file.Path).Msg("Removing file") + + err := fs.RemoveAll(file.Path) + if err != nil { + out <- ResourceLoadProgress{ + Name: name, + Operation: ResourceOperationRemoveObsolete, + Err: err, + } + return + } + + log.Debug().Str("file", file.Path).Msg("Removed file") + + 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().Str("file", file.Path).Msg("Making directories") + + err = fs.MkdirAll(filepath.Dir(file.Path)) + if err != nil { + log.Debug().Err(err).Msg("Error making directories") + out <- ResourceLoadProgress{ + Name: file.Name, + Operation: ResourceOperationUpload, + Total: srcFi.Size(), + Err: err, + } + src.Close() + return + } + + log.Debug(). + Str("file", file.Path). + Int64("size", srcFi.Size()). + Msg("Creating file") + + dst, err := fs.Create(file.Path, uint32(srcFi.Size())) + if err != nil { + log.Debug().Err(err).Msg("Error creating file") + 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(). + Int64("total", srcFi.Size()). + Uint32("sent", sent). + Msg("Progress event sent") + + 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().Err(err).Msg("Error writing to file") + 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 +} diff --git a/weather/weather.go b/weather/weather.go index 7a8765d..1265c50 100644 --- a/weather/weather.go +++ b/weather/weather.go @@ -82,8 +82,12 @@ type TimelineHeader struct { // NewHeader creates and populates a new timeline header // and returns it func NewHeader(evtType EventType, expires time.Duration) TimelineHeader { + now := time.Now() + _, offset := now.Zone() + now = now.Add(time.Duration(offset) * time.Second) + return TimelineHeader{ - Timestamp: uint64(time.Now().Unix()), + Timestamp: uint64(now.Unix()), Expires: uint32(expires.Seconds()), EventType: evtType, }