Files

127 lines
4.1 KiB
Go

// Package service — 服务器身份与移动端能力声明。
package service
import (
"context"
"errors"
"strings"
"sync"
"github.com/google/uuid"
"github.com/truewhile/MeBox/internal/repository"
)
// 服务器身份与移动端契约常量。
const (
// ServerIDSettingKey 是持久化实例身份的设置键。普通重启、升级和同实例
// 数据恢复都沿用同一个值;把备份克隆成独立服务时需要显式重置。
ServerIDSettingKey = "mobile.server_id"
// MobileAPIVersion 是 /api/v1 契约版本。
MobileAPIVersion = "v1"
// MinSupportedAppVersionCode 是首个移动端契约要求的最低 App versionCode。
MinSupportedAppVersionCode = 1
// MobileServerName 是 server-info 暴露的实例名称。
MobileServerName = "MeBox"
)
// ServerCapabilities 是 server-info 的能力开关。
//
// 每项都由「接口已实现且当前可运行」共同决定;仅注册了 handler 但功能
// 不可用时必须返回 false,避免 App 依据虚假能力进入不可用流程。
type ServerCapabilities struct {
DeviceSessions bool `json:"device_sessions"`
PlaybackSessions bool `json:"playback_sessions"`
ReaderProgressV1 bool `json:"reader_progress_v1"`
ReaderContentBatch bool `json:"reader_content_batch"`
LocalTranscode bool `json:"local_transcode"`
}
// ServerInfo 是 GET /api/v1/server-info 的响应。
type ServerInfo struct {
Name string `json:"name"`
ServerID string `json:"server_id"`
ServerVersion string `json:"server_version"`
APIVersion string `json:"api_version"`
MinSupportedAppVersionCode int `json:"min_supported_app_version_code"`
Capabilities ServerCapabilities `json:"capabilities"`
}
// ServerIdentityService 负责生成并持久化实例身份。
type ServerIdentityService struct {
repo *repository.Container
version string
mu sync.Mutex
cached string
}
// NewServerIdentityService 创建实例身份服务。
func NewServerIdentityService(repo *repository.Container, version string) *ServerIdentityService {
return &ServerIdentityService{repo: repo, version: strings.TrimSpace(version)}
}
// ServerID 返回持久化的实例 ID,首次调用时生成并写入设置表。
func (s *ServerIdentityService) ServerID(ctx context.Context) (string, error) {
if s == nil || s.repo == nil || s.repo.Setting == nil {
return "", errors.New("server identity unavailable")
}
s.mu.Lock()
defer s.mu.Unlock()
if s.cached != "" {
return s.cached, nil
}
stored, err := s.repo.Setting.Get(ctx, ServerIDSettingKey)
if err != nil {
return "", err
}
stored = strings.TrimSpace(stored)
if stored != "" {
s.cached = stored
return stored, nil
}
generated := uuid.NewString()
if err := s.repo.Setting.Set(ctx, ServerIDSettingKey, generated); err != nil {
return "", err
}
s.cached = generated
return generated, nil
}
// Info 组装 server-info 响应。capabilities 由调用方按真实可用服务推导。
func (s *ServerIdentityService) Info(ctx context.Context, capabilities ServerCapabilities) (ServerInfo, error) {
serverID, err := s.ServerID(ctx)
if err != nil {
return ServerInfo{}, err
}
version := strings.TrimSpace(s.version)
if version == "" {
version = "dev"
}
return ServerInfo{
Name: MobileServerName,
ServerID: serverID,
ServerVersion: version,
APIVersion: MobileAPIVersion,
MinSupportedAppVersionCode: MinSupportedAppVersionCode,
Capabilities: capabilities,
}, nil
}
// CurrentServerCapabilities 依据服务容器推导移动端能力。
//
// M1 只启用了设备会话;播放会话、阅读 v1 进度与批量正文属于后续阶段,
// 在对应任务真正实现并通过契约测试前保持 false。
func CurrentServerCapabilities(c *Container) ServerCapabilities {
return ServerCapabilities{
DeviceSessions: c != nil && c.AuthSessions != nil,
PlaybackSessions: false,
ReaderProgressV1: false,
ReaderContentBatch: false,
LocalTranscode: false,
}
}