Pārlūkot izejas kodu

Add recorder disk quota enforcement

master
Jan Svabenik pirms 4 dienas
vecāks
revīzija
3ed2b8a3e3
9 mainītis faili ar 98 papildinājumiem un 1 dzēšanām
  1. +1
    -1
      README.md
  2. +1
    -0
      cmd/sdrd/main.go
  3. +1
    -0
      config.yaml
  4. +2
    -0
      internal/config/config.go
  5. +72
    -0
      internal/recorder/quota.go
  6. +1
    -0
      internal/recorder/recorder.go
  7. +4
    -0
      internal/runtime/runtime.go
  8. +15
    -0
      web/app.js
  9. +1
    -0
      web/index.html

+ 1
- 1
README.md Parādīt failu

@@ -66,7 +66,7 @@ Edit `config.yaml`:
- `iq_balance`: enable basic IQ imbalance correction
- `detector.threshold_db`: power threshold in dB
- `detector.min_duration_ms`, `detector.hold_ms`: debounce/merge
- `recorder.*`: enable IQ/audio recording, preroll, output_dir
- `recorder.*`: enable IQ/audio recording, preroll, output_dir, max_disk_mb
- `decoder.*`: external decode commands (use `{iq}` and `{sr}` placeholders)

## APIs


+ 1
- 0
cmd/sdrd/main.go Parādīt failu

@@ -678,6 +678,7 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
RecordAudio: cfg.Recorder.RecordAudio,
AutoDemod: cfg.Recorder.AutoDemod,
AutoDecode: cfg.Recorder.AutoDecode,
MaxDiskMB: cfg.Recorder.MaxDiskMB,
OutputDir: cfg.Recorder.OutputDir,
ClassFilter: cfg.Recorder.ClassFilter,
RingSeconds: cfg.Recorder.RingSeconds,


+ 1
- 0
config.yaml Parādīt failu

@@ -25,6 +25,7 @@ recorder:
record_audio: true
auto_demod: true
auto_decode: false
max_disk_mb: 0
output_dir: "data/recordings"
class_filter: []
ring_seconds: 8


+ 2
- 0
internal/config/config.go Parādīt failu

@@ -29,6 +29,7 @@ type RecorderConfig struct {
RecordAudio bool `yaml:"record_audio" json:"record_audio"`
AutoDemod bool `yaml:"auto_demod" json:"auto_demod"`
AutoDecode bool `yaml:"auto_decode" json:"auto_decode"`
MaxDiskMB int `yaml:"max_disk_mb" json:"max_disk_mb"`
OutputDir string `yaml:"output_dir" json:"output_dir"`
ClassFilter []string `yaml:"class_filter" json:"class_filter"`
RingSeconds int `yaml:"ring_seconds" json:"ring_seconds"`
@@ -89,6 +90,7 @@ func Default() Config {
RecordAudio: false,
AutoDemod: true,
AutoDecode: false,
MaxDiskMB: 0,
OutputDir: "data/recordings",
RingSeconds: 8,
},


+ 72
- 0
internal/recorder/quota.go Parādīt failu

@@ -0,0 +1,72 @@
package recorder

import (
"os"
"path/filepath"
"sort"
)

type recInfo struct {
id string
path string
start int64
size int64
}

func enforceQuota(root string, maxMB int) {
if maxMB <= 0 {
return
}
maxBytes := int64(maxMB) * 1024 * 1024
infos, total := scanRecordings(root)
if total <= maxBytes {
return
}
// oldest first
sort.Slice(infos, func(i, j int) bool { return infos[i].start < infos[j].start })
for _, info := range infos {
if total <= maxBytes {
break
}
_ = os.RemoveAll(info.path)
total -= info.size
}
}

func scanRecordings(root string) ([]recInfo, int64) {
entries, err := os.ReadDir(root)
if err != nil {
return nil, 0
}
infos := make([]recInfo, 0, len(entries))
var total int64
for _, e := range entries {
if !e.IsDir() {
continue
}
id := e.Name()
path := filepath.Join(root, id)
size := dirSize(path)
start := int64(0)
if meta, err := ReadMeta(filepath.Join(path, "meta.json")); err == nil {
start = meta.Start.UnixMilli()
}
infos = append(infos, recInfo{id: id, path: path, start: start, size: size})
total += size
}
return infos, total
}

func dirSize(path string) int64 {
var size int64
_ = filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
size += info.Size()
}
return nil
})
return size
}

+ 1
- 0
internal/recorder/recorder.go Parādīt failu

