mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
Introduce a pluggable metadata persistence layer: a MetaStore of typed repositories (AccountRepo, ProjectRepo, OrgRepo, ShareRepo, DeviceRepo). The five registries keep their in-memory maps + logic and now persist each change as one record through a repo, instead of rewriting a whole JSON file inline. The `file` backend (db_file.go) reproduces the exact on-disk JSON formats, so a running hub loads unchanged. Open*(path) constructors stay as thin wrappers over the file backend. Zero behavior change: full webapp suite green; the example hub boots and existing auth.json/orgs.json/projects.json load and sign-in works. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
package webapp
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// DeviceInfo is what the server knows about one syncing device: self-reported
|
|
// name/OS (headers sent by the client), plus what the server itself observed
|
|
// (public IP of the last push, last activity, the signed-in account). History
|
|
// joins ops against this registry, so IPs are real — as the server saw them —
|
|
// and ops stay small.
|
|
type DeviceInfo struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name,omitempty"`
|
|
OS string `json:"os,omitempty"`
|
|
User string `json:"user,omitempty"` // account email last seen using this device
|
|
IP string `json:"ip,omitempty"` // as observed by the server
|
|
LastSeen time.Time `json:"last_seen"`
|
|
}
|
|
|
|
// DeviceRegistry is the in-memory device table over a MetaStore DeviceRepo.
|
|
type DeviceRegistry struct {
|
|
repo DeviceRepo
|
|
|
|
mu sync.Mutex
|
|
byID map[string]DeviceInfo
|
|
lastSav map[string]time.Time // throttle writes per device
|
|
}
|
|
|
|
// NewDeviceRegistry builds the registry over a repo, loading its contents.
|
|
func NewDeviceRegistry(repo DeviceRepo) (*DeviceRegistry, error) {
|
|
r := &DeviceRegistry{repo: repo, byID: make(map[string]DeviceInfo), lastSav: make(map[string]time.Time)}
|
|
list, err := repo.Load()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, d := range list {
|
|
r.byID[d.ID] = d
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// OpenDeviceRegistry loads the file-backed registry at path.
|
|
func OpenDeviceRegistry(path string) (*DeviceRegistry, error) {
|
|
return NewDeviceRegistry(newFileDeviceRepo(path))
|
|
}
|
|
|
|
// Observe merges what a request revealed about a device. Disk writes are
|
|
// throttled: identity changes persist immediately, bare last-seen bumps at
|
|
// most once a minute.
|
|
func (r *DeviceRegistry) Observe(d DeviceInfo) {
|
|
if r == nil || d.ID == "" {
|
|
return
|
|
}
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
cur := r.byID[d.ID]
|
|
changed := cur.ID == "" || cur.Name != d.Name || cur.OS != d.OS || cur.User != d.User || cur.IP != d.IP
|
|
cur.ID = d.ID
|
|
if d.Name != "" {
|
|
cur.Name = d.Name
|
|
}
|
|
if d.OS != "" {
|
|
cur.OS = d.OS
|
|
}
|
|
if d.User != "" {
|
|
cur.User = d.User
|
|
}
|
|
if d.IP != "" {
|
|
cur.IP = d.IP
|
|
}
|
|
cur.LastSeen = time.Now().UTC()
|
|
r.byID[d.ID] = cur
|
|
if changed || time.Since(r.lastSav[d.ID]) > time.Minute {
|
|
if r.repo.Put(cur) == nil {
|
|
r.lastSav[d.ID] = time.Now()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *DeviceRegistry) Get(id string) (DeviceInfo, bool) {
|
|
if r == nil {
|
|
return DeviceInfo{}, false
|
|
}
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
d, ok := r.byID[id]
|
|
return d, ok
|
|
}
|
|
|
|
// requestIP extracts the client address the server actually saw.
|
|
func requestIP(r *http.Request) string {
|
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
|
return strings.TrimSpace(strings.Split(xff, ",")[0])
|
|
}
|
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
return r.RemoteAddr
|
|
}
|
|
return host
|
|
}
|
|
|
|
// observeDevice records the device behind a store-API request.
|
|
func (s *Server) observeDevice(r *http.Request) {
|
|
if s.Devices == nil {
|
|
return
|
|
}
|
|
id := r.Header.Get("X-Bdrive-Device")
|
|
if id == "" {
|
|
return
|
|
}
|
|
s.Devices.Observe(DeviceInfo{
|
|
ID: id,
|
|
Name: r.Header.Get("X-Bdrive-Device-Name"),
|
|
OS: r.Header.Get("X-Bdrive-Os"),
|
|
User: s.requestUser(r).Email,
|
|
IP: requestIP(r),
|
|
})
|
|
}
|