Wworking on improving the tool
This commit is contained in:
@@ -26,6 +26,7 @@ import "C"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -66,10 +67,14 @@ type caChannel struct {
|
||||
|
||||
// EPICS is the Channel Access data source.
|
||||
type EPICS struct {
|
||||
caAddrList string
|
||||
archiveURL string
|
||||
caAddrList string
|
||||
archiveURL string
|
||||
cfURL string
|
||||
autoSyncFilter string
|
||||
autoSyncFromArchiver bool
|
||||
|
||||
// caCtx is the CA context created in Connect(). Stored as unsafe.Pointer
|
||||
// caCtx is the CA context created in Connect().
|
||||
Stored as unsafe.Pointer
|
||||
// because the C type (ca_client_context *) is opaque. Every goroutine
|
||||
// that calls CA functions must call caAttachContext(caCtx) first, because
|
||||
// Go goroutines can run on any OS thread and CA contexts are thread-local.
|
||||
@@ -87,12 +92,15 @@ type EPICS struct {
|
||||
// Appliance instance for history queries (may be empty).
|
||||
// pvNames is accepted for API compatibility with the pure-Go build but ignored
|
||||
// in the CGo build (the CGo implementation populates ListSignals via callbacks).
|
||||
func New(caAddrList, archiveURL string, pvNames []string) datasource.DataSource {
|
||||
func New(caAddrList, archiveURL, cfURL, autoSyncFilter string, autoSyncFromArchiver bool, pvNames []string) datasource.DataSource {
|
||||
return &EPICS{
|
||||
caAddrList: caAddrList,
|
||||
archiveURL: archiveURL,
|
||||
channels: make(map[string]*caChannel),
|
||||
metadata: make(map[string]datasource.Metadata),
|
||||
caAddrList: caAddrList,
|
||||
archiveURL: archiveURL,
|
||||
cfURL: cfURL,
|
||||
autoSyncFilter: autoSyncFilter,
|
||||
autoSyncFromArchiver: autoSyncFromArchiver,
|
||||
channels: make(map[string]*caChannel),
|
||||
metadata: make(map[string]datasource.Metadata),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,9 +130,62 @@ func (e *EPICS) Connect(_ context.Context) error {
|
||||
}
|
||||
// Save the context so goroutines on other OS threads can attach to it.
|
||||
e.caCtx = C.caCurrentContext()
|
||||
|
||||
// Perform background sync from Channel Finder if configured.
|
||||
if e.cfURL != "" && e.autoSyncFilter != "" {
|
||||
go e.syncFromChannelFinder(context.Background())
|
||||
}
|
||||
|
||||
// Perform background sync from Archive Appliance if configured.
|
||||
if e.archiveURL != "" && e.autoSyncFromArchiver {
|
||||
go e.syncFromArchiver(context.Background())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EPICS) syncFromChannelFinder(ctx context.Context) {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
slog.Info("epics: syncing signals from Channel Finder", "url", e.cfURL, "filter", e.autoSyncFilter)
|
||||
channels, err := queryChannelFinder(e.cfURL, e.autoSyncFilter)
|
||||
if err != nil {
|
||||
slog.Error("epics: Channel Finder sync failed", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ch := range channels {
|
||||
// Convert CFS properties and tags
|
||||
props := make(map[string]string)
|
||||
for _, p := range ch.Properties {
|
||||
props[p.Name] = p.Value
|
||||
}
|
||||
tags := make([]string, len(ch.Tags))
|
||||
for i, t := range ch.Tags {
|
||||
tags[i] = t.Name
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
if _, exists := e.metadata[ch.Name]; !exists {
|
||||
e.metadata[ch.Name] = datasource.Metadata{
|
||||
Name: ch.Name,
|
||||
Properties: props,
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
// Background pre-fetch of actual record info
|
||||
go e.prefetchPV(ctx, ch.Name)
|
||||
}
|
||||
slog.Info("epics: synced signals from Channel Finder", "count", len(channels))
|
||||
}
|
||||
|
||||
func (e *EPICS) prefetchPV(ctx context.Context, pv string) {
|
||||
mctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||||
defer cancel()
|
||||
_, _ = e.GetMetadata(mctx, pv)
|
||||
}
|
||||
|
||||
// attachCAContext attaches the saved CA context to the calling OS thread.
|
||||
// Must be called at the top of every goroutine that uses CA functions,
|
||||
// because Go goroutines can be scheduled onto any OS thread.
|
||||
@@ -422,13 +483,12 @@ func (e *EPICS) fetchMetadata(chid C.chid, name string) datasource.Metadata {
|
||||
}
|
||||
|
||||
// ListSignals returns metadata for all currently tracked channels.
|
||||
// Channels with cached metadata return full info; others return a stub entry
|
||||
// with the PV name so the signal tree can display them immediately after
|
||||
// a manual add, before GetMetadata has been called.
|
||||
func (e *EPICS) ListSignals(_ context.Context) ([]datasource.Metadata, error) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
slog.Debug("epics: listing signals", "count", len(e.metadata))
|
||||
|
||||
// Start with all fully-fetched metadata entries.
|
||||
out := make([]datasource.Metadata, 0, len(e.channels))
|
||||
seen := make(map[string]bool, len(e.metadata))
|
||||
@@ -583,3 +643,27 @@ func nativeTimeType(chid C.chid) C.chtype {
|
||||
return C.DBR_TIME_DOUBLE
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EPICS) syncFromArchiver(ctx context.Context) {
|
||||
time.Sleep(1 * time.Second) // wait for network
|
||||
slog.Info("epics: syncing signals from Archiver", "url", e.archiveURL)
|
||||
pvs, err := searchArchivePVs(ctx, e.archiveURL, "*")
|
||||
if err != nil {
|
||||
slog.Error("epics: Archiver sync failed", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, name := range pvs {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
e.mu.Lock()
|
||||
if _, exists := e.metadata[name]; !exists {
|
||||
e.metadata[name] = datasource.Metadata{Name: name}
|
||||
}
|
||||
e.mu.Unlock()
|
||||
// Background pre-fetch of record info
|
||||
go e.prefetchPV(ctx, name)
|
||||
}
|
||||
slog.Info("epics: synced signals from Archiver", "count", len(pvs))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user