Compare commits
No commits in common. "a9ef386883e09b0793d130ceaadf507b0fac3a54" and "3a877c41a4b890f594512fd145ebc8850d5e1524" have entirely different histories.
a9ef386883
...
3a877c41a4
@ -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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
96
api/fs.go
96
api/fs.go
@ -1,96 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"go.arsenm.dev/itd/internal/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) Rename(old, new string) error {
|
|
||||||
_, err := c.request(types.Request{
|
|
||||||
Type: types.ReqTypeFS,
|
|
||||||
Data: types.ReqDataFS{
|
|
||||||
Type: types.FSTypeMove,
|
|
||||||
Files: []string{old, new},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Remove(paths ...string) error {
|
|
||||||
_, err := c.request(types.Request{
|
|
||||||
Type: types.ReqTypeFS,
|
|
||||||
Data: types.ReqDataFS{
|
|
||||||
Type: types.FSTypeDelete,
|
|
||||||
Files: paths,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Mkdir(paths ...string) error {
|
|
||||||
_, 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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var out []types.FileInfo
|
|
||||||
err = mapstructure.Decode(res.Value, &out)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return res.Value.(string), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WriteFile(path, data string) error {
|
|
||||||
_, err := c.request(types.Request{
|
|
||||||
Type: types.ReqTypeFS,
|
|
||||||
Data: types.ReqDataFS{
|
|
||||||
Type: types.FSTypeWrite,
|
|
||||||
Files: []string{path},
|
|
||||||
Data: data,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
222
calls.go
222
calls.go
@ -1,90 +1,85 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"go.arsenm.dev/infinitime"
|
"go.arsenm.dev/infinitime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initCallNotifs(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 := newSystemBusConn()
|
rule := "type='signal',sender='org.freedesktop.ModemManager1',interface='org.freedesktop.ModemManager1.Modem.Voice',member='CallAdded'"
|
||||||
|
|
||||||
|
// Use dbus-monitor command with profiling output as a workaround
|
||||||
|
// because go-bluetooth seems to monopolize the system bus connection
|
||||||
|
// which makes monitoring show only bluez-related messages.
|
||||||
|
cmd := exec.Command("dbus-monitor", "--system", "--profile", rule)
|
||||||
|
// Get command output pipe
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Run command asynchronously
|
||||||
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if modem manager interface exists
|
// Create new scanner for command output
|
||||||
exists, err := modemManagerExists(conn)
|
scanner := bufio.NewScanner(stdout)
|
||||||
if err != nil {
|
|
||||||
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 := newSystemBusConn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add match for new calls to monitor connection
|
|
||||||
err = monitorConn.AddMatchSignal(
|
|
||||||
dbus.WithMatchSender("org.freedesktop.ModemManager1"),
|
|
||||||
dbus.WithMatchInterface("org.freedesktop.ModemManager1.Modem.Voice"),
|
|
||||||
dbus.WithMatchMember("CallAdded"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create channel to receive calls
|
|
||||||
callCh := make(chan *dbus.Message, 5)
|
|
||||||
// Notify channel upon received message
|
|
||||||
monitorConn.Eavesdrop(callCh)
|
|
||||||
|
|
||||||
var respHandlerOnce sync.Once
|
|
||||||
var callObj dbus.BusObject
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// For every message received
|
// For each line in output
|
||||||
for event := range callCh {
|
for scanner.Scan() {
|
||||||
// Get path to call object
|
// Get line as string
|
||||||
callPath := event.Body[0].(dbus.ObjectPath)
|
text := scanner.Text()
|
||||||
// Get call object
|
|
||||||
callObj = conn.Object("org.freedesktop.ModemManager1", callPath)
|
|
||||||
|
|
||||||
// Get phone number from call object using method call connection
|
// If line starts with "#", it is part of
|
||||||
phoneNum, err := getPhoneNum(conn, callObj)
|
// the field format, skip it.
|
||||||
if err != nil {
|
if strings.HasPrefix(text, "#") {
|
||||||
log.Error().Err(err).Msg("Error getting phone number")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send call notification to InfiniTime
|
// Split line into fields. The order is as follows:
|
||||||
resCh, err := dev.NotifyCall(phoneNum)
|
// type timestamp serial sender destination path interface member
|
||||||
if err != nil {
|
fields := strings.Fields(text)
|
||||||
continue
|
// Field 7 is Member. Make sure it is "CallAdded".
|
||||||
}
|
if fields[7] == "CallAdded" {
|
||||||
|
// Get Modem ID from modem path
|
||||||
go respHandlerOnce.Do(func() {
|
modemID := parseModemID(fields[5])
|
||||||
// Wait for PineTime response
|
// Get call ID of current call
|
||||||
for res := range resCh {
|
callID, err := getCurrentCallID(modemID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Get phone number of current call
|
||||||
|
phoneNum, err := getPhoneNum(callID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Send call notification to PineTime
|
||||||
|
resCh, err := dev.NotifyCall(phoneNum)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
// Wait for PineTime response
|
||||||
|
res := <-resCh
|
||||||
switch res {
|
switch res {
|
||||||
case infinitime.CallStatusAccepted:
|
case infinitime.CallStatusAccepted:
|
||||||
// Attempt to accept call
|
// Attempt to accept call
|
||||||
err = acceptCall(conn, callObj)
|
err = acceptCall(callID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("Error accepting call")
|
log.Warn().Err(err).Msg("Error accepting call")
|
||||||
}
|
}
|
||||||
case infinitime.CallStatusDeclined:
|
case infinitime.CallStatusDeclined:
|
||||||
// Attempt to decline call
|
// Attempt to decline call
|
||||||
err = declineCall(conn, callObj)
|
err = declineCall(callID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("Error declining call")
|
log.Warn().Err(err).Msg("Error declining call")
|
||||||
}
|
}
|
||||||
@ -92,51 +87,90 @@ func initCallNotifs(dev *infinitime.Device) error {
|
|||||||
// Warn about unimplemented muting
|
// Warn about unimplemented muting
|
||||||
log.Warn().Msg("Muting calls is not implemented")
|
log.Warn().Msg("Muting calls is not implemented")
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Info().Msg("Relaying calls to InfiniTime")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func modemManagerExists(conn *dbus.Conn) (bool, error) {
|
func parseModemID(modemPath string) int {
|
||||||
var names []string
|
// Split path by "/"
|
||||||
err := conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names)
|
splitPath := strings.Split(modemPath, "/")
|
||||||
if err != nil {
|
// Get last element and convert to integer
|
||||||
return false, err
|
id, _ := strconv.Atoi(splitPath[len(splitPath)-1])
|
||||||
}
|
return id
|
||||||
return strSlcContains(names, "org.freedesktop.ModemManager1"), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPhoneNum gets a phone number from a call object using a DBus connection
|
func getCurrentCallID(modemID int) (int, error) {
|
||||||
func getPhoneNum(conn *dbus.Conn, callObj dbus.BusObject) (string, error) {
|
// Create mmcli command
|
||||||
var out string
|
cmd := exec.Command("mmcli", "--voice-list-calls", "-m", fmt.Sprint(modemID), "-J")
|
||||||
// Get number property on DBus object and store return value in out
|
// Run command and get output
|
||||||
err := callObj.StoreProperty("org.freedesktop.ModemManager1.Call.Number", &out)
|
data, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var calls map[string][]string
|
||||||
|
// Decode JSON from command output
|
||||||
|
err = json.Unmarshal(data, &calls)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// Get first call in output
|
||||||
|
firstCall := calls["modem.voice.call"][0]
|
||||||
|
// Split path by "/"
|
||||||
|
splitCall := strings.Split(firstCall, "/")
|
||||||
|
// Return last element converted to integer
|
||||||
|
return strconv.Atoi(splitCall[len(splitCall)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPhoneNum(callID int) (string, error) {
|
||||||
|
// Create dbus-send command
|
||||||
|
cmd := exec.Command("dbus-send",
|
||||||
|
"--dest=org.freedesktop.ModemManager1",
|
||||||
|
"--system",
|
||||||
|
"--print-reply=literal",
|
||||||
|
"--type=method_call",
|
||||||
|
fmt.Sprintf("/org/freedesktop/ModemManager1/Call/%d", callID),
|
||||||
|
"org.freedesktop.DBus.Properties.Get",
|
||||||
|
"string:org.freedesktop.ModemManager1.Call",
|
||||||
|
"string:Number",
|
||||||
|
)
|
||||||
|
// Run command and get output
|
||||||
|
numData, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return out, nil
|
// Split output into fields
|
||||||
|
num := strings.Fields(string(numData))
|
||||||
|
// Return last field
|
||||||
|
return num[len(num)-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPhoneNum accepts a call using a DBus connection
|
func acceptCall(callID int) error {
|
||||||
func acceptCall(conn *dbus.Conn, callObj dbus.BusObject) error {
|
// Create dbus-send command
|
||||||
// Call Accept() method on DBus object
|
cmd := exec.Command("dbus-send",
|
||||||
call := callObj.Call("org.freedesktop.ModemManager1.Call.Accept", 0)
|
"--dest=org.freedesktop.ModemManager1",
|
||||||
if call.Err != nil {
|
"--print-reply",
|
||||||
return call.Err
|
"--system",
|
||||||
}
|
"--type=method_call",
|
||||||
return nil
|
fmt.Sprintf("/org/freedesktop/ModemManager1/Call/%d", callID),
|
||||||
|
"org.freedesktop.ModemManager1.Call.Accept",
|
||||||
|
)
|
||||||
|
// Run command and return errpr
|
||||||
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPhoneNum declines a call using a DBus connection
|
func declineCall(callID int) error {
|
||||||
func declineCall(conn *dbus.Conn, callObj dbus.BusObject) error {
|
// Create dbus-send command
|
||||||
// Call Hangup() method on DBus object
|
cmd := exec.Command("dbus-send",
|
||||||
call := callObj.Call("org.freedesktop.ModemManager1.Call.Hangup", 0)
|
"--dest=org.freedesktop.ModemManager1",
|
||||||
if call.Err != nil {
|
"--print-reply",
|
||||||
return call.Err
|
"--system",
|
||||||
}
|
"--type=method_call",
|
||||||
return nil
|
fmt.Sprintf("/org/freedesktop/ModemManager1/Call/%d", callID),
|
||||||
|
"org.freedesktop.ModemManager1.Call.Hangup",
|
||||||
|
)
|
||||||
|
// Run command and return errpr
|
||||||
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 filesystem command
|
|
||||||
var filesystemCmd = &cobra.Command{
|
|
||||||
Use: "filesystem",
|
|
||||||
Aliases: []string{"fs"},
|
|
||||||
Short: "Perform filesystem operations on the PineTime",
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
root.RootCmd.AddCommand(filesystemCmd)
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 list 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)
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mkdirCmd represents the mkdir 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)
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// moveCmd represents the move 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)
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// readCmd represents the read 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 suffix string
|
|
||||||
var out *os.File
|
|
||||||
if args[1] == "-" {
|
|
||||||
out = os.Stdout
|
|
||||||
suffix = "\n"
|
|
||||||
} 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")
|
|
||||||
}
|
|
||||||
out.WriteString(suffix)
|
|
||||||
|
|
||||||
log.Info().Msgf("Read %d bytes in %s", n, time.Since(start))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
filesystemCmd.AddCommand(readCmd)
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// removeCmd represents the remove 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)
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// writeCmd represents the write 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)
|
|
||||||
}
|
|
@ -25,7 +25,6 @@ import (
|
|||||||
"go.arsenm.dev/itd/cmd/itctl/root"
|
"go.arsenm.dev/itd/cmd/itctl/root"
|
||||||
_ "go.arsenm.dev/itd/cmd/itctl/set"
|
_ "go.arsenm.dev/itd/cmd/itctl/set"
|
||||||
_ "go.arsenm.dev/itd/cmd/itctl/watch"
|
_ "go.arsenm.dev/itd/cmd/itctl/watch"
|
||||||
_ "go.arsenm.dev/itd/cmd/itctl/filesystem"
|
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
37
dbus.go
37
dbus.go
@ -1,37 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "github.com/godbus/dbus/v5"
|
|
||||||
|
|
||||||
func newSystemBusConn() (*dbus.Conn, error) {
|
|
||||||
// Connect to dbus session bus
|
|
||||||
conn, err := dbus.SystemBusPrivate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = conn.Auth(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = conn.Hello()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSessionBusConn() (*dbus.Conn, error) {
|
|
||||||
// Connect to dbus session bus
|
|
||||||
conn, err := dbus.SessionBusPrivate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = conn.Auth(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = conn.Hello()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
2
go.mod
2
go.mod
@ -25,7 +25,7 @@ require (
|
|||||||
github.com/srwiley/oksvg v0.0.0-20210519022825-9fc0c575d5fe // indirect
|
github.com/srwiley/oksvg v0.0.0-20210519022825-9fc0c575d5fe // indirect
|
||||||
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
|
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
|
||||||
github.com/yuin/goldmark v1.4.1 // indirect
|
github.com/yuin/goldmark v1.4.1 // indirect
|
||||||
go.arsenm.dev/infinitime v0.0.0-20211126043306-522c10a9c0a7
|
go.arsenm.dev/infinitime v0.0.0-20211122091416-ec43bad46652
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
||||||
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0 // indirect
|
golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -367,8 +367,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||||||
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.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
|
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.arsenm.dev/infinitime v0.0.0-20211126043306-522c10a9c0a7 h1:m6BVtAiWMRbfUgBZVthXq2eZhSwDxdtOi8Dh/59hXik=
|
go.arsenm.dev/infinitime v0.0.0-20211122091416-ec43bad46652 h1:2Z09crdXIs+aMy1xwuD6w2kml+EObfmzgCBaOxoZIv0=
|
||||||
go.arsenm.dev/infinitime v0.0.0-20211126043306-522c10a9c0a7/go.mod h1:TzAhsz7TAqEm/vWhgMvmIxHS5jt46hqKkPvr6cqvVyA=
|
go.arsenm.dev/infinitime v0.0.0-20211122091416-ec43bad46652/go.mod h1:kNBKxQfqeLUfi13GM6tB1kSvLm8HlZ7PM47AYeJQIiw=
|
||||||
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/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/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ReqTypeHeartRate = iota
|
ReqTypeHeartRate = iota
|
||||||
ReqTypeBattLevel
|
ReqTypeBattLevel
|
||||||
@ -20,7 +15,6 @@ const (
|
|||||||
ReqTypeStepCount
|
ReqTypeStepCount
|
||||||
ReqTypeWatchStepCount
|
ReqTypeWatchStepCount
|
||||||
ReqTypeCancel
|
ReqTypeCancel
|
||||||
ReqTypeFS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -28,21 +22,6 @@ const (
|
|||||||
UpgradeTypeFiles
|
UpgradeTypeFiles
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
FSTypeWrite = iota
|
|
||||||
FSTypeRead
|
|
||||||
FSTypeMove
|
|
||||||
FSTypeDelete
|
|
||||||
FSTypeList
|
|
||||||
FSTypeMkdir
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReqDataFS struct {
|
|
||||||
Type int `json:"type"`
|
|
||||||
Files []string `json:"files"`
|
|
||||||
Data string `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReqDataFwUpgrade struct {
|
type ReqDataFwUpgrade struct {
|
||||||
Type int
|
Type int
|
||||||
Files []string
|
Files []string
|
||||||
@ -77,66 +56,3 @@ type MotionValues struct {
|
|||||||
Y int16
|
Y int16
|
||||||
Z int16
|
Z int16
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileInfo struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
IsDir bool `json:"isDir"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi FileInfo) String() string {
|
|
||||||
var isDirChar rune
|
|
||||||
if fi.IsDir {
|
|
||||||
isDirChar = 'd'
|
|
||||||
} else {
|
|
||||||
isDirChar = '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get human-readable value for file size
|
|
||||||
val, unit := bytesHuman(fi.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,
|
|
||||||
fi.Name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bytesHuman returns a human-readable string for
|
|
||||||
// the amount of bytes inputted.
|
|
||||||
func bytesHuman(b int64) (float64, string) {
|
|
||||||
const unit = 1000
|
|
||||||
// Set possible units 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 := int64(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
|
|
||||||
}
|
|
||||||
|
8
main.go
8
main.go
@ -26,11 +26,7 @@ import (
|
|||||||
"go.arsenm.dev/infinitime"
|
"go.arsenm.dev/infinitime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var firmwareUpdating = false
|
||||||
firmwareUpdating = false
|
|
||||||
// The FS must be updated when the watch is reconnected
|
|
||||||
updateFS = false
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
infinitime.Init()
|
infinitime.Init()
|
||||||
@ -65,8 +61,6 @@ func main() {
|
|||||||
log.Error().Err(err).Msg("Error sending notification to InfiniTime")
|
log.Error().Err(err).Msg("Error sending notification to InfiniTime")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFS = true
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get firmware version
|
// Get firmware version
|
||||||
|
49
music.go
49
music.go
@ -26,22 +26,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initMusicCtrl(dev *infinitime.Device) error {
|
func initMusicCtrl(dev *infinitime.Device) error {
|
||||||
player.Init()
|
// On player status change, set status
|
||||||
|
err := player.Status(func(newStatus bool) {
|
||||||
player.OnChange(func(ct player.ChangeType, val string) {
|
|
||||||
if !firmwareUpdating {
|
if !firmwareUpdating {
|
||||||
switch ct {
|
dev.Music.SetStatus(newStatus)
|
||||||
case player.ChangeTypeStatus:
|
|
||||||
dev.Music.SetStatus(val == "Playing")
|
|
||||||
case player.ChangeTypeTitle:
|
|
||||||
dev.Music.SetTrack(val)
|
|
||||||
case player.ChangeTypeAlbum:
|
|
||||||
dev.Music.SetAlbum(val)
|
|
||||||
case player.ChangeTypeArtist:
|
|
||||||
dev.Music.SetArtist(val)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// On player title change, set track
|
||||||
|
err = player.Metadata("title", func(newTitle string) {
|
||||||
|
if !firmwareUpdating {
|
||||||
|
dev.Music.SetTrack(newTitle)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// On player album change, set album
|
||||||
|
err = player.Metadata("album", func(newAlbum string) {
|
||||||
|
if !firmwareUpdating {
|
||||||
|
dev.Music.SetAlbum(newAlbum)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// On player artist change, set artist
|
||||||
|
err = player.Metadata("artist", func(newArtist string) {
|
||||||
|
if !firmwareUpdating {
|
||||||
|
dev.Music.SetArtist(newArtist)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Watch for music events
|
// Watch for music events
|
||||||
musicEvtCh, err := dev.Music.WatchEvents()
|
musicEvtCh, err := dev.Music.WatchEvents()
|
||||||
|
@ -30,7 +30,7 @@ import (
|
|||||||
|
|
||||||
func initNotifRelay(dev *infinitime.Device) error {
|
func initNotifRelay(dev *infinitime.Device) error {
|
||||||
// Connect to dbus session bus
|
// Connect to dbus session bus
|
||||||
bus, err := newSessionBusConn()
|
bus, err := dbus.SessionBus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
143
socket.go
143
socket.go
@ -22,7 +22,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -33,7 +32,6 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.arsenm.dev/infinitime"
|
"go.arsenm.dev/infinitime"
|
||||||
"go.arsenm.dev/infinitime/blefs"
|
|
||||||
"go.arsenm.dev/itd/internal/types"
|
"go.arsenm.dev/itd/internal/types"
|
||||||
"go.arsenm.dev/itd/translit"
|
"go.arsenm.dev/itd/translit"
|
||||||
)
|
)
|
||||||
@ -80,11 +78,6 @@ func startSocket(dev *infinitime.Device) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, err := dev.FS()
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Err(err).Msg("Error getting BLE filesystem")
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
// Accept socket connection
|
// Accept socket connection
|
||||||
@ -94,7 +87,7 @@ func startSocket(dev *infinitime.Device) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Concurrently handle connection
|
// Concurrently handle connection
|
||||||
go handleConnection(conn, dev, fs)
|
go handleConnection(conn, dev)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -104,23 +97,9 @@ func startSocket(dev *infinitime.Device) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConnection(conn net.Conn, dev *infinitime.Device, fs *blefs.FS) {
|
func handleConnection(conn net.Conn, dev *infinitime.Device) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
// If an FS update is required (reconnect ocurred)
|
|
||||||
if updateFS {
|
|
||||||
// Get new FS
|
|
||||||
newFS, err := dev.FS()
|
|
||||||
if err != nil {
|
|
||||||
fs = nil
|
|
||||||
log.Warn().Err(err).Msg("Error updating BLE filesystem")
|
|
||||||
}
|
|
||||||
// Set FS pointer to new FS
|
|
||||||
*fs = *newFS
|
|
||||||
// Reset updateFS
|
|
||||||
updateFS = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new scanner on connection
|
// Create new scanner on connection
|
||||||
scanner := bufio.NewScanner(conn)
|
scanner := bufio.NewScanner(conn)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
@ -452,124 +431,6 @@ func handleConnection(conn net.Conn, dev *infinitime.Device, fs *blefs.FS) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
firmwareUpdating = false
|
firmwareUpdating = false
|
||||||
case types.ReqTypeFS:
|
|
||||||
if fs == nil {
|
|
||||||
connErr(conn, req.Type, nil, "BLE filesystem is not available")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// If no data, return error
|
|
||||||
if req.Data == nil {
|
|
||||||
connErr(conn, req.Type, nil, "Data required for filesystem operations")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var reqData types.ReqDataFS
|
|
||||||
// Decode data map to firmware upgrade request data
|
|
||||||
err = mapstructure.Decode(req.Data, &reqData)
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error decoding request data")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch reqData.Type {
|
|
||||||
case types.FSTypeDelete:
|
|
||||||
if len(reqData.Files) == 0 {
|
|
||||||
connErr(conn, req.Type, nil, "Remove FS command requires at least one file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, file := range reqData.Files {
|
|
||||||
err := fs.Remove(file)
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error removing file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json.NewEncoder(conn).Encode(types.Response{Type: req.Type})
|
|
||||||
case types.FSTypeMove:
|
|
||||||
if len(reqData.Files) != 2 {
|
|
||||||
connErr(conn, req.Type, nil, "Move FS command requires an old path and new path in the files list")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err := fs.Rename(reqData.Files[0], reqData.Files[1])
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error moving file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
json.NewEncoder(conn).Encode(types.Response{Type: req.Type})
|
|
||||||
case types.FSTypeMkdir:
|
|
||||||
if len(reqData.Files) == 0 {
|
|
||||||
connErr(conn, req.Type, nil, "Mkdir FS command requires at least one file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, file := range reqData.Files {
|
|
||||||
err := fs.Mkdir(file)
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error creating directory")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json.NewEncoder(conn).Encode(types.Response{Type: req.Type})
|
|
||||||
case types.FSTypeList:
|
|
||||||
if len(reqData.Files) != 1 {
|
|
||||||
connErr(conn, req.Type, nil, "List FS command requires a path to list in the files list")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
entries, err := fs.ReadDir(reqData.Files[0])
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error reading directory")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var out []types.FileInfo
|
|
||||||
for _, entry := range entries {
|
|
||||||
info, err := entry.Info()
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error getting file info")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
out = append(out, types.FileInfo{
|
|
||||||
Name: info.Name(),
|
|
||||||
Size: info.Size(),
|
|
||||||
IsDir: info.IsDir(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
json.NewEncoder(conn).Encode(types.Response{
|
|
||||||
Type: req.Type,
|
|
||||||
Value: out,
|
|
||||||
})
|
|
||||||
case types.FSTypeWrite:
|
|
||||||
if len(reqData.Files) != 1 {
|
|
||||||
connErr(conn, req.Type, nil, "Write FS command requires a path to the file to write")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
file, err := fs.Create(reqData.Files[0], uint32(len(reqData.Data)))
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error creating file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
_, err = file.WriteString(reqData.Data)
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error writing to file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
json.NewEncoder(conn).Encode(types.Response{Type: req.Type})
|
|
||||||
case types.FSTypeRead:
|
|
||||||
if len(reqData.Files) != 1 {
|
|
||||||
connErr(conn, req.Type, nil, "Read FS command requires a path to the file to read")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
file, err := fs.Open(reqData.Files[0])
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error opening file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
connErr(conn, req.Type, err, "Error reading from file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
json.NewEncoder(conn).Encode(types.Response{
|
|
||||||
Type: req.Type,
|
|
||||||
Value: string(data),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case types.ReqTypeCancel:
|
case types.ReqTypeCancel:
|
||||||
if req.Data == nil {
|
if req.Data == nil {
|
||||||
connErr(conn, req.Type, nil, "No data provided. Cancel request requires request ID string as data.")
|
connErr(conn, req.Type, nil, "No data provided. Cancel request requires request ID string as data.")
|
||||||
|
Loading…
Reference in New Issue
Block a user