Seen on the first claimed demo install: 'session expired' on every screen, signed in as a user from the previous demo's head office, and 'Watching 3 cameras' for a shop with one - the PC had offered its two leftover cameras up to head office, without their passwords, so the same lens was listed twice and one copy could never be pushed anywhere. Claiming now clears any stored session (a new head office is a new world), a session whose refresh fails is forgotten on disk as well as in memory so the app returns to Login by itself, and the demo setup removes cameras left from an earlier install before it joins the shop, because head office is the source of truth from then on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
89 lines
2.8 KiB
Go
89 lines
2.8 KiB
Go
// Package paths mirrors behavision/paths.py.
|
|
//
|
|
// The two processes must agree on where state lives or they will quietly use
|
|
// different databases: the engine would write footfall into one file while the
|
|
// agent reads another and reports an empty store. The rule is the same on both
|
|
// sides — BEHAVISION_DATA_DIR wins, then %PROGRAMDATA%\Behavision on Windows —
|
|
// and `behavision paths` prints the engine's answer so the two can be compared
|
|
// on a real machine rather than assumed equal.
|
|
package paths
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
const AppName = "Behavision"
|
|
|
|
// StateRoot is the writable root: database, logs, spool, agent config.
|
|
func StateRoot() string {
|
|
if v := os.Getenv("BEHAVISION_DATA_DIR"); v != "" {
|
|
if abs, err := filepath.Abs(v); err == nil {
|
|
return abs
|
|
}
|
|
return v
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
base := os.Getenv("PROGRAMDATA")
|
|
if base == "" {
|
|
base = `C:\ProgramData`
|
|
}
|
|
return filepath.Join(base, AppName)
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "."
|
|
}
|
|
if runtime.GOOS == "darwin" {
|
|
return filepath.Join(home, "Library", "Application Support", AppName)
|
|
}
|
|
if v := os.Getenv("XDG_DATA_HOME"); v != "" {
|
|
return filepath.Join(v, "behavision")
|
|
}
|
|
return filepath.Join(home, ".local", "share", "behavision")
|
|
}
|
|
|
|
// InstallRoot is the directory holding this executable.
|
|
func InstallRoot() string {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "."
|
|
}
|
|
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
|
|
exe = resolved
|
|
}
|
|
return filepath.Dir(exe)
|
|
}
|
|
|
|
func AgentConfig() string { return filepath.Join(StateRoot(), "agent.json") }
|
|
|
|
// BrokerCA is the broker's CA certificate, written at enrolment.
|
|
func BrokerCA() string { return filepath.Join(StateRoot(), "broker-ca.crt") }
|
|
func SpoolDir() string { return filepath.Join(StateRoot(), "spool") }
|
|
func EngineLog() string { return filepath.Join(StateRoot(), "engine.log") }
|
|
|
|
// APICredentials is the file the engine writes when it generates its own
|
|
// Basic credentials. The agent reads it rather than storing a second copy,
|
|
// so a regenerated credential does not silently break the tray.
|
|
func APICredentials() string {
|
|
return filepath.Join(StateRoot(), "data", "api_credentials.txt")
|
|
}
|
|
|
|
// CamerasFile is the engine's own camera store. The agent never edits it -
|
|
// cameras go through the engine's API so passwords are sealed - but setup
|
|
// removes it when a PC joins a shop, because head office is the source of
|
|
// truth from then on.
|
|
func CamerasFile() string { return filepath.Join(StateRoot(), "data", "cameras.json") }
|
|
|
|
// EnsureState creates the writable tree. Called before anything opens a file
|
|
// under it, so a first run on a fresh machine does not fail on a missing dir.
|
|
func EnsureState() error {
|
|
for _, d := range []string{StateRoot(), SpoolDir()} {
|
|
if err := os.MkdirAll(d, 0o700); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|