The GitHub account that hosted this repository is suspended, so the remote is dead and the vanity import github.com/ZacharyZhang-NY/ely-eye/... no longer resolves. The Go module path is that import, so it moves with the host: git.zacharyzhang.com serves a go-import meta tag for the nested ely-eye-mcp submodule, which go build and go get both need. The installers read release assets from the host too, so their API and download URLs move with it. The release workflow's -ldflags symbol path is the module path, so it moves as well; a build stamped with the new path prints the stamped version.
137 lines
3.8 KiB
Go
137 lines
3.8 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"net/url"
|
|
"path/filepath"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/config"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/ely-eye/ely-eye-mcp/internal/domain"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
type Store struct {
|
|
cfg config.Config
|
|
db *sql.DB
|
|
}
|
|
|
|
func Open(ctx context.Context, cfg config.Config) (*Store, error) {
|
|
dsn := sqliteReadOnlyDSN(cfg.Database)
|
|
db, err := sql.Open("sqlite", dsn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db.SetMaxOpenConns(1)
|
|
if err := db.PingContext(ctx); err != nil {
|
|
db.Close()
|
|
return nil, err
|
|
}
|
|
return &Store{cfg: cfg, db: db}, nil
|
|
}
|
|
|
|
func (s *Store) Close() error {
|
|
return s.db.Close()
|
|
}
|
|
|
|
func (s *Store) Status(ctx context.Context) (domain.Status, error) {
|
|
atomCount, err := s.scalarInt(ctx, "SELECT COUNT(*) FROM atoms")
|
|
if err != nil {
|
|
return domain.Status{}, err
|
|
}
|
|
sourceCount, err := s.scalarInt(ctx, "SELECT COUNT(*) FROM sources")
|
|
if err != nil {
|
|
return domain.Status{}, err
|
|
}
|
|
cartridgeCount, err := s.scalarInt(ctx, "SELECT COUNT(*) FROM cartridges")
|
|
if err != nil {
|
|
return domain.Status{}, err
|
|
}
|
|
activeTokens, err := s.scalarInt64(ctx, "SELECT COALESCE(SUM(token_equivalent), 0) FROM atoms")
|
|
if err != nil {
|
|
return domain.Status{}, err
|
|
}
|
|
cartridgeTokens, err := s.scalarInt64(ctx, "SELECT COALESCE(SUM(token_equivalent), 0) FROM cartridges")
|
|
if err != nil {
|
|
return domain.Status{}, err
|
|
}
|
|
layers, err := s.CacheLayers(ctx)
|
|
if err != nil {
|
|
return domain.Status{}, err
|
|
}
|
|
libraryTokens := activeTokens
|
|
if cartridgeTokens > libraryTokens {
|
|
libraryTokens = cartridgeTokens
|
|
}
|
|
return domain.Status{
|
|
App: "Ely-Eye MCP",
|
|
ProjectRoot: s.cfg.ProjectRoot,
|
|
DataHome: s.cfg.ElyEyeHome,
|
|
Database: s.cfg.Database,
|
|
AtomCount: atomCount,
|
|
SourceCount: sourceCount,
|
|
CartridgeCount: cartridgeCount,
|
|
ActiveTokenEquivalent: activeTokens,
|
|
LibraryTokenEquivalent: libraryTokens,
|
|
CacheLayers: layers,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Store) CacheLayers(ctx context.Context) ([]domain.CacheLayer, error) {
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT
|
|
layer,
|
|
COUNT(DISTINCT cache_key) AS entries,
|
|
COALESCE(SUM(bytes_estimate), 0) AS bytes_estimate,
|
|
SUM(CASE WHEN event = 'hit' THEN 1 ELSE 0 END) AS hit_count,
|
|
SUM(CASE WHEN event = 'miss' THEN 1 ELSE 0 END) AS miss_count,
|
|
COALESCE(MAX(ts), '') AS last_event_at
|
|
FROM cache_events
|
|
GROUP BY layer
|
|
ORDER BY layer`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
layers := []domain.CacheLayer{}
|
|
for rows.Next() {
|
|
var layer domain.CacheLayer
|
|
if err := rows.Scan(&layer.Layer, &layer.Entries, &layer.BytesEstimate, &layer.HitCount, &layer.MissCount, &layer.LastEventAt); err != nil {
|
|
return nil, err
|
|
}
|
|
layers = append(layers, layer)
|
|
}
|
|
return layers, rows.Err()
|
|
}
|
|
|
|
func (s *Store) scalarInt(ctx context.Context, query string) (int, error) {
|
|
var value int
|
|
if err := s.db.QueryRowContext(ctx, query).Scan(&value); err != nil {
|
|
return 0, err
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func (s *Store) scalarInt64(ctx context.Context, query string) (int64, error) {
|
|
var value int64
|
|
if err := s.db.QueryRowContext(ctx, query).Scan(&value); err != nil {
|
|
return 0, err
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func sqliteReadOnlyDSN(path string) string {
|
|
absolute, _ := filepath.Abs(path)
|
|
uri := "file:" + filepath.ToSlash(absolute)
|
|
query := url.Values{}
|
|
query.Set("mode", "ro")
|
|
query.Add("_pragma", "query_only(1)")
|
|
// The production database runs in WAL mode and is written by the Ely-Eye
|
|
// app concurrently. busy_timeout lets a reader wait through a checkpoint or
|
|
// a momentary write lock instead of failing with SQLITE_BUSY. immutable is
|
|
// deliberately not set because the database does change underneath us.
|
|
query.Add("_pragma", "busy_timeout(5000)")
|
|
return fmt.Sprintf("%s?%s", uri, query.Encode())
|
|
}
|