Compare commits
No commits in common. "master" and "0b5d777077cb3b1bd4c28997d9650b8a65b74b17" have entirely different histories.
master
...
0b5d777077
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
|||||||
liberapay: Elara6331
|
|
3
.gitignore
vendored
@ -1,6 +1,3 @@
|
|||||||
/itctl
|
/itctl
|
||||||
/itd
|
/itd
|
||||||
/itgui
|
/itgui
|
||||||
/itgui-linux-*
|
|
||||||
/version.txt
|
|
||||||
dist/
|
|
||||||
|
3
.gitm.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[repos]
|
||||||
|
origin = "ssh://git@192.168.100.62:2222/Arsen6331/itd.git"
|
||||||
|
gitlab = "git@gitlab.com:moussaelianarsen/itd.git"
|
121
.goreleaser.yaml
@ -1,121 +0,0 @@
|
|||||||
before:
|
|
||||||
hooks:
|
|
||||||
- go generate
|
|
||||||
- go mod tidy
|
|
||||||
builds:
|
|
||||||
- id: itd
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
binary: itd
|
|
||||||
goos:
|
|
||||||
- linux
|
|
||||||
goarch:
|
|
||||||
- 386
|
|
||||||
- amd64
|
|
||||||
- arm
|
|
||||||
- arm64
|
|
||||||
- riscv64
|
|
||||||
goarm:
|
|
||||||
- 7
|
|
||||||
- id: itctl
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=0
|
|
||||||
main: ./cmd/itctl
|
|
||||||
binary: itctl
|
|
||||||
goos:
|
|
||||||
- linux
|
|
||||||
goarch:
|
|
||||||
- 386
|
|
||||||
- amd64
|
|
||||||
- arm
|
|
||||||
- arm64
|
|
||||||
goarm:
|
|
||||||
- 7
|
|
||||||
archives:
|
|
||||||
- name_template: >-
|
|
||||||
{{- .ProjectName }}-{{.Version}}-{{.Os}}-
|
|
||||||
{{- if eq .Arch "386" }}i386
|
|
||||||
{{- else if eq .Arch "amd64" }}x86_64
|
|
||||||
{{- else if eq .Arch "arm64" }}aarch64
|
|
||||||
{{- else }}{{.Arch}}
|
|
||||||
{{- end }}
|
|
||||||
files:
|
|
||||||
- LICENSE
|
|
||||||
- README.md
|
|
||||||
- itd.toml
|
|
||||||
- itd.service
|
|
||||||
allow_different_binary_count: true
|
|
||||||
nfpms:
|
|
||||||
- id: itd
|
|
||||||
file_name_template: >-
|
|
||||||
{{- .PackageName }}-{{.Version}}-{{.Os}}-
|
|
||||||
{{- if eq .Arch "386" }}i386
|
|
||||||
{{- else if eq .Arch "amd64" }}x86_64
|
|
||||||
{{- else if eq .Arch "arm64" }}aarch64
|
|
||||||
{{- else }}{{.Arch}}
|
|
||||||
{{- end }}
|
|
||||||
description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch"
|
|
||||||
homepage: 'https://gitea.elara.ws/Elara6331/itd'
|
|
||||||
maintainer: 'Elara Ivy <elara@elara.ws>'
|
|
||||||
license: GPLv3
|
|
||||||
formats:
|
|
||||||
- apk
|
|
||||||
- deb
|
|
||||||
- rpm
|
|
||||||
- archlinux
|
|
||||||
dependencies:
|
|
||||||
- dbus
|
|
||||||
- bluez
|
|
||||||
contents:
|
|
||||||
- src: itd.toml
|
|
||||||
dst: /etc/itd.toml
|
|
||||||
type: "config|noreplace"
|
|
||||||
- src: itd.service
|
|
||||||
dst: /usr/lib/systemd/user/itd.service
|
|
||||||
file_info:
|
|
||||||
mode: 0755
|
|
||||||
aurs:
|
|
||||||
- name: itd-bin
|
|
||||||
homepage: 'https://gitea.elara.ws/Elara6331/itd'
|
|
||||||
description: "Companion daemon for the InfiniTime firmware on the PineTime smartwatch"
|
|
||||||
maintainers:
|
|
||||||
- 'Elara Ivy <elara@elara.ws>'
|
|
||||||
license: GPLv3
|
|
||||||
private_key: '{{ .Env.AUR_KEY }}'
|
|
||||||
git_url: 'ssh://aur@aur.archlinux.org/itd-bin.git'
|
|
||||||
provides:
|
|
||||||
- itd
|
|
||||||
- itctl
|
|
||||||
conflicts:
|
|
||||||
- itd
|
|
||||||
- itctl
|
|
||||||
depends:
|
|
||||||
- dbus
|
|
||||||
- bluez
|
|
||||||
package: |-
|
|
||||||
# binaries
|
|
||||||
install -Dm755 ./itd "${pkgdir}/usr/bin/itd"
|
|
||||||
install -Dm755 ./itctl "${pkgdir}/usr/bin/itctl"
|
|
||||||
|
|
||||||
# service
|
|
||||||
install -Dm644 "./itd.service" ${pkgdir}/usr/lib/systemd/user/itd.service
|
|
||||||
|
|
||||||
# config
|
|
||||||
install -Dm644 "./itd.toml" ${pkgdir}/etc/itd.toml
|
|
||||||
|
|
||||||
# license
|
|
||||||
install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/itd/LICENSE"
|
|
||||||
release:
|
|
||||||
gitea:
|
|
||||||
owner: Elara6331
|
|
||||||
name: itd
|
|
||||||
gitea_urls:
|
|
||||||
api: 'https://gitea.elara.ws/api/v1/'
|
|
||||||
download: 'https://gitea.elara.ws'
|
|
||||||
skip_tls_verify: false
|
|
||||||
checksum:
|
|
||||||
name_template: 'checksums.txt'
|
|
||||||
snapshot:
|
|
||||||
name_template: "{{ incpatch .Version }}-next"
|
|
||||||
changelog:
|
|
||||||
sort: asc
|
|
@ -1,8 +0,0 @@
|
|||||||
pipeline:
|
|
||||||
release:
|
|
||||||
image: goreleaser/goreleaser
|
|
||||||
commands:
|
|
||||||
- goreleaser release
|
|
||||||
secrets: [ gitea_token, aur_key ]
|
|
||||||
when:
|
|
||||||
event: tag
|
|
10
Makefile
@ -3,14 +3,13 @@ BIN_PREFIX = $(DESTDIR)$(PREFIX)/bin
|
|||||||
SERVICE_PREFIX = $(DESTDIR)$(PREFIX)/lib/systemd/user
|
SERVICE_PREFIX = $(DESTDIR)$(PREFIX)/lib/systemd/user
|
||||||
CFG_PREFIX = $(DESTDIR)/etc
|
CFG_PREFIX = $(DESTDIR)/etc
|
||||||
|
|
||||||
all: version.txt
|
all:
|
||||||
go build
|
go build $(GOFLAGS)
|
||||||
go build ./cmd/itctl
|
go build ./cmd/itctl $(GOFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f itctl
|
rm -f itctl
|
||||||
rm -f itd
|
rm -f itd
|
||||||
rm -f version.txt
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
install -Dm755 ./itd $(BIN_PREFIX)/itd
|
install -Dm755 ./itd $(BIN_PREFIX)/itd
|
||||||
@ -24,7 +23,4 @@ uninstall:
|
|||||||
rm $(SERVICE_PREFIX)/itd.service
|
rm $(SERVICE_PREFIX)/itd.service
|
||||||
rm $(CFG_PREFIX)/itd.toml
|
rm $(CFG_PREFIX)/itd.toml
|
||||||
|
|
||||||
version.txt:
|
|
||||||
go generate
|
|
||||||
|
|
||||||
.PHONY: all clean install uninstall
|
.PHONY: all clean install uninstall
|
180
README.md
@ -1,17 +1,11 @@
|
|||||||
# ITD
|
# ITD
|
||||||
## InfiniTime Daemon
|
## InfiniTime Daemon
|
||||||
|
|
||||||
`itd` is a daemon that uses my infinitime [library](https://go.elara.ws/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
|
`itd` is a daemon that uses my infinitime [library](https://go.arsenm.dev/infinitime) to interact with the [PineTime](https://www.pine64.org/pinetime/) running [InfiniTime](https://infinitime.io).
|
||||||
|
|
||||||
[![status-badge](https://ci.elara.ws/api/badges/Elara6331/itd/status.svg)](https://ci.elara.ws/Elara6331/itd)
|
[![Build status](https://ci.appveyor.com/api/projects/status/xgj5sobw76ndqaod?svg=true)](https://ci.appveyor.com/project/moussaelianarsen/itd)
|
||||||
[![itd-git AUR package](https://img.shields.io/aur/version/itd-git?label=itd-git&logo=archlinux)](https://aur.archlinux.org/packages/itd-git/)
|
[![Binary downloads](https://img.shields.io/badge/download-binary-orange)](https://minio.arsenm.dev/minio/itd/)
|
||||||
[![itd-bin AUR package](https://img.shields.io/aur/version/itd-bin?label=itd-bin&logo=archlinux)](https://aur.archlinux.org/packages/itd-bin/)
|
[![AUR package](https://img.shields.io/aur/version/itd-git?label=itd-git&logo=archlinux)](https://aur.archlinux.org/packages/itd-git/)
|
||||||
[![LURE badge for itd-git](https://lure.sh/pkg/default/itd-git/badge.svg)](https://lure.sh/pkg/default/itd-git)
|
|
||||||
[![LURE badge for itd-bin](https://lure.sh/pkg/default/itd-bin/badge.svg)](https://lure.sh/pkg/default/itd-bin)
|
|
||||||
|
|
||||||
This repository is part of the Software Heritage Archive:
|
|
||||||
|
|
||||||
[![SWH](https://archive.softwareheritage.org/badge/swh:1:dir:1374aa47b5c0a0d636d6f9c69f77af5e5bae99b2/)](https://archive.softwareheritage.org/swh:1:dir:1374aa47b5c0a0d636d6f9c69f77af5e5bae99b2;origin=https://gitea.elara.ws/Elara6331/itd;visit=swh:1:snp:d2935acbc966dfe1b15c771927bb08b5fc2ec89f;anchor=swh:1:rev:395cded9758dccc020fcd5b666f83a62308c9ab7)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -25,40 +19,58 @@ This repository is part of the Software Heritage Archive:
|
|||||||
- Set current time
|
- Set current time
|
||||||
- Control socket
|
- Control socket
|
||||||
- Firmware upgrades
|
- Firmware upgrades
|
||||||
- Weather
|
|
||||||
- BLE Filesystem
|
|
||||||
- Navigation (PureMaps)
|
|
||||||
- FUSE Filesystem
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Installation
|
### Socket
|
||||||
|
|
||||||
Since ITD 0.0.7, packages are built and uploaded whenever a new release is created.
|
This daemon creates a UNIX socket at `/tmp/itd/socket`. It allows you to directly control the daemon and, by extension, the connected watch.
|
||||||
|
|
||||||
#### Arch Linux
|
The socket accepts JSON requests. For example, sending a notification looks like this:
|
||||||
|
|
||||||
Use the `itd-bin` or `itd-git` AUR packages.
|
```json
|
||||||
|
{"type": 5, "data": {"title": "title1", "body": "body1"}}
|
||||||
|
```
|
||||||
|
|
||||||
#### Debian/Ubuntu
|
It will return a JSON response. A response can have 3 fields: `error`, `msg`, and `value`. Error is a boolean that signals whether an error was returned. If error is true, the msg field will contain the error. Value can contain any data and depends on what the request was.
|
||||||
|
|
||||||
- Go to the [latest release](https://gitea.elara.ws/Elara6331/itd/releases/latest) and download the `.deb` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal.
|
The various request types and their data requirements can be seen in `internal/types`. I can make separate docs for it if I get enough requests.
|
||||||
- Run `sudo apt install <package>`, replacing `<package>` with the path to the downloaded file. Note: relative paths must begin with `./`.
|
|
||||||
- Example: `sudo apt install ~/Downloads/itd-0.0.7-linux-aarch64.deb`
|
|
||||||
|
|
||||||
#### Fedora
|
---
|
||||||
|
|
||||||
- Go to the [latest release](https://gitea.elara.ws/Elara6331/itd/releases/latest) and download the `.rpm` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal.
|
### Transliteration
|
||||||
- Run `sudo dnf install <package>`, replacing `<package>` with the path to the downloaded file.
|
|
||||||
- Example: `sudo dnf install ~/Downloads/itd-0.0.7-linux-aarch64.rpm`
|
|
||||||
|
|
||||||
#### Alpine (and postmarketOS)
|
Since the PineTime does not have enough space to store all unicode glyphs, it only stores the ASCII space and Cyrillic. Therefore, this daemon can transliterate unsupported characters into supported ones. Since some languages have different transliterations, the transliterators to be used must be specified in the config. Here are the available transliterators:
|
||||||
|
|
||||||
- Go to the [latest release](https://gitea.elara.ws/Elara6331/itd/releases/latest) and download the `.apk` package for your CPU architecture. You can find your architecture by running `uname -m` in the terminal.
|
- eASCII
|
||||||
- Run `sudo apk add --allow-untrusted <package>`, replacing `<package>` with the path to the downloaded file.
|
- Scandinavian
|
||||||
- Example: `sudo apk add --allow-untrusted ~/Downloads/itd-0.0.7-linux-aarch64.apk`
|
- German
|
||||||
|
- Hebrew
|
||||||
|
- Greek
|
||||||
|
- Russian
|
||||||
|
- Ukranian
|
||||||
|
- Arabic
|
||||||
|
- Farsi
|
||||||
|
- Polish
|
||||||
|
- Lithuanian
|
||||||
|
- Estonian
|
||||||
|
- Icelandic
|
||||||
|
- Czeck
|
||||||
|
- French
|
||||||
|
- Armenian
|
||||||
|
- Korean
|
||||||
|
- Chinese
|
||||||
|
- Emoji
|
||||||
|
|
||||||
Note: `--allow-untrusted` is required because ITD isn't part of a repository, and therefore is not signed.
|
Place the desired map names in an array as `notifs.translit.use`. They will be evaluated in order. You can also put custom transliterations in `notifs.translit.custom`. These take priority over any other maps. The `notifs.translit` config section should look like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[notifs.translit]
|
||||||
|
use = ["eASCII", "Russian", "Emoji"]
|
||||||
|
custom = [
|
||||||
|
"test", "replaced"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -68,89 +80,77 @@ This daemon comes with a binary called `itctl` which uses the socket to control
|
|||||||
|
|
||||||
This is the `itctl` usage screen:
|
This is the `itctl` usage screen:
|
||||||
```
|
```
|
||||||
NAME:
|
Control the itd daemon for InfiniTime smartwatches
|
||||||
itctl - A new cli application
|
|
||||||
|
|
||||||
USAGE:
|
Usage:
|
||||||
itctl [global options] command [command options] [arguments...]
|
itctl [flags]
|
||||||
|
itctl [command]
|
||||||
|
|
||||||
COMMANDS:
|
Available Commands:
|
||||||
help Display help screen for a command
|
firmware Manage InfiniTime firmware
|
||||||
resources, res Handle InfiniTime resource loading
|
|
||||||
filesystem, fs Perform filesystem operations on the PineTime
|
|
||||||
firmware, fw Manage InfiniTime firmware
|
|
||||||
get Get information from InfiniTime
|
get Get information from InfiniTime
|
||||||
|
help Help about any command
|
||||||
notify Send notification to InfiniTime
|
notify Send notification to InfiniTime
|
||||||
set Set information on InfiniTime
|
set Set information on InfiniTime
|
||||||
update, upd Update information on InfiniTime
|
|
||||||
watch Watch a value for changes
|
|
||||||
|
|
||||||
GLOBAL OPTIONS:
|
Flags:
|
||||||
--socket-path value, -s value Path to itd socket (default: "/tmp/itd/socket")
|
-h, --help help for itctl
|
||||||
|
-s, --socket-path string Path to itd socket
|
||||||
|
|
||||||
|
Use "itctl [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `itgui`
|
### `itgui`
|
||||||
|
|
||||||
In `cmd/itgui`, there is a gui frontend to the socket of `itd`. It uses the [Fyne library](https://fyne.io/) for Go.
|
In `cmd/itgui`, there is a gui frontend to the socket of `itd`. It uses the [fyne library](https://fyne.io/) for Go. It can be compiled by running:
|
||||||
|
|
||||||
#### Easy Installation
|
|
||||||
|
|
||||||
The easiest way to install `itgui` is to use my other project, [LURE](https://gitea.elara.ws/Elara6331/lure). LURE will only work if your package manager is `apt`, `dnf`, `yum`, `zypper`, `pacman`, or `apk`.
|
|
||||||
|
|
||||||
Instructions:
|
|
||||||
|
|
||||||
1. Install LURE. This can be done with the following command: `curl https://www.elara.ws/lure.sh | bash`.
|
|
||||||
2. Check to make sure LURE is properly installed by running `lure ref`.
|
|
||||||
3. Run `lure in itgui`. This process may take a while as it will compile `itgui` from source and package it for your distro.
|
|
||||||
4. Once the process is complete, you should be able to open and use `itgui` like any other app.
|
|
||||||
|
|
||||||
#### Compilation
|
|
||||||
|
|
||||||
Before compiling, certain prerequisites must be installed. These are listed on the following page: https://developer.fyne.io/started/#prerequisites
|
|
||||||
|
|
||||||
It can be compiled by running:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go build ./cmd/itgui
|
go build ./cmd/itgui
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Cross-compilation
|
|
||||||
|
|
||||||
Due to the use of OpenGL, cross-compilation of `itgui` isn't as simple as that of `itd` and `itctl`. The following guide from the Fyne website should work for `itgui`: https://developer.fyne.io/started/cross-compiling.
|
|
||||||
|
|
||||||
#### Screenshots
|
#### Screenshots
|
||||||
|
|
||||||
![Info tab](cmd/itgui/screenshots/info.png)
|
![Info tab](https://i.imgur.com/okxG9EI.png)
|
||||||
|
|
||||||
![Motion tab](cmd/itgui/screenshots/motion.png)
|
![Notify tab](https://i.imgur.com/DrVhOAq.png)
|
||||||
|
|
||||||
![Notify tab](cmd/itgui/screenshots/notify.png)
|
![Set time tab](https://i.imgur.com/j9civeY.png)
|
||||||
|
|
||||||
![FS tab](cmd/itgui/screenshots/fs.png)
|
![Upgrade tab](https://i.imgur.com/1KY6fG4.png)
|
||||||
|
|
||||||
![FS mkdir](cmd/itgui/screenshots/mkdir.png)
|
![Upgrade in progress](https://i.imgur.com/w5qbWAw.png)
|
||||||
|
|
||||||
![FS resource upload](cmd/itgui/screenshots/resources.png)
|
|
||||||
|
|
||||||
![Time tab](cmd/itgui/screenshots/time.png)
|
|
||||||
|
|
||||||
![Firmware tab](cmd/itgui/screenshots/firmware.png)
|
|
||||||
|
|
||||||
![Upgrade in progress](cmd/itgui/screenshots/progress.png)
|
|
||||||
|
|
||||||
![Metrics tab](cmd/itgui/screenshots/metrics.png)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Socket
|
#### Interactive mode
|
||||||
|
|
||||||
This daemon creates a UNIX socket at `/tmp/itd/socket`. It allows you to directly control the daemon and, by extension, the connected watch.
|
Running `itctl` by itself will open interactive mode. It's essentially a shell where you can enter commands. For example:
|
||||||
|
|
||||||
The socket uses the [DRPC](https://github.com/storj/drpc) library for requests. The code generated by this framework is located in [`internal/rpc`](internal/rpc)
|
```
|
||||||
|
$ itctl
|
||||||
|
itctl> fw ver
|
||||||
|
1.3.0
|
||||||
|
itctl> get batt
|
||||||
|
81%
|
||||||
|
itctl> get heart
|
||||||
|
92 BPM
|
||||||
|
itctl> set time 2021-08-22T00:06:18-07:00
|
||||||
|
itctl> set time now
|
||||||
|
itctl> exit
|
||||||
|
```
|
||||||
|
|
||||||
The API description is located in the [`internal/rpc/itd.proto`](internal/rpc/itd.proto) file.
|
---
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
To install, install the go compiler and make. Usually, go is provided by a package either named `go` or `golang`, and make is usually provided by `make`. The go compiler must be version 1.16 or newer for the `io/fs` module.
|
||||||
|
|
||||||
|
To install, run
|
||||||
|
```shell
|
||||||
|
make && sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ To cross compile, simply set the go environment variables. For example, for Pine
|
|||||||
make GOOS=linux GOARCH=arm64
|
make GOOS=linux GOARCH=arm64
|
||||||
```
|
```
|
||||||
|
|
||||||
This will compile `itd` and `itctl` for Linux aarch64 which is what runs on the PinePhone. This daemon only runs on Linux due to the library's dependencies (`dbus`, and `bluez` specifically).
|
This will compile `itd` and `itctl` for Linux aarch64 which is what runs on the PinePhone. This daemon only runs on Linux due to the library's dependencies (`dbus`, `bluez`, and `playerctl` specifically).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -186,11 +186,3 @@ This will compile `itd` and `itctl` for Linux aarch64 which is what runs on the
|
|||||||
This daemon places a config file at `/etc/itd.toml`. This is the global config. `itd` will also look for a config at `~/.config/itd.toml`.
|
This daemon places a config file at `/etc/itd.toml`. This is the global config. `itd` will also look for a config at `~/.config/itd.toml`.
|
||||||
|
|
||||||
Most of the time, the daemon does not need to be restarted for config changes to take effect.
|
Most of the time, the daemon does not need to be restarted for config changes to take effect.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Attribution
|
|
||||||
|
|
||||||
Location data from OpenStreetMap Nominatim, © [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors
|
|
||||||
|
|
||||||
Weather data from the [Norwegian Meteorological Institute](https://www.met.no/en)
|
|
||||||
|
62
api/api.go
@ -1,62 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"go.elara.ws/drpc/muxconn"
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
"storj.io/drpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultAddr = "/tmp/itd/socket"
|
|
||||||
|
|
||||||
// Client is a client for ITD's socket API
|
|
||||||
type Client struct {
|
|
||||||
conn drpc.Conn
|
|
||||||
client rpc.DRPCITDClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// New connects to the UNIX socket at the given
|
|
||||||
// path, and returns a client that communicates
|
|
||||||
// with that socket.
|
|
||||||
func New(sockPath string) (*Client, error) {
|
|
||||||
conn, err := net.Dial("unix", sockPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mconn, err := muxconn.New(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
conn: mconn,
|
|
||||||
client: rpc.NewDRPCITDClient(mconn),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFromConn returns a client that communicates
|
|
||||||
// over the given connection.
|
|
||||||
func NewFromConn(conn io.ReadWriteCloser) (*Client, error) {
|
|
||||||
mconn, err := muxconn.New(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
conn: mconn,
|
|
||||||
client: rpc.NewDRPCITDClient(mconn),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FS returns the filesystem API client
|
|
||||||
func (c *Client) FS() *FSClient {
|
|
||||||
return &FSClient{rpc.NewDRPCFSClient(c.conn)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the client connection
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
133
api/client.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"go.arsenm.dev/infinitime"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default socket address
|
||||||
|
const DefaultAddr = "/tmp/itd/socket"
|
||||||
|
|
||||||
|
// Client is the socket API client
|
||||||
|
type Client struct {
|
||||||
|
conn net.Conn
|
||||||
|
respCh chan types.Response
|
||||||
|
heartRateCh chan types.Response
|
||||||
|
battLevelCh chan types.Response
|
||||||
|
stepCountCh chan types.Response
|
||||||
|
motionCh chan types.Response
|
||||||
|
dfuProgressCh chan types.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new client and sets it up
|
||||||
|
func New(addr string) (*Client, error) {
|
||||||
|
conn, err := net.Dial("unix", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &Client{
|
||||||
|
conn: conn,
|
||||||
|
respCh: make(chan types.Response, 5),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
scanner := bufio.NewScanner(conn)
|
||||||
|
for scanner.Scan() {
|
||||||
|
var res types.Response
|
||||||
|
err = json.Unmarshal(scanner.Bytes(), &res)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out.handleResp(res)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
err := c.conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
close(c.respCh)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// request sends a request to itd and waits for and returns the response
|
||||||
|
func (c *Client) request(req types.Request) (types.Response, error) {
|
||||||
|
// Encode request into connection
|
||||||
|
err := json.NewEncoder(c.conn).Encode(req)
|
||||||
|
if err != nil {
|
||||||
|
return types.Response{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := <-c.respCh
|
||||||
|
|
||||||
|
if res.Error {
|
||||||
|
return res, errors.New(res.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestNoRes sends a request to itd and does not wait for the response
|
||||||
|
func (c *Client) requestNoRes(req types.Request) error {
|
||||||
|
// Encode request into connection
|
||||||
|
err := json.NewEncoder(c.conn).Encode(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleResp handles the received response as needed
|
||||||
|
func (c *Client) handleResp(res types.Response) error {
|
||||||
|
switch res.Type {
|
||||||
|
case types.ReqTypeWatchHeartRate:
|
||||||
|
c.heartRateCh <- res
|
||||||
|
case types.ReqTypeWatchBattLevel:
|
||||||
|
c.battLevelCh <- res
|
||||||
|
case types.ReqTypeWatchStepCount:
|
||||||
|
c.stepCountCh <- res
|
||||||
|
case types.ReqTypeWatchMotion:
|
||||||
|
c.motionCh <- res
|
||||||
|
case types.ReqTypeFwUpgrade:
|
||||||
|
c.dfuProgressCh <- res
|
||||||
|
default:
|
||||||
|
c.respCh <- res
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUint8(val interface{}) uint8 {
|
||||||
|
return uint8(val.(float64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUint32(val interface{}) uint32 {
|
||||||
|
return uint32(val.(float64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMotion(val interface{}) (infinitime.MotionValues, error) {
|
||||||
|
out := infinitime.MotionValues{}
|
||||||
|
err := mapstructure.Decode(val, &out)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDFUProgress(val interface{}) (DFUProgress, error) {
|
||||||
|
out := DFUProgress{}
|
||||||
|
err := mapstructure.Decode(val, &out)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DFUProgress struct {
|
|
||||||
Sent int64
|
|
||||||
Received int64
|
|
||||||
Total int64
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) FirmwareUpgrade(ctx context.Context, upgType UpgradeType, files ...string) (chan DFUProgress, error) {
|
|
||||||
progressCh := make(chan DFUProgress, 5)
|
|
||||||
fc, err := c.client.FirmwareUpgrade(ctx, &rpc.FirmwareUpgradeRequest{
|
|
||||||
Type: rpc.FirmwareUpgradeRequest_Type(upgType),
|
|
||||||
Files: files,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go fsRecvToChannel[rpc.DFUProgress](fc, progressCh, func(evt *rpc.DFUProgress, err error) DFUProgress {
|
|
||||||
return DFUProgress{
|
|
||||||
Sent: evt.Sent,
|
|
||||||
Received: evt.Recieved,
|
|
||||||
Total: evt.Total,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return progressCh, nil
|
|
||||||
}
|
|
164
api/fs.go
@ -1,122 +1,96 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/mitchellh/mapstructure"
|
||||||
"errors"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
"io"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FSClient struct {
|
func (c *Client) Rename(old, new string) error {
|
||||||
client rpc.DRPCFSClient
|
_, err := c.request(types.Request{
|
||||||
}
|
Type: types.ReqTypeFS,
|
||||||
|
Data: types.ReqDataFS{
|
||||||
func (c *FSClient) RemoveAll(ctx context.Context, paths ...string) error {
|
Type: types.FSTypeMove,
|
||||||
_, err := c.client.RemoveAll(ctx, &rpc.PathsRequest{Paths: paths})
|
Files: []string{old, new},
|
||||||
return err
|
},
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FSClient) Remove(ctx context.Context, paths ...string) error {
|
|
||||||
_, err := c.client.Remove(ctx, &rpc.PathsRequest{Paths: paths})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FSClient) Rename(ctx context.Context, old, new string) error {
|
|
||||||
_, err := c.client.Rename(ctx, &rpc.RenameRequest{
|
|
||||||
From: old,
|
|
||||||
To: new,
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
func (c *FSClient) MkdirAll(ctx context.Context, paths ...string) error {
|
|
||||||
_, err := c.client.MkdirAll(ctx, &rpc.PathsRequest{Paths: paths})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FSClient) Mkdir(ctx context.Context, paths ...string) error {
|
func (c *Client) Remove(paths ...string) error {
|
||||||
_, err := c.client.Mkdir(ctx, &rpc.PathsRequest{Paths: paths})
|
_, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeFS,
|
||||||
|
Data: types.ReqDataFS{
|
||||||
|
Type: types.FSTypeDelete,
|
||||||
|
Files: paths,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *FSClient) ReadDir(ctx context.Context, dir string) ([]FileInfo, error) {
|
func (c *Client) Mkdir(paths ...string) error {
|
||||||
res, err := c.client.ReadDir(ctx, &rpc.PathRequest{Path: dir})
|
_, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeFS,
|
||||||
|
Data: types.ReqDataFS{
|
||||||
|
Type: types.FSTypeMkdir,
|
||||||
|
Files: paths,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ReadDir(path string) ([]types.FileInfo, error) {
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeFS,
|
||||||
|
Data: types.ReqDataFS{
|
||||||
|
Type: types.FSTypeList,
|
||||||
|
Files: []string{path},
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return convertEntries(res.Entries), nil
|
var out []types.FileInfo
|
||||||
}
|
err = mapstructure.Decode(res.Value, &out)
|
||||||
|
|
||||||
func convertEntries(e []*rpc.FileInfo) []FileInfo {
|
|
||||||
out := make([]FileInfo, len(e))
|
|
||||||
for i, fi := range e {
|
|
||||||
out[i] = FileInfo{
|
|
||||||
Name: fi.Name,
|
|
||||||
Size: fi.Size,
|
|
||||||
IsDir: fi.IsDir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FSClient) Upload(ctx context.Context, dst, src string) (chan FSTransferProgress, error) {
|
|
||||||
progressCh := make(chan FSTransferProgress, 5)
|
|
||||||
tc, err := c.client.Upload(ctx, &rpc.TransferRequest{Source: src, Destination: dst})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return out, nil
|
||||||
go fsRecvToChannel[rpc.TransferProgress](tc, progressCh, func(evt *rpc.TransferProgress, err error) FSTransferProgress {
|
|
||||||
return FSTransferProgress{
|
|
||||||
Sent: evt.Sent,
|
|
||||||
Total: evt.Total,
|
|
||||||
Err: err,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ReadFile(path string) (string, error) {
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeFS,
|
||||||
|
Data: types.ReqDataFS{
|
||||||
|
Type: types.FSTypeRead,
|
||||||
|
Files: []string{path},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return progressCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FSClient) Download(ctx context.Context, dst, src string) (chan FSTransferProgress, error) {
|
|
||||||
progressCh := make(chan FSTransferProgress, 5)
|
|
||||||
tc, err := c.client.Download(ctx, &rpc.TransferRequest{Source: src, Destination: dst})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
|
}
|
||||||
|
return res.Value.(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
go fsRecvToChannel[rpc.TransferProgress](tc, progressCh, func(evt *rpc.TransferProgress, err error) FSTransferProgress {
|
func (c *Client) WriteFile(path, data string) error {
|
||||||
return FSTransferProgress{
|
_, err := c.request(types.Request{
|
||||||
Sent: evt.Sent,
|
Type: types.ReqTypeFS,
|
||||||
Total: evt.Total,
|
Data: types.ReqDataFS{
|
||||||
Err: err,
|
Type: types.FSTypeWrite,
|
||||||
}
|
Files: []string{path},
|
||||||
|
Data: data,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
return progressCh, nil
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
// fsRecvToChannel converts a DRPC stream client to a Go channel, using cf to convert
|
|
||||||
// RPC generated types to API response types.
|
|
||||||
func fsRecvToChannel[R any, A any](s StreamClient[R], ch chan<- A, cf func(evt *R, err error) A) {
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var evt *R
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.Context().Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
evt, err = s.Recv()
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
ch <- cf(new(R), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch <- cf(evt, nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
41
api/get.go
@ -1,41 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) HeartRate(ctx context.Context) (uint8, error) {
|
|
||||||
res, err := c.client.HeartRate(ctx, &rpc.Empty{})
|
|
||||||
return uint8(res.Value), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) BatteryLevel(ctx context.Context) (uint8, error) {
|
|
||||||
res, err := c.client.BatteryLevel(ctx, &rpc.Empty{})
|
|
||||||
return uint8(res.Value), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type MotionValues struct {
|
|
||||||
X, Y, Z int16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Motion(ctx context.Context) (MotionValues, error) {
|
|
||||||
res, err := c.client.Motion(ctx, &rpc.Empty{})
|
|
||||||
return MotionValues{int16(res.X), int16(res.Y), int16(res.Z)}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) StepCount(ctx context.Context) (out uint32, err error) {
|
|
||||||
res, err := c.client.StepCount(ctx, &rpc.Empty{})
|
|
||||||
return res.Value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Version(ctx context.Context) (out string, err error) {
|
|
||||||
res, err := c.client.Version(ctx, &rpc.Empty{})
|
|
||||||
return res.Value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Address(ctx context.Context) (out string, err error) {
|
|
||||||
res, err := c.client.Address(ctx, &rpc.Empty{})
|
|
||||||
return res.Value, err
|
|
||||||
}
|
|
209
api/info.go
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"go.arsenm.dev/infinitime"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Address gets the bluetooth address of the connected device
|
||||||
|
func (c *Client) Address() (string, error) {
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeBtAddress,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Value.(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version gets the firmware version of the connected device
|
||||||
|
func (c *Client) Version() (string, error) {
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeFwVersion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Value.(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatteryLevel gets the battery level of the connected device
|
||||||
|
func (c *Client) BatteryLevel() (uint8, error) {
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeBattLevel,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint8(res.Value.(float64)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchBatteryLevel returns a channel which will contain
|
||||||
|
// new battery level values as they update. Do not use after
|
||||||
|
// calling cancellation function
|
||||||
|
func (c *Client) WatchBatteryLevel() (<-chan uint8, func(), error) {
|
||||||
|
c.battLevelCh = make(chan types.Response, 2)
|
||||||
|
err := c.requestNoRes(types.Request{
|
||||||
|
Type: types.ReqTypeWatchBattLevel,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
res := <-c.battLevelCh
|
||||||
|
done, cancel := c.cancelFn(res.ID, c.battLevelCh)
|
||||||
|
out := make(chan uint8, 2)
|
||||||
|
go func() {
|
||||||
|
for res := range c.battLevelCh {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
out <- decodeUint8(res.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return out, cancel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeartRate gets the heart rate from the connected device
|
||||||
|
func (c *Client) HeartRate() (uint8, error) {
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeHeartRate,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeUint8(res.Value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchHeartRate returns a channel which will contain
|
||||||
|
// new heart rate values as they update. Do not use after
|
||||||
|
// calling cancellation function
|
||||||
|
func (c *Client) WatchHeartRate() (<-chan uint8, func(), error) {
|
||||||
|
c.heartRateCh = make(chan types.Response, 2)
|
||||||
|
err := c.requestNoRes(types.Request{
|
||||||
|
Type: types.ReqTypeWatchHeartRate,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
res := <-c.heartRateCh
|
||||||
|
done, cancel := c.cancelFn(res.ID, c.heartRateCh)
|
||||||
|
out := make(chan uint8, 2)
|
||||||
|
go func() {
|
||||||
|
for res := range c.heartRateCh {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
out <- decodeUint8(res.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return out, cancel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancelFn generates a cancellation function for the given
|
||||||
|
// request type and channel
|
||||||
|
func (c *Client) cancelFn(reqID string, ch chan types.Response) (chan struct{}, func()) {
|
||||||
|
done := make(chan struct{}, 1)
|
||||||
|
return done, func() {
|
||||||
|
done <- struct{}{}
|
||||||
|
close(ch)
|
||||||
|
c.requestNoRes(types.Request{
|
||||||
|
Type: types.ReqTypeCancel,
|
||||||
|
Data: reqID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepCount gets the step count from the connected device
|
||||||
|
func (c *Client) StepCount() (uint32, error) {
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeStepCount,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint32(res.Value.(float64)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchStepCount returns a channel which will contain
|
||||||
|
// new step count values as they update. Do not use after
|
||||||
|
// calling cancellation function
|
||||||
|
func (c *Client) WatchStepCount() (<-chan uint32, func(), error) {
|
||||||
|
c.stepCountCh = make(chan types.Response, 2)
|
||||||
|
err := c.requestNoRes(types.Request{
|
||||||
|
Type: types.ReqTypeWatchStepCount,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
res := <-c.stepCountCh
|
||||||
|
done, cancel := c.cancelFn(res.ID, c.stepCountCh)
|
||||||
|
out := make(chan uint32, 2)
|
||||||
|
go func() {
|
||||||
|
for res := range c.stepCountCh {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
out <- decodeUint32(res.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return out, cancel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Motion gets the motion values from the connected device
|
||||||
|
func (c *Client) Motion() (infinitime.MotionValues, error) {
|
||||||
|
out := infinitime.MotionValues{}
|
||||||
|
res, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeMotion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
err = mapstructure.Decode(res.Value, &out)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchMotion returns a channel which will contain
|
||||||
|
// new motion values as they update. Do not use after
|
||||||
|
// calling cancellation function
|
||||||
|
func (c *Client) WatchMotion() (<-chan infinitime.MotionValues, func(), error) {
|
||||||
|
c.motionCh = make(chan types.Response, 5)
|
||||||
|
err := c.requestNoRes(types.Request{
|
||||||
|
Type: types.ReqTypeWatchMotion,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
res := <-c.motionCh
|
||||||
|
done, cancel := c.cancelFn(res.ID, c.motionCh)
|
||||||
|
out := make(chan infinitime.MotionValues, 5)
|
||||||
|
go func() {
|
||||||
|
for res := range c.motionCh {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
motion, err := decodeMotion(res.Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out <- motion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return out, cancel, nil
|
||||||
|
}
|
@ -1,15 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import "go.arsenm.dev/itd/internal/types"
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
func (c *Client) Notify(title string, body string) error {
|
||||||
)
|
_, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeNotify,
|
||||||
func (c *Client) Notify(ctx context.Context, title, body string) error {
|
Data: types.ReqDataNotify{
|
||||||
_, err := c.client.Notify(ctx, &rpc.NotifyRequest{
|
|
||||||
Title: title,
|
Title: title,
|
||||||
Body: body,
|
Body: body,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
@ -1,51 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/infinitime"
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResourceOperation infinitime.ResourceOperation
|
|
||||||
|
|
||||||
const (
|
|
||||||
ResourceRemove = infinitime.ResourceRemove
|
|
||||||
ResourceUpload = infinitime.ResourceUpload
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResourceLoadProgress struct {
|
|
||||||
Operation ResourceOperation
|
|
||||||
Name string
|
|
||||||
Total int64
|
|
||||||
Sent int64
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadResources loads resources onto the watch from the given
|
|
||||||
// file path to the resources zip
|
|
||||||
func (c *FSClient) LoadResources(ctx context.Context, path string) (<-chan ResourceLoadProgress, error) {
|
|
||||||
progCh := make(chan ResourceLoadProgress, 2)
|
|
||||||
|
|
||||||
rc, err := c.client.LoadResources(ctx, &rpc.PathRequest{Path: path})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go fsRecvToChannel[rpc.ResourceLoadProgress](rc, progCh, func(evt *rpc.ResourceLoadProgress, err error) ResourceLoadProgress {
|
|
||||||
return ResourceLoadProgress{
|
|
||||||
Operation: ResourceOperation(evt.Operation),
|
|
||||||
Name: evt.Name,
|
|
||||||
Sent: evt.Sent,
|
|
||||||
Total: evt.Total,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return progCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamClient[T any] interface {
|
|
||||||
Recv() (*T, error)
|
|
||||||
Context() context.Context
|
|
||||||
}
|
|
13
api/set.go
@ -1,13 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) SetTime(ctx context.Context, t time.Time) error {
|
|
||||||
_, err := c.client.SetTime(ctx, &rpc.SetTimeRequest{UnixNano: t.UnixNano()})
|
|
||||||
return err
|
|
||||||
}
|
|
33
api/time.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetTime sets the given time on the connected device
|
||||||
|
func (c *Client) SetTime(t time.Time) error {
|
||||||
|
_, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeSetTime,
|
||||||
|
Data: t.Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeNow sets the time on the connected device to
|
||||||
|
// the current time. This is more accurate than
|
||||||
|
// SetTime(time.Now()) due to RFC3339 formatting
|
||||||
|
func (c *Client) SetTimeNow() error {
|
||||||
|
_, err := c.request(types.Request{
|
||||||
|
Type: types.ReqTypeSetTime,
|
||||||
|
Data: "now",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) WeatherUpdate(ctx context.Context) error {
|
|
||||||
_, err := c.client.WeatherUpdate(ctx, &rpc.Empty{})
|
|
||||||
return err
|
|
||||||
}
|
|
48
api/upgrade.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DFUProgress stores the progress of a DFU upfate
|
||||||
|
type DFUProgress types.DFUProgress
|
||||||
|
|
||||||
|
// UpgradeType indicates the type of upgrade to be performed
|
||||||
|
type UpgradeType uint8
|
||||||
|
|
||||||
|
// Type of DFU upgrade
|
||||||
|
const (
|
||||||
|
UpgradeTypeArchive UpgradeType = iota
|
||||||
|
UpgradeTypeFiles
|
||||||
|
)
|
||||||
|
|
||||||
|
// FirmwareUpgrade initiates a DFU update and returns the progress channel
|
||||||
|
func (c *Client) FirmwareUpgrade(upgType UpgradeType, files ...string) (<-chan DFUProgress, error) {
|
||||||
|
err := json.NewEncoder(c.conn).Encode(types.Request{
|
||||||
|
Type: types.ReqTypeFwUpgrade,
|
||||||
|
Data: types.ReqDataFwUpgrade{
|
||||||
|
Type: int(upgType),
|
||||||
|
Files: files,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.dfuProgressCh = make(chan types.Response, 5)
|
||||||
|
|
||||||
|
out := make(chan DFUProgress, 5)
|
||||||
|
go func() {
|
||||||
|
for res := range c.dfuProgressCh {
|
||||||
|
progress, err := decodeDFUProgress(res.Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out <- progress
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
135
api/watch.go
@ -1,135 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) WatchHeartRate(ctx context.Context) (<-chan uint8, error) {
|
|
||||||
outCh := make(chan uint8, 2)
|
|
||||||
wc, err := c.client.WatchHeartRate(ctx, &rpc.Empty{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(outCh)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var evt *rpc.IntResponse
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
wc.Close()
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
evt, err = wc.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outCh <- uint8(evt.Value)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return outCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WatchBatteryLevel(ctx context.Context) (<-chan uint8, error) {
|
|
||||||
outCh := make(chan uint8, 2)
|
|
||||||
wc, err := c.client.WatchBatteryLevel(ctx, &rpc.Empty{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(outCh)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var evt *rpc.IntResponse
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
wc.Close()
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
evt, err = wc.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outCh <- uint8(evt.Value)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return outCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WatchStepCount(ctx context.Context) (<-chan uint32, error) {
|
|
||||||
outCh := make(chan uint32, 2)
|
|
||||||
wc, err := c.client.WatchStepCount(ctx, &rpc.Empty{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(outCh)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var evt *rpc.IntResponse
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
wc.Close()
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
evt, err = wc.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outCh <- evt.Value
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return outCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WatchMotion(ctx context.Context) (<-chan MotionValues, error) {
|
|
||||||
outCh := make(chan MotionValues, 2)
|
|
||||||
wc, err := c.client.WatchMotion(ctx, &rpc.Empty{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(outCh)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var evt *rpc.MotionResponse
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
wc.Close()
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
evt, err = wc.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outCh <- MotionValues{int16(evt.X), int16(evt.Y), int16(evt.Z)}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return outCh, nil
|
|
||||||
}
|
|
142
calls.go
@ -1,40 +1,26 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
"go.elara.ws/itd/infinitime"
|
"github.com/rs/zerolog/log"
|
||||||
"go.elara.ws/itd/internal/utils"
|
"go.arsenm.dev/infinitime"
|
||||||
"go.elara.ws/logger/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func initCallNotifs(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
|
func initCallNotifs(dev *infinitime.Device) error {
|
||||||
// Connect to system bus. This connection is for method calls.
|
// Define rule to filter dbus messages
|
||||||
conn, err := utils.NewSystemBusConn(ctx)
|
//rule := "type='signal',sender='org.freedesktop.ModemManager1',interface='org.freedesktop.ModemManager1.Modem.Voice',member='CallAdded'"
|
||||||
|
|
||||||
|
// Connect to dbus session monitorConn
|
||||||
|
monitorConn, err := newSystemBusConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if modem manager interface exists
|
conn, err := newSystemBusConn()
|
||||||
exists, err := modemManagerExists(ctx, conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it does not exist, stop function
|
|
||||||
if !exists {
|
|
||||||
conn.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to system bus. This connection is for monitoring.
|
|
||||||
monitorConn, err := utils.NewSystemBusConn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add match for new calls to monitor connection
|
|
||||||
err = monitorConn.AddMatchSignal(
|
err = monitorConn.AddMatchSignal(
|
||||||
dbus.WithMatchSender("org.freedesktop.ModemManager1"),
|
dbus.WithMatchSender("org.freedesktop.ModemManager1"),
|
||||||
dbus.WithMatchInterface("org.freedesktop.ModemManager1.Modem.Voice"),
|
dbus.WithMatchInterface("org.freedesktop.ModemManager1.Modem.Voice"),
|
||||||
@ -44,90 +30,51 @@ func initCallNotifs(ctx context.Context, wg WaitGroup, dev *infinitime.Device) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create channel to receive calls
|
c := make(chan *dbus.Message, 10)
|
||||||
callCh := make(chan *dbus.Message, 5)
|
monitorConn.Eavesdrop(c)
|
||||||
// Notify channel upon received message
|
|
||||||
monitorConn.Eavesdrop(callCh)
|
|
||||||
|
|
||||||
var callObj dbus.BusObject
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done("callNotifs")
|
for x := range c {
|
||||||
for {
|
callPath := x.Body[0].(dbus.ObjectPath)
|
||||||
select {
|
callObj := conn.Object("org.freedesktop.ModemManager1", callPath)
|
||||||
case event := <-callCh:
|
|
||||||
// Get path to call object
|
|
||||||
callPath := event.Body[0].(dbus.ObjectPath)
|
|
||||||
// Get call object
|
|
||||||
callObj = conn.Object("org.freedesktop.ModemManager1", callPath)
|
|
||||||
|
|
||||||
// Get phone number from call object using method call connection
|
|
||||||
phoneNum, err := getPhoneNum(conn, callObj)
|
phoneNum, err := getPhoneNum(conn, callObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting phone number").Err(err).Send()
|
log.Fatal().Err(err).Send()
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get direction of call object using method call connection
|
resCh, err := dev.NotifyCall(phoneNum)
|
||||||
direction, err := getDirection(conn, callObj)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting call direction").Err(err).Send()
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if direction != MMCallDirectionIncoming {
|
go func() {
|
||||||
continue
|
// Wait for PineTime response
|
||||||
}
|
res := <-resCh
|
||||||
|
switch res {
|
||||||
// Send call notification to InfiniTime
|
|
||||||
err = dev.NotifyCall(phoneNum, func(cs infinitime.CallStatus) {
|
|
||||||
switch cs {
|
|
||||||
case infinitime.CallStatusAccepted:
|
case infinitime.CallStatusAccepted:
|
||||||
// Attempt to accept call
|
// Attempt to accept call
|
||||||
err = acceptCall(ctx, conn, callObj)
|
err = acceptCall(conn, callObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error accepting call").Err(err).Send()
|
log.Warn().Err(err).Msg("Error accepting call")
|
||||||
}
|
}
|
||||||
case infinitime.CallStatusDeclined:
|
case infinitime.CallStatusDeclined:
|
||||||
// Attempt to decline call
|
// Attempt to decline call
|
||||||
err = declineCall(ctx, conn, callObj)
|
err = declineCall(conn, callObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Error declining call").Err(err).Send()
|
log.Warn().Err(err).Msg("Error declining call")
|
||||||
}
|
}
|
||||||
case infinitime.CallStatusMuted:
|
case infinitime.CallStatusMuted:
|
||||||
// Warn about unimplemented muting
|
// Warn about unimplemented muting
|
||||||
log.Warn("Muting calls is not implemented").Send()
|
log.Warn().Msg("Muting calls is not implemented")
|
||||||
}
|
}
|
||||||
})
|
}()
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info("Relaying calls to InfiniTime").Send()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func modemManagerExists(ctx context.Context, conn *dbus.Conn) (bool, error) {
|
|
||||||
var names []string
|
|
||||||
err := conn.BusObject().CallWithContext(
|
|
||||||
ctx, "org.freedesktop.DBus.ListNames", 0,
|
|
||||||
).Store(&names)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return strSlcContains(names, "org.freedesktop.ModemManager1"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPhoneNum gets a phone number from a call object using a DBus connection
|
|
||||||
func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
|
func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
|
||||||
var out string
|
var out string
|
||||||
// Get number property on DBus object and store return value in out
|
|
||||||
err := callObj.StoreProperty("org.freedesktop.ModemManager1.Call.Number", &out)
|
err := callObj.StoreProperty("org.freedesktop.ModemManager1.Call.Number", &out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -135,43 +82,16 @@ func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type MMCallDirection int
|
func acceptCall(conn *dbus.Conn, callObj dbus.BusObject) error {
|
||||||
|
call := callObj.Call("org.freedesktop.ModemManager1.Call.Accept", 0)
|
||||||
const (
|
|
||||||
MMCallDirectionUnknown MMCallDirection = iota
|
|
||||||
MMCallDirectionIncoming
|
|
||||||
MMCallDirectionOutgoing
|
|
||||||
)
|
|
||||||
|
|
||||||
// getDirection gets the direction of a call object using a DBus connection
|
|
||||||
func getDirection(conn *dbus.Conn, callObj dbus.BusObject) (MMCallDirection, error) {
|
|
||||||
var out MMCallDirection
|
|
||||||
// Get number property on DBus object and store return value in out
|
|
||||||
err := callObj.StoreProperty("org.freedesktop.ModemManager1.Call.Direction", &out)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPhoneNum accepts a call using a DBus connection
|
|
||||||
func acceptCall(ctx context.Context, conn *dbus.Conn, callObj dbus.BusObject) error {
|
|
||||||
// Call Accept() method on DBus object
|
|
||||||
call := callObj.CallWithContext(
|
|
||||||
ctx, "org.freedesktop.ModemManager1.Call.Accept", 0,
|
|
||||||
)
|
|
||||||
if call.Err != nil {
|
if call.Err != nil {
|
||||||
return call.Err
|
return call.Err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPhoneNum declines a call using a DBus connection
|
func declineCall(conn *dbus.Conn, callObj dbus.BusObject) error {
|
||||||
func declineCall(ctx context.Context, conn *dbus.Conn, callObj dbus.BusObject) error {
|
call := callObj.Call("org.freedesktop.ModemManager1.Call.Hangup", 0)
|
||||||
// Call Hangup() method on DBus object
|
|
||||||
call := callObj.CallWithContext(
|
|
||||||
ctx, "org.freedesktop.ModemManager1.Call.Hangup", 0,
|
|
||||||
)
|
|
||||||
if call.Err != nil {
|
if call.Err != nil {
|
||||||
return call.Err
|
return call.Err
|
||||||
}
|
}
|
||||||
|
35
cmd/itctl/filesystem/filesystem.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// filesystemCmd represents the get command
|
||||||
|
var filesystemCmd = &cobra.Command{
|
||||||
|
Use: "filesystem",
|
||||||
|
Aliases: []string{"fs"},
|
||||||
|
Short: "Perform filesystem operations on the PineTime",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
root.RootCmd.AddCommand(filesystemCmd)
|
||||||
|
}
|
56
cmd/itctl/filesystem/list.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// listCmd represents the heart command
|
||||||
|
var listCmd = &cobra.Command{
|
||||||
|
Use: "list [path]",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "List a directory",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
dirPath := "/"
|
||||||
|
if len(args) > 0 {
|
||||||
|
dirPath = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
listing, err := client.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting directory listing")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range listing {
|
||||||
|
fmt.Println(entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filesystemCmd.AddCommand(listCmd)
|
||||||
|
}
|
49
cmd/itctl/filesystem/mkdir.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the heart command
|
||||||
|
var mkdirCmd = &cobra.Command{
|
||||||
|
Use: "mkdir <path...>",
|
||||||
|
Short: "Create a new directory",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Fatal().Msg("Command mkdir requires one or more arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
err := client.Mkdir(args...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating directory")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filesystemCmd.AddCommand(mkdirCmd)
|
||||||
|
}
|
50
cmd/itctl/filesystem/move.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the heart command
|
||||||
|
var moveCmd = &cobra.Command{
|
||||||
|
Use: "move <old> <new>",
|
||||||
|
Aliases: []string{"mv"},
|
||||||
|
Short: "Move a file or directory",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Fatal().Msg("Command move requires two arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
err := client.Rename(args[0], args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error moving file or directory")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filesystemCmd.AddCommand(moveCmd)
|
||||||
|
}
|
69
cmd/itctl/filesystem/read.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the heart command
|
||||||
|
var readCmd = &cobra.Command{
|
||||||
|
Use: `read <remote path> <local path | "-">`,
|
||||||
|
Short: "Read a file from InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Fatal().Msg("Command read requires two arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
data, err := client.ReadFile(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error moving file or directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
var out *os.File
|
||||||
|
if args[1] == "-" {
|
||||||
|
out = os.Stdout
|
||||||
|
} else {
|
||||||
|
out, err = os.Create(args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error opening local file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := out.WriteString(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error writing to local file")
|
||||||
|
}
|
||||||
|
log.Info().Msgf("Read %d bytes in %s", n, time.Since(start))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filesystemCmd.AddCommand(readCmd)
|
||||||
|
}
|
50
cmd/itctl/filesystem/remove.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the heart command
|
||||||
|
var removeCmd = &cobra.Command{
|
||||||
|
Use: "remove <path...>",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Short: "Create a new directory",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Fatal().Msg("Command mkdir requires one or more arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
err := client.Remove(args...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error removing file or directory")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filesystemCmd.AddCommand(removeCmd)
|
||||||
|
}
|
72
cmd/itctl/filesystem/write.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the heart command
|
||||||
|
var writeCmd = &cobra.Command{
|
||||||
|
Use: `write <local path | "-"> <remote path>`,
|
||||||
|
Short: "Write a file to InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Fatal().Msg("Command write requires two arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
var in *os.File
|
||||||
|
if args[0] == "-" {
|
||||||
|
in = os.Stdin
|
||||||
|
} else {
|
||||||
|
fl, err := os.Open(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error opening local file")
|
||||||
|
}
|
||||||
|
in = fl
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(in)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error moving file or directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.WriteFile(args[1], string(data))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error writing to remote file")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("Wrote %d bytes in %s", len(data), time.Since(start))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filesystemCmd.AddCommand(writeCmd)
|
||||||
|
}
|
@ -1,98 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cheggaaa/pb/v3"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.elara.ws/itd/api"
|
|
||||||
"go.elara.ws/logger/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fwUpgrade(c *cli.Context) error {
|
|
||||||
resources := c.String("resources")
|
|
||||||
if resources != "" {
|
|
||||||
absRes, err := filepath.Abs(resources)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = resLoad(c.Context, []string{absRes})
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Resource loading has returned an error. This can happen if your current version of InfiniTime doesn't support BLE FS. Try updating without resource loading, and then load them after using the `itctl res load` command.").Send()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
var upgType api.UpgradeType
|
|
||||||
var files []string
|
|
||||||
// Get relevant data struct
|
|
||||||
if c.String("archive") != "" {
|
|
||||||
// Get archive data struct
|
|
||||||
upgType = api.UpgradeTypeArchive
|
|
||||||
files = []string{c.String("archive")}
|
|
||||||
} else if c.String("init-packet") != "" && c.String("firmware") != "" {
|
|
||||||
// Get files data struct
|
|
||||||
upgType = api.UpgradeTypeFiles
|
|
||||||
files = []string{c.String("init-packet"), c.String("firmware")}
|
|
||||||
} else {
|
|
||||||
return cli.Exit("Upgrade command requires either archive or init packet and firmware.", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
progress, err := client.FirmwareUpgrade(c.Context, upgType, abs(files)...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create progress bar template
|
|
||||||
barTmpl := `{{counters . }} B {{bar . "|" "-" (cycle .) " " "|"}} {{percent . }} {{rtime . "%s"}}`
|
|
||||||
// Start full bar at 0 total
|
|
||||||
bar := pb.ProgressBarTemplate(barTmpl).Start(0)
|
|
||||||
// Create new scanner of connection
|
|
||||||
for event := range progress {
|
|
||||||
if event.Err != nil {
|
|
||||||
return event.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set total bytes in progress bar
|
|
||||||
bar.SetTotal(event.Total)
|
|
||||||
// Set amount of bytes received in progress bar
|
|
||||||
bar.SetCurrent(int64(event.Received))
|
|
||||||
// If transfer finished, break
|
|
||||||
if int64(event.Sent) == event.Total {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Finish progress bar
|
|
||||||
bar.Finish()
|
|
||||||
|
|
||||||
fmt.Printf("Transferred %d B in %s.\n", bar.Total(), time.Since(start))
|
|
||||||
fmt.Println("Remember to validate the new firmware in the InfiniTime settings.")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fwVersion(c *cli.Context) error {
|
|
||||||
version, err := client.Version(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(version)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func abs(paths []string) []string {
|
|
||||||
for index, path := range paths {
|
|
||||||
newPath, err := filepath.Abs(path)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
paths[index] = newPath
|
|
||||||
}
|
|
||||||
return paths
|
|
||||||
}
|
|
35
cmd/itctl/firmware/firmware.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package firmware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// firmwareCmd represents the firmware command
|
||||||
|
var firmwareCmd = &cobra.Command{
|
||||||
|
Use: "firmware",
|
||||||
|
Short: "Manage InfiniTime firmware",
|
||||||
|
Aliases: []string{"fw"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
root.RootCmd.AddCommand(firmwareCmd)
|
||||||
|
}
|
105
cmd/itctl/firmware/upgrade.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package firmware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cheggaaa/pb/v3"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DFUProgress struct {
|
||||||
|
Received int64 `mapstructure:"recvd"`
|
||||||
|
Total int64 `mapstructure:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// upgradeCmd represents the upgrade command
|
||||||
|
var upgradeCmd = &cobra.Command{
|
||||||
|
Use: "upgrade",
|
||||||
|
Short: "Upgrade InfiniTime firmware using files or archive",
|
||||||
|
Aliases: []string{"upg"},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
var upgType api.UpgradeType
|
||||||
|
var files []string
|
||||||
|
// Get relevant data struct
|
||||||
|
if viper.GetString("archive") != "" {
|
||||||
|
// Get archive data struct
|
||||||
|
upgType = types.UpgradeTypeArchive
|
||||||
|
files = []string{viper.GetString("archive")}
|
||||||
|
} else if viper.GetString("initPkt") != "" && viper.GetString("firmware") != "" {
|
||||||
|
// Get files data struct
|
||||||
|
upgType = types.UpgradeTypeFiles
|
||||||
|
files = []string{viper.GetString("initPkt"), viper.GetString("firmware")}
|
||||||
|
} else {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Warn().Msg("Upgrade command requires either archive or init packet and firmware.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
progress, err := client.FirmwareUpgrade(upgType, files...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error initiating DFU")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create progress bar template
|
||||||
|
barTmpl := `{{counters . }} B {{bar . "|" "-" (cycle .) " " "|"}} {{percent . }} {{rtime . "%s"}}`
|
||||||
|
// Start full bar at 0 total
|
||||||
|
bar := pb.ProgressBarTemplate(barTmpl).Start(0)
|
||||||
|
// Create new scanner of connection
|
||||||
|
for event := range progress {
|
||||||
|
// Set total bytes in progress bar
|
||||||
|
bar.SetTotal(event.Total)
|
||||||
|
// Set amount of bytes received in progress bar
|
||||||
|
bar.SetCurrent(event.Received)
|
||||||
|
// If transfer finished, break
|
||||||
|
if event.Sent == event.Total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Finish progress bar
|
||||||
|
bar.Finish()
|
||||||
|
|
||||||
|
fmt.Printf("Transferred %d B in %s.\n", bar.Total(), time.Since(start))
|
||||||
|
fmt.Println("Remember to validate the new firmware in the InfiniTime settings.")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
firmwareCmd.AddCommand(upgradeCmd)
|
||||||
|
|
||||||
|
// Register flags
|
||||||
|
upgradeCmd.Flags().StringP("archive", "a", "", "Path to firmware archive")
|
||||||
|
upgradeCmd.Flags().StringP("init-pkt", "i", "", "Path to init packet (.dat file)")
|
||||||
|
upgradeCmd.Flags().StringP("firmware", "f", "", "Path to firmware image (.bin file)")
|
||||||
|
|
||||||
|
// Bind flags to viper keys
|
||||||
|
viper.BindPFlag("archive", upgradeCmd.Flags().Lookup("archive"))
|
||||||
|
viper.BindPFlag("initPkt", upgradeCmd.Flags().Lookup("init-pkt"))
|
||||||
|
viper.BindPFlag("firmware", upgradeCmd.Flags().Lookup("firmware"))
|
||||||
|
}
|
49
cmd/itctl/firmware/version.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package firmware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// versionCmd represents the version command
|
||||||
|
var versionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Aliases: []string{"ver"},
|
||||||
|
Short: "Get firmware version of InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
version, err := client.Version()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting firmware version")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(version)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
firmwareCmd.AddCommand(versionCmd)
|
||||||
|
}
|
182
cmd/itctl/fs.go
@ -1,182 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/cheggaaa/pb/v3"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fsList(c *cli.Context) error {
|
|
||||||
dirPath := "/"
|
|
||||||
if c.Args().Len() > 0 {
|
|
||||||
dirPath = c.Args().Get(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
listing, err := client.FS().ReadDir(c.Context, dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range listing {
|
|
||||||
fmt.Println(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsMkdir(c *cli.Context) error {
|
|
||||||
if c.Args().Len() < 1 {
|
|
||||||
return cli.Exit("Command mkdir requires one or more arguments", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if c.Bool("parents") {
|
|
||||||
err = client.FS().MkdirAll(c.Context, c.Args().Slice()...)
|
|
||||||
} else {
|
|
||||||
err = client.FS().Mkdir(c.Context, c.Args().Slice()...)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsMove(c *cli.Context) error {
|
|
||||||
if c.Args().Len() != 2 {
|
|
||||||
return cli.Exit("Command move requires two arguments", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.FS().Rename(c.Context, c.Args().Get(0), c.Args().Get(1))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsRead(c *cli.Context) error {
|
|
||||||
if c.Args().Len() != 2 {
|
|
||||||
return cli.Exit("Command read requires two arguments", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmpFile *os.File
|
|
||||||
var path string
|
|
||||||
var err error
|
|
||||||
if c.Args().Get(1) == "-" {
|
|
||||||
tmpFile, err = os.CreateTemp("/tmp", "itctl.*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path = tmpFile.Name()
|
|
||||||
} else {
|
|
||||||
path, err = filepath.Abs(c.Args().Get(1))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progress, err := client.FS().Download(c.Context, path, c.Args().Get(0))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create progress bar template
|
|
||||||
barTmpl := `{{counters . }} B {{bar . "|" "-" (cycle .) " " "|"}} {{percent . }} {{rtime . "%s"}}`
|
|
||||||
// Start full bar at 0 total
|
|
||||||
bar := pb.ProgressBarTemplate(barTmpl).Start(0)
|
|
||||||
// Get progress events
|
|
||||||
for event := range progress {
|
|
||||||
if event.Err != nil {
|
|
||||||
return event.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set total bytes in progress bar
|
|
||||||
bar.SetTotal(int64(event.Total))
|
|
||||||
// Set amount of bytes sent in progress bar
|
|
||||||
bar.SetCurrent(int64(event.Sent))
|
|
||||||
}
|
|
||||||
bar.Finish()
|
|
||||||
|
|
||||||
if c.Args().Get(1) == "-" {
|
|
||||||
io.Copy(os.Stdout, tmpFile)
|
|
||||||
os.Stdout.WriteString("\n")
|
|
||||||
os.Stdout.Sync()
|
|
||||||
tmpFile.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsRemove(c *cli.Context) error {
|
|
||||||
if c.Args().Len() < 1 {
|
|
||||||
return cli.Exit("Command remove requires one or more arguments", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if c.Bool("recursive") {
|
|
||||||
err = client.FS().RemoveAll(c.Context, c.Args().Slice()...)
|
|
||||||
} else {
|
|
||||||
err = client.FS().Remove(c.Context, c.Args().Slice()...)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsWrite(c *cli.Context) error {
|
|
||||||
if c.Args().Len() != 2 {
|
|
||||||
return cli.Exit("Command write requires two arguments", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmpFile *os.File
|
|
||||||
var path string
|
|
||||||
var err error
|
|
||||||
if c.Args().Get(0) == "-" {
|
|
||||||
tmpFile, err = os.CreateTemp("/tmp", "itctl.*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path = tmpFile.Name()
|
|
||||||
} else {
|
|
||||||
path, err = filepath.Abs(c.Args().Get(0))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Args().Get(0) == "-" {
|
|
||||||
io.Copy(tmpFile, os.Stdin)
|
|
||||||
defer tmpFile.Close()
|
|
||||||
defer os.Remove(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
progress, err := client.FS().Upload(c.Context, c.Args().Get(1), path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create progress bar template
|
|
||||||
barTmpl := `{{counters . }} B {{bar . "|" "-" (cycle .) " " "|"}} {{percent . }} {{rtime . "%s"}}`
|
|
||||||
// Start full bar at 0 total
|
|
||||||
bar := pb.ProgressBarTemplate(barTmpl).Start(0)
|
|
||||||
// Get progress events
|
|
||||||
for event := range progress {
|
|
||||||
if event.Err != nil {
|
|
||||||
return event.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set total bytes in progress bar
|
|
||||||
bar.SetTotal(int64(event.Total))
|
|
||||||
// Set amount of bytes sent in progress bar
|
|
||||||
bar.SetCurrent(int64(event.Sent))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getAddress(c *cli.Context) error {
|
|
||||||
address, err := client.Address(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(address)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBattery(c *cli.Context) error {
|
|
||||||
battLevel, err := client.BatteryLevel(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print returned percentage
|
|
||||||
fmt.Printf("%d%%\n", battLevel)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHeart(c *cli.Context) error {
|
|
||||||
heartRate, err := client.HeartRate(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print returned BPM
|
|
||||||
fmt.Printf("%d BPM\n", heartRate)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMotion(c *cli.Context) error {
|
|
||||||
motionVals, err := client.Motion(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bool("shell") {
|
|
||||||
fmt.Printf(
|
|
||||||
"X=%d\nY=%d\nZ=%d\n",
|
|
||||||
motionVals.X,
|
|
||||||
motionVals.Y,
|
|
||||||
motionVals.Z,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return json.NewEncoder(os.Stdout).Encode(motionVals)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSteps(c *cli.Context) error {
|
|
||||||
stepCount, err := client.StepCount(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print returned BPM
|
|
||||||
fmt.Printf("%d Steps\n", stepCount)
|
|
||||||
return nil
|
|
||||||
}
|
|
59
cmd/itctl/get/address.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package get
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// addressCmd represents the address command
|
||||||
|
var addressCmd = &cobra.Command{
|
||||||
|
Use: "address",
|
||||||
|
Aliases: []string{"addr"},
|
||||||
|
Short: "Get InfiniTime's bluetooth address",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
address, err := client.Address()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting bluetooth address")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(address)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getCmd.AddCommand(addressCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
50
cmd/itctl/get/battery.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package get
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// batteryCmd represents the batt command
|
||||||
|
var batteryCmd = &cobra.Command{
|
||||||
|
Use: "battery",
|
||||||
|
Aliases: []string{"batt"},
|
||||||
|
Short: "Get battery level from InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
battLevel, err := client.BatteryLevel()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting battery level")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print returned percentage
|
||||||
|
fmt.Printf("%d%%\n", battLevel)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getCmd.AddCommand(batteryCmd)
|
||||||
|
}
|
34
cmd/itctl/get/get.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package get
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getCmd represents the get command
|
||||||
|
var getCmd = &cobra.Command{
|
||||||
|
Use: "get",
|
||||||
|
Short: "Get information from InfiniTime",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
root.RootCmd.AddCommand(getCmd)
|
||||||
|
}
|
49
cmd/itctl/get/heart.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package get
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the heart command
|
||||||
|
var heartCmd = &cobra.Command{
|
||||||
|
Use: "heart",
|
||||||
|
Short: "Get heart rate from InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
heartRate, err := client.HeartRate()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting heart rate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print returned BPM
|
||||||
|
fmt.Printf("%d BPM\n", heartRate)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getCmd.AddCommand(heartCmd)
|
||||||
|
}
|
68
cmd/itctl/get/motion.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package get
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// steps.goCmd represents the steps.go command
|
||||||
|
var motionCmd = &cobra.Command{
|
||||||
|
Use: "motion",
|
||||||
|
Short: "Get motion values from InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
motionVals, err := client.Motion()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting motion values")
|
||||||
|
}
|
||||||
|
|
||||||
|
if viper.GetBool("shell") {
|
||||||
|
fmt.Printf(
|
||||||
|
"X=%d\nY=%d\nZ=%d",
|
||||||
|
motionVals.X,
|
||||||
|
motionVals.Y,
|
||||||
|
motionVals.Z,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%+v\n", motionVals)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getCmd.AddCommand(motionCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// steps.goCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
motionCmd.Flags().BoolP("shell", "s", false, "Output data in shell-compatible format")
|
||||||
|
viper.BindPFlag("shell", motionCmd.Flags().Lookup("shell"))
|
||||||
|
}
|
59
cmd/itctl/get/steps.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package get
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// steps.goCmd represents the steps.go command
|
||||||
|
var stepsCmd = &cobra.Command{
|
||||||
|
Use: "steps",
|
||||||
|
Short: "Get step count from InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
stepCount, err := client.StepCount()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting step count")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print returned BPM
|
||||||
|
fmt.Printf("%d Steps\n", stepCount)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getCmd.AddCommand(stepsCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// steps.goCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// steps.goCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
@ -1,319 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
_ "go.arsenm.dev/itd/cmd/itctl/firmware"
|
||||||
"os"
|
_ "go.arsenm.dev/itd/cmd/itctl/get"
|
||||||
"os/signal"
|
_ "go.arsenm.dev/itd/cmd/itctl/notify"
|
||||||
"syscall"
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
"time"
|
_ "go.arsenm.dev/itd/cmd/itctl/set"
|
||||||
|
_ "go.arsenm.dev/itd/cmd/itctl/watch"
|
||||||
|
_ "go.arsenm.dev/itd/cmd/itctl/filesystem"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"os"
|
||||||
"go.elara.ws/itd/api"
|
|
||||||
"go.elara.ws/logger"
|
"github.com/rs/zerolog"
|
||||||
"go.elara.ws/logger/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var client *api.Client
|
func init() {
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Logger = logger.NewPretty(os.Stderr)
|
root.Execute()
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx, _ = signal.NotifyContext(
|
|
||||||
ctx,
|
|
||||||
syscall.SIGINT,
|
|
||||||
syscall.SIGTERM,
|
|
||||||
)
|
|
||||||
|
|
||||||
// This goroutine ensures that itctl will exit
|
|
||||||
// at most 200ms after the user sends SIGINT/SIGTERM.
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
os.Exit(0)
|
|
||||||
}()
|
|
||||||
|
|
||||||
app := cli.App{
|
|
||||||
Name: "itctl",
|
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "socket-path",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Value: api.DefaultAddr,
|
|
||||||
Usage: "Path to itd socket",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Commands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "help",
|
|
||||||
ArgsUsage: "<command>",
|
|
||||||
Usage: "Display help screen for a command",
|
|
||||||
Action: helpCmd,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "resources",
|
|
||||||
Aliases: []string{"res"},
|
|
||||||
Usage: "Handle InfiniTime resource loading",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "load",
|
|
||||||
ArgsUsage: "<path>",
|
|
||||||
Usage: "Load an InifiniTime resources package",
|
|
||||||
Action: resourcesLoad,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "filesystem",
|
|
||||||
Aliases: []string{"fs"},
|
|
||||||
Usage: "Perform filesystem operations on the PineTime",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "list",
|
|
||||||
ArgsUsage: "[dir]",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Usage: "List a directory",
|
|
||||||
Action: fsList,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "parents",
|
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Make parent directories if needed, no error if already existing",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "mkdir",
|
|
||||||
ArgsUsage: "<paths...>",
|
|
||||||
Usage: "Create new directories",
|
|
||||||
Action: fsMkdir,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "move",
|
|
||||||
ArgsUsage: "<old> <new>",
|
|
||||||
Aliases: []string{"mv"},
|
|
||||||
Usage: "Move a file or directory",
|
|
||||||
Action: fsMove,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "read",
|
|
||||||
ArgsUsage: `<remote path> <local path>`,
|
|
||||||
Usage: "Read a file from InfiniTime.",
|
|
||||||
Description: `Read is used to read files from InfiniTime's filesystem. A "-" can be used to signify stdout`,
|
|
||||||
Action: fsRead,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "recursive",
|
|
||||||
Aliases: []string{"r", "R"},
|
|
||||||
Usage: "Remove directories and their contents recursively",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "remove",
|
|
||||||
ArgsUsage: "<paths...>",
|
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Usage: "Remove a file from InfiniTime",
|
|
||||||
Action: fsRemove,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "write",
|
|
||||||
ArgsUsage: `<local path> <remote path>`,
|
|
||||||
Usage: "Write a file to InfiniTime",
|
|
||||||
Description: `Write is used to write files to InfiniTime's filesystem. A "-" can be used to signify stdin`,
|
|
||||||
Action: fsWrite,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "firmware",
|
|
||||||
Aliases: []string{"fw"},
|
|
||||||
Usage: "Manage InfiniTime firmware",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.PathFlag{
|
|
||||||
Name: "init-packet",
|
|
||||||
Aliases: []string{"i"},
|
|
||||||
Usage: "Path to init packet (.dat file)",
|
|
||||||
},
|
|
||||||
&cli.PathFlag{
|
|
||||||
Name: "firmware",
|
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: "Path to firmware image (.bin file)",
|
|
||||||
},
|
|
||||||
&cli.PathFlag{
|
|
||||||
Name: "resources",
|
|
||||||
Aliases: []string{"r"},
|
|
||||||
Usage: "Path to resources file (.zip file)",
|
|
||||||
},
|
|
||||||
&cli.PathFlag{
|
|
||||||
Name: "archive",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Path to firmware archive (.zip file)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: "upgrade",
|
|
||||||
Aliases: []string{"upg"},
|
|
||||||
Usage: "Upgrade InfiniTime firmware using files or archive",
|
|
||||||
Action: fwUpgrade,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Aliases: []string{"ver"},
|
|
||||||
Usage: "Get firmware version of InfiniTime",
|
|
||||||
Action: fwVersion,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "get",
|
|
||||||
Usage: "Get information from InfiniTime",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "address",
|
|
||||||
Aliases: []string{"addr"},
|
|
||||||
Usage: "Get InfiniTime's bluetooth address",
|
|
||||||
Action: getAddress,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "battery",
|
|
||||||
Aliases: []string{"batt"},
|
|
||||||
Usage: "Get InfiniTime's battery percentage",
|
|
||||||
Action: getBattery,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "heart",
|
|
||||||
Usage: "Get heart rate from InfiniTime",
|
|
||||||
Action: getHeart,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{Name: "shell"},
|
|
||||||
},
|
|
||||||
Name: "motion",
|
|
||||||
Usage: "Get motion values from InfiniTime",
|
|
||||||
Action: getMotion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "steps",
|
|
||||||
Usage: "Get step count from InfiniTime",
|
|
||||||
Action: getSteps,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "notify",
|
|
||||||
Usage: "Send notification to InfiniTime",
|
|
||||||
Action: notify,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "set",
|
|
||||||
Usage: "Set information on InfiniTime",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "time",
|
|
||||||
ArgsUsage: `<ISO8601|"now">`,
|
|
||||||
Usage: "Set InfiniTime's clock to specified time",
|
|
||||||
Action: setTime,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "update",
|
|
||||||
Usage: "Update information on InfiniTime",
|
|
||||||
Aliases: []string{"upd"},
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "weather",
|
|
||||||
Usage: "Force an immediate update of weather data",
|
|
||||||
Action: updateWeather,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "watch",
|
|
||||||
Usage: "Watch a value for changes",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{Name: "json"},
|
|
||||||
&cli.BoolFlag{Name: "shell"},
|
|
||||||
},
|
|
||||||
Name: "heart",
|
|
||||||
Usage: "Watch heart rate value for changes",
|
|
||||||
Action: watchHeart,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{Name: "json"},
|
|
||||||
&cli.BoolFlag{Name: "shell"},
|
|
||||||
},
|
|
||||||
Name: "steps",
|
|
||||||
Usage: "Watch step count value for changes",
|
|
||||||
Action: watchStepCount,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{Name: "json"},
|
|
||||||
&cli.BoolFlag{Name: "shell"},
|
|
||||||
},
|
|
||||||
Name: "motion",
|
|
||||||
Usage: "Watch motion coordinates for changes",
|
|
||||||
Action: watchMotion,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
&cli.BoolFlag{Name: "json"},
|
|
||||||
&cli.BoolFlag{Name: "shell"},
|
|
||||||
},
|
|
||||||
Name: "battery",
|
|
||||||
Aliases: []string{"batt"},
|
|
||||||
Usage: "Watch battery level value for changes",
|
|
||||||
Action: watchBattLevel,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Before: func(c *cli.Context) error {
|
|
||||||
if !isHelpCmd() {
|
|
||||||
newClient, err := api.New(c.String("socket-path"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client = newClient
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
After: func(*cli.Context) error {
|
|
||||||
if client != nil {
|
|
||||||
client.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := app.RunContext(ctx, os.Args)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error while running app").Err(err).Send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func helpCmd(c *cli.Context) error {
|
|
||||||
cmdArgs := append([]string{os.Args[0]}, c.Args().Slice()...)
|
|
||||||
cmdArgs = append(cmdArgs, "-h")
|
|
||||||
return c.App.RunContext(c.Context, cmdArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHelpCmd() bool {
|
|
||||||
if len(os.Args) == 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, arg := range os.Args {
|
|
||||||
if arg == "-h" || arg == "help" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
func notify(c *cli.Context) error {
|
|
||||||
// Ensure required arguments
|
|
||||||
if c.Args().Len() != 2 {
|
|
||||||
return cli.Exit("Command notify requires two arguments", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.Notify(c.Context, c.Args().Get(0), c.Args().Get(1))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
51
cmd/itctl/notify/notify.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// notifyCmd represents the notify command
|
||||||
|
var notifyCmd = &cobra.Command{
|
||||||
|
Use: "notify <title> <body>",
|
||||||
|
Short: "Send notification to InfiniTime",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// Ensure required arguments
|
||||||
|
if len(args) != 2 {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Fatal().Msg("Command notify requires two arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
err := client.Notify(args[0], args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error sending notification")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
root.RootCmd.AddCommand(notifyCmd)
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/cheggaaa/pb/v3"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go.elara.ws/itd/infinitime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func resourcesLoad(c *cli.Context) error {
|
|
||||||
return resLoad(c.Context, c.Args().Slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
func resLoad(ctx context.Context, args []string) error {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return cli.Exit("Command load requires one argument.", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create progress bar templates
|
|
||||||
rmTmpl := `Removing {{string . "filename"}}`
|
|
||||||
upTmpl := `Uploading {{string . "filename"}} {{counters . }} B {{bar . "|" "-" (cycle .) " " "|"}} {{percent . }} {{rtime . "%s"}}`
|
|
||||||
// Start full bar at 0 total
|
|
||||||
bar := pb.ProgressBarTemplate(rmTmpl).Start(0)
|
|
||||||
|
|
||||||
path, err := filepath.Abs(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
progCh, err := client.FS().LoadResources(ctx, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for evt := range progCh {
|
|
||||||
if evt.Err != nil {
|
|
||||||
return evt.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
if evt.Operation == infinitime.ResourceRemove {
|
|
||||||
bar.SetTemplateString(rmTmpl)
|
|
||||||
bar.Set("filename", evt.Name)
|
|
||||||
} else {
|
|
||||||
bar.SetTemplateString(upTmpl)
|
|
||||||
bar.Set("filename", evt.Name)
|
|
||||||
|
|
||||||
bar.SetTotal(evt.Total)
|
|
||||||
bar.SetCurrent(evt.Sent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bar.Finish()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
87
cmd/itctl/root/root.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package root
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/abiosoft/ishell"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RootCmd represents the base command when called without any subcommands
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
|
Use: "itctl",
|
||||||
|
Short: "Control the itd daemon for InfiniTime smartwatches",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
|
// Create new shell
|
||||||
|
sh := ishell.New()
|
||||||
|
sh.SetPrompt("itctl> ")
|
||||||
|
|
||||||
|
// For every command in cobra
|
||||||
|
for _, subCmd := range cmd.Commands() {
|
||||||
|
// Add top level command to ishell
|
||||||
|
sh.AddCmd(&ishell.Cmd{
|
||||||
|
Name: subCmd.Name(),
|
||||||
|
Help: subCmd.Short,
|
||||||
|
Aliases: subCmd.Aliases,
|
||||||
|
LongHelp: subCmd.Long,
|
||||||
|
Func: func(ctx *ishell.Context) {
|
||||||
|
// Append name and arguments of command
|
||||||
|
args := append([]string{ctx.Cmd.Name}, ctx.Args...)
|
||||||
|
// Set root command arguments
|
||||||
|
cmd.SetArgs(args)
|
||||||
|
// Execute root command with new arguments
|
||||||
|
cmd.Execute()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start shell
|
||||||
|
sh.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
|
func Execute() {
|
||||||
|
client, err := api.New(viper.GetString("sockPath"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error connecting to socket. Is itd running?")
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
viper.Set("client", client)
|
||||||
|
RootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||||
|
cobra.CheckErr(RootCmd.Execute())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Register flag for socket path
|
||||||
|
RootCmd.Flags().StringP("socket-path", "s", api.DefaultAddr, "Path to itd socket")
|
||||||
|
|
||||||
|
// Bind flag and environment variable to viper key
|
||||||
|
viper.BindPFlag("sockPath", RootCmd.Flags().Lookup("socket-path"))
|
||||||
|
viper.BindEnv("sockPath", "ITCTL_SOCKET_PATH")
|
||||||
|
|
||||||
|
// Set default value for socket path
|
||||||
|
viper.SetDefault("sockPath", api.DefaultAddr)
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setTime(c *cli.Context) error {
|
|
||||||
// Ensure required arguments
|
|
||||||
if c.Args().Len() < 1 {
|
|
||||||
return cli.Exit("Command time requires one argument", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Args().Get(0) == "now" {
|
|
||||||
return client.SetTime(c.Context, time.Now())
|
|
||||||
} else {
|
|
||||||
parsed, err := time.Parse(time.RFC3339, c.Args().Get(0))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.SetTime(c.Context, parsed)
|
|
||||||
}
|
|
||||||
}
|
|
34
cmd/itctl/set/set.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package set
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setCmd represents the set command
|
||||||
|
var setCmd = &cobra.Command{
|
||||||
|
Use: "set",
|
||||||
|
Short: "Set information on InfiniTime",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
root.RootCmd.AddCommand(setCmd)
|
||||||
|
}
|
81
cmd/itctl/set/time.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package set
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// timeCmd represents the time command
|
||||||
|
var timeCmd = &cobra.Command{
|
||||||
|
Use: `time <ISO8601|"now">`,
|
||||||
|
Short: "Set InfiniTime's clock to specified time",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// Ensure required arguments
|
||||||
|
if len(args) != 1 {
|
||||||
|
cmd.Usage()
|
||||||
|
log.Warn().Msg("Command time requires one argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to itd UNIX socket
|
||||||
|
conn, err := net.Dial("unix", viper.GetString("sockPath"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error dialing socket. Is itd running?")
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Encode request into connection
|
||||||
|
err = json.NewEncoder(conn).Encode(types.Request{
|
||||||
|
Type: types.ReqTypeSetTime,
|
||||||
|
Data: args[0],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error making request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read one line from connetion
|
||||||
|
line, _, err := bufio.NewReader(conn).ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error reading line from connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res types.Response
|
||||||
|
// Decode line into response
|
||||||
|
err = json.Unmarshal(line, &res)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error decoding JSON data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Error {
|
||||||
|
log.Fatal().Msg(res.Message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setCmd.AddCommand(timeCmd)
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "github.com/urfave/cli/v2"
|
|
||||||
|
|
||||||
func updateWeather(c *cli.Context) error {
|
|
||||||
return client.WeatherUpdate(c.Context)
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func watchHeart(c *cli.Context) error {
|
|
||||||
heartCh, err := client.WatchHeartRate(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case heartRate := <-heartCh:
|
|
||||||
if c.Bool("json") {
|
|
||||||
json.NewEncoder(os.Stdout).Encode(
|
|
||||||
map[string]uint8{"heartRate": heartRate},
|
|
||||||
)
|
|
||||||
} else if c.Bool("shell") {
|
|
||||||
fmt.Printf("HEART_RATE=%d\n", heartRate)
|
|
||||||
} else {
|
|
||||||
fmt.Println(heartRate, "BPM")
|
|
||||||
}
|
|
||||||
case <-c.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchBattLevel(c *cli.Context) error {
|
|
||||||
battLevelCh, err := client.WatchBatteryLevel(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case battLevel := <-battLevelCh:
|
|
||||||
if c.Bool("json") {
|
|
||||||
json.NewEncoder(os.Stdout).Encode(
|
|
||||||
map[string]uint8{"battLevel": battLevel},
|
|
||||||
)
|
|
||||||
} else if c.Bool("shell") {
|
|
||||||
fmt.Printf("BATTERY_LEVEL=%d\n", battLevel)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%d%%\n", battLevel)
|
|
||||||
}
|
|
||||||
case <-c.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchStepCount(c *cli.Context) error {
|
|
||||||
stepCountCh, err := client.WatchStepCount(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case stepCount := <-stepCountCh:
|
|
||||||
if c.Bool("json") {
|
|
||||||
json.NewEncoder(os.Stdout).Encode(
|
|
||||||
map[string]uint32{"stepCount": stepCount},
|
|
||||||
)
|
|
||||||
} else if c.Bool("shell") {
|
|
||||||
fmt.Printf("STEP_COUNT=%d\n", stepCount)
|
|
||||||
} else {
|
|
||||||
fmt.Println(stepCount, "Steps")
|
|
||||||
}
|
|
||||||
case <-c.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchMotion(c *cli.Context) error {
|
|
||||||
motionCh, err := client.WatchMotion(c.Context)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case motionVals := <-motionCh:
|
|
||||||
if c.Bool("json") {
|
|
||||||
json.NewEncoder(os.Stdout).Encode(motionVals)
|
|
||||||
} else if c.Bool("shell") {
|
|
||||||
fmt.Printf(
|
|
||||||
"X=%d\nY=%d\nZ=%d\n",
|
|
||||||
motionVals.X,
|
|
||||||
motionVals.Y,
|
|
||||||
motionVals.Z,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
fmt.Println(motionVals)
|
|
||||||
}
|
|
||||||
case <-c.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
76
cmd/itctl/watch/battery.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the address command
|
||||||
|
var batteryCmd = &cobra.Command{
|
||||||
|
Use: "battery",
|
||||||
|
Aliases: []string{"batt"},
|
||||||
|
Short: "Watch InfiniTime's battery level for changes",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
battLevelCh, cancel, err := client.WatchBatteryLevel()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting battery level channel")
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
signalCh := make(chan os.Signal, 1)
|
||||||
|
go func() {
|
||||||
|
<-signalCh
|
||||||
|
cancel()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
signal.Notify(signalCh,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
)
|
||||||
|
|
||||||
|
for battlevel := range battLevelCh {
|
||||||
|
fmt.Printf("%d%%\n", battlevel)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
watchCmd.AddCommand(batteryCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
75
cmd/itctl/watch/heart.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the address command
|
||||||
|
var heartCmd = &cobra.Command{
|
||||||
|
Use: "heart",
|
||||||
|
Short: "Watch InfiniTime's heart rate for changes",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
heartRateCh, cancel, err := client.WatchHeartRate()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting heart rate channel")
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
signalCh := make(chan os.Signal, 1)
|
||||||
|
go func() {
|
||||||
|
<-signalCh
|
||||||
|
cancel()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
signal.Notify(signalCh,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
)
|
||||||
|
|
||||||
|
for heartRate := range heartRateCh {
|
||||||
|
fmt.Println(heartRate, "BPM")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
watchCmd.AddCommand(heartCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
86
cmd/itctl/watch/motion.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the address command
|
||||||
|
var motionCmd = &cobra.Command{
|
||||||
|
Use: "motion",
|
||||||
|
Short: "Watch InfiniTime's motion values for changes",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
motionValCh, cancel, err := client.WatchMotion()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting motion value channel")
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
signalCh := make(chan os.Signal, 1)
|
||||||
|
go func() {
|
||||||
|
<-signalCh
|
||||||
|
cancel()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
signal.Notify(signalCh,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
)
|
||||||
|
|
||||||
|
for motionVals := range motionValCh {
|
||||||
|
if viper.GetBool("shell") {
|
||||||
|
fmt.Printf(
|
||||||
|
"X=%d\nY=%d\nZ=%d\n",
|
||||||
|
motionVals.X,
|
||||||
|
motionVals.Y,
|
||||||
|
motionVals.Z,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%+v\n", motionVals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
watchCmd.AddCommand(motionCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
motionCmd.Flags().BoolP("shell", "s", false, "Output data in shell-compatible format")
|
||||||
|
viper.BindPFlag("shell", motionCmd.Flags().Lookup("shell"))
|
||||||
|
}
|
75
cmd/itctl/watch/steps.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// heartCmd represents the address command
|
||||||
|
var stepsCmd = &cobra.Command{
|
||||||
|
Use: "steps",
|
||||||
|
Short: "Watch InfiniTime's step count for changes",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
client := viper.Get("client").(*api.Client)
|
||||||
|
|
||||||
|
stepCountCh, cancel, err := client.WatchStepCount()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting step count channel")
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
signalCh := make(chan os.Signal, 1)
|
||||||
|
go func() {
|
||||||
|
<-signalCh
|
||||||
|
cancel()
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
signal.Notify(signalCh,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
)
|
||||||
|
|
||||||
|
for stepCount := range stepCountCh {
|
||||||
|
fmt.Println(stepCount, "Steps")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
watchCmd.AddCommand(stepsCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// addressCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// addressCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
34
cmd/itctl/watch/watch.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* itd uses bluetooth low energy to communicate with InfiniTime devices
|
||||||
|
* Copyright (C) 2021 Arsen Musayelyan
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package watch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// watchCmd represents the watch command
|
||||||
|
var watchCmd = &cobra.Command{
|
||||||
|
Use: "watch",
|
||||||
|
Short: "Watch values from InfiniTime for changes",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
root.RootCmd.AddCommand(watchCmd)
|
||||||
|
}
|
@ -28,15 +28,10 @@ func guiErr(err error, msg string, fatal bool, parent fyne.Window) {
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Create new label containing error text
|
// Create new label containing error text
|
||||||
errEntry := widget.NewEntry()
|
errLbl := widget.NewLabel(err.Error())
|
||||||
errEntry.SetText(err.Error())
|
|
||||||
// If text changed, change it back
|
|
||||||
errEntry.OnChanged = func(string) {
|
|
||||||
errEntry.SetText(err.Error())
|
|
||||||
}
|
|
||||||
// Create new dropdown containing error label
|
// Create new dropdown containing error label
|
||||||
content.Add(widget.NewAccordion(
|
content.Add(widget.NewAccordion(
|
||||||
widget.NewAccordionItem("More Details", errEntry),
|
widget.NewAccordionItem("More Details", errLbl),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
if fatal {
|
if fatal {
|
||||||
@ -54,4 +49,5 @@ func guiErr(err error, msg string, fatal bool, parent fyne.Window) {
|
|||||||
// Show error dialog
|
// Show error dialog
|
||||||
dialog.NewCustom("Error", "Ok", content, parent).Show()
|
dialog.NewCustom("Error", "Ok", content, parent).Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,163 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/dialog"
|
|
||||||
"fyne.io/fyne/v2/layout"
|
|
||||||
"fyne.io/fyne/v2/storage"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
"go.elara.ws/itd/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func firmwareTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
|
|
||||||
// Create select to chose between archive and files upgrade
|
|
||||||
typeSelect := widget.NewSelect([]string{"Archive", "Files"}, nil)
|
|
||||||
typeSelect.PlaceHolder = "Upgrade Type"
|
|
||||||
|
|
||||||
// Create map to store files
|
|
||||||
files := map[string]string{}
|
|
||||||
|
|
||||||
// Create and disable start button
|
|
||||||
startBtn := widget.NewButton("Start", nil)
|
|
||||||
startBtn.Disable()
|
|
||||||
|
|
||||||
// Create new file open dialog for archive
|
|
||||||
archiveDlg := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
|
|
||||||
if err != nil || uc == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer uc.Close()
|
|
||||||
// Set archive path in map
|
|
||||||
files[".zip"] = uc.URI().Path()
|
|
||||||
// Enable start button
|
|
||||||
startBtn.Enable()
|
|
||||||
}, w)
|
|
||||||
// Only allow .zip files
|
|
||||||
archiveDlg.SetFilter(storage.NewExtensionFileFilter([]string{".zip"}))
|
|
||||||
// Create button to show dialog
|
|
||||||
archiveBtn := widget.NewButton("Select Archive (.zip)", archiveDlg.Show)
|
|
||||||
|
|
||||||
// Create new file open dialog for firmware image
|
|
||||||
imageDlg := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
|
|
||||||
if err != nil || uc == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer uc.Close()
|
|
||||||
|
|
||||||
// Set firmware image path in map
|
|
||||||
files[".bin"] = uc.URI().Path()
|
|
||||||
|
|
||||||
// If the init packet was already selected
|
|
||||||
_, datOk := files[".dat"]
|
|
||||||
if datOk {
|
|
||||||
// Enable start button
|
|
||||||
startBtn.Enable()
|
|
||||||
}
|
|
||||||
}, w)
|
|
||||||
// Only allow .bin files
|
|
||||||
imageDlg.SetFilter(storage.NewExtensionFileFilter([]string{".bin"}))
|
|
||||||
// Create button to show dialog
|
|
||||||
imageBtn := widget.NewButton("Select Firmware (.bin)", imageDlg.Show)
|
|
||||||
|
|
||||||
// Create new file open dialog for init packet
|
|
||||||
initDlg := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
|
|
||||||
if err != nil || uc == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer uc.Close()
|
|
||||||
|
|
||||||
// Set init packet path in map
|
|
||||||
files[".dat"] = uc.URI().Path()
|
|
||||||
|
|
||||||
// If the firmware image was already selected
|
|
||||||
_, binOk := files[".bin"]
|
|
||||||
if binOk {
|
|
||||||
// Enable start button
|
|
||||||
startBtn.Enable()
|
|
||||||
}
|
|
||||||
}, w)
|
|
||||||
// Only allow .dat files
|
|
||||||
initDlg.SetFilter(storage.NewExtensionFileFilter([]string{".dat"}))
|
|
||||||
// Create button to show dialog
|
|
||||||
initBtn := widget.NewButton("Select Init Packet (.dat)", initDlg.Show)
|
|
||||||
|
|
||||||
var upgType api.UpgradeType = 255
|
|
||||||
// When upgrade type changes
|
|
||||||
typeSelect.OnChanged = func(s string) {
|
|
||||||
// Delete all files from map
|
|
||||||
delete(files, ".bin")
|
|
||||||
delete(files, ".dat")
|
|
||||||
delete(files, ".zip")
|
|
||||||
// Hide all dialog buttons
|
|
||||||
imageBtn.Hide()
|
|
||||||
initBtn.Hide()
|
|
||||||
archiveBtn.Hide()
|
|
||||||
// Disable start button
|
|
||||||
startBtn.Disable()
|
|
||||||
|
|
||||||
switch s {
|
|
||||||
case "Files":
|
|
||||||
// Set file upgrade type
|
|
||||||
upgType = api.UpgradeTypeFiles
|
|
||||||
// Show firmware image and init packet buttons
|
|
||||||
imageBtn.Show()
|
|
||||||
initBtn.Show()
|
|
||||||
case "Archive":
|
|
||||||
// Set archive upgrade type
|
|
||||||
upgType = api.UpgradeTypeArchive
|
|
||||||
// Show archive button
|
|
||||||
archiveBtn.Show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Select archive by default
|
|
||||||
typeSelect.SetSelectedIndex(0)
|
|
||||||
|
|
||||||
// When start button pressed
|
|
||||||
startBtn.OnTapped = func() {
|
|
||||||
var args []string
|
|
||||||
// Append the appropriate files for upgrade type
|
|
||||||
switch upgType {
|
|
||||||
case api.UpgradeTypeArchive:
|
|
||||||
args = append(args, files[".zip"])
|
|
||||||
case api.UpgradeTypeFiles:
|
|
||||||
args = append(args, files[".dat"], files[".bin"])
|
|
||||||
}
|
|
||||||
|
|
||||||
// If args are nil (invalid upgrade type)
|
|
||||||
if args == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new progress dialog
|
|
||||||
progress := newProgress(w)
|
|
||||||
// Start firmware upgrade
|
|
||||||
progressCh, err := client.FirmwareUpgrade(ctx, upgType, args...)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error performing firmware upgrade", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Show progress dialog
|
|
||||||
progress.Show()
|
|
||||||
// For every progress event
|
|
||||||
for progressEvt := range progressCh {
|
|
||||||
// Set progress bar values
|
|
||||||
progress.SetTotal(float64(progressEvt.Total))
|
|
||||||
progress.SetValue(float64(progressEvt.Sent))
|
|
||||||
}
|
|
||||||
// Hide progress dialog
|
|
||||||
progress.Hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
return container.NewVBox(
|
|
||||||
layout.NewSpacer(),
|
|
||||||
typeSelect,
|
|
||||||
archiveBtn,
|
|
||||||
imageBtn,
|
|
||||||
initBtn,
|
|
||||||
startBtn,
|
|
||||||
layout.NewSpacer(),
|
|
||||||
)
|
|
||||||
}
|
|
402
cmd/itgui/fs.go
@ -1,402 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/data/binding"
|
|
||||||
"fyne.io/fyne/v2/dialog"
|
|
||||||
"fyne.io/fyne/v2/storage"
|
|
||||||
"fyne.io/fyne/v2/theme"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
"go.elara.ws/itd/api"
|
|
||||||
"go.elara.ws/itd/infinitime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fsTab(ctx context.Context, client *api.Client, w fyne.Window, opened chan struct{}) fyne.CanvasObject {
|
|
||||||
c := container.NewVBox()
|
|
||||||
|
|
||||||
// Create new binding to store current directory
|
|
||||||
cwdData := binding.NewString()
|
|
||||||
cwdData.Set("/")
|
|
||||||
|
|
||||||
// Create new list binding to store fs listing entries
|
|
||||||
lsData := binding.NewUntypedList()
|
|
||||||
|
|
||||||
// This goroutine waits until the fs tab is opened to
|
|
||||||
// request the listing from the watch
|
|
||||||
go func() {
|
|
||||||
// Wait for opened signal
|
|
||||||
<-opened
|
|
||||||
|
|
||||||
// Show loading pop up
|
|
||||||
loading := newLoadingPopUp(w)
|
|
||||||
loading.Show()
|
|
||||||
|
|
||||||
// Read root directory
|
|
||||||
ls, err := client.FS().ReadDir(ctx, "/")
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error reading directory", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Set ls binding
|
|
||||||
lsData.Set(lsToAny(ls))
|
|
||||||
|
|
||||||
// Hide loading pop up
|
|
||||||
loading.Hide()
|
|
||||||
}()
|
|
||||||
|
|
||||||
toolbar := widget.NewToolbar(
|
|
||||||
widget.NewToolbarAction(
|
|
||||||
theme.ViewRefreshIcon(),
|
|
||||||
func() {
|
|
||||||
refresh(ctx, cwdData, lsData, client, w, c)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
widget.NewToolbarAction(
|
|
||||||
theme.FileApplicationIcon(),
|
|
||||||
func() {
|
|
||||||
dlg := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
|
|
||||||
if err != nil || uc == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resPath := uc.URI().Path()
|
|
||||||
uc.Close()
|
|
||||||
|
|
||||||
progressDlg := newProgress(w)
|
|
||||||
progressDlg.Show()
|
|
||||||
|
|
||||||
progCh, err := client.FS().LoadResources(ctx, resPath)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error loading resources", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for evt := range progCh {
|
|
||||||
switch evt.Operation {
|
|
||||||
case infinitime.ResourceRemove:
|
|
||||||
progressDlg.SetText("Removing " + evt.Name)
|
|
||||||
case infinitime.ResourceUpload:
|
|
||||||
progressDlg.SetText("Uploading " + evt.Name)
|
|
||||||
progressDlg.SetTotal(float64(evt.Total))
|
|
||||||
progressDlg.SetValue(float64(evt.Sent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressDlg.Hide()
|
|
||||||
refresh(ctx, cwdData, lsData, client, w, c)
|
|
||||||
}, w)
|
|
||||||
dlg.SetConfirmText("Upload Resources")
|
|
||||||
dlg.SetFilter(storage.NewExtensionFileFilter([]string{
|
|
||||||
".zip",
|
|
||||||
}))
|
|
||||||
dlg.Show()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
widget.NewToolbarAction(
|
|
||||||
theme.UploadIcon(),
|
|
||||||
func() {
|
|
||||||
// Create open dialog for file that will be uploaded
|
|
||||||
dlg := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
|
|
||||||
if err != nil || uc == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get filepath and close
|
|
||||||
localPath := uc.URI().Path()
|
|
||||||
uc.Close()
|
|
||||||
|
|
||||||
// Create new entry to store filepath
|
|
||||||
filenameEntry := widget.NewEntry()
|
|
||||||
// Set entry text to the file name of the selected file
|
|
||||||
filenameEntry.SetText(filepath.Base(localPath))
|
|
||||||
// Create new dialog asking for the filename of the file to be stored on the watch
|
|
||||||
uploadDlg := dialog.NewForm("Upload", "Upload", "Cancel", []*widget.FormItem{
|
|
||||||
widget.NewFormItem("Filename", filenameEntry),
|
|
||||||
}, func(ok bool) {
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current directory
|
|
||||||
cwd, _ := cwdData.Get()
|
|
||||||
// Get remote path by joining current directory with filename
|
|
||||||
remotePath := filepath.Join(cwd, filenameEntry.Text)
|
|
||||||
|
|
||||||
// Create new progress dialog
|
|
||||||
progressDlg := newProgress(w)
|
|
||||||
progressDlg.Show()
|
|
||||||
|
|
||||||
// Upload file
|
|
||||||
progressCh, err := client.FS().Upload(ctx, remotePath, localPath)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error uploading file", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for progressEvt := range progressCh {
|
|
||||||
progressDlg.SetTotal(float64(progressEvt.Total))
|
|
||||||
progressDlg.SetValue(float64(progressEvt.Sent))
|
|
||||||
if progressEvt.Sent == progressEvt.Total {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close progress dialog
|
|
||||||
progressDlg.Hide()
|
|
||||||
|
|
||||||
// Add file to listing (avoids full refresh)
|
|
||||||
lsData.Append(api.FileInfo{
|
|
||||||
IsDir: false,
|
|
||||||
Name: filepath.Base(remotePath),
|
|
||||||
})
|
|
||||||
}, w)
|
|
||||||
uploadDlg.Show()
|
|
||||||
}, w)
|
|
||||||
dlg.Show()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
widget.NewToolbarAction(
|
|
||||||
theme.FolderNewIcon(),
|
|
||||||
func() {
|
|
||||||
// Create new entry for filename
|
|
||||||
filenameEntry := widget.NewEntry()
|
|
||||||
// Create new dialog to ask for the filename
|
|
||||||
mkdirDialog := dialog.NewForm("Make Directory", "Create", "Cancel", []*widget.FormItem{
|
|
||||||
widget.NewFormItem("Filename", filenameEntry),
|
|
||||||
}, func(ok bool) {
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current directory
|
|
||||||
cwd, _ := cwdData.Get()
|
|
||||||
// Get remote path by joining current directory and filename
|
|
||||||
remotePath := filepath.Join(cwd, filenameEntry.Text)
|
|
||||||
|
|
||||||
// Make directory
|
|
||||||
err := client.FS().Mkdir(ctx, remotePath)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error creating directory", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add directory to listing (avoids full refresh)
|
|
||||||
lsData.Append(api.FileInfo{
|
|
||||||
IsDir: true,
|
|
||||||
Name: filepath.Base(remotePath),
|
|
||||||
})
|
|
||||||
}, w)
|
|
||||||
mkdirDialog.Show()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add listener to listing data to create the new items on the GUI
|
|
||||||
// whenever the listing changes
|
|
||||||
lsData.AddListener(binding.NewDataListener(func() {
|
|
||||||
c.Objects = makeItems(ctx, client, lsData, cwdData, w, c)
|
|
||||||
c.Refresh()
|
|
||||||
}))
|
|
||||||
|
|
||||||
return container.NewBorder(
|
|
||||||
nil,
|
|
||||||
toolbar,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
container.NewVScroll(c),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeItems creates GUI objects from listing data
|
|
||||||
func makeItems(
|
|
||||||
ctx context.Context,
|
|
||||||
client *api.Client,
|
|
||||||
lsData binding.UntypedList,
|
|
||||||
cwdData binding.String,
|
|
||||||
w fyne.Window,
|
|
||||||
c *fyne.Container,
|
|
||||||
) []fyne.CanvasObject {
|
|
||||||
// Get listing data
|
|
||||||
ls, _ := lsData.Get()
|
|
||||||
|
|
||||||
// Create output slice with dame length as listing
|
|
||||||
out := make([]fyne.CanvasObject, len(ls))
|
|
||||||
for index, val := range ls {
|
|
||||||
// Assert value as file info
|
|
||||||
item := val.(api.FileInfo)
|
|
||||||
|
|
||||||
var icon fyne.Resource
|
|
||||||
// Decide which icon to use
|
|
||||||
if item.IsDir {
|
|
||||||
if item.Name == ".." {
|
|
||||||
icon = theme.NavigateBackIcon()
|
|
||||||
} else {
|
|
||||||
icon = theme.FolderIcon()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
icon = theme.FileIcon()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new button with the decided icon and the item name
|
|
||||||
btn := widget.NewButtonWithIcon(item.Name, icon, nil)
|
|
||||||
// Align left
|
|
||||||
btn.Alignment = widget.ButtonAlignLeading
|
|
||||||
// Decide which callback function to use
|
|
||||||
if item.IsDir {
|
|
||||||
btn.OnTapped = func() {
|
|
||||||
// Get current directory
|
|
||||||
cwd, _ := cwdData.Get()
|
|
||||||
// Join current directory with item name
|
|
||||||
cwd = filepath.Join(cwd, item.Name)
|
|
||||||
// Set new current directory
|
|
||||||
cwdData.Set(cwd)
|
|
||||||
// Refresh GUI to display new directory
|
|
||||||
refresh(ctx, cwdData, lsData, client, w, c)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
btn.OnTapped = func() {
|
|
||||||
// Get current directory
|
|
||||||
cwd, _ := cwdData.Get()
|
|
||||||
// Join current directory with item name
|
|
||||||
remotePath := filepath.Join(cwd, item.Name)
|
|
||||||
// Create new save dialog
|
|
||||||
dlg := dialog.NewFileSave(func(uc fyne.URIWriteCloser, err error) {
|
|
||||||
if err != nil || uc == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get path of selected file
|
|
||||||
localPath := uc.URI().Path()
|
|
||||||
// Close WriteCloser (it's not needed)
|
|
||||||
uc.Close()
|
|
||||||
|
|
||||||
// Create new progress dialog
|
|
||||||
progressDlg := newProgress(w)
|
|
||||||
progressDlg.Show()
|
|
||||||
|
|
||||||
// Download file
|
|
||||||
progressCh, err := client.FS().Download(ctx, localPath, remotePath)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error downloading file", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// For every progress event
|
|
||||||
for progressEvt := range progressCh {
|
|
||||||
progressDlg.SetTotal(float64(progressEvt.Total))
|
|
||||||
progressDlg.SetValue(float64(progressEvt.Sent))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close progress dialog
|
|
||||||
progressDlg.Hide()
|
|
||||||
}, w)
|
|
||||||
// Set filename to the item name
|
|
||||||
dlg.SetFileName(item.Name)
|
|
||||||
dlg.Show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.Name == ".." {
|
|
||||||
out[index] = btn
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
moveBtn := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() {
|
|
||||||
moveEntry := widget.NewEntry()
|
|
||||||
dlg := dialog.NewForm("Move", "Move", "Cancel", []*widget.FormItem{
|
|
||||||
widget.NewFormItem("New Path", moveEntry),
|
|
||||||
}, func(ok bool) {
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current directory
|
|
||||||
cwd, _ := cwdData.Get()
|
|
||||||
// Join current directory with item name
|
|
||||||
oldPath := filepath.Join(cwd, item.Name)
|
|
||||||
|
|
||||||
// Rename file
|
|
||||||
err := client.FS().Rename(ctx, oldPath, moveEntry.Text)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error renaming file", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh GUI
|
|
||||||
refresh(ctx, cwdData, lsData, client, w, c)
|
|
||||||
}, w)
|
|
||||||
dlg.Show()
|
|
||||||
})
|
|
||||||
|
|
||||||
removeBtn := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
|
|
||||||
// Get current directory
|
|
||||||
cwd, _ := cwdData.Get()
|
|
||||||
// Join current directory with item name
|
|
||||||
path := filepath.Join(cwd, item.Name)
|
|
||||||
|
|
||||||
// Remove file
|
|
||||||
err := client.FS().Remove(ctx, path)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error removing file", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh GUI
|
|
||||||
refresh(ctx, cwdData, lsData, client, w, c)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add button to GUI component list
|
|
||||||
out[index] = container.NewBorder(
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
container.NewHBox(moveBtn, removeBtn),
|
|
||||||
btn,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func refresh(
|
|
||||||
ctx context.Context,
|
|
||||||
cwdData binding.String,
|
|
||||||
lsData binding.UntypedList,
|
|
||||||
client *api.Client,
|
|
||||||
w fyne.Window,
|
|
||||||
c *fyne.Container,
|
|
||||||
) {
|
|
||||||
// Create and show new loading pop up
|
|
||||||
loading := newLoadingPopUp(w)
|
|
||||||
loading.Show()
|
|
||||||
// Close pop up at the end of the function
|
|
||||||
defer loading.Hide()
|
|
||||||
|
|
||||||
// Get current directory
|
|
||||||
cwd, _ := cwdData.Get()
|
|
||||||
// Read directory
|
|
||||||
ls, err := client.FS().ReadDir(ctx, cwd)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error reading directory", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Set new listing data
|
|
||||||
lsData.Set(lsToAny(ls))
|
|
||||||
// Create new GUI objects
|
|
||||||
c.Objects = makeItems(ctx, client, lsData, cwdData, w, c)
|
|
||||||
// Refresh GUI
|
|
||||||
c.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func lsToAny(ls []api.FileInfo) []interface{} {
|
|
||||||
out := make([]interface{}, len(ls)-1)
|
|
||||||
for i, e := range ls {
|
|
||||||
// Skip first element as it is always "."
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out[i-1] = e
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"image/color"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/theme"
|
|
||||||
"fyne.io/x/fyne/widget/charts"
|
|
||||||
"go.elara.ws/itd/api"
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
func graphTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
|
|
||||||
// Get user configuration directory
|
|
||||||
userCfgDir, err := os.UserConfigDir()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cfgDir := filepath.Join(userCfgDir, "itd")
|
|
||||||
dbPath := filepath.Join(cfgDir, "metrics.db")
|
|
||||||
|
|
||||||
// If stat on database returns error, return nil
|
|
||||||
if _, err := os.Stat(dbPath); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open database
|
|
||||||
db, err := sql.Open("sqlite", dbPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get heart rate data and create chart
|
|
||||||
heartRateData := getData(db, "bpm", "heartRate")
|
|
||||||
heartRate := newLineChartData(nil, heartRateData)
|
|
||||||
|
|
||||||
// Get step count data and create chart
|
|
||||||
stepCountData := getData(db, "steps", "stepCount")
|
|
||||||
stepCount := newLineChartData(nil, stepCountData)
|
|
||||||
|
|
||||||
// Get battery level data and create chart
|
|
||||||
battLevelData := getData(db, "percent", "battLevel")
|
|
||||||
battLevel := newLineChartData(nil, battLevelData)
|
|
||||||
|
|
||||||
// Get motion data
|
|
||||||
motionData := getMotionData(db)
|
|
||||||
// Create chart for each coordinate
|
|
||||||
xChart := newLineChartData(theme.PrimaryColorNamed(theme.ColorRed), motionData["X"])
|
|
||||||
yChart := newLineChartData(theme.PrimaryColorNamed(theme.ColorGreen), motionData["Y"])
|
|
||||||
zChart := newLineChartData(theme.PrimaryColorNamed(theme.ColorBlue), motionData["Z"])
|
|
||||||
|
|
||||||
// Create new max container with all the charts
|
|
||||||
motion := container.NewMax(xChart, yChart, zChart)
|
|
||||||
|
|
||||||
// Create tabs for charts
|
|
||||||
chartTabs := container.NewAppTabs(
|
|
||||||
container.NewTabItem("Heart Rate", heartRate),
|
|
||||||
container.NewTabItem("Step Count", stepCount),
|
|
||||||
container.NewTabItem("Battery Level", battLevel),
|
|
||||||
container.NewTabItem("Motion", motion),
|
|
||||||
)
|
|
||||||
// Place tabs on left
|
|
||||||
chartTabs.SetTabLocation(container.TabLocationLeading)
|
|
||||||
return chartTabs
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLineChartData(col color.Color, data []float64) *charts.LineChart {
|
|
||||||
// Create new line chart
|
|
||||||
lc := charts.NewLineChart(nil)
|
|
||||||
setOpts(lc, col)
|
|
||||||
// If no data, make the stroke transparent
|
|
||||||
if len(data) == 0 {
|
|
||||||
lc.Options().StrokeColor = color.RGBA{0, 0, 0, 0}
|
|
||||||
}
|
|
||||||
// Set data
|
|
||||||
lc.SetData(data)
|
|
||||||
return lc
|
|
||||||
}
|
|
||||||
|
|
||||||
func setOpts(lc *charts.LineChart, col color.Color) {
|
|
||||||
// Get pointer to options
|
|
||||||
opts := lc.Options()
|
|
||||||
// Set fill color to transparent
|
|
||||||
opts.FillColor = color.RGBA{0, 0, 0, 0}
|
|
||||||
// Set stroke width
|
|
||||||
opts.StrokeWidth = 2
|
|
||||||
// If color provided
|
|
||||||
if col != nil {
|
|
||||||
// Set stroke color
|
|
||||||
opts.StrokeColor = col
|
|
||||||
} else {
|
|
||||||
// Set stroke color to orange primary color
|
|
||||||
opts.StrokeColor = theme.PrimaryColorNamed(theme.ColorOrange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getData(db *sql.DB, field, table string) []float64 {
|
|
||||||
// Get data from database
|
|
||||||
rows, err := db.Query("SELECT " + field + " FROM " + table + " ORDER BY time;")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var out []float64
|
|
||||||
for rows.Next() {
|
|
||||||
var val int64
|
|
||||||
// Scan data into int
|
|
||||||
err := rows.Scan(&val)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to float64 and append to data slice
|
|
||||||
out = append(out, float64(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMotionData(db *sql.DB) map[string][]float64 {
|
|
||||||
// Get data from database
|
|
||||||
rows, err := db.Query("SELECT X, Y, Z FROM motion ORDER BY time;")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
out := map[string][]float64{}
|
|
||||||
for rows.Next() {
|
|
||||||
var x, y, z int64
|
|
||||||
// Scan data into ints
|
|
||||||
err := rows.Scan(&x, &y, &z)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to float64 and append to appropriate slice
|
|
||||||
out["X"] = append(out["X"], float64(x))
|
|
||||||
out["Y"] = append(out["Y"], float64(y))
|
|
||||||
out["Z"] = append(out["Z"], float64(z))
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
@ -1,86 +1,123 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"go.elara.ws/itd/api"
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func infoTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
|
func infoTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||||
c := container.NewVBox()
|
infoLayout := container.NewVBox(
|
||||||
|
// Add rectangle for a bit of padding
|
||||||
|
canvas.NewRectangle(color.Transparent),
|
||||||
|
)
|
||||||
|
|
||||||
// Create titled text for heart rate
|
// Create label for heart rate
|
||||||
heartRateText := newTitledText("Heart Rate", "0 BPM")
|
heartRateLbl := newText("0 BPM", 24)
|
||||||
c.Add(heartRateText)
|
// Creae container to store heart rate section
|
||||||
// Watch heart rate
|
heartRateSect := container.NewVBox(
|
||||||
heartRateCh, err := client.WatchHeartRate(ctx)
|
newText("Heart Rate", 12),
|
||||||
|
heartRateLbl,
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
infoLayout.Add(heartRateSect)
|
||||||
|
|
||||||
|
heartRateCh, cancel, err := client.WatchHeartRate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
guiErr(err, "Error watching heart rate", true, w)
|
guiErr(err, "Error getting heart rate channel", true, parent)
|
||||||
}
|
}
|
||||||
|
onClose = append(onClose, cancel)
|
||||||
go func() {
|
go func() {
|
||||||
// For every heart rate sample
|
|
||||||
for heartRate := range heartRateCh {
|
for heartRate := range heartRateCh {
|
||||||
// Set body of titled text
|
// Change text of heart rate label
|
||||||
heartRateText.SetBody(fmt.Sprintf("%d BPM", heartRate))
|
heartRateLbl.Text = fmt.Sprintf("%d BPM", heartRate)
|
||||||
|
// Refresh label
|
||||||
|
heartRateLbl.Refresh()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create titled text for battery level
|
// Create label for heart rate
|
||||||
battLevelText := newTitledText("Battery Level", "0%")
|
stepCountLbl := newText("0 Steps", 24)
|
||||||
c.Add(battLevelText)
|
// Creae container to store heart rate section
|
||||||
// Watch battery level
|
stepCountSect := container.NewVBox(
|
||||||
battLevelCh, err := client.WatchBatteryLevel(ctx)
|
newText("Step Count", 12),
|
||||||
if err != nil {
|
stepCountLbl,
|
||||||
guiErr(err, "Error watching battery level", true, w)
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
}
|
)
|
||||||
go func() {
|
infoLayout.Add(stepCountSect)
|
||||||
// For every battery level sample
|
|
||||||
for battLevel := range battLevelCh {
|
|
||||||
// Set body of titled text
|
|
||||||
battLevelText.SetBody(fmt.Sprintf("%d%%", battLevel))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Create titled text for step count
|
stepCountCh, cancel, err := client.WatchStepCount()
|
||||||
stepCountText := newTitledText("Step Count", "0 Steps")
|
|
||||||
c.Add(stepCountText)
|
|
||||||
// Watch step count
|
|
||||||
stepCountCh, err := client.WatchStepCount(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
guiErr(err, "Error watching step count", true, w)
|
guiErr(err, "Error getting step count channel", true, parent)
|
||||||
}
|
}
|
||||||
|
onClose = append(onClose, cancel)
|
||||||
go func() {
|
go func() {
|
||||||
// For every step count sample
|
|
||||||
for stepCount := range stepCountCh {
|
for stepCount := range stepCountCh {
|
||||||
// Set body of titled text
|
// Change text of heart rate label
|
||||||
stepCountText.SetBody(fmt.Sprintf("%d Steps", stepCount))
|
stepCountLbl.Text = fmt.Sprintf("%d Steps", stepCount)
|
||||||
|
// Refresh label
|
||||||
|
stepCountLbl.Refresh()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create new titled text for address
|
// Create label for battery level
|
||||||
addressText := newTitledText("Address", "")
|
battLevelLbl := newText("0%", 24)
|
||||||
c.Add(addressText)
|
// Create container to store battery level section
|
||||||
// Get address
|
battLevel := container.NewVBox(
|
||||||
address, err := client.Address(ctx)
|
newText("Battery Level", 12),
|
||||||
if err != nil {
|
battLevelLbl,
|
||||||
guiErr(err, "Error getting address", true, w)
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
}
|
)
|
||||||
// Set body of titled text
|
infoLayout.Add(battLevel)
|
||||||
addressText.SetBody(address)
|
|
||||||
|
|
||||||
// Create new titled text for version
|
battLevelCh, cancel, err := client.WatchBatteryLevel()
|
||||||
versionText := newTitledText("Version", "")
|
|
||||||
c.Add(versionText)
|
|
||||||
// Get version
|
|
||||||
version, err := client.Version(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
guiErr(err, "Error getting version", true, w)
|
guiErr(err, "Error getting battery level channel", true, parent)
|
||||||
}
|
}
|
||||||
// Set body of titled text
|
onClose = append(onClose, cancel)
|
||||||
versionText.SetBody(version)
|
go func() {
|
||||||
|
for battLevel := range battLevelCh {
|
||||||
|
// Change text of battery level label
|
||||||
|
battLevelLbl.Text = fmt.Sprintf("%d%%", battLevel)
|
||||||
|
// Refresh label
|
||||||
|
battLevelLbl.Refresh()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return container.NewVScroll(c)
|
fwVerString, err := client.Version()
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error getting firmware string", true, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fwVer := container.NewVBox(
|
||||||
|
newText("Firmware Version", 12),
|
||||||
|
newText(fwVerString, 24),
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
infoLayout.Add(fwVer)
|
||||||
|
|
||||||
|
btAddrString, err := client.Address()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
btAddr := container.NewVBox(
|
||||||
|
newText("Bluetooth Address", 12),
|
||||||
|
newText(btAddrString, 24),
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
infoLayout.Add(btAddr)
|
||||||
|
|
||||||
|
return infoLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
func newText(t string, size float32) *canvas.Text {
|
||||||
|
text := canvas.NewText(t, theme.ForegroundColor())
|
||||||
|
text.TextSize = size
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/canvas"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newLoadingPopUp(w fyne.Window) *widget.PopUp {
|
|
||||||
pb := widget.NewProgressBarInfinite()
|
|
||||||
rect := canvas.NewRectangle(color.Transparent)
|
|
||||||
rect.SetMinSize(fyne.NewSize(200, 0))
|
|
||||||
|
|
||||||
return widget.NewModalPopUp(
|
|
||||||
container.NewMax(rect, pb),
|
|
||||||
w.Canvas(),
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,60 +1,43 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2/app"
|
"fyne.io/fyne/v2/app"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"go.elara.ws/itd/api"
|
"go.arsenm.dev/itd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var onClose []func()
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Create new app
|
||||||
a := app.New()
|
a := app.New()
|
||||||
w := a.NewWindow("itgui")
|
// Create new window with title "itgui"
|
||||||
|
window := a.NewWindow("itgui")
|
||||||
|
window.SetOnClosed(func() {
|
||||||
|
for _, closeFn := range onClose {
|
||||||
|
closeFn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Create new context for use with the API client
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
// Connect to ITD API
|
|
||||||
client, err := api.New(api.DefaultAddr)
|
client, err := api.New(api.DefaultAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
guiErr(err, "Error connecting to ITD", true, w)
|
guiErr(err, "Error connecting to itd", true, window)
|
||||||
}
|
}
|
||||||
|
onClose = append(onClose, func() {
|
||||||
|
client.Close()
|
||||||
|
})
|
||||||
|
|
||||||
// Create channel to signal that the fs tab has been opened
|
// Create new app tabs container
|
||||||
fsOpened := make(chan struct{})
|
|
||||||
fsOnce := &sync.Once{}
|
|
||||||
|
|
||||||
// Create app tabs
|
|
||||||
tabs := container.NewAppTabs(
|
tabs := container.NewAppTabs(
|
||||||
container.NewTabItem("Info", infoTab(ctx, client, w)),
|
container.NewTabItem("Info", infoTab(window, client)),
|
||||||
container.NewTabItem("Motion", motionTab(ctx, client, w)),
|
container.NewTabItem("Motion", motionTab(window, client)),
|
||||||
container.NewTabItem("Notify", notifyTab(ctx, client, w)),
|
container.NewTabItem("Notify", notifyTab(window, client)),
|
||||||
container.NewTabItem("FS", fsTab(ctx, client, w, fsOpened)),
|
container.NewTabItem("Set Time", timeTab(window, client)),
|
||||||
container.NewTabItem("Time", timeTab(ctx, client, w)),
|
container.NewTabItem("Upgrade", upgradeTab(window, client)),
|
||||||
container.NewTabItem("Firmware", firmwareTab(ctx, client, w)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
metricsTab := graphTab(ctx, client, w)
|
// Set tabs as window content
|
||||||
if metricsTab != nil {
|
window.SetContent(tabs)
|
||||||
tabs.Append(container.NewTabItem("Metrics", metricsTab))
|
// Show window and run app
|
||||||
}
|
window.ShowAndRun()
|
||||||
|
|
||||||
// When a tab is selected
|
|
||||||
tabs.OnSelected = func(ti *container.TabItem) {
|
|
||||||
// If the tab's name is FS
|
|
||||||
if ti.Text == "FS" {
|
|
||||||
// Signal fsOpened only once
|
|
||||||
fsOnce.Do(func() {
|
|
||||||
fsOpened <- struct{}{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel context on close
|
|
||||||
w.SetOnClosed(cancel)
|
|
||||||
// Set content and show window
|
|
||||||
w.SetContent(tabs)
|
|
||||||
w.ShowAndRun()
|
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,105 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"image/color"
|
||||||
"fmt"
|
"strconv"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"go.elara.ws/itd/api"
|
"go.arsenm.dev/itd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func motionTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
|
func motionTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||||
// Create titledText for each coordinate
|
// Create label for heart rate
|
||||||
xText := newTitledText("X Coordinate", "0")
|
xCoordLbl := newText("0", 24)
|
||||||
yText := newTitledText("Y Coordinate", "0")
|
// Creae container to store heart rate section
|
||||||
zText := newTitledText("Z Coordinate", "0")
|
xCoordSect := container.NewVBox(
|
||||||
|
newText("X Coordinate", 12),
|
||||||
|
xCoordLbl,
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
|
||||||
var ctxCancel func()
|
// Create label for heart rate
|
||||||
|
yCoordLbl := newText("0", 24)
|
||||||
|
// Creae container to store heart rate section
|
||||||
|
yCoordSect := container.NewVBox(
|
||||||
|
newText("Y Coordinate", 12),
|
||||||
|
yCoordLbl,
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
// Create label for heart rate
|
||||||
|
zCoordLbl := newText("0", 24)
|
||||||
|
// Creae container to store heart rate section
|
||||||
|
zCoordSect := container.NewVBox(
|
||||||
|
newText("Z Coordinate", 12),
|
||||||
|
zCoordLbl,
|
||||||
|
canvas.NewLine(theme.ShadowColor()),
|
||||||
|
)
|
||||||
|
|
||||||
// Create start button
|
// Create variable to keep track of whether motion started
|
||||||
toggleBtn := widget.NewButton("Start", nil)
|
started := false
|
||||||
// Set button's on tapped callback
|
|
||||||
toggleBtn.OnTapped = func() {
|
// Create button to stop motion
|
||||||
switch toggleBtn.Text {
|
stopBtn := widget.NewButton("Stop", nil)
|
||||||
case "Start":
|
// Create button to start motion
|
||||||
// Create new context for motion
|
startBtn := widget.NewButton("Start", func() {
|
||||||
motionCtx, cancel := context.WithCancel(ctx)
|
// if motion is started
|
||||||
// Set ctxCancel to function so that stop button can run it
|
if started {
|
||||||
ctxCancel = cancel
|
// Do nothing
|
||||||
// Watch motion
|
|
||||||
motionCh, err := client.WatchMotion(motionCtx)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error watching motion", false, w)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Set motion started
|
||||||
|
started = true
|
||||||
|
// Watch motion values
|
||||||
|
motionCh, cancel, err := client.WatchMotion()
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error getting heart rate channel", true, parent)
|
||||||
|
}
|
||||||
|
// Create done channel
|
||||||
|
done := make(chan struct{}, 1)
|
||||||
go func() {
|
go func() {
|
||||||
// For every motion event
|
for {
|
||||||
for motion := range motionCh {
|
select {
|
||||||
// Set coordinates
|
case <-done:
|
||||||
xText.SetBody(fmt.Sprint(motion.X))
|
return
|
||||||
yText.SetBody(fmt.Sprint(motion.Y))
|
case motion := <-motionCh:
|
||||||
zText.SetBody(fmt.Sprint(motion.Z))
|
// Set labels to new values
|
||||||
|
xCoordLbl.Text = strconv.Itoa(int(motion.X))
|
||||||
|
yCoordLbl.Text = strconv.Itoa(int(motion.Y))
|
||||||
|
zCoordLbl.Text = strconv.Itoa(int(motion.Z))
|
||||||
|
// Refresh labels to display new values
|
||||||
|
xCoordLbl.Refresh()
|
||||||
|
yCoordLbl.Refresh()
|
||||||
|
zCoordLbl.Refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Set button text to "Stop"
|
// Create stop function
|
||||||
toggleBtn.SetText("Stop")
|
stopBtn.OnTapped = func() {
|
||||||
case "Stop":
|
done <- struct{}{}
|
||||||
// Cancel motion context
|
started = false
|
||||||
ctxCancel()
|
cancel()
|
||||||
// Set button text to "Start"
|
|
||||||
toggleBtn.SetText("Start")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return container.NewVScroll(container.NewVBox(
|
})
|
||||||
toggleBtn,
|
// Run stop button function on close if possible
|
||||||
xText,
|
onClose = append(onClose, func() {
|
||||||
yText,
|
if stopBtn.OnTapped != nil {
|
||||||
zText,
|
stopBtn.OnTapped()
|
||||||
))
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return new container containing all elements
|
||||||
|
return container.NewVBox(
|
||||||
|
// Add rectangle for a bit of padding
|
||||||
|
canvas.NewRectangle(color.Transparent),
|
||||||
|
startBtn,
|
||||||
|
stopBtn,
|
||||||
|
xCoordSect,
|
||||||
|
yCoordSect,
|
||||||
|
zCoordSect,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,37 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/layout"
|
"fyne.io/fyne/v2/layout"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"go.elara.ws/itd/api"
|
"go.arsenm.dev/itd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
|
func notifyTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||||
c := container.NewVBox()
|
// Create new entry for notification title
|
||||||
c.Add(layout.NewSpacer())
|
|
||||||
|
|
||||||
// Create new entry for title
|
|
||||||
titleEntry := widget.NewEntry()
|
titleEntry := widget.NewEntry()
|
||||||
titleEntry.SetPlaceHolder("Title")
|
titleEntry.SetPlaceHolder("Title")
|
||||||
c.Add(titleEntry)
|
|
||||||
|
|
||||||
// Create new multiline entry for body
|
// Create multiline entry for notification body
|
||||||
bodyEntry := widget.NewMultiLineEntry()
|
bodyEntry := widget.NewMultiLineEntry()
|
||||||
bodyEntry.SetPlaceHolder("Body")
|
bodyEntry.SetPlaceHolder("Body")
|
||||||
c.Add(bodyEntry)
|
|
||||||
|
|
||||||
// Create new send button
|
// Create new button to send notification
|
||||||
sendBtn := widget.NewButton("Send", func() {
|
sendBtn := widget.NewButton("Send", func() {
|
||||||
// Send notification
|
err := client.Notify(titleEntry.Text, bodyEntry.Text)
|
||||||
err := client.Notify(ctx, titleEntry.Text, bodyEntry.Text)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
guiErr(err, "Error sending notification", false, w)
|
guiErr(err, "Error sending notification", false, parent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
c.Add(sendBtn)
|
|
||||||
|
|
||||||
c.Add(layout.NewSpacer())
|
// Return new container containing all elements
|
||||||
return container.NewVScroll(c)
|
return container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
titleEntry,
|
||||||
|
bodyEntry,
|
||||||
|
sendBtn,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
|
||||||
"fyne.io/fyne/v2/canvas"
|
|
||||||
"fyne.io/fyne/v2/container"
|
|
||||||
"fyne.io/fyne/v2/widget"
|
|
||||||
)
|
|
||||||
|
|
||||||
type progress struct {
|
|
||||||
lbl *widget.Label
|
|
||||||
progLbl *widget.Label
|
|
||||||
pb *widget.ProgressBar
|
|
||||||
*widget.PopUp
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProgress(w fyne.Window) progress {
|
|
||||||
out := progress{}
|
|
||||||
|
|
||||||
out.lbl = widget.NewLabel("")
|
|
||||||
out.lbl.Hide()
|
|
||||||
|
|
||||||
// Create label to show how many bytes transfered and center it
|
|
||||||
out.progLbl = widget.NewLabel("0 / 0 B")
|
|
||||||
out.progLbl.Alignment = fyne.TextAlignCenter
|
|
||||||
|
|
||||||
// Create new progress bar
|
|
||||||
out.pb = widget.NewProgressBar()
|
|
||||||
|
|
||||||
// Create new rectangle to set the size of the popup
|
|
||||||
sizeRect := canvas.NewRectangle(color.Transparent)
|
|
||||||
sizeRect.SetMinSize(fyne.NewSize(300, 50))
|
|
||||||
|
|
||||||
// Create vbox for label and progress bar
|
|
||||||
l := container.NewVBox(out.lbl, out.progLbl, out.pb)
|
|
||||||
// Create popup
|
|
||||||
out.PopUp = widget.NewModalPopUp(container.NewMax(l, sizeRect), w.Canvas())
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p progress) SetText(s string) {
|
|
||||||
p.lbl.SetText(s)
|
|
||||||
|
|
||||||
if s == "" {
|
|
||||||
p.lbl.Hide()
|
|
||||||
} else {
|
|
||||||
p.lbl.Show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p progress) SetTotal(v float64) {
|
|
||||||
p.pb.Max = v
|
|
||||||
p.pb.Refresh()
|
|
||||||
p.progLbl.SetText(fmt.Sprintf("%.0f / %.0f B", p.pb.Value, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p progress) SetValue(v float64) {
|
|
||||||
p.pb.SetValue(v)
|
|
||||||
p.progLbl.SetText(fmt.Sprintf("%.0f / %.0f B", v, p.pb.Max))
|
|
||||||
}
|
|
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -1,57 +1,60 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/layout"
|
"fyne.io/fyne/v2/layout"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
"go.elara.ws/itd/api"
|
"go.arsenm.dev/itd/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func timeTab(ctx context.Context, client *api.Client, w fyne.Window) fyne.CanvasObject {
|
func timeTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||||
c := container.NewVBox()
|
// Create new entry for time string
|
||||||
c.Add(layout.NewSpacer())
|
|
||||||
|
|
||||||
// Create entry for time string
|
|
||||||
timeEntry := widget.NewEntry()
|
timeEntry := widget.NewEntry()
|
||||||
|
// Set text to current time formatter properly
|
||||||
timeEntry.SetText(time.Now().Format(time.RFC1123))
|
timeEntry.SetText(time.Now().Format(time.RFC1123))
|
||||||
timeEntry.SetPlaceHolder("RFC1123")
|
|
||||||
|
|
||||||
// Create button to set current time
|
// Create button to set current time
|
||||||
setCurrentBtn := widget.NewButton("Set current time", func() {
|
currentBtn := widget.NewButton("Set Current", func() {
|
||||||
// Set current time
|
|
||||||
err := client.SetTime(ctx, time.Now())
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error setting time", false, w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Set time entry to current time
|
|
||||||
timeEntry.SetText(time.Now().Format(time.RFC1123))
|
timeEntry.SetText(time.Now().Format(time.RFC1123))
|
||||||
|
setTime(client, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create button to set time from entry
|
// Create button to set time inside entry
|
||||||
setBtn := widget.NewButton("Set", func() {
|
timeBtn := widget.NewButton("Set", func() {
|
||||||
// Parse RFC1123 time string in entry
|
// Parse time as RFC1123 string
|
||||||
newTime, err := time.Parse(time.RFC1123, timeEntry.Text)
|
parsedTime, err := time.Parse(time.RFC1123, timeEntry.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
guiErr(err, "Error parsing time string", false, w)
|
guiErr(err, "Error parsing time string", false, parent)
|
||||||
return
|
|
||||||
}
|
|
||||||
// Set time from parsed string
|
|
||||||
err = client.SetTime(ctx, newTime)
|
|
||||||
if err != nil {
|
|
||||||
guiErr(err, "Error setting time", false, w)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Set time to parsed time
|
||||||
|
setTime(client, false, parsedTime)
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Add(timeEntry)
|
// Return new container with all elements centered
|
||||||
c.Add(setBtn)
|
return container.NewVBox(
|
||||||
c.Add(setCurrentBtn)
|
layout.NewSpacer(),
|
||||||
|
timeEntry,
|
||||||
c.Add(layout.NewSpacer())
|
currentBtn,
|
||||||
return c
|
timeBtn,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTime sets the first element in the variadic parameter
|
||||||
|
// if current is false, otherwise, it sets the current time.
|
||||||
|
func setTime(client *api.Client, current bool, t ...time.Time) error {
|
||||||
|
var err error
|
||||||
|
if current {
|
||||||
|
err = client.SetTimeNow()
|
||||||
|
} else {
|
||||||
|
err = client.SetTime(t[0])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "fyne.io/fyne/v2/widget"
|
|
||||||
|
|
||||||
type titledText struct {
|
|
||||||
*widget.RichText
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTitledText(title, text string) titledText {
|
|
||||||
titleStyle := widget.RichTextStyleHeading
|
|
||||||
titleStyle.TextStyle.Bold = false
|
|
||||||
return titledText{
|
|
||||||
widget.NewRichText(
|
|
||||||
&widget.TextSegment{
|
|
||||||
Style: widget.RichTextStyleParagraph,
|
|
||||||
Text: title,
|
|
||||||
},
|
|
||||||
&widget.TextSegment{
|
|
||||||
Style: titleStyle,
|
|
||||||
Text: text,
|
|
||||||
},
|
|
||||||
&widget.SeparatorSegment{},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t titledText) SetTitle(s string) {
|
|
||||||
t.RichText.Segments[0].(*widget.TextSegment).Text = s
|
|
||||||
t.Refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t titledText) SetBody(s string) {
|
|
||||||
t.RichText.Segments[1].(*widget.TextSegment).Text = s
|
|
||||||
t.Refresh()
|
|
||||||
}
|
|
181
cmd/itgui/upgrade.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/storage"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"go.arsenm.dev/itd/api"
|
||||||
|
"go.arsenm.dev/itd/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func upgradeTab(parent fyne.Window, client *api.Client) *fyne.Container {
|
||||||
|
var (
|
||||||
|
archivePath string
|
||||||
|
firmwarePath string
|
||||||
|
initPktPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
var archiveBtn *widget.Button
|
||||||
|
// Create archive selection dialog
|
||||||
|
archiveDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
|
||||||
|
if e != nil || uc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.Close()
|
||||||
|
archivePath = uc.URI().Path()
|
||||||
|
archiveBtn.SetText(fmt.Sprintf("Select archive (.zip) [%s]", filepath.Base(archivePath)))
|
||||||
|
}, parent)
|
||||||
|
// Limit dialog to .zip files
|
||||||
|
archiveDialog.SetFilter(storage.NewExtensionFileFilter([]string{".zip"}))
|
||||||
|
// Create button to show dialog
|
||||||
|
archiveBtn = widget.NewButton("Select archive (.zip)", archiveDialog.Show)
|
||||||
|
|
||||||
|
var firmwareBtn *widget.Button
|
||||||
|
// Create firmware selection dialog
|
||||||
|
firmwareDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
|
||||||
|
if e != nil || uc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.Close()
|
||||||
|
firmwarePath = uc.URI().Path()
|
||||||
|
firmwareBtn.SetText(fmt.Sprintf("Select firmware (.bin) [%s]", filepath.Base(firmwarePath)))
|
||||||
|
}, parent)
|
||||||
|
// Limit dialog to .bin files
|
||||||
|
firmwareDialog.SetFilter(storage.NewExtensionFileFilter([]string{".bin"}))
|
||||||
|
// Create button to show dialog
|
||||||
|
firmwareBtn = widget.NewButton("Select firmware (.bin)", firmwareDialog.Show)
|
||||||
|
|
||||||
|
var initPktBtn *widget.Button
|
||||||
|
// Create init packet selection dialog
|
||||||
|
initPktDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, e error) {
|
||||||
|
if e != nil || uc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uc.Close()
|
||||||
|
initPktPath = uc.URI().Path()
|
||||||
|
initPktBtn.SetText(fmt.Sprintf("Select init packet (.dat) [%s]", filepath.Base(initPktPath)))
|
||||||
|
}, parent)
|
||||||
|
// Limit dialog to .dat files
|
||||||
|
initPktDialog.SetFilter(storage.NewExtensionFileFilter([]string{".dat"}))
|
||||||
|
// Create button to show dialog
|
||||||
|
initPktBtn = widget.NewButton("Select init packet (.dat)", initPktDialog.Show)
|
||||||
|
|
||||||
|
// Hide init packet and firmware buttons
|
||||||
|
initPktBtn.Hide()
|
||||||
|
firmwareBtn.Hide()
|
||||||
|
|
||||||
|
// Create dropdown to select upgrade type
|
||||||
|
upgradeTypeSelect := widget.NewSelect([]string{
|
||||||
|
"Archive",
|
||||||
|
"Files",
|
||||||
|
}, func(s string) {
|
||||||
|
// Hide all buttons
|
||||||
|
archiveBtn.Hide()
|
||||||
|
initPktBtn.Hide()
|
||||||
|
firmwareBtn.Hide()
|
||||||
|
// Unhide appropriate button(s)
|
||||||
|
switch s {
|
||||||
|
case "Archive":
|
||||||
|
archiveBtn.Show()
|
||||||
|
case "Files":
|
||||||
|
initPktBtn.Show()
|
||||||
|
firmwareBtn.Show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Select first elemetn
|
||||||
|
upgradeTypeSelect.SetSelectedIndex(0)
|
||||||
|
|
||||||
|
// Create new button to start DFU
|
||||||
|
startBtn := widget.NewButton("Start", func() {
|
||||||
|
// If archive path does not exist and both init packet and firmware paths
|
||||||
|
// also do not exist, return error
|
||||||
|
if archivePath == "" && (initPktPath == "" && firmwarePath == "") {
|
||||||
|
guiErr(nil, "Upgrade requires archive or files selected", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new label for byte progress
|
||||||
|
progressLbl := widget.NewLabelWithStyle("0 / 0 B", fyne.TextAlignCenter, fyne.TextStyle{})
|
||||||
|
// Create new progress bar
|
||||||
|
progressBar := widget.NewProgressBar()
|
||||||
|
// Create modal dialog containing label and progress bar
|
||||||
|
progressDlg := widget.NewModalPopUp(container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
progressLbl,
|
||||||
|
progressBar,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
), parent.Canvas())
|
||||||
|
// Resize modal to 300x100
|
||||||
|
progressDlg.Resize(fyne.NewSize(300, 100))
|
||||||
|
|
||||||
|
var fwUpgType api.UpgradeType
|
||||||
|
var files []string
|
||||||
|
// Get appropriate upgrade type and file paths
|
||||||
|
switch upgradeTypeSelect.Selected {
|
||||||
|
case "Archive":
|
||||||
|
fwUpgType = types.UpgradeTypeArchive
|
||||||
|
files = append(files, archivePath)
|
||||||
|
case "Files":
|
||||||
|
fwUpgType = types.UpgradeTypeFiles
|
||||||
|
files = append(files, initPktPath, firmwarePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
progress, err := client.FirmwareUpgrade(fwUpgType, files...)
|
||||||
|
if err != nil {
|
||||||
|
guiErr(err, "Error initiating DFU", false, parent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show progress dialog
|
||||||
|
progressDlg.Show()
|
||||||
|
|
||||||
|
for event := range progress {
|
||||||
|
// Set label text to received / total B
|
||||||
|
progressLbl.SetText(fmt.Sprintf("%d / %d B", event.Received, event.Total))
|
||||||
|
// Set progress bar values
|
||||||
|
progressBar.Max = float64(event.Total)
|
||||||
|
progressBar.Value = float64(event.Received)
|
||||||
|
// Refresh progress bar
|
||||||
|
progressBar.Refresh()
|
||||||
|
// If transfer finished, break
|
||||||
|
if event.Sent == event.Total {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide progress dialog after completion
|
||||||
|
progressDlg.Hide()
|
||||||
|
|
||||||
|
// Reset screen to default
|
||||||
|
upgradeTypeSelect.SetSelectedIndex(0)
|
||||||
|
firmwareBtn.SetText("Select firmware (.bin)")
|
||||||
|
initPktBtn.SetText("Select init packet (.dat)")
|
||||||
|
archiveBtn.SetText("Select archive (.zip)")
|
||||||
|
firmwarePath = ""
|
||||||
|
initPktPath = ""
|
||||||
|
archivePath = ""
|
||||||
|
|
||||||
|
dialog.NewInformation(
|
||||||
|
"Upgrade Complete",
|
||||||
|
"The firmware was transferred successfully.\nRemember to validate the firmware in InfiniTime settings.",
|
||||||
|
parent,
|
||||||
|
).Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return container containing all elements
|
||||||
|
return container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
upgradeTypeSelect,
|
||||||
|
archiveBtn,
|
||||||
|
firmwareBtn,
|
||||||
|
initPktBtn,
|
||||||
|
startBtn,
|
||||||
|
layout.NewSpacer(),
|
||||||
|
)
|
||||||
|
}
|
118
config.go
@ -2,112 +2,50 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/knadh/koanf/parsers/toml"
|
"github.com/rs/zerolog"
|
||||||
"github.com/knadh/koanf/providers/confmap"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/knadh/koanf/providers/env"
|
"github.com/spf13/viper"
|
||||||
"github.com/knadh/koanf/providers/file"
|
|
||||||
"go.elara.ws/logger"
|
|
||||||
"go.elara.ws/logger/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfgDir string
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
etcPath := "/etc/itd.toml"
|
|
||||||
|
|
||||||
// Set up logger
|
// Set up logger
|
||||||
log.Logger = logger.NewPretty(os.Stderr)
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
// Get user's configuration directory
|
// Set config settings
|
||||||
userCfgDir, err := os.UserConfigDir()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
cfgDir = filepath.Join(userCfgDir, "itd")
|
|
||||||
|
|
||||||
// If config dir is not readable
|
|
||||||
if _, err = os.ReadDir(cfgDir); err != nil {
|
|
||||||
// Create config dir with 700 permissions
|
|
||||||
err = os.MkdirAll(cfgDir, 0o700)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current and old config paths
|
|
||||||
cfgPath := filepath.Join(cfgDir, "itd.toml")
|
|
||||||
oldCfgPath := filepath.Join(userCfgDir, "itd.toml")
|
|
||||||
|
|
||||||
// If old config path exists
|
|
||||||
if _, err = os.Stat(oldCfgPath); err == nil {
|
|
||||||
// Move old config to new path
|
|
||||||
err = os.Rename(oldCfgPath, cfgPath)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set config defaults
|
|
||||||
setCfgDefaults()
|
setCfgDefaults()
|
||||||
|
viper.AddConfigPath("$HOME/.config")
|
||||||
// Load and watch config files
|
viper.AddConfigPath("/etc")
|
||||||
loadAndwatchCfgFile(etcPath)
|
viper.SetConfigName("itd")
|
||||||
loadAndwatchCfgFile(cfgPath)
|
viper.SetConfigType("toml")
|
||||||
|
viper.WatchConfig()
|
||||||
// Load envireonment variables
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
k.Load(env.Provider("ITD_", "_", func(s string) string {
|
viper.SetEnvPrefix("itd")
|
||||||
return strings.ToLower(strings.TrimPrefix(s, "ITD_"))
|
// Ignore error because defaults set
|
||||||
}), nil)
|
viper.ReadInConfig()
|
||||||
}
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
func loadAndwatchCfgFile(filename string) {
|
|
||||||
provider := file.Provider(filename)
|
|
||||||
|
|
||||||
if cfgError := k.Load(provider, toml.Parser()); cfgError != nil {
|
|
||||||
log.Warn("Error while trying to read config file").Str("filename", filename).Err(cfgError).Send()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for changes and reload when detected
|
|
||||||
provider.Watch(func(_ interface{}, err error) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfgError := k.Load(provider, toml.Parser()); cfgError != nil {
|
|
||||||
log.Warn("Error while trying to read config file").Str("filename", filename).Err(cfgError).Send()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCfgDefaults() {
|
func setCfgDefaults() {
|
||||||
k.Load(confmap.Provider(map[string]interface{}{
|
viper.SetDefault("socket.path", "/tmp/itd/socket")
|
||||||
"bluetooth.adapter": "hci0",
|
|
||||||
|
|
||||||
"socket.path": "/tmp/itd/socket",
|
viper.SetDefault("conn.reconnect", true)
|
||||||
|
|
||||||
"conn.reconnect": true,
|
viper.SetDefault("conn.whitelist.enabled", false)
|
||||||
|
viper.SetDefault("conn.whitelist.devices", []string{})
|
||||||
|
|
||||||
"conn.whitelist.enabled": false,
|
viper.SetDefault("on.connect.notify", true)
|
||||||
"conn.whitelist.devices": []string{},
|
|
||||||
|
|
||||||
"on.connect.notify": true,
|
viper.SetDefault("on.reconnect.notify", true)
|
||||||
|
viper.SetDefault("on.reconnect.setTime", true)
|
||||||
|
|
||||||
"on.reconnect.notify": true,
|
viper.SetDefault("notifs.translit.use", []string{"eASCII"})
|
||||||
"on.reconnect.setTime": true,
|
viper.SetDefault("notifs.translit.custom", []string{})
|
||||||
|
|
||||||
"notifs.translit.use": []string{"eASCII"},
|
viper.SetDefault("notifs.ignore.sender", []string{})
|
||||||
"notifs.translit.custom": []string{},
|
viper.SetDefault("notifs.ignore.summary", []string{"InfiniTime"})
|
||||||
|
viper.SetDefault("notifs.ignore.body", []string{})
|
||||||
|
|
||||||
"notifs.ignore.sender": []string{},
|
viper.SetDefault("music.vol.interval", 5)
|
||||||
"notifs.ignore.summary": []string{"InfiniTime"},
|
|
||||||
"notifs.ignore.body": []string{},
|
|
||||||
|
|
||||||
"music.vol.interval": 5,
|
|
||||||
|
|
||||||
"fuse.enabled": false,
|
|
||||||
"fuse.mountpoint": "/tmp/itd/mnt",
|
|
||||||
}, "."), nil)
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
package utils
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/godbus/dbus/v5"
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
func newSystemBusConn() (*dbus.Conn, error) {
|
||||||
)
|
|
||||||
|
|
||||||
func NewSystemBusConn(ctx context.Context) (*dbus.Conn, error) {
|
|
||||||
// Connect to dbus session bus
|
// Connect to dbus session bus
|
||||||
conn, err := dbus.SystemBusPrivate(dbus.WithContext(ctx))
|
conn, err := dbus.SystemBusPrivate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -23,9 +19,9 @@ func NewSystemBusConn(ctx context.Context) (*dbus.Conn, error) {
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionBusConn(ctx context.Context) (*dbus.Conn, error) {
|
func newSessionBusConn() (*dbus.Conn, error) {
|
||||||
// Connect to dbus session bus
|
// Connect to dbus session bus
|
||||||
conn, err := dbus.SessionBusPrivate(dbus.WithContext(ctx))
|
conn, err := dbus.SessionBusPrivate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
66
fuse.go
@ -1,66 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/hanwen/go-fuse/v2/fs"
|
|
||||||
"github.com/hanwen/go-fuse/v2/fuse"
|
|
||||||
"go.elara.ws/itd/infinitime"
|
|
||||||
"go.elara.ws/itd/internal/fusefs"
|
|
||||||
"go.elara.ws/logger/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func startFUSE(ctx context.Context, wg WaitGroup, dev *infinitime.Device) error {
|
|
||||||
// This is where we'll mount the FS
|
|
||||||
err := os.MkdirAll(k.String("fuse.mountpoint"), 0o755)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore the error because nothing might be mounted on the mountpoint
|
|
||||||
_ = fusefs.Unmount(k.String("fuse.mountpoint"))
|
|
||||||
|
|
||||||
root, err := fusefs.BuildRootNode(dev)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Building root node failed").
|
|
||||||
Err(err).
|
|
||||||
Send()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := fs.Mount(k.String("fuse.mountpoint"), root, &fs.Options{
|
|
||||||
MountOptions: fuse.MountOptions{
|
|
||||||
// Set to true to see how the file system works.
|
|
||||||
Debug: false,
|
|
||||||
SingleThreaded: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Mounting failed").
|
|
||||||
Str("target", k.String("fuse.mountpoint")).
|
|
||||||
Err(err).
|
|
||||||
Send()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Mounted on target").
|
|
||||||
Str("target", k.String("fuse.mountpoint")).
|
|
||||||
Send()
|
|
||||||
|
|
||||||
fusefs.BuildProperties(dev)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Error getting BLE filesystem").Err(err).Send()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done("fuse")
|
|
||||||
<-ctx.Done()
|
|
||||||
server.Unmount()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
112
go.mod
@ -1,91 +1,33 @@
|
|||||||
module go.elara.ws/itd
|
module go.arsenm.dev/itd
|
||||||
|
|
||||||
go 1.18
|
go 1.16
|
||||||
|
|
||||||
replace fyne.io/x/fyne => github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb
|
|
||||||
|
|
||||||
replace tinygo.org/x/bluetooth => github.com/elara6331/bluetooth v0.9.1-0.20240413234149-a0e71474a768
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.3.0
|
fyne.io/fyne/v2 v2.1.0
|
||||||
fyne.io/x/fyne v0.0.0-20220107050838-c4a1de51d4ce
|
|
||||||
github.com/cheggaaa/pb/v3 v3.1.0
|
|
||||||
github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d
|
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
|
||||||
github.com/hanwen/go-fuse/v2 v2.2.0
|
|
||||||
github.com/knadh/koanf v1.4.4
|
|
||||||
github.com/mattn/go-isatty v0.0.17
|
|
||||||
github.com/mozillazg/go-pinyin v0.19.0
|
|
||||||
github.com/urfave/cli/v2 v2.23.7
|
|
||||||
go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d
|
|
||||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae
|
|
||||||
golang.org/x/text v0.5.0
|
|
||||||
google.golang.org/protobuf v1.28.1
|
|
||||||
modernc.org/sqlite v1.20.1
|
|
||||||
storj.io/drpc v0.0.32
|
|
||||||
tinygo.org/x/bluetooth v0.9.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
fyne.io/systray v1.10.1-0.20221115204952-d16a6177e6f1 // indirect
|
|
||||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||||
github.com/benoitkugler/textlayout v0.3.0 // indirect
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/cheggaaa/pb/v3 v3.0.8
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
|
||||||
github.com/fatih/color v1.13.0 // indirect
|
github.com/fatih/color v1.13.0 // indirect
|
||||||
github.com/fredbi/uri v1.0.0 // indirect
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 // indirect
|
||||||
github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 // indirect
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be // indirect
|
||||||
github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 // indirect
|
github.com/godbus/dbus/v5 v5.0.5
|
||||||
github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 // indirect
|
github.com/google/uuid v1.1.2
|
||||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
|
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/mitchellh/mapstructure v1.4.2
|
||||||
github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181 // indirect
|
github.com/mozillazg/go-pinyin v0.18.0
|
||||||
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
|
github.com/rs/zerolog v1.25.0
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/gookit/color v1.5.1 // indirect
|
github.com/spf13/cobra v1.2.1
|
||||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
github.com/spf13/viper v1.9.0
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/srwiley/oksvg v0.0.0-20210519022825-9fc0c575d5fe // indirect
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/yuin/goldmark v1.4.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
go.arsenm.dev/infinitime v0.0.0-20211123052001-ec1548ec0f30
|
||||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
golang.org/x/text v0.3.7
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.3 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20240320113951-a2e4fc03f5f4 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
|
||||||
github.com/stretchr/testify v1.8.1 // indirect
|
|
||||||
github.com/tevino/abool v1.2.0 // indirect
|
|
||||||
github.com/tinygo-org/cbgo v0.0.4 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
|
||||||
github.com/yuin/goldmark v1.5.3 // indirect
|
|
||||||
github.com/zeebo/errs v1.3.0 // indirect
|
|
||||||
golang.org/x/image v0.2.0 // indirect
|
|
||||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 // indirect
|
|
||||||
golang.org/x/mod v0.7.0 // indirect
|
|
||||||
golang.org/x/net v0.4.0 // indirect
|
|
||||||
golang.org/x/sys v0.11.0 // indirect
|
|
||||||
golang.org/x/tools v0.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 // indirect
|
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
|
||||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
|
||||||
modernc.org/libc v1.22.2 // indirect
|
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
|
||||||
modernc.org/memory v1.5.0 // indirect
|
|
||||||
modernc.org/opt v0.1.3 // indirect
|
|
||||||
modernc.org/strutil v1.1.3 // indirect
|
|
||||||
modernc.org/token v1.1.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
488
go.sum
@ -18,6 +18,11 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
|
|||||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||||
|
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||||
|
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||||
|
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||||
|
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||||
|
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
@ -27,6 +32,7 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
|
|||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
@ -37,144 +43,90 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
fyne.io/fyne/v2 v2.1.0 h1:qzdkaXL/UpmMtG4FlsX9xMZ0Q93CRzLxkoiSXyplP/I=
|
||||||
fyne.io/fyne/v2 v2.1.0/go.mod h1:c1vwI38Ebd0dAdxVa6H1Pj6/+cK1xtDy61+I31g+s14=
|
fyne.io/fyne/v2 v2.1.0/go.mod h1:c1vwI38Ebd0dAdxVa6H1Pj6/+cK1xtDy61+I31g+s14=
|
||||||
fyne.io/fyne/v2 v2.3.0 h1:g9tPI3lyBK50IvyPbXqv2zI3JJ4uhMAffu89f3nX5PU=
|
|
||||||
fyne.io/fyne/v2 v2.3.0/go.mod h1:odfJmbFnODiKn1MXdL44JR6CK+0v8lrmgdPlrUF6w0M=
|
|
||||||
fyne.io/systray v1.10.1-0.20221115204952-d16a6177e6f1 h1:OiHw+bZAGEaSreHsA8dDkBOVJmSFzsNTOc/htpM+fOc=
|
|
||||||
fyne.io/systray v1.10.1-0.20221115204952-d16a6177e6f1/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
|
|
||||||
github.com/Andrew-M-C/go.jsonvalue v1.1.2-0.20211223013816-e873b56b4a84/go.mod h1:oTJGG91FhtsxvUFVwHSvr6zuaTcAuroj/ToxfT7Ox8U=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
|
||||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||||
|
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
|
||||||
|
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
|
||||||
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
||||||
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
|
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
|
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
|
|
||||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
|
||||||
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
|
|
||||||
github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk=
|
|
||||||
github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
|
|
||||||
github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk=
|
|
||||||
github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cheggaaa/pb/v3 v3.1.0 h1:3uouEsl32RL7gTiQsuaXD4Bzbfl5tGztXGUvXbs4O04=
|
github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA=
|
||||||
github.com/cheggaaa/pb/v3 v3.1.0/go.mod h1:YjrevcBqadFDaGQKRdmZxTY42pXEqda48Ea3lt0K/BE=
|
github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
|
||||||
|
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
|
|
||||||
github.com/elara6331/bluetooth v0.9.1-0.20240413234149-a0e71474a768 h1:iWP52WinMhd+pQB+2GedWvUxkd4pMqFvV0S6MjMFQSc=
|
|
||||||
github.com/elara6331/bluetooth v0.9.1-0.20240413234149-a0e71474a768/go.mod h1:V9XwH/xQ2SmCIW+T0pmpL7VzijY53JRVsJcDM0YN6PI=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
|
||||||
|
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
||||||
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
||||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
||||||
github.com/fredbi/uri v0.1.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
|
|
||||||
github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg=
|
|
||||||
github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
|
||||||
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
|
|
||||||
github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381 h1:SFtj9yo9C7F4CxyJeSJi9AjT6x9c88gnY1tjlXWh9QU=
|
|
||||||
github.com/fyne-io/gl-js v0.0.0-20220802150000-8e339395f381/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
|
|
||||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
|
|
||||||
github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 h1:0Ayg0/do/sqX2R7NonoLZvWxGrd9utTVf3A0QvCbC88=
|
|
||||||
github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
|
|
||||||
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
|
|
||||||
github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 h1:ONkcbJmsWUOHyjUm0wlnkFc/uaacFFtStVbsG6qJfew=
|
|
||||||
github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
|
|
||||||
github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d h1:dHYKX8CBAs1zSGXm3q3M15CLAEwPEkwrK1ed8FCo+Xo=
|
|
||||||
github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
||||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
|
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 h1:8q7+xl2D2qHPLTII1t4vSMNP2VKwDcn+Avf2WXvdB1A=
|
||||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
|
||||||
github.com/go-text/typesetting v0.0.0-20221212183139-1eb938670a1f/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI=
|
|
||||||
github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181 h1:J6XG/Xx7uCCpskM71R6YAgPHd/E8FzhyPhL6Ll94uMY=
|
|
||||||
github.com/go-text/typesetting v0.0.0-20221219135543-5d0d724ee181/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.0.5 h1:9Eg0XUhQxtkV8ykTMKtMMYY72g4NgxtRq4jgh4Ih5YM=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.5/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8=
|
|
||||||
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
@ -187,6 +139,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@ -204,7 +157,7 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
@ -219,12 +172,11 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
@ -236,51 +188,35 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||||
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
|
|
||||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
|
||||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
|
|
||||||
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hanwen/go-fuse/v2 v2.2.0 h1:jo5QZYmBLNcl9ovypWaQ5yXMSSV+Ch68xoC3rtZvvBM=
|
|
||||||
github.com/hanwen/go-fuse/v2 v2.2.0/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc=
|
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
|
||||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
|
||||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
@ -288,129 +224,79 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
|
|
||||||
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
|
||||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
|
||||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
|
||||||
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
|
|
||||||
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
|
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
|
||||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||||
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
|
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/knadh/koanf v1.4.4 h1:d2jY5nCCeoaiqvEKSBW9rEc93EfNy/XWgWsSB3j7JEA=
|
|
||||||
github.com/knadh/koanf v1.4.4/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
|
||||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||||
|
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
|
|
||||||
github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb h1:+fP6ENsbd+BUOmD/kSjNtrOmi2vgJ/JfWDSWjTKmTVY=
|
|
||||||
github.com/metal3d/fyne-x v0.0.0-20220508095732-177117e583fb/go.mod h1:jBspDudEQ+Rdono8vBGHDtMUPE8ZpB/xq7FUYRqT3CI=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c=
|
github.com/mozillazg/go-pinyin v0.18.0 h1:hQompXO23/0ohH8YNjvfsAITnCQImCiR/Fny8EhIeW0=
|
||||||
github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
github.com/mozillazg/go-pinyin v0.18.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/muka/go-bluetooth v0.0.0-20211122080231-b99792bbe62a h1:KxRXeSWoBM5FCPAnSUYxt1qwEzmoH/K7upb4fiSDwdc=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/muka/go-bluetooth v0.0.0-20211122080231-b99792bbe62a/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
|
||||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
|
||||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
|
||||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@ -419,122 +305,73 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
|
||||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa h1:tEkEyxYeZ43TR55QU/hsIt9aRGBxbgGuz9CGykjvogY=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
|
||||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II=
|
||||||
|
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
|
||||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20240320113951-a2e4fc03f5f4 h1:zurEWtOr/OYiTb5bcD7eeHLOfj6vCR30uldlwse1cSM=
|
|
||||||
github.com/saltosystems/winrt-go v0.0.0-20240320113951-a2e4fc03f5f4/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
|
||||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||||
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
||||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||||
|
github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
|
||||||
|
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
|
||||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||||
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
github.com/srwiley/oksvg v0.0.0-20210519022825-9fc0c575d5fe h1:J5Ga/gb+4/WgJBupg9Fp8F6JQnUT3UF+asoTweLi9Jc=
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
github.com/srwiley/oksvg v0.0.0-20210519022825-9fc0c575d5fe/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
|
||||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 h1:oDMiXaTMyBEuZMU53atpxqYsSB3U1CHkeAu2zr6wTeY=
|
||||||
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8=
|
||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
|
||||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
|
||||||
github.com/tinygo-org/cbgo v0.0.4 h1:3D76CRYbH03Rudi8sEgs/YO0x3JIMdyq8jlQtk/44fU=
|
|
||||||
github.com/tinygo-org/cbgo v0.0.4/go.mod h1:7+HgWIHd4nbAz0ESjGlJ1/v9LDU1Ox8MGzP9mah/fLk=
|
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg=
|
|
||||||
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
|
|
||||||
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
|
||||||
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
|
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
|
||||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
|
go.arsenm.dev/infinitime v0.0.0-20211123052001-ec1548ec0f30 h1:nhuJfwWxBxfolMU5go3rBLhvl59HMRIMqEKi405Ja9w=
|
||||||
github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
go.arsenm.dev/infinitime v0.0.0-20211123052001-ec1548ec0f30/go.mod h1:kNBKxQfqeLUfi13GM6tB1kSvLm8HlZ7PM47AYeJQIiw=
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
|
||||||
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
|
|
||||||
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
|
||||||
go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d h1:ANb8YPtcxPipwKgmnW688e5PGpNaLh+22nO2LBpIPOU=
|
|
||||||
go.elara.ws/drpc v0.0.0-20230421021209-fe4c05460a3d/go.mod h1:NDprjiVqKXQKVGzX7jp2g/jctsUbvOxz1nN15QOBEGk=
|
|
||||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae h1:d+gJUhEWSrOjrrfgeydYWEr8TTnx0DLvcVhghaOsFeE=
|
|
||||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
|
|
||||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
|
||||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@ -542,10 +379,10 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@ -554,12 +391,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
@ -570,12 +405,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
|
|||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
|
||||||
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
|
|
||||||
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -590,9 +421,6 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI
|
|||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
|
||||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8=
|
|
||||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
@ -602,13 +430,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
|
||||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -617,7 +441,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@ -629,7 +452,6 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
@ -645,13 +467,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0 h1:qOfNqBm5gk93LjGZo1MJaKY6Bph39zOKz1Hz2ogHj1w=
|
||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
|
||||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -664,6 +482,10 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
|
|||||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -675,18 +497,12 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -703,7 +519,6 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -718,8 +533,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -727,9 +541,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -737,33 +549,27 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -812,18 +618,18 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
|||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
|
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4=
|
|
||||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -850,6 +656,12 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
|
|||||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||||
|
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||||
|
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||||
|
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||||
|
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||||
|
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||||
|
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@ -859,7 +671,6 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
|
|||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
@ -898,12 +709,21 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
|
|||||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||||
|
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||||
|
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||||
|
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||||
|
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||||
|
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||||
|
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||||
|
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||||
|
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||||
|
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||||
|
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
@ -920,7 +740,13 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
|
|||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
|
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
|
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||||
|
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||||
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@ -933,10 +759,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -944,22 +767,16 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X
|
|||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
|
|
||||||
honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 h1:2ZZFiPwRLxiNX2E/YO6Jgw1pCjDRDgmx20PGyw/cw+M=
|
|
||||||
honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
@ -967,33 +784,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
||||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
|
||||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
|
||||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
|
||||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
|
||||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
|
||||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
|
||||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sqlite v1.20.1 h1:z6qRLw72B0VfRrJjs3l6hWkzYDx1bo0WGVrBGP4ohhM=
|
|
||||||
modernc.org/sqlite v1.20.1/go.mod h1:fODt+bFmc/j8LcoCbMSkAuKuGmhxjG45KGc25N2705M=
|
|
||||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
|
||||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
|
||||||
storj.io/drpc v0.0.32 h1:5p5ZwsK/VOgapaCu+oxaPVwO6UwIs+iwdMiD50+R4PI=
|
|
||||||
storj.io/drpc v0.0.32/go.mod h1:6rcOyR/QQkSTX/9L5ZGtlZaE2PtXTTZl8d+ulSeeYEg=
|
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import "tinygo.org/x/bluetooth"
|
|
||||||
|
|
||||||
type btChar struct {
|
|
||||||
Name string
|
|
||||||
ID bluetooth.UUID
|
|
||||||
ServiceID bluetooth.UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
musicServiceUUID = mustParse("00000000-78fc-48fe-8e23-433b3a1942d0")
|
|
||||||
navigationServiceUUID = mustParse("00010000-78fc-48fe-8e23-433b3a1942d0")
|
|
||||||
motionServiceUUID = mustParse("00030000-78fc-48fe-8e23-433b3a1942d0")
|
|
||||||
weatherServiceUUID = mustParse("00050000-78fc-48fe-8e23-433b3a1942d0")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
newAlertChar = btChar{
|
|
||||||
"New Alert",
|
|
||||||
bluetooth.CharacteristicUUIDNewAlert,
|
|
||||||
bluetooth.ServiceUUIDAlertNotification,
|
|
||||||
}
|
|
||||||
notifEventChar = btChar{
|
|
||||||
"Notification Event",
|
|
||||||
mustParse("00020001-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
bluetooth.ServiceUUIDAlertNotification,
|
|
||||||
}
|
|
||||||
stepCountChar = btChar{
|
|
||||||
"Step Count",
|
|
||||||
mustParse("00030001-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
motionServiceUUID,
|
|
||||||
}
|
|
||||||
rawMotionChar = btChar{
|
|
||||||
"Raw Motion",
|
|
||||||
mustParse("00030002-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
motionServiceUUID,
|
|
||||||
}
|
|
||||||
firmwareVerChar = btChar{
|
|
||||||
"Firmware Version",
|
|
||||||
bluetooth.CharacteristicUUIDFirmwareRevisionString,
|
|
||||||
bluetooth.ServiceUUIDDeviceInformation,
|
|
||||||
}
|
|
||||||
currentTimeChar = btChar{
|
|
||||||
"Current Time",
|
|
||||||
bluetooth.CharacteristicUUIDCurrentTime,
|
|
||||||
bluetooth.ServiceUUIDCurrentTime,
|
|
||||||
}
|
|
||||||
localTimeChar = btChar{
|
|
||||||
"Local Time",
|
|
||||||
bluetooth.CharacteristicUUIDLocalTimeInformation,
|
|
||||||
bluetooth.ServiceUUIDCurrentTime,
|
|
||||||
}
|
|
||||||
batteryLevelChar = btChar{
|
|
||||||
"Battery Level",
|
|
||||||
bluetooth.CharacteristicUUIDBatteryLevel,
|
|
||||||
bluetooth.ServiceUUIDBattery,
|
|
||||||
}
|
|
||||||
heartRateChar = btChar{
|
|
||||||
"Heart Rate",
|
|
||||||
bluetooth.CharacteristicUUIDHeartRateMeasurement,
|
|
||||||
bluetooth.ServiceUUIDHeartRate,
|
|
||||||
}
|
|
||||||
fsVersionChar = btChar{
|
|
||||||
"Filesystem Version",
|
|
||||||
mustParse("adaf0200-4669-6c65-5472-616e73666572"),
|
|
||||||
bluetooth.ServiceUUIDFileTransferByAdafruit,
|
|
||||||
}
|
|
||||||
fsTransferChar = btChar{
|
|
||||||
"Filesystem Transfer",
|
|
||||||
mustParse("adaf0200-4669-6c65-5472-616e73666572"),
|
|
||||||
bluetooth.ServiceUUIDFileTransferByAdafruit,
|
|
||||||
}
|
|
||||||
dfuCtrlPointChar = btChar{
|
|
||||||
"DFU Control Point",
|
|
||||||
bluetooth.CharacteristicUUIDLegacyDFUControlPoint,
|
|
||||||
bluetooth.ServiceUUIDLegacyDFU,
|
|
||||||
}
|
|
||||||
dfuPacketChar = btChar{
|
|
||||||
"DFU Packet",
|
|
||||||
bluetooth.CharacteristicUUIDLegacyDFUPacket,
|
|
||||||
bluetooth.ServiceUUIDLegacyDFU,
|
|
||||||
}
|
|
||||||
navigationFlagsChar = btChar{
|
|
||||||
"Navigation Flags",
|
|
||||||
mustParse("00010001-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
navigationServiceUUID,
|
|
||||||
}
|
|
||||||
navigationNarrativeChar = btChar{
|
|
||||||
"Navigation Narrative",
|
|
||||||
mustParse("00010002-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
navigationServiceUUID,
|
|
||||||
}
|
|
||||||
navigationManDist = btChar{
|
|
||||||
"Navigation Man Dist",
|
|
||||||
mustParse("00010003-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
navigationServiceUUID,
|
|
||||||
}
|
|
||||||
navigationProgress = btChar{
|
|
||||||
"Navigation Progress",
|
|
||||||
mustParse("00010004-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
navigationServiceUUID,
|
|
||||||
}
|
|
||||||
weatherDataChar = btChar{
|
|
||||||
"Weather Data",
|
|
||||||
mustParse("00050001-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
weatherServiceUUID,
|
|
||||||
}
|
|
||||||
musicEventChar = btChar{
|
|
||||||
"Music Event",
|
|
||||||
mustParse("00000001-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
musicServiceUUID,
|
|
||||||
}
|
|
||||||
musicStatusChar = btChar{
|
|
||||||
"Music Status",
|
|
||||||
mustParse("00000002-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
musicServiceUUID,
|
|
||||||
}
|
|
||||||
musicArtistChar = btChar{
|
|
||||||
"Music Artist",
|
|
||||||
mustParse("00000003-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
musicServiceUUID,
|
|
||||||
}
|
|
||||||
musicTrackChar = btChar{
|
|
||||||
"Music Track",
|
|
||||||
mustParse("00000004-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
musicServiceUUID,
|
|
||||||
}
|
|
||||||
musicAlbumChar = btChar{
|
|
||||||
"Music Album",
|
|
||||||
mustParse("00000005-78fc-48fe-8e23-433b3a1942d0"),
|
|
||||||
musicServiceUUID,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func mustParse(s string) bluetooth.UUID {
|
|
||||||
uuid, err := bluetooth.ParseUUID(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
@ -1,222 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
|
|
||||||
"tinygo.org/x/bluetooth"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
dfuSegmentSize = 20 // Size of each firmware packet
|
|
||||||
dfuPktRecvInterval = 10 // Amount of packets to send before checking for receipt
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
dfuCmdStart = []byte{0x01, 0x04}
|
|
||||||
dfuCmdRecvInitPkt = []byte{0x02, 0x00}
|
|
||||||
dfuCmdInitPktComplete = []byte{0x02, 0x01}
|
|
||||||
dfuCmdPktReceiptInterval = []byte{0x08}
|
|
||||||
dfuCmdRecvFirmware = []byte{0x03}
|
|
||||||
dfuCmdValidate = []byte{0x04}
|
|
||||||
dfuCmdActivateReset = []byte{0x05}
|
|
||||||
|
|
||||||
dfuResponseStart = []byte{0x10, 0x01, 0x01}
|
|
||||||
dfuResponseInitParams = []byte{0x10, 0x02, 0x01}
|
|
||||||
dfuResponseRecvFwImgSuccess = []byte{0x10, 0x03, 0x01}
|
|
||||||
dfuResponseValidate = []byte{0x10, 0x04, 0x01}
|
|
||||||
)
|
|
||||||
|
|
||||||
// DFUOptions contains options for [UpgradeFirmware]
|
|
||||||
type DFUOptions struct {
|
|
||||||
InitPacket fs.File
|
|
||||||
FirmwareImage fs.File
|
|
||||||
ProgressFunc func(sent, received, total uint32)
|
|
||||||
SegmentSize int
|
|
||||||
ReceiveInterval uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpgradeFirmware upgrades the firmware running on the PineTime.
|
|
||||||
func (d *Device) UpgradeFirmware(opts DFUOptions) error {
|
|
||||||
if opts.SegmentSize <= 0 {
|
|
||||||
opts.SegmentSize = dfuSegmentSize
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.ReceiveInterval <= 0 {
|
|
||||||
opts.ReceiveInterval = dfuPktRecvInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrlPoint, err := d.getChar(dfuCtrlPointChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := d.getChar(dfuPacketChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.deviceMtx.Lock()
|
|
||||||
defer d.deviceMtx.Unlock()
|
|
||||||
|
|
||||||
d.updating.Store(true)
|
|
||||||
defer d.updating.Store(false)
|
|
||||||
|
|
||||||
_, err = ctrlPoint.WriteWithoutResponse(dfuCmdStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err := opts.FirmwareImage.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
size := uint32(fi.Size())
|
|
||||||
|
|
||||||
sizePacket := make([]byte, 8, 12)
|
|
||||||
sizePacket = binary.LittleEndian.AppendUint32(sizePacket, size)
|
|
||||||
_, err = packet.WriteWithoutResponse(sizePacket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = awaitDFUResponse(ctrlPoint, dfuResponseStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = writeDFUInitPacket(ctrlPoint, packet, opts.InitPacket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setRecvInterval(ctrlPoint, opts.ReceiveInterval)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sendFirmware(ctrlPoint, packet, opts, size)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalize(ctrlPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func finalize(ctrlPoint *bluetooth.DeviceCharacteristic) error {
|
|
||||||
_, err := ctrlPoint.WriteWithoutResponse(dfuCmdValidate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = awaitDFUResponse(ctrlPoint, dfuResponseValidate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = ctrlPoint.WriteWithoutResponse(dfuCmdActivateReset)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendFirmware(ctrlPoint, packet *bluetooth.DeviceCharacteristic, opts DFUOptions, totalSize uint32) error {
|
|
||||||
_, err := ctrlPoint.WriteWithoutResponse(dfuCmdRecvFirmware)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
chunksSinceReceipt uint8
|
|
||||||
bytesSent uint32
|
|
||||||
)
|
|
||||||
|
|
||||||
chunk := make([]byte, opts.SegmentSize)
|
|
||||||
for {
|
|
||||||
n, err := opts.FirmwareImage.Read(chunk)
|
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
|
||||||
return err
|
|
||||||
} else if n == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
bytesSent += uint32(n)
|
|
||||||
_, err = packet.WriteWithoutResponse(chunk[:n])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
chunksSinceReceipt += 1
|
|
||||||
if chunksSinceReceipt == opts.ReceiveInterval {
|
|
||||||
sizeData, err := awaitDFUResponse(ctrlPoint, []byte{0x11})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
size := binary.LittleEndian.Uint32(sizeData)
|
|
||||||
if size != bytesSent {
|
|
||||||
return fmt.Errorf("size mismatch: expected %d, got %d", bytesSent, size)
|
|
||||||
}
|
|
||||||
if opts.ProgressFunc != nil {
|
|
||||||
opts.ProgressFunc(bytesSent, size, totalSize)
|
|
||||||
}
|
|
||||||
chunksSinceReceipt = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeDFUInitPacket(ctrlPoint, packet *bluetooth.DeviceCharacteristic, initPkt fs.File) error {
|
|
||||||
_, err := ctrlPoint.WriteWithoutResponse(dfuCmdRecvInitPkt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
initData, err := io.ReadAll(initPkt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = packet.WriteWithoutResponse(initData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ctrlPoint.WriteWithoutResponse(dfuCmdInitPktComplete)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = awaitDFUResponse(ctrlPoint, dfuResponseInitParams)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func setRecvInterval(ctrlPoint *bluetooth.DeviceCharacteristic, interval uint8) error {
|
|
||||||
_, err := ctrlPoint.WriteWithoutResponse(append(dfuCmdPktReceiptInterval, interval))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func awaitDFUResponse(ctrlPoint *bluetooth.DeviceCharacteristic, expect []byte) ([]byte, error) {
|
|
||||||
respCh := make(chan []byte, 1)
|
|
||||||
err := ctrlPoint.EnableNotifications(func(buf []byte) {
|
|
||||||
respCh <- buf
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := <-respCh
|
|
||||||
ctrlPoint.EnableNotifications(nil)
|
|
||||||
|
|
||||||
if !bytes.HasPrefix(data, expect) {
|
|
||||||
return nil, fmt.Errorf("unexpected dfu response %x (expected %x)", data, expect)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.TrimPrefix(data, expect), nil
|
|
||||||
}
|
|
617
infinitime/fs.go
@ -1,617 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"math"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/fsproto"
|
|
||||||
"tinygo.org/x/bluetooth"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FS represents a remote BLE filesystem
|
|
||||||
type FS struct {
|
|
||||||
mtx sync.Mutex
|
|
||||||
dev *Device
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat gets information about a file at the given path.
|
|
||||||
//
|
|
||||||
// WARNING: Since there's no stat command in the BLE FS protocol,
|
|
||||||
// this function does a ReadDir and then finds the requested file
|
|
||||||
// in the results, which makes it pretty slow.
|
|
||||||
func (ifs *FS) Stat(p string) (fs.FileInfo, error) {
|
|
||||||
dir := path.Dir(p)
|
|
||||||
entries, err := ifs.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.Name() == path.Base(p) {
|
|
||||||
return entry.Info()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fsproto.ErrFileNotExists
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes a file or empty directory at the given path.
|
|
||||||
//
|
|
||||||
// For a function that removes directories recursively, see [FS.RemoveAll]
|
|
||||||
func (ifs *FS) Remove(path string) error {
|
|
||||||
ifs.mtx.Lock()
|
|
||||||
defer ifs.mtx.Unlock()
|
|
||||||
|
|
||||||
char, err := ifs.dev.getChar(fsTransferChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ifs.requestThenAwaitResponse(
|
|
||||||
char,
|
|
||||||
fsproto.DeleteFileOpcode,
|
|
||||||
fsproto.DeleteFileRequest{
|
|
||||||
PathLen: uint16(len(path)),
|
|
||||||
Path: path,
|
|
||||||
},
|
|
||||||
func(buf []byte) (bool, error) {
|
|
||||||
var mdr fsproto.DeleteFileResponse
|
|
||||||
return true, fsproto.ReadResponse(buf, fsproto.DeleteFileResp, &mdr)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename moves a file or directory from an old path to a new path.
|
|
||||||
func (ifs *FS) Rename(old, new string) error {
|
|
||||||
ifs.mtx.Lock()
|
|
||||||
defer ifs.mtx.Unlock()
|
|
||||||
|
|
||||||
char, err := ifs.dev.getChar(fsTransferChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ifs.requestThenAwaitResponse(
|
|
||||||
char,
|
|
||||||
fsproto.MoveFileOpcode,
|
|
||||||
fsproto.MoveFileRequest{
|
|
||||||
OldPathLen: uint16(len(old)),
|
|
||||||
OldPath: old,
|
|
||||||
NewPathLen: uint16(len(new)),
|
|
||||||
NewPath: new,
|
|
||||||
},
|
|
||||||
func(buf []byte) (bool, error) {
|
|
||||||
var mfr fsproto.MoveFileResponse
|
|
||||||
return true, fsproto.ReadResponse(buf, fsproto.MoveFileResp, &mfr)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdir creates a new directory at the specified path.
|
|
||||||
//
|
|
||||||
// For a function that creates necessary parents as well, see [FS.MkdirAll]
|
|
||||||
func (ifs *FS) Mkdir(path string) error {
|
|
||||||
ifs.mtx.Lock()
|
|
||||||
defer ifs.mtx.Unlock()
|
|
||||||
|
|
||||||
char, err := ifs.dev.getChar(fsTransferChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ifs.requestThenAwaitResponse(
|
|
||||||
char,
|
|
||||||
fsproto.MakeDirectoryOpcode,
|
|
||||||
fsproto.MkdirRequest{
|
|
||||||
PathLen: uint16(len(path)),
|
|
||||||
Path: path,
|
|
||||||
},
|
|
||||||
func(buf []byte) (bool, error) {
|
|
||||||
var mdr fsproto.MkdirResponse
|
|
||||||
return true, fsproto.ReadResponse(buf, fsproto.MakeDirectoryResp, &mdr)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadDir reads the directory at the specified path and returns a list of directory entries.
|
|
||||||
func (ifs *FS) ReadDir(path string) ([]fs.DirEntry, error) {
|
|
||||||
ifs.mtx.Lock()
|
|
||||||
defer ifs.mtx.Unlock()
|
|
||||||
|
|
||||||
char, err := ifs.dev.getChar(fsTransferChar)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out []fs.DirEntry
|
|
||||||
return out, ifs.requestThenAwaitResponse(
|
|
||||||
char,
|
|
||||||
fsproto.ListDirectoryOpcode,
|
|
||||||
fsproto.ListDirRequest{
|
|
||||||
PathLen: uint16(len(path)),
|
|
||||||
Path: path,
|
|
||||||
},
|
|
||||||
func(buf []byte) (bool, error) {
|
|
||||||
var ldr fsproto.ListDirResponse
|
|
||||||
err := fsproto.ReadResponse(buf, fsproto.ListDirectoryResp, &ldr)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ldr.EntryNum == ldr.TotalEntries {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, DirEntry{
|
|
||||||
flags: ldr.Flags,
|
|
||||||
modtime: ldr.ModTime,
|
|
||||||
size: ldr.FileSize,
|
|
||||||
path: string(ldr.Path),
|
|
||||||
})
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAll removes the file at the specified path and any children it contains,
|
|
||||||
// similar to the rm -r command.
|
|
||||||
func (ifs *FS) RemoveAll(p string) error {
|
|
||||||
if p == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.Clean(p) == "/" {
|
|
||||||
return fsproto.ErrNoRemoveRoot
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err := ifs.Stat(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.IsDir() {
|
|
||||||
return ifs.removeWithChildren(p)
|
|
||||||
} else {
|
|
||||||
err = ifs.Remove(p)
|
|
||||||
|
|
||||||
var code int8
|
|
||||||
if err, ok := err.(fsproto.Error); ok {
|
|
||||||
code = err.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && code != -2 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeWithChildren removes the directory at the given path and its children recursively.
|
|
||||||
func (ifs *FS) removeWithChildren(p string) error {
|
|
||||||
list, err := ifs.ReadDir(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range list {
|
|
||||||
name := entry.Name()
|
|
||||||
|
|
||||||
if name == "." || name == ".." {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entryPath := path.Join(p, name)
|
|
||||||
|
|
||||||
if entry.IsDir() {
|
|
||||||
err = ifs.removeWithChildren(entryPath)
|
|
||||||
} else {
|
|
||||||
err = ifs.Remove(entryPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
var code int8
|
|
||||||
if err, ok := err.(fsproto.Error); ok {
|
|
||||||
code = err.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && code != -2 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ifs.Remove(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MkdirAll creates a directory and any necessary parents in the file system,
|
|
||||||
// similar to the mkdir -p command.
|
|
||||||
func (ifs *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 := ifs.Mkdir(curPath)
|
|
||||||
|
|
||||||
var code int8
|
|
||||||
if err, ok := err.(fsproto.Error); ok {
|
|
||||||
code = err.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && code != -17 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.File = (*File)(nil)
|
|
||||||
|
|
||||||
// File represents a remote file on a BLE filesystem.
|
|
||||||
//
|
|
||||||
// If ProgressFunc is set, it will be called whenever a read or write happens
|
|
||||||
// with the amount of bytes transferred and the total size of the file.
|
|
||||||
type File struct {
|
|
||||||
fs *FS
|
|
||||||
path string
|
|
||||||
offset uint32
|
|
||||||
size uint32
|
|
||||||
readOnly bool
|
|
||||||
closed bool
|
|
||||||
ProgressFunc func(transferred, total uint32)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open opens an existing file at the specified path.
|
|
||||||
// It returns a handle for the file and an error, if any.
|
|
||||||
func (ifs *FS) Open(path string) (*File, error) {
|
|
||||||
return &File{
|
|
||||||
fs: ifs,
|
|
||||||
path: path,
|
|
||||||
offset: 0,
|
|
||||||
readOnly: true,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a new file with the specified path and size.
|
|
||||||
// It returns a handle for the created file and an error, if any.
|
|
||||||
func (ifs *FS) Create(path string, size uint32) (*File, error) {
|
|
||||||
return &File{
|
|
||||||
fs: ifs,
|
|
||||||
path: path,
|
|
||||||
offset: 0,
|
|
||||||
size: size,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes data from the byte slice b to the file.
|
|
||||||
// It returns the number of bytes written and an error, if any.
|
|
||||||
func (fl *File) Write(b []byte) (int, error) {
|
|
||||||
if fl.closed {
|
|
||||||
return 0, fsproto.ErrFileClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
if fl.readOnly {
|
|
||||||
return 0, fsproto.ErrFileReadOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
fl.fs.mtx.Lock()
|
|
||||||
defer fl.fs.mtx.Unlock()
|
|
||||||
|
|
||||||
char, err := fl.fs.dev.getChar(fsTransferChar)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer char.EnableNotifications(nil)
|
|
||||||
|
|
||||||
var chunkLen uint32
|
|
||||||
|
|
||||||
dataLen := uint32(len(b))
|
|
||||||
transferred := uint32(0)
|
|
||||||
mtu := uint32(fl.fs.mtu(char))
|
|
||||||
|
|
||||||
// continueCh is used to prevent race conditions. When the
|
|
||||||
// request loop starts, it reads from continueCh, blocking it
|
|
||||||
// until it's "released" by the notification function after
|
|
||||||
// the response is processed.
|
|
||||||
continueCh := make(chan struct{}, 2)
|
|
||||||
var notifErr error
|
|
||||||
err = char.EnableNotifications(func(buf []byte) {
|
|
||||||
var wfr fsproto.WriteFileResponse
|
|
||||||
err = fsproto.ReadResponse(buf, fsproto.WriteFileResp, &wfr)
|
|
||||||
if err != nil {
|
|
||||||
notifErr = err
|
|
||||||
char.EnableNotifications(nil)
|
|
||||||
close(continueCh)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
transferred += chunkLen
|
|
||||||
fl.offset += chunkLen
|
|
||||||
|
|
||||||
if wfr.FreeSpace == 0 || transferred == dataLen {
|
|
||||||
char.EnableNotifications(nil)
|
|
||||||
close(continueCh)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if fl.ProgressFunc != nil {
|
|
||||||
fl.ProgressFunc(transferred, fl.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release the request loop
|
|
||||||
continueCh <- struct{}{}
|
|
||||||
})
|
|
||||||
|
|
||||||
err = fsproto.WriteRequest(char, fsproto.WriteFileHeaderOpcode, fsproto.WriteFileHeaderRequest{
|
|
||||||
PathLen: uint16(len(fl.path)),
|
|
||||||
Offset: fl.offset,
|
|
||||||
FileSize: fl.size,
|
|
||||||
Path: fl.path,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return int(transferred), err
|
|
||||||
}
|
|
||||||
|
|
||||||
for range continueCh {
|
|
||||||
if notifErr != nil {
|
|
||||||
return int(transferred), notifErr
|
|
||||||
}
|
|
||||||
|
|
||||||
amountLeft := dataLen - transferred
|
|
||||||
chunkLen = mtu
|
|
||||||
if amountLeft < mtu {
|
|
||||||
chunkLen = amountLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fsproto.WriteRequest(char, fsproto.WriteFileOpcode, fsproto.WriteFileRequest{
|
|
||||||
Status: 0x01,
|
|
||||||
Offset: fl.offset,
|
|
||||||
ChunkLen: chunkLen,
|
|
||||||
Data: b[transferred : transferred+chunkLen],
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return int(transferred), err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(transferred), notifErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads data from the file into the byte slice b.
|
|
||||||
// It returns the number of bytes read and an error, if any.
|
|
||||||
func (fl *File) Read(b []byte) (int, error) {
|
|
||||||
if fl.closed {
|
|
||||||
return 0, fsproto.ErrFileClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
fl.fs.mtx.Lock()
|
|
||||||
defer fl.fs.mtx.Unlock()
|
|
||||||
|
|
||||||
char, err := fl.fs.dev.getChar(fsTransferChar)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer char.EnableNotifications(nil)
|
|
||||||
|
|
||||||
transferred := uint32(0)
|
|
||||||
maxLen := uint32(len(b))
|
|
||||||
mtu := uint32(fl.fs.mtu(char))
|
|
||||||
|
|
||||||
var (
|
|
||||||
notifErr error
|
|
||||||
done bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// continueCh is used to prevent race conditions. When the
|
|
||||||
// request loop starts, it reads from continueCh, blocking it
|
|
||||||
// until it's "released" by the notification function after
|
|
||||||
// the response is processed.
|
|
||||||
continueCh := make(chan struct{}, 2)
|
|
||||||
err = char.EnableNotifications(func(buf []byte) {
|
|
||||||
var rfr fsproto.ReadFileResponse
|
|
||||||
err = fsproto.ReadResponse(buf, fsproto.ReadFileResp, &rfr)
|
|
||||||
if err != nil {
|
|
||||||
notifErr = err
|
|
||||||
char.EnableNotifications(nil)
|
|
||||||
close(continueCh)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fl.size = rfr.FileSize
|
|
||||||
|
|
||||||
if rfr.Offset == rfr.FileSize || rfr.ChunkLen == 0 {
|
|
||||||
notifErr = io.EOF
|
|
||||||
done = true
|
|
||||||
char.EnableNotifications(nil)
|
|
||||||
close(continueCh)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n := copy(b[transferred:], rfr.Data[:rfr.ChunkLen])
|
|
||||||
fl.offset += uint32(n)
|
|
||||||
transferred += uint32(n)
|
|
||||||
|
|
||||||
if fl.ProgressFunc != nil {
|
|
||||||
fl.ProgressFunc(transferred, rfr.FileSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release the request loop
|
|
||||||
continueCh <- struct{}{}
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer char.EnableNotifications(nil)
|
|
||||||
|
|
||||||
amountLeft := maxLen - transferred
|
|
||||||
chunkLen := mtu
|
|
||||||
if amountLeft < mtu {
|
|
||||||
chunkLen = amountLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fsproto.WriteRequest(char, fsproto.ReadFileHeaderOpcode, fsproto.ReadFileHeaderRequest{
|
|
||||||
PathLen: uint16(len(fl.path)),
|
|
||||||
Offset: fl.offset,
|
|
||||||
ReadLen: chunkLen,
|
|
||||||
Path: fl.path,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if notifErr != nil {
|
|
||||||
return int(transferred), notifErr
|
|
||||||
}
|
|
||||||
|
|
||||||
for !done {
|
|
||||||
// Wait for the notification function to release the loop
|
|
||||||
<-continueCh
|
|
||||||
|
|
||||||
if notifErr != nil {
|
|
||||||
return int(transferred), notifErr
|
|
||||||
}
|
|
||||||
|
|
||||||
amountLeft = maxLen - transferred
|
|
||||||
chunkLen = mtu
|
|
||||||
if amountLeft < mtu {
|
|
||||||
chunkLen = amountLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fsproto.WriteRequest(char, fsproto.ReadFileOpcode, fsproto.ReadFileRequest{
|
|
||||||
Status: 0x01,
|
|
||||||
Offset: fl.offset,
|
|
||||||
ReadLen: chunkLen,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return int(transferred), err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(transferred), notifErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat returns information about the file,
|
|
||||||
func (fl *File) Stat() (fs.FileInfo, error) {
|
|
||||||
return fl.fs.Stat(fl.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek sets the offset for the next Read or Write on the file to the specified offset.
|
|
||||||
// The whence parameter specifies the seek reference point:
|
|
||||||
//
|
|
||||||
// io.SeekStart: offset is relative to the start of the file.
|
|
||||||
// io.SeekCurrent: offset is relative to the current offset.
|
|
||||||
// io.SeekEnd: offset is relative to the end of the file.
|
|
||||||
//
|
|
||||||
// Seek returns the new offset and an error, if any.
|
|
||||||
func (fl *File) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
if fl.closed {
|
|
||||||
return 0, fsproto.ErrFileClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset > math.MaxUint32 {
|
|
||||||
return 0, fsproto.ErrInvalidOffset
|
|
||||||
}
|
|
||||||
u32Offset := uint32(offset)
|
|
||||||
|
|
||||||
fl.fs.mtx.Lock()
|
|
||||||
defer fl.fs.mtx.Unlock()
|
|
||||||
|
|
||||||
if fl.size == 0 {
|
|
||||||
return 0, errors.New("file size unknown")
|
|
||||||
}
|
|
||||||
|
|
||||||
var newOffset uint32
|
|
||||||
switch whence {
|
|
||||||
case io.SeekStart:
|
|
||||||
newOffset = u32Offset
|
|
||||||
case io.SeekCurrent:
|
|
||||||
newOffset = fl.offset + u32Offset
|
|
||||||
case io.SeekEnd:
|
|
||||||
newOffset = fl.size + u32Offset
|
|
||||||
}
|
|
||||||
|
|
||||||
if newOffset > fl.size || newOffset < 0 {
|
|
||||||
return 0, fsproto.ErrInvalidOffset
|
|
||||||
}
|
|
||||||
fl.offset = newOffset
|
|
||||||
|
|
||||||
return int64(fl.offset), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the file for future operations
|
|
||||||
func (fl *File) Close() error {
|
|
||||||
fl.fs.mtx.Lock()
|
|
||||||
defer fl.fs.mtx.Unlock()
|
|
||||||
fl.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// requestThenAwaitResponse executes a BLE FS request and then waits for one or more responses,
|
|
||||||
// until fn returns true or an error is encountered.
|
|
||||||
func (ifs *FS) requestThenAwaitResponse(char *bluetooth.DeviceCharacteristic, opcode fsproto.FSReqOpcode, req any, fn func(buf []byte) (bool, error)) error {
|
|
||||||
var stopped atomic.Bool
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
char.EnableNotifications(func(buf []byte) {
|
|
||||||
stop, err := fn(buf)
|
|
||||||
if err != nil && !stopped.Load() {
|
|
||||||
errCh <- err
|
|
||||||
char.EnableNotifications(nil)
|
|
||||||
return
|
|
||||||
} else if !stopped.Load() {
|
|
||||||
errCh <- nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if stop && !stopped.Load() {
|
|
||||||
stopped.Store(true)
|
|
||||||
close(errCh)
|
|
||||||
char.EnableNotifications(nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
defer char.EnableNotifications(nil)
|
|
||||||
|
|
||||||
err := fsproto.WriteRequest(char, opcode, req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for err := range errCh {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ifs *FS) mtu(char *bluetooth.DeviceCharacteristic) uint16 {
|
|
||||||
mtuVal, _ := char.GetMTU()
|
|
||||||
if mtuVal == 0 {
|
|
||||||
mtuVal = 256
|
|
||||||
}
|
|
||||||
return mtuVal - 20
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FS = (*GoFS)(nil)
|
|
||||||
var _ fs.StatFS = (*GoFS)(nil)
|
|
||||||
var _ fs.ReadDirFS = (*GoFS)(nil)
|
|
||||||
|
|
||||||
// GoFS implements [io/fs.FS], [io/fs.StatFS], and [io/fs.ReadDirFS]
|
|
||||||
// for the InfiniTime filesystem
|
|
||||||
type GoFS struct {
|
|
||||||
*FS
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open opens an existing file at the specified path.
|
|
||||||
// It returns a handle for the file and an error, if any.
|
|
||||||
func (gfs GoFS) Open(path string) (fs.File, error) {
|
|
||||||
return gfs.FS.Open(path)
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DirEntry represents an entry from a directory listing
|
|
||||||
type DirEntry struct {
|
|
||||||
flags uint32
|
|
||||||
modtime uint64
|
|
||||||
size uint32
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the file described by the entry
|
|
||||||
func (de DirEntry) Name() string {
|
|
||||||
return de.path
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir reports whether the entry describes a directory.
|
|
||||||
func (de DirEntry) IsDir() bool {
|
|
||||||
return de.flags&0b1 == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type bits for the entry.
|
|
||||||
func (de DirEntry) Type() fs.FileMode {
|
|
||||||
if de.IsDir() {
|
|
||||||
return fs.ModeDir
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info returns the FileInfo for the file or subdirectory described by the entry.
|
|
||||||
func (de DirEntry) Info() (fs.FileInfo, error) {
|
|
||||||
return FileInfo{
|
|
||||||
name: de.path,
|
|
||||||
size: de.size,
|
|
||||||
modtime: de.modtime,
|
|
||||||
mode: de.Type(),
|
|
||||||
isDir: de.IsDir(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (de DirEntry) String() string {
|
|
||||||
var isDirChar rune
|
|
||||||
if de.IsDir() {
|
|
||||||
isDirChar = 'd'
|
|
||||||
} else {
|
|
||||||
isDirChar = '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get human-readable value for file size
|
|
||||||
val, unit := bytesHuman(de.size)
|
|
||||||
prec := 0
|
|
||||||
// If value is less than 10, set precision to 1
|
|
||||||
if val < 10 {
|
|
||||||
prec = 1
|
|
||||||
}
|
|
||||||
// Convert float to string
|
|
||||||
valStr := strconv.FormatFloat(val, 'f', prec, 64)
|
|
||||||
|
|
||||||
// Return string formatted like so:
|
|
||||||
// - 10 kB file
|
|
||||||
// or:
|
|
||||||
// d 0 B .
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%c %3s %-2s %s",
|
|
||||||
isDirChar,
|
|
||||||
valStr,
|
|
||||||
unit,
|
|
||||||
de.path,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bytesHuman(b uint32) (float64, string) {
|
|
||||||
const unit = 1000
|
|
||||||
// Set possible unit prefixes (PineTime flash is 4MB)
|
|
||||||
units := [2]rune{'k', 'M'}
|
|
||||||
// If amount of bytes is less than smallest unit
|
|
||||||
if b < unit {
|
|
||||||
// Return unchanged with unit "B"
|
|
||||||
return float64(b), "B"
|
|
||||||
}
|
|
||||||
|
|
||||||
div, exp := uint32(unit), 0
|
|
||||||
// Get decimal values and unit prefix index
|
|
||||||
for n := b / unit; n >= unit; n /= unit {
|
|
||||||
div *= unit
|
|
||||||
exp++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create string for full unit
|
|
||||||
unitStr := string([]rune{units[exp], 'B'})
|
|
||||||
|
|
||||||
// Return decimal with unit string
|
|
||||||
return float64(b) / float64(div), unitStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileInfo implements fs.FileInfo
|
|
||||||
type FileInfo struct {
|
|
||||||
name string
|
|
||||||
size uint32
|
|
||||||
modtime uint64
|
|
||||||
mode fs.FileMode
|
|
||||||
isDir bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the base name of the file
|
|
||||||
func (fi FileInfo) Name() string {
|
|
||||||
return fi.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the total size of the file
|
|
||||||
func (fi FileInfo) Size() int64 {
|
|
||||||
return int64(fi.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the mode of the file
|
|
||||||
func (fi FileInfo) Mode() fs.FileMode {
|
|
||||||
return fi.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModTime returns the modification time of the file
|
|
||||||
// As of now, this is unimplemented in InfiniTime, and
|
|
||||||
// will always return 0.
|
|
||||||
func (fi FileInfo) ModTime() time.Time {
|
|
||||||
return time.Unix(0, int64(fi.modtime))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns whether the file is a directory
|
|
||||||
func (fi FileInfo) IsDir() bool {
|
|
||||||
return fi.isDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sys is unimplemented and returns nil
|
|
||||||
func (fi FileInfo) Sys() any {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,173 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/bluetooth"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Allowlist []string
|
|
||||||
Blocklist []string
|
|
||||||
ScanInterval time.Duration
|
|
||||||
|
|
||||||
OnDisconnect func(dev *Device)
|
|
||||||
OnReconnect func(dev *Device)
|
|
||||||
OnConnect func(dev *Device)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reconnect(opts Options, adapter *bluetooth.Adapter, device *Device, mac string) {
|
|
||||||
if device == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
done := false
|
|
||||||
for {
|
|
||||||
adapter.Scan(func(a *bluetooth.Adapter, sr bluetooth.ScanResult) {
|
|
||||||
if sr.Address.String() != mac {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dev, err := a.Connect(sr.Address, bluetooth.ConnectionParams{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
adapter.StopScan()
|
|
||||||
|
|
||||||
device.deviceMtx.Lock()
|
|
||||||
device.device = dev
|
|
||||||
device.deviceMtx.Unlock()
|
|
||||||
|
|
||||||
device.notifierMtx.Lock()
|
|
||||||
for char, notifier := range device.notifierMap {
|
|
||||||
c, err := device.getChar(char)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.EnableNotifications(nil)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.EnableNotifications(notifier.notify)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
device.notifierMtx.Unlock()
|
|
||||||
|
|
||||||
done = true
|
|
||||||
})
|
|
||||||
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(opts.ScanInterval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Connect(opts Options) (device *Device, err error) {
|
|
||||||
adapter := bluetooth.DefaultAdapter
|
|
||||||
|
|
||||||
if opts.ScanInterval == 0 {
|
|
||||||
opts.ScanInterval = 2 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
var mac string
|
|
||||||
adapter.SetConnectHandler(func(dev bluetooth.Device, connected bool) {
|
|
||||||
if mac == "" || dev.Address.String() != mac {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if connected {
|
|
||||||
if opts.OnReconnect != nil {
|
|
||||||
opts.OnReconnect(device)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if opts.OnDisconnect != nil {
|
|
||||||
opts.OnDisconnect(device)
|
|
||||||
}
|
|
||||||
go reconnect(opts, adapter, device, mac)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
err = adapter.Enable()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var scanErr error
|
|
||||||
err = adapter.Scan(func(a *bluetooth.Adapter, sr bluetooth.ScanResult) {
|
|
||||||
if sr.LocalName() != "InfiniTime" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dev, err := a.Connect(sr.Address, bluetooth.ConnectionParams{})
|
|
||||||
if err != nil {
|
|
||||||
scanErr = err
|
|
||||||
adapter.StopScan()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mac = dev.Address.String()
|
|
||||||
|
|
||||||
device = &Device{adapter: a, device: dev, notifierMap: map[btChar]notifier{}}
|
|
||||||
if opts.OnConnect != nil {
|
|
||||||
opts.OnConnect(device)
|
|
||||||
}
|
|
||||||
adapter.StopScan()
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if scanErr != nil {
|
|
||||||
return nil, scanErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return device, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device represents an InfiniTime device
|
|
||||||
type Device struct {
|
|
||||||
adapter *bluetooth.Adapter
|
|
||||||
|
|
||||||
deviceMtx sync.Mutex
|
|
||||||
device bluetooth.Device
|
|
||||||
updating atomic.Bool
|
|
||||||
|
|
||||||
notifierMtx sync.Mutex
|
|
||||||
notifierMap map[btChar]notifier
|
|
||||||
}
|
|
||||||
|
|
||||||
// FS returns a handle for InifniTime's filesystem'
|
|
||||||
func (d *Device) FS() *FS {
|
|
||||||
return &FS{
|
|
||||||
dev: d,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Device) getChar(c btChar) (*bluetooth.DeviceCharacteristic, error) {
|
|
||||||
if d.updating.Load() {
|
|
||||||
return nil, fmt.Errorf("device is currently updating")
|
|
||||||
}
|
|
||||||
|
|
||||||
d.deviceMtx.Lock()
|
|
||||||
defer d.deviceMtx.Unlock()
|
|
||||||
|
|
||||||
services, err := d.device.DiscoverServices([]bluetooth.UUID{c.ServiceID})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("characteristic %s (%s) not found", c.ID, c.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
chars, err := services[0].DiscoverCharacteristics([]bluetooth.UUID{c.ID})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("characteristic %s (%s) not found", c.ID, c.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return chars[0], err
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Address returns the MAC address of the connected device.
|
|
||||||
func (d *Device) Address() string {
|
|
||||||
return d.device.Address.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns the version of InifniTime that the connected device is running.
|
|
||||||
func (d *Device) Version() (string, error) {
|
|
||||||
c, err := d.getChar(firmwareVerChar)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ver := make([]byte, 16)
|
|
||||||
n, err := c.Read(ver)
|
|
||||||
return string(ver[:n]), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatteryLevel returns the current battery level of the connected PineTime.
|
|
||||||
func (d *Device) BatteryLevel() (lvl uint8, err error) {
|
|
||||||
c, err := d.getChar(batteryLevelChar)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Read(c, binary.LittleEndian, &lvl)
|
|
||||||
return lvl, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchBatteryLevel calls fn whenever the battery level changes.
|
|
||||||
func (d *Device) WatchBatteryLevel(ctx context.Context, fn func(level uint8, err error)) error {
|
|
||||||
return watchChar(ctx, d, batteryLevelChar, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StepCount returns the current step count recorded on the watch.
|
|
||||||
func (d *Device) StepCount() (sc uint32, err error) {
|
|
||||||
c, err := d.getChar(stepCountChar)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Read(c, binary.LittleEndian, &sc)
|
|
||||||
return sc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchStepCount calls fn whenever the step count changes.
|
|
||||||
func (d *Device) WatchStepCount(ctx context.Context, fn func(count uint32, err error)) error {
|
|
||||||
return watchChar(ctx, d, stepCountChar, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeartRate returns the current heart rate recorded on the watch.
|
|
||||||
func (d *Device) HeartRate() (uint8, error) {
|
|
||||||
c, err := d.getChar(heartRateChar)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := make([]byte, 2)
|
|
||||||
_, err = c.Read(data)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return data[1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchHeartRate calls fn whenever the heart rate changes.
|
|
||||||
func (d *Device) WatchHeartRate(ctx context.Context, fn func(rate uint8, err error)) error {
|
|
||||||
return watchChar(ctx, d, heartRateChar, func(rate [2]uint8, err error) {
|
|
||||||
fn(rate[1], err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// MotionValues represents gyroscope coordinates.
|
|
||||||
type MotionValues struct {
|
|
||||||
X int16
|
|
||||||
Y int16
|
|
||||||
Z int16
|
|
||||||
}
|
|
||||||
|
|
||||||
// Motion returns the current gyroscope coordinates of the PineTime.
|
|
||||||
func (d *Device) Motion() (mv MotionValues, err error) {
|
|
||||||
c, err := d.getChar(rawMotionChar)
|
|
||||||
if err != nil {
|
|
||||||
return MotionValues{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = binary.Read(c, binary.LittleEndian, &mv)
|
|
||||||
return mv, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchMotion calls fn whenever the gyroscope coordinates change.
|
|
||||||
func (d *Device) WatchMotion(ctx context.Context, fn func(level MotionValues, err error)) error {
|
|
||||||
return watchChar(ctx, d, rawMotionChar, fn)
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type MusicEvent uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
MusicEventOpen MusicEvent = 0xe0
|
|
||||||
MusicEventPlay MusicEvent = 0x00
|
|
||||||
MusicEventPause MusicEvent = 0x01
|
|
||||||
MusicEventNext MusicEvent = 0x03
|
|
||||||
MusicEventPrev MusicEvent = 0x04
|
|
||||||
MusicEventVolUp MusicEvent = 0x05
|
|
||||||
MusicEventVolDown MusicEvent = 0x06
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetMusicStatus sets whether the music is playing or paused.
|
|
||||||
func (d *Device) SetMusicStatus(playing bool) error {
|
|
||||||
char, err := d.getChar(musicStatusChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if playing {
|
|
||||||
_, err = char.WriteWithoutResponse([]byte{0x1})
|
|
||||||
} else {
|
|
||||||
_, err = char.WriteWithoutResponse([]byte{0x0})
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMusicArtist sets the music artist.
|
|
||||||
func (d *Device) SetMusicArtist(artist string) error {
|
|
||||||
char, err := d.getChar(musicArtistChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = char.WriteWithoutResponse([]byte(artist))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMusicTrack sets the music track name.
|
|
||||||
func (d *Device) SetMusicTrack(track string) error {
|
|
||||||
char, err := d.getChar(musicTrackChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = char.WriteWithoutResponse([]byte(track))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMusicAlbum sets the music album name.
|
|
||||||
func (d *Device) SetMusicAlbum(album string) error {
|
|
||||||
char, err := d.getChar(musicAlbumChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = char.WriteWithoutResponse([]byte(album))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchMusicEvents calls fn whenever the InfiniTime music app broadcasts an event.
|
|
||||||
func (d *Device) WatchMusicEvents(ctx context.Context, fn func(event MusicEvent, err error)) error {
|
|
||||||
return watchChar(ctx, d, musicEventChar, fn)
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
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"
|
|
||||||
NavFlagTurnStraight NavFlag = "turn-straight"
|
|
||||||
NavFlagUpDown NavFlag = "updown"
|
|
||||||
NavFlagUTurn NavFlag = "uturn"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetNavFlag sets the navigation flag icon.
|
|
||||||
func (d *Device) SetNavFlag(flag NavFlag) error {
|
|
||||||
char, err := d.getChar(navigationFlagsChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = char.WriteWithoutResponse([]byte(flag))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNavNarrative sets the navigation narrative string.
|
|
||||||
func (d *Device) SetNavNarrative(narrative string) error {
|
|
||||||
char, err := d.getChar(navigationNarrativeChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = char.WriteWithoutResponse([]byte(narrative))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNavManeuverDistance sets the navigation maneuver distance.
|
|
||||||
func (d *Device) SetNavManeuverDistance(manDist string) error {
|
|
||||||
char, err := d.getChar(navigationManDist)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = char.WriteWithoutResponse([]byte(manDist))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNavProgress sets the navigation progress.
|
|
||||||
func (d *Device) SetNavProgress(progress uint8) error {
|
|
||||||
char, err := d.getChar(navigationProgress)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = char.WriteWithoutResponse([]byte{progress})
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
var (
|
|
||||||
regularNotifHeader = []byte{0x00, 0x01, 0x00}
|
|
||||||
callNotifHeader = []byte{0x03, 0x01, 0x00}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Notify sends a notification to the PineTime using the Alert Notification Service
|
|
||||||
func (d *Device) Notify(title, body string) error {
|
|
||||||
c, err := d.getChar(newAlertChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
content := title + "\x00" + body
|
|
||||||
_, err = c.WriteWithoutResponse(append(regularNotifHeader, content...))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type CallStatus uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
CallStatusDeclined CallStatus = iota
|
|
||||||
CallStatusAccepted
|
|
||||||
CallStatusMuted
|
|
||||||
)
|
|
||||||
|
|
||||||
// NotifyCall sends a call to the PineTime using the Alert Notification Service,
|
|
||||||
// then executes fn once the user presses a button on the watch.
|
|
||||||
func (d *Device) NotifyCall(from string, fn func(CallStatus)) error {
|
|
||||||
c, err := d.getChar(newAlertChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.WriteWithoutResponse(append(callNotifHeader, from...))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return watchCharOnce(d, notifEventChar, fn)
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResourceOperation int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ResourceUpload represents the upload phase
|
|
||||||
// of resource loading
|
|
||||||
ResourceUpload = iota
|
|
||||||
// ResourceRemove represents the obsolete
|
|
||||||
// file removal phase of resource loading
|
|
||||||
ResourceRemove
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 uint32
|
|
||||||
Transferred uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadResources accepts the path of an InfiniTime resource archive and loads its contents to the watch's filesystem.
|
|
||||||
func LoadResources(archivePath string, fs *FS, progress func(ResourceLoadProgress)) error {
|
|
||||||
r, err := zip.OpenReader(archivePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
manifestFl, err := r.Open("resources.json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var manifest resourceManifest
|
|
||||||
err = json.NewDecoder(manifestFl).Decode(&manifest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = manifestFl.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range manifest.Obsolete {
|
|
||||||
err := fs.RemoveAll(file.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
progress(ResourceLoadProgress{
|
|
||||||
Operation: ResourceRemove,
|
|
||||||
Name: filepath.Base(file.Path),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range manifest.Resources {
|
|
||||||
src, err := r.Open(file.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err := src.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fs.MkdirAll(filepath.Dir(file.Path))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dst, err := fs.Create(file.Path, uint32(fi.Size()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dst.ProgressFunc = func(transferred, total uint32) {
|
|
||||||
progress(ResourceLoadProgress{
|
|
||||||
Name: file.Name,
|
|
||||||
Transferred: transferred,
|
|
||||||
Total: total,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(dst, src)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Join(
|
|
||||||
err,
|
|
||||||
src.Close(),
|
|
||||||
dst.Close(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = src.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dst.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetTime sets the current time, and then sets the timezone data,
|
|
||||||
// if the local time characteristic is available.
|
|
||||||
func (d *Device) SetTime(t time.Time) error {
|
|
||||||
c, err := d.getChar(currentTimeChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint16(t.Year()))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(t.Month()))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(t.Day()))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(t.Hour()))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(t.Minute()))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(t.Second()))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(t.Weekday()))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8((t.Nanosecond()/1000)/1e6*256))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(0b0001))
|
|
||||||
|
|
||||||
_, err = c.WriteWithoutResponse(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ltc, err := d.getChar(localTimeChar)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, offset := t.Zone()
|
|
||||||
dst := 0
|
|
||||||
|
|
||||||
// Local time expects two values: the timezone offset and the dst offset, both
|
|
||||||
// expressed in quarters of an hour.
|
|
||||||
// Timezone offset is to be constant over DST, with dst offset holding the offset != 0
|
|
||||||
// when DST is in effect.
|
|
||||||
// As there is no standard way in go to get the actual dst offset, we assume it to be 1h
|
|
||||||
// when DST is in effect
|
|
||||||
if t.IsDST() {
|
|
||||||
dst = 3600
|
|
||||||
offset -= 3600
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.Reset()
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(offset/3600*4))
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint8(dst/3600*4))
|
|
||||||
|
|
||||||
_, err = ltc.WriteWithoutResponse(buf.Bytes())
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"tinygo.org/x/bluetooth"
|
|
||||||
)
|
|
||||||
|
|
||||||
type notifier interface {
|
|
||||||
notify([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
type watcher[T any] struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
nextFuncID int
|
|
||||||
callbacks map[int]func(T, error)
|
|
||||||
char *bluetooth.DeviceCharacteristic
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watcher[T]) addCallback(fn func(T, error)) int {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
funcID := w.nextFuncID
|
|
||||||
w.callbacks[funcID] = fn
|
|
||||||
w.nextFuncID++
|
|
||||||
return funcID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watcher[T]) notify(b []byte) {
|
|
||||||
var val T
|
|
||||||
err := binary.Read(bytes.NewReader(b), binary.LittleEndian, &val)
|
|
||||||
w.mu.Lock()
|
|
||||||
for _, fn := range w.callbacks {
|
|
||||||
go fn(val, err)
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watcher[T]) cancelFn(d *Device, ch btChar, id int) func() {
|
|
||||||
return func() {
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.callbacks, id)
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if len(w.callbacks) == 0 {
|
|
||||||
d.notifierMtx.Lock()
|
|
||||||
delete(d.notifierMap, ch)
|
|
||||||
d.notifierMtx.Unlock()
|
|
||||||
w.char.EnableNotifications(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchChar[T any](ctx context.Context, d *Device, ch btChar, fn func(T, error)) error {
|
|
||||||
d.notifierMtx.Lock()
|
|
||||||
defer d.notifierMtx.Unlock()
|
|
||||||
|
|
||||||
if n, ok := d.notifierMap[ch]; ok {
|
|
||||||
w := n.(*watcher[T])
|
|
||||||
funcID := w.addCallback(fn)
|
|
||||||
context.AfterFunc(ctx, w.cancelFn(d, ch, funcID))
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
w.cancelFn(d, ch, funcID)()
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
c, err := d.getChar(ch)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := &watcher[T]{callbacks: map[int]func(T, error){}}
|
|
||||||
err = c.EnableNotifications(w.notify)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.char = c
|
|
||||||
funcID := w.addCallback(fn)
|
|
||||||
d.notifierMap[ch] = w
|
|
||||||
|
|
||||||
context.AfterFunc(ctx, w.cancelFn(d, ch, funcID))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchCharOnce[T any](d *Device, ch btChar, fn func(T)) error {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
var watchErr error
|
|
||||||
err := watchChar(ctx, d, ch, func(val T, err error) {
|
|
||||||
defer cancel()
|
|
||||||
if err != nil {
|
|
||||||
watchErr = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fn(val)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
<-ctx.Done()
|
|
||||||
return watchErr
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
package infinitime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
weatherVersion = 0
|
|
||||||
|
|
||||||
currentWeatherType = 0
|
|
||||||
forecastWeatherType = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type WeatherIcon uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
WeatherIconClear WeatherIcon = iota
|
|
||||||
WeatherIconFewClouds
|
|
||||||
WeatherIconClouds
|
|
||||||
WeatherIconHeavyClouds
|
|
||||||
WeatherIconCloudsWithRain
|
|
||||||
WeatherIconRain
|
|
||||||
WeatherIconThunderstorm
|
|
||||||
WeatherIconSnow
|
|
||||||
WeatherIconMist
|
|
||||||
)
|
|
||||||
|
|
||||||
// CurrentWeather represents the current weather
|
|
||||||
type CurrentWeather struct {
|
|
||||||
Time time.Time
|
|
||||||
CurrentTemp float32
|
|
||||||
MinTemp float32
|
|
||||||
MaxTemp float32
|
|
||||||
Location string
|
|
||||||
Icon WeatherIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the [CurrentWeather] struct encoded using the InfiniTime
|
|
||||||
// weather wire protocol.
|
|
||||||
func (cw CurrentWeather) Bytes() []byte {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
buf.WriteByte(currentWeatherType)
|
|
||||||
buf.WriteByte(weatherVersion)
|
|
||||||
|
|
||||||
_, offset := cw.Time.Zone()
|
|
||||||
binary.Write(buf, binary.LittleEndian, cw.Time.Unix()+int64(offset))
|
|
||||||
|
|
||||||
binary.Write(buf, binary.LittleEndian, int16(cw.CurrentTemp*100))
|
|
||||||
binary.Write(buf, binary.LittleEndian, int16(cw.MinTemp*100))
|
|
||||||
binary.Write(buf, binary.LittleEndian, int16(cw.MaxTemp*100))
|
|
||||||
|
|
||||||
location := make([]byte, 32)
|
|
||||||
copy(location, cw.Location)
|
|
||||||
buf.Write(location)
|
|
||||||
|
|
||||||
buf.WriteByte(byte(cw.Icon))
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forecast represents a weather forecast
|
|
||||||
type Forecast struct {
|
|
||||||
Time time.Time
|
|
||||||
Days []ForecastDay
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForecastDay represents a forecast for a single day
|
|
||||||
type ForecastDay struct {
|
|
||||||
MinTemp int16
|
|
||||||
MaxTemp int16
|
|
||||||
Icon WeatherIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the [Forecast] struct encoded using the InfiniTime
|
|
||||||
// weather wire protocol.
|
|
||||||
func (f Forecast) Bytes() []byte {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
buf.WriteByte(forecastWeatherType)
|
|
||||||
buf.WriteByte(weatherVersion)
|
|
||||||
|
|
||||||
_, offset := f.Time.Zone()
|
|
||||||
binary.Write(buf, binary.LittleEndian, f.Time.Unix()+int64(offset))
|
|
||||||
|
|
||||||
buf.WriteByte(uint8(len(f.Days)))
|
|
||||||
|
|
||||||
for _, day := range f.Days {
|
|
||||||
binary.Write(buf, binary.LittleEndian, day.MinTemp*100)
|
|
||||||
binary.Write(buf, binary.LittleEndian, day.MaxTemp*100)
|
|
||||||
buf.WriteByte(byte(day.Icon))
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCurrentWeather updates the current weather data on the PineTime
|
|
||||||
func (d *Device) SetCurrentWeather(cw CurrentWeather) error {
|
|
||||||
c, err := d.getChar(weatherDataChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.WriteWithoutResponse(cw.Bytes())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetForecast sets future forecast data on the PineTime
|
|
||||||
func (d *Device) SetForecast(f Forecast) error {
|
|
||||||
c, err := d.getChar(weatherDataChar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(f.Days) > 5 {
|
|
||||||
return errors.New("amount of forecast days exceeds maximum of 5")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.WriteWithoutResponse(f.Bytes())
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
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")
|
|
||||||
ErrFileClosed = errors.New("cannot perform operation on a closed file")
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,600 +0,0 @@
|
|||||||
package fusefs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/hanwen/go-fuse/v2/fs"
|
|
||||||
"github.com/hanwen/go-fuse/v2/fuse"
|
|
||||||
"go.elara.ws/itd/infinitime"
|
|
||||||
"go.elara.ws/logger/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ITProperty struct {
|
|
||||||
name string
|
|
||||||
Ino uint64
|
|
||||||
gen func() ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DirEntry struct {
|
|
||||||
isDir bool
|
|
||||||
modtime uint64
|
|
||||||
size uint32
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ITNode struct {
|
|
||||||
fs.Inode
|
|
||||||
kind nodeKind
|
|
||||||
Ino uint64
|
|
||||||
|
|
||||||
lst []DirEntry
|
|
||||||
self DirEntry
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
type nodeKind uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
nodeKindRoot = iota
|
|
||||||
nodeKindInfo
|
|
||||||
nodeKindFS
|
|
||||||
nodeKindReadOnly
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
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 = dev.FS()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Failed to get filesystem").Err(err).Send()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ITNode{kind: nodeKindRoot}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var properties = make([]ITProperty, 6)
|
|
||||||
|
|
||||||
func BuildProperties(dev *infinitime.Device) {
|
|
||||||
properties[0] = ITProperty{
|
|
||||||
"heartrate", 2,
|
|
||||||
func() ([]byte, error) {
|
|
||||||
ans, err := dev.HeartRate()
|
|
||||||
return []byte(strconv.Itoa(int(ans)) + "\n"), err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
properties[1] = ITProperty{
|
|
||||||
"battery", 3,
|
|
||||||
func() ([]byte, error) {
|
|
||||||
ans, err := dev.BatteryLevel()
|
|
||||||
return []byte(strconv.Itoa(int(ans)) + "\n"), err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
properties[2] = ITProperty{
|
|
||||||
"motion", 4,
|
|
||||||
func() ([]byte, error) {
|
|
||||||
ans, err := dev.Motion()
|
|
||||||
return []byte(strconv.Itoa(int(ans.X)) + " " + strconv.Itoa(int(ans.Y)) + " " + strconv.Itoa(int(ans.Z)) + "\n"), err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
properties[3] = ITProperty{
|
|
||||||
"stepcount", 6,
|
|
||||||
func() ([]byte, error) {
|
|
||||||
ans, err := dev.StepCount()
|
|
||||||
return []byte(strconv.Itoa(int(ans)) + "\n"), err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
properties[4] = ITProperty{
|
|
||||||
"version", 7,
|
|
||||||
func() ([]byte, error) {
|
|
||||||
ans, err := dev.Version()
|
|
||||||
return []byte(ans + "\n"), err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
properties[5] = ITProperty{
|
|
||||||
"address", 8,
|
|
||||||
func() ([]byte, error) {
|
|
||||||
ans := dev.Address()
|
|
||||||
return []byte(ans + "\n"), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeReaddirer = (*ITNode)(nil)
|
|
||||||
|
|
||||||
// Readdir is part of the NodeReaddirer interface
|
|
||||||
func (n *ITNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
|
|
||||||
switch n.kind {
|
|
||||||
case 0:
|
|
||||||
// root folder
|
|
||||||
r := make([]fuse.DirEntry, 2)
|
|
||||||
r[0] = fuse.DirEntry{
|
|
||||||
Name: "info",
|
|
||||||
Ino: 0,
|
|
||||||
Mode: fuse.S_IFDIR,
|
|
||||||
}
|
|
||||||
r[1] = fuse.DirEntry{
|
|
||||||
Name: "fs",
|
|
||||||
Ino: 1,
|
|
||||||
Mode: fuse.S_IFDIR,
|
|
||||||
}
|
|
||||||
return fs.NewListDirStream(r), 0
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
// info folder
|
|
||||||
r := make([]fuse.DirEntry, 6)
|
|
||||||
for ind, value := range properties {
|
|
||||||
r[ind] = fuse.DirEntry{
|
|
||||||
Name: value.name,
|
|
||||||
Ino: value.Ino,
|
|
||||||
Mode: fuse.S_IFREG,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.NewListDirStream(r), 0
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
// on info
|
|
||||||
files, err := myfs.ReadDir(n.path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE ReadDir failed").Str("path", n.path).Err(err).Send()
|
|
||||||
return nil, syscallErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("FUSE ReadDir succeeded").Str("path", n.path).Int("objects", len(files)).Send()
|
|
||||||
r := make([]fuse.DirEntry, len(files))
|
|
||||||
n.lst = make([]DirEntry, len(files))
|
|
||||||
for ind, entry := range files {
|
|
||||||
info, err := entry.Info()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Info failed").Str("path", n.path).Err(err).Send()
|
|
||||||
return nil, syscallErr(err)
|
|
||||||
}
|
|
||||||
name := info.Name()
|
|
||||||
|
|
||||||
file := DirEntry{
|
|
||||||
path: n.path + "/" + name,
|
|
||||||
size: uint32(info.Size()),
|
|
||||||
modtime: uint64(info.ModTime().Unix()),
|
|
||||||
isDir: info.IsDir(),
|
|
||||||
}
|
|
||||||
n.lst[ind] = file
|
|
||||||
|
|
||||||
ino := inodemap[file.path]
|
|
||||||
if ino == 0 {
|
|
||||||
ino = uint64(len(inodemap)) + 1
|
|
||||||
inodemap[file.path] = ino
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.isDir {
|
|
||||||
r[ind] = fuse.DirEntry{
|
|
||||||
Name: name,
|
|
||||||
Mode: fuse.S_IFDIR,
|
|
||||||
Ino: ino + 10,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
r[ind] = fuse.DirEntry{
|
|
||||||
Name: name,
|
|
||||||
Mode: fuse.S_IFREG,
|
|
||||||
Ino: ino + 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fs.NewListDirStream(r), 0
|
|
||||||
}
|
|
||||||
r := make([]fuse.DirEntry, 0)
|
|
||||||
return fs.NewListDirStream(r), 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeLookuper = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (n *ITNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
|
|
||||||
switch n.kind {
|
|
||||||
case 0:
|
|
||||||
// root folder
|
|
||||||
if name == "info" {
|
|
||||||
stable := fs.StableAttr{
|
|
||||||
Mode: fuse.S_IFDIR,
|
|
||||||
Ino: uint64(0),
|
|
||||||
}
|
|
||||||
operations := &ITNode{kind: nodeKindInfo, Ino: 0}
|
|
||||||
child := n.NewInode(ctx, operations, stable)
|
|
||||||
return child, 0
|
|
||||||
} else if name == "fs" {
|
|
||||||
stable := fs.StableAttr{
|
|
||||||
Mode: fuse.S_IFDIR,
|
|
||||||
Ino: uint64(1),
|
|
||||||
}
|
|
||||||
operations := &ITNode{kind: nodeKindFS, Ino: 1, path: ""}
|
|
||||||
child := n.NewInode(ctx, operations, stable)
|
|
||||||
return child, 0
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
// info folder
|
|
||||||
for _, value := range properties {
|
|
||||||
if value.name == name {
|
|
||||||
stable := fs.StableAttr{
|
|
||||||
Mode: fuse.S_IFREG,
|
|
||||||
Ino: uint64(value.Ino),
|
|
||||||
}
|
|
||||||
operations := &ITNode{kind: nodeKindReadOnly, Ino: value.Ino}
|
|
||||||
child := n.NewInode(ctx, operations, stable)
|
|
||||||
return child, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
// FS object
|
|
||||||
if len(n.lst) == 0 {
|
|
||||||
n.Readdir(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range n.lst {
|
|
||||||
if file.path != n.path+"/"+name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Debug("FUSE Lookup successful").Str("path", file.path).Send()
|
|
||||||
|
|
||||||
if file.isDir {
|
|
||||||
stable := fs.StableAttr{
|
|
||||||
Mode: fuse.S_IFDIR,
|
|
||||||
Ino: inodemap[file.path],
|
|
||||||
}
|
|
||||||
operations := &ITNode{kind: nodeKindFS, path: file.path}
|
|
||||||
child := n.NewInode(ctx, operations, stable)
|
|
||||||
return child, 0
|
|
||||||
} else {
|
|
||||||
stable := fs.StableAttr{
|
|
||||||
Mode: fuse.S_IFREG,
|
|
||||||
Ino: inodemap[file.path],
|
|
||||||
}
|
|
||||||
operations := &ITNode{
|
|
||||||
kind: nodeKindFS, path: file.path,
|
|
||||||
self: file,
|
|
||||||
}
|
|
||||||
child := n.NewInode(ctx, operations, stable)
|
|
||||||
return child, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Warn("FUSE Lookup failed").Str("path", n.path+"/"+name).Send()
|
|
||||||
}
|
|
||||||
return nil, syscall.ENOENT
|
|
||||||
}
|
|
||||||
|
|
||||||
type bytesFileReadHandle struct {
|
|
||||||
content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FileReader = (*bytesFileReadHandle)(nil)
|
|
||||||
|
|
||||||
func (fh *bytesFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
|
||||||
log.Debug("FUSE Executing Read").Int("size", len(fh.content)).Send()
|
|
||||||
end := off + int64(len(dest))
|
|
||||||
if end > int64(len(fh.content)) {
|
|
||||||
end = int64(len(fh.content))
|
|
||||||
}
|
|
||||||
return fuse.ReadResultData(fh.content[off:end]), 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type sensorFileReadHandle struct {
|
|
||||||
content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FileReader = (*sensorFileReadHandle)(nil)
|
|
||||||
|
|
||||||
func (fh *sensorFileReadHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
|
||||||
log.Debug("FUSE Executing Read").Int("size", len(fh.content)).Send()
|
|
||||||
end := off + int64(len(dest))
|
|
||||||
if end > int64(len(fh.content)) {
|
|
||||||
end = int64(len(fh.content))
|
|
||||||
}
|
|
||||||
return fuse.ReadResultData(fh.content[off:end]), 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FileFlusher = (*sensorFileReadHandle)(nil)
|
|
||||||
|
|
||||||
func (fh *sensorFileReadHandle) Flush(ctx context.Context) (errno syscall.Errno) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type bytesFileWriteHandle struct {
|
|
||||||
content []byte
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FileWriter = (*bytesFileWriteHandle)(nil)
|
|
||||||
|
|
||||||
func (fh *bytesFileWriteHandle) Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno) {
|
|
||||||
log.Debug("FUSE Executing Write").Str("path", fh.path).Int("prev_size", len(fh.content)).Int("next_size", len(data)).Send()
|
|
||||||
if off != int64(len(fh.content)) {
|
|
||||||
log.Error("FUSE Write file size changed unexpectedly").Int("expect", int(off)).Int("received", len(fh.content)).Send()
|
|
||||||
return 0, syscall.ENXIO
|
|
||||||
}
|
|
||||||
fh.content = append(fh.content[:], data[:]...)
|
|
||||||
return uint32(len(data)), 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FileFlusher = (*bytesFileWriteHandle)(nil)
|
|
||||||
|
|
||||||
func (fh *bytesFileWriteHandle) Flush(ctx context.Context) (errno syscall.Errno) {
|
|
||||||
log.Debug("FUSE Attempting flush").Str("path", fh.path).Send()
|
|
||||||
fp, err := myfs.Create(fh.path, uint32(len(fh.content)))
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Flush failed: create").Str("path", fh.path).Err(err).Send()
|
|
||||||
return syscallErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fh.content) == 0 {
|
|
||||||
log.Debug("FUSE Flush no data to write").Str("path", fh.path).Send()
|
|
||||||
err = fp.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Flush failed during close").Str("path", fh.path).Err(err).Send()
|
|
||||||
return syscallErr(err)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Flush failed during write").Str("path", fh.path).Err(err).Send()
|
|
||||||
fp.Close()
|
|
||||||
return syscallErr(err)
|
|
||||||
}
|
|
||||||
if int(nread) != len(fh.content) {
|
|
||||||
log.Error("FUSE Flush failed during write").Str("path", fh.path).Int("expect", len(fh.content)).Int("got", int(nread)).Send()
|
|
||||||
fp.Close()
|
|
||||||
return syscall.EIO
|
|
||||||
}
|
|
||||||
err = fp.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Flush failed during close").Str("path", fh.path).Err(err).Send()
|
|
||||||
return syscallErr(err)
|
|
||||||
}
|
|
||||||
log.Debug("FUSE Flush done").Str("path", fh.path).Int("size", len(fh.content)).Send()
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.FileFsyncer = (*bytesFileWriteHandle)(nil)
|
|
||||||
|
|
||||||
func (fh *bytesFileWriteHandle) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
|
|
||||||
return fh.Flush(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeGetattrer = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (bn *ITNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
|
|
||||||
log.Debug("FUSE getattr").Str("path", bn.path).Send()
|
|
||||||
out.Ino = bn.Ino
|
|
||||||
out.Mtime = bn.self.modtime
|
|
||||||
out.Ctime = bn.self.modtime
|
|
||||||
out.Atime = bn.self.modtime
|
|
||||||
out.Size = uint64(bn.self.size)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeSetattrer = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (bn *ITNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
|
||||||
log.Debug("FUSE setattr").Str("path", bn.path).Send()
|
|
||||||
out.Size = 0
|
|
||||||
out.Mtime = 0
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeOpener = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (f *ITNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
|
||||||
switch f.kind {
|
|
||||||
case 2:
|
|
||||||
// FS file
|
|
||||||
if openFlags&syscall.O_RDWR != 0 {
|
|
||||||
log.Error("FUSE Open failed: RDWR").Str("path", f.path).Send()
|
|
||||||
return nil, 0, syscall.EROFS
|
|
||||||
}
|
|
||||||
|
|
||||||
if openFlags&syscall.O_WRONLY != 0 {
|
|
||||||
log.Debug("FUSE Opening for write").Str("path", f.path).Send()
|
|
||||||
fh = &bytesFileWriteHandle{
|
|
||||||
path: f.path,
|
|
||||||
content: make([]byte, 0),
|
|
||||||
}
|
|
||||||
return fh, fuse.FOPEN_DIRECT_IO, 0
|
|
||||||
} else {
|
|
||||||
log.Debug("FUSE Opening for read").Str("path", f.path).Send()
|
|
||||||
fp, err := myfs.Open(f.path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE: Opening failed").Str("path", f.path).Err(err).Send()
|
|
||||||
return nil, 0, syscallErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
log.Error("FUSE Read failed").Str("path", f.path).Err(err).Send()
|
|
||||||
fp.Close()
|
|
||||||
return nil, 0, syscallErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fh = &bytesFileReadHandle{
|
|
||||||
content: b.Bytes(),
|
|
||||||
}
|
|
||||||
return fh, fuse.FOPEN_DIRECT_IO, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
// Device file
|
|
||||||
|
|
||||||
// disallow writes
|
|
||||||
if openFlags&(syscall.O_RDWR|syscall.O_WRONLY) != 0 {
|
|
||||||
return nil, 0, syscall.EROFS
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, value := range properties {
|
|
||||||
if value.Ino == f.Ino {
|
|
||||||
ans, err := value.gen()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, syscallErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fh = &sensorFileReadHandle{
|
|
||||||
content: ans,
|
|
||||||
}
|
|
||||||
return fh, fuse.FOPEN_DIRECT_IO, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, 0, syscall.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeCreater = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (f *ITNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
|
||||||
if f.kind != 2 {
|
|
||||||
return nil, nil, 0, syscall.EROFS
|
|
||||||
}
|
|
||||||
|
|
||||||
path := f.path + "/" + name
|
|
||||||
ino := uint64(len(inodemap)) + 11
|
|
||||||
inodemap[path] = ino
|
|
||||||
|
|
||||||
stable := fs.StableAttr{
|
|
||||||
Mode: fuse.S_IFREG,
|
|
||||||
Ino: ino,
|
|
||||||
}
|
|
||||||
operations := &ITNode{
|
|
||||||
kind: nodeKindFS, Ino: ino,
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
node = f.NewInode(ctx, operations, stable)
|
|
||||||
|
|
||||||
fh = &bytesFileWriteHandle{
|
|
||||||
path: path,
|
|
||||||
content: make([]byte, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("FUSE Creating file").Str("path", path).Send()
|
|
||||||
|
|
||||||
errno = 0
|
|
||||||
return node, fh, fuseFlags, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeMkdirer = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (f *ITNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
|
|
||||||
if f.kind != 2 {
|
|
||||||
return nil, syscall.EROFS
|
|
||||||
}
|
|
||||||
|
|
||||||
path := f.path + "/" + name
|
|
||||||
err := myfs.Mkdir(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Mkdir failed").
|
|
||||||
Str("path", path).
|
|
||||||
Err(err).
|
|
||||||
Send()
|
|
||||||
return nil, syscallErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ino := uint64(len(inodemap)) + 11
|
|
||||||
inodemap[path] = ino
|
|
||||||
|
|
||||||
stable := fs.StableAttr{
|
|
||||||
Mode: fuse.S_IFDIR,
|
|
||||||
Ino: ino,
|
|
||||||
}
|
|
||||||
operations := &ITNode{
|
|
||||||
kind: nodeKindFS, Ino: ino,
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
node := f.NewInode(ctx, operations, stable)
|
|
||||||
|
|
||||||
log.Debug("FUSE Mkdir success").
|
|
||||||
Str("path", path).
|
|
||||||
Int("ino", int(ino)).
|
|
||||||
Send()
|
|
||||||
return node, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeRenamer = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (f *ITNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno {
|
|
||||||
if f.kind != 2 {
|
|
||||||
return syscall.EROFS
|
|
||||||
}
|
|
||||||
|
|
||||||
p1 := f.path + "/" + name
|
|
||||||
p2 := newParent.EmbeddedInode().Path(nil)[2:] + "/" + newName
|
|
||||||
|
|
||||||
err := myfs.Rename(p1, p2)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Rename failed").
|
|
||||||
Str("src", p1).
|
|
||||||
Str("dest", p2).
|
|
||||||
Err(err).
|
|
||||||
Send()
|
|
||||||
|
|
||||||
return syscallErr(err)
|
|
||||||
}
|
|
||||||
log.Debug("FUSE Rename sucess").
|
|
||||||
Str("src", p1).
|
|
||||||
Str("dest", p2).
|
|
||||||
Send()
|
|
||||||
|
|
||||||
ino := inodemap[p1]
|
|
||||||
delete(inodemap, p1)
|
|
||||||
inodemap[p2] = ino
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeUnlinker = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (f *ITNode) Unlink(ctx context.Context, name string) syscall.Errno {
|
|
||||||
if f.kind != 2 {
|
|
||||||
return syscall.EROFS
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(inodemap, f.path+"/"+name)
|
|
||||||
err := myfs.Remove(f.path + "/" + name)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FUSE Unlink failed").
|
|
||||||
Str("file", f.path+"/"+name).
|
|
||||||
Err(err).
|
|
||||||
Send()
|
|
||||||
|
|
||||||
return syscallErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("FUSE Unlink success").
|
|
||||||
Str("file", f.path+"/"+name).
|
|
||||||
Send()
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fs.NodeRmdirer = (*ITNode)(nil)
|
|
||||||
|
|
||||||
func (f *ITNode) Rmdir(ctx context.Context, name string) syscall.Errno {
|
|
||||||
return f.Unlink(ctx, name)
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
package fusefs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"go.elara.ws/itd/internal/fsproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func syscallErr(err error) syscall.Errno {
|
|
||||||
if err == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
switch err := err.(type) {
|
|
||||||
case fsproto.Error:
|
|
||||||
switch err.Code {
|
|
||||||
case 0x02: // filesystem error
|
|
||||||
return syscall.EIO
|
|
||||||
case 0x05: // read-only filesystem
|
|
||||||
return syscall.EROFS
|
|
||||||
case 0x03: // no such file
|
|
||||||
return syscall.ENOENT
|
|
||||||
case 0x04: // protocol error
|
|
||||||
return syscall.EPROTO
|
|
||||||
case -5: // input/output error
|
|
||||||
return syscall.EIO
|
|
||||||
case -84: // filesystem is corrupted
|
|
||||||
return syscall.ENOTRECOVERABLE
|
|
||||||
case -2: // no such directory entry
|
|
||||||
return syscall.ENOENT
|
|
||||||
case -17: // entry already exists
|
|
||||||
return syscall.EEXIST
|
|
||||||
case -20: // entry is not a directory
|
|
||||||
return syscall.ENOTDIR
|
|
||||||
case -39: // directory is not empty
|
|
||||||
return syscall.ENOTEMPTY
|
|
||||||
case -9: // bad file number
|
|
||||||
return syscall.EBADF
|
|
||||||
case -27: // file is too large
|
|
||||||
return syscall.EFBIG
|
|
||||||
case -22: // invalid parameter
|
|
||||||
return syscall.EINVAL
|
|
||||||
case -28: // no space left on device
|
|
||||||
return syscall.ENOSPC
|
|
||||||
case -12: // no more memory available
|
|
||||||
return syscall.ENOMEM
|
|
||||||
case -61: // no attr available
|
|
||||||
return syscall.ENODATA
|
|
||||||
case -36: // file name is too long
|
|
||||||
return syscall.ENAMETOOLONG
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
switch err {
|
|
||||||
case fsproto.ErrFileNotExists: // file does not exist
|
|
||||||
return syscall.ENOENT
|
|
||||||
case fsproto.ErrFileReadOnly: // file is read only
|
|
||||||
return syscall.EACCES
|
|
||||||
case fsproto.ErrFileWriteOnly: // file is write only
|
|
||||||
return syscall.EACCES
|
|
||||||
case fsproto.ErrInvalidOffset: // invalid file offset
|
|
||||||
return syscall.EINVAL
|
|
||||||
case fsproto.ErrNoRemoveRoot: // refusing to remove root directory
|
|
||||||
return syscall.EPERM
|
|
||||||
case fsproto.ErrFileClosed: // cannot perform operation on closed file
|
|
||||||
return syscall.EBADF
|
|
||||||
default:
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return syscall.EIO
|
|
||||||
}
|
|