@@ -21,6 +21,7 @@ type Policy struct {
RecordAudio bool `yaml:"record_audio" json:"record_audio"`
AutoDemod bool `yaml:"auto_demod" json:"auto_demod"`
AutoDecode bool `yaml:"auto_decode" json:"auto_decode"`
MaxDiskMB int `yaml:"max_disk_mb" json:"max_disk_mb"`
OutputDir string `yaml:"output_dir" json:"output_dir"`
ClassFilter []string `yaml:"class_filter" json:"class_filter"`
RingSeconds int `yaml:"ring_seconds" json:"ring_seconds"`


+ 4
- 0
internal/runtime/runtime.go Parādīt failu

@@ -40,6 +40,7 @@ type RecorderUpdate struct {
RecordAudio *bool `json:"record_audio"`
AutoDemod *bool `json:"auto_demod"`
AutoDecode *bool `json:"auto_decode"`
MaxDiskMB *int `json:"max_disk_mb"`
OutputDir *string `json:"output_dir"`
ClassFilter *[]string `json:"class_filter"`
RingSeconds *int `json:"ring_seconds"`
@@ -149,6 +150,9 @@ func (m *Manager) ApplyConfig(update ConfigUpdate) (config.Config, error) {
if update.Recorder.AutoDecode != nil {
next.Recorder.AutoDecode = *update.Recorder.AutoDecode
}
if update.Recorder.MaxDiskMB != nil {
next.Recorder.MaxDiskMB = *update.Recorder.MaxDiskMB
}
if update.Recorder.OutputDir != nil {
next.Recorder.OutputDir = *update.Recorder.OutputDir
}


+ 15
- 0
web/app.js Parādīt failu

@@ -35,6 +35,13 @@ const iqToggle = qs('iqToggle');
const avgSelect = qs('avgSelect');
const maxHoldToggle = qs('maxHoldToggle');
const gpuToggle = qs('gpuToggle');
const recEnableToggle = qs('recEnableToggle');
const recIQToggle = qs('recIQToggle');
const recAudioToggle = qs('recAudioToggle');
const recDemodToggle = qs('recDemodToggle');
const recDecodeToggle = qs('recDecodeToggle');
const recMinSNR = qs('recMinSNR');
const recMaxDisk = qs('recMaxDisk');

const signalList = qs('signalList');
const eventList = qs('eventList');
@@ -281,6 +288,7 @@ function applyConfigToUI(cfg) {
if (recDemodToggle) recDemodToggle.checked = !!cfg.recorder.auto_demod;
if (recDecodeToggle) recDecodeToggle.checked = !!cfg.recorder.auto_decode;
if (recMinSNR) recMinSNR.value = cfg.recorder.min_snr_db ?? 10;
if (recMaxDisk) recMaxDisk.value = cfg.recorder.max_disk_mb ?? 0;
}
spanInput.value = (cfg.sample_rate / zoom / 1e6).toFixed(3);
isSyncingConfig = false;
@@ -1101,6 +1109,13 @@ agcToggle.addEventListener('change', () => queueSettingsUpdate({ agc: agcToggle.
dcToggle.addEventListener('change', () => queueSettingsUpdate({ dc_block: dcToggle.checked }));
iqToggle.addEventListener('change', () => queueSettingsUpdate({ iq_balance: iqToggle.checked }));
gpuToggle.addEventListener('change', () => queueConfigUpdate({ use_gpu_fft: gpuToggle.checked }));
if (recEnableToggle) recEnableToggle.addEventListener('change', () => queueConfigUpdate({ recorder: { enabled: recEnableToggle.checked } }));
if (recIQToggle) recIQToggle.addEventListener('change', () => queueConfigUpdate({ recorder: { record_iq: recIQToggle.checked } }));
if (recAudioToggle) recAudioToggle.addEventListener('change', () => queueConfigUpdate({ recorder: { record_audio: recAudioToggle.checked } }));
if (recDemodToggle) recDemodToggle.addEventListener('change', () => queueConfigUpdate({ recorder: { auto_demod: recDemodToggle.checked } }));
if (recDecodeToggle) recDecodeToggle.addEventListener('change', () => queueConfigUpdate({ recorder: { auto_decode: recDecodeToggle.checked } }));
if (recMinSNR) recMinSNR.addEventListener('change', () => queueConfigUpdate({ recorder: { min_snr_db: parseFloat(recMinSNR.value) } }));
if (recMaxDisk) recMaxDisk.addEventListener('change', () => queueConfigUpdate({ recorder: { max_disk_mb: parseInt(recMaxDisk.value || '0', 10) } }));

avgSelect.addEventListener('change', () => {
avgAlpha = parseFloat(avgSelect.value) || 0;


+ 1
- 0
web/index.html Parādīt failu

@@ -151,6 +151,7 @@
<label class="pill-toggle"><input id="recDecodeToggle" type="checkbox" /><span class="pt"><span class="pk"></span></span><span class="pl">Auto Decode</span></label>
</div>
<label class="field"><span>Min SNR (dB)</span><input id="recMinSNR" type="number" step="1" min="0" /></label>
<label class="field"><span>Max Disk (MB)</span><input id="recMaxDisk" type="number" step="256" min="0" /></label>
</div>

<div class="form-group">


Notiek ielāde…
Atcelt
Saglabāt