Skip to content

Commit 4114daf

Browse files
authored
feat: Add configurable/static device ID support (#37, PR: #52)
* feat: Add ability to set static device ID and also added testing for device ID stability * docs: Update README to clarify device_id option * feat: Implement device ID persistence with file storage and update README
1 parent 0eb2838 commit 4114daf

File tree

9 files changed

+524
-17
lines changed

9 files changed

+524
-17
lines changed

.github/workflows/test.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1-
name: Test Build
1+
name: Test & Build
22

33
on:
44
pull_request:
55
types: [synchronize, opened]
6+
push:
7+
branches:
8+
- master
69

710
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Check out code
15+
uses: actions/checkout@v4
16+
- name: Set up Go
17+
uses: actions/setup-go@v5
18+
with:
19+
go-version: 1.21
20+
- name: Get dependencies
21+
run: go mod download
22+
- name: Run tests
23+
run: go test -v ./...
24+
- name: Run tests with race detector
25+
run: go test -race -short ./...
26+
827
build:
928
runs-on: ubuntu-latest
1029
steps:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ channels.json
2828
config.json
2929
routes/.DS_Store
3030
.DS_Store
31+
.device_id

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ services:
4343
2. Create a `config.json` in the working directory and fill in the necessary.
4444
- Possible values for `encoder_profile` are `vaapi`, `video_toolbox`, `omx`, `nvenc` and `cpu`. A sample `config.example.json` is available on GitHub.
4545
- `nvenc` requires an NVIDIA GPU and ffmpeg with NVENC support. For Docker, see [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html).
46+
- **Optional:** Set `device_id` to a specific value (e.g. `"30480554"`) to maintain a stable device ID. If omitted, a random ID will be generated on first run and automatically saved to `.device_id` for persistence across restarts. Priority order: `config.json` > `.device_id` file > auto-generate new.
4647
3. Create a `channels.json` and fill in the necessary.
4748
- A sample `channels.example.json` is available on GitHub.
4849
4. Copy the `templates` folder from this repository into the working directory (alongside the two JSON files)
@@ -53,3 +54,19 @@ services:
5354
1. Clone the repo
5455
2. Run `go mod download`
5556
3. To run the server, run `go run cmd/main.go`
57+
58+
### Testing
59+
Run the test suite:
60+
```bash
61+
go test ./...
62+
```
63+
64+
Run tests with verbose output:
65+
```bash
66+
go test -v ./...
67+
```
68+
69+
Run tests with race detection:
70+
```bash
71+
go test -race ./...
72+
```

config.example.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "Amazing Tuner",
33
"encoder_profile": "cpu",
4-
"tuner_count": 3
4+
"tuner_count": 3,
5+
"device_id": "30480554"
56
}

config/channels.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package config
22

33
import (
44
"encoding/json"
5+
"errors"
56
"log"
67
"os"
7-
"errors"
8+
"strings"
89
"sync"
910

1011
"github.com/fsnotify/fsnotify"
@@ -111,7 +112,14 @@ func LoadChannels() error {
111112
}
112113

113114
func init() {
114-
var playlist = os.Getenv("PLAYLIST")
115+
// Skip initialization during tests by checking command line args
116+
for _, arg := range os.Args {
117+
if strings.HasPrefix(arg, "-test.") {
118+
return
119+
}
120+
}
121+
122+
var playlist = os.Getenv("PLAYLIST")
115123
if len(playlist) > 0 {
116124
err := LoadChannelsFromPl(playlist)
117125
if err == nil {

config/config.go

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package config
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"log"
7+
"math/rand"
68
"os"
9+
"strings"
710
)
811

912
type EncoderProfile string
@@ -20,6 +23,7 @@ type Config struct {
2023
Name string `json:"name"`
2124
EncoderProfile *EncoderProfile `json:"encoder_profile"`
2225
TunerCount *int `json:"tuner_count"`
26+
DeviceID *string `json:"device_id"`
2327
}
2428

2529
func (c Config) GetEncoderProfile() EncoderProfile {
@@ -45,18 +49,66 @@ var (
4549
Cfg Config
4650
)
4751

48-
func init() {
49-
file, err := os.Open("config.json")
52+
const deviceIDFile = ".device_id"
53+
54+
func loadDeviceIDFromFile() (string, error) {
55+
data, err := os.ReadFile(deviceIDFile)
5056
if err != nil {
51-
log.Fatal(err)
57+
return "", err
5258
}
59+
return strings.TrimSpace(string(data)), nil
60+
}
5361

62+
func saveDeviceIDToFile(deviceID string) error {
63+
return os.WriteFile(deviceIDFile, []byte(deviceID), 0644)
64+
}
65+
66+
func LoadConfig() error {
67+
file, err := os.Open("config.json")
68+
if err != nil {
69+
return err
70+
}
5471
defer file.Close()
5572

5673
var decoder = json.NewDecoder(file)
5774
err = decoder.Decode(&Cfg)
58-
5975
if err != nil {
76+
return err
77+
}
78+
79+
// Device ID priority: config.json > .device_id file > generate new
80+
if Cfg.DeviceID == nil {
81+
// Try to load from state file
82+
if savedID, err := loadDeviceIDFromFile(); err == nil && savedID != "" {
83+
Cfg.DeviceID = &savedID
84+
log.Printf("Loaded device_id from %s: %s", deviceIDFile, savedID)
85+
} else {
86+
// Generate new ID and save it
87+
deviceID := fmt.Sprintf("%d", rand.Int63n(90000000-10000000)+10000000)
88+
Cfg.DeviceID = &deviceID
89+
90+
// Try to save to file, but don't fail if we can't (e.g., read-only filesystem)
91+
if err := saveDeviceIDToFile(deviceID); err != nil {
92+
log.Printf("Warning: Generated device_id %s but could not save to %s: %v", deviceID, deviceIDFile, err)
93+
log.Printf("Device ID will change on restart. Set device_id in config.json to make it permanent.")
94+
} else {
95+
log.Printf("Generated and saved device_id to %s: %s", deviceIDFile, deviceID)
96+
}
97+
}
98+
}
99+
100+
return nil
101+
}
102+
103+
func init() {
104+
// Skip initialization during tests by checking command line args
105+
for _, arg := range os.Args {
106+
if strings.HasPrefix(arg, "-test.") {
107+
return
108+
}
109+
}
110+
111+
if err := LoadConfig(); err != nil {
60112
log.Fatal(err)
61113
}
62114
}

0 commit comments

Comments
 (0)