mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 04:26:36 +08:00
fix: refresh emby realtime user sessions
This commit is contained in:
@@ -53,6 +53,7 @@ type realtimeSessionInput struct {
|
||||
RuntimeTicks int64
|
||||
IsPlaying bool
|
||||
IsPaused bool
|
||||
PlaybackUpdate bool
|
||||
}
|
||||
|
||||
type userRealtimeActivity struct {
|
||||
@@ -81,6 +82,10 @@ func NewSessionTrackerService(log *zap.Logger) *SessionTrackerService {
|
||||
}
|
||||
|
||||
func (s *SessionTrackerService) RecordLogin(ctx context.Context, userID, userName, deviceID, deviceName, client, remoteEndPoint string) {
|
||||
s.RecordActivity(ctx, userID, userName, deviceID, deviceName, client, remoteEndPoint)
|
||||
}
|
||||
|
||||
func (s *SessionTrackerService) RecordActivity(ctx context.Context, userID, userName, deviceID, deviceName, client, remoteEndPoint string) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
@@ -109,6 +114,7 @@ func (s *SessionTrackerService) RecordPlayback(ctx context.Context, userID, user
|
||||
PositionTicks: positionTicks,
|
||||
RuntimeTicks: runtimeTicks,
|
||||
IsPlaying: !stopped,
|
||||
PlaybackUpdate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -242,7 +248,7 @@ func (s *SessionTrackerService) upsert(ctx context.Context, in realtimeSessionIn
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.pruneLocked(now)
|
||||
existing := s.sessions[key]
|
||||
existing, existed := s.sessions[key]
|
||||
if strings.TrimSpace(in.UserName) == "" {
|
||||
in.UserName = existing.UserName
|
||||
}
|
||||
@@ -256,7 +262,23 @@ func (s *SessionTrackerService) upsert(ctx context.Context, in realtimeSessionIn
|
||||
in.RemoteEndPoint = existing.RemoteEndPoint
|
||||
}
|
||||
lastPlaybackAt := existing.LastPlaybackAt
|
||||
if in.ItemID != "" || in.IsPlaying {
|
||||
itemID := existing.ItemID
|
||||
positionTicks := existing.PositionTicks
|
||||
runtimeTicks := existing.RuntimeTicks
|
||||
isPlaying := existing.IsPlaying
|
||||
isPaused := existing.IsPaused
|
||||
if in.PlaybackUpdate {
|
||||
itemID = firstNonEmptyString(in.ItemID, existing.ItemID)
|
||||
if in.ItemID != "" || in.PositionTicks != 0 {
|
||||
positionTicks = in.PositionTicks
|
||||
}
|
||||
if in.ItemID != "" || in.RuntimeTicks != 0 {
|
||||
runtimeTicks = in.RuntimeTicks
|
||||
}
|
||||
isPlaying = in.IsPlaying
|
||||
isPaused = in.IsPaused
|
||||
}
|
||||
if in.PlaybackUpdate && (in.ItemID != "" || in.IsPlaying) {
|
||||
t := now
|
||||
lastPlaybackAt = &t
|
||||
}
|
||||
@@ -269,13 +291,21 @@ func (s *SessionTrackerService) upsert(ctx context.Context, in realtimeSessionIn
|
||||
Client: in.Client,
|
||||
RemoteEndPoint: in.RemoteEndPoint,
|
||||
LastActivityAt: now,
|
||||
ItemID: firstNonEmptyString(in.ItemID, existing.ItemID),
|
||||
PositionTicks: in.PositionTicks,
|
||||
RuntimeTicks: in.RuntimeTicks,
|
||||
IsPlaying: in.IsPlaying,
|
||||
IsPaused: in.IsPaused,
|
||||
ItemID: itemID,
|
||||
PositionTicks: positionTicks,
|
||||
RuntimeTicks: runtimeTicks,
|
||||
IsPlaying: isPlaying,
|
||||
IsPaused: isPaused,
|
||||
LastPlaybackAt: lastPlaybackAt,
|
||||
}
|
||||
if !existed && s.log != nil {
|
||||
s.log.Debug("realtime session started",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("device_id", in.DeviceID),
|
||||
zap.String("client", in.Client),
|
||||
zap.String("remote", in.RemoteEndPoint),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SessionTrackerService) pruneLocked(now time.Time) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -56,6 +57,56 @@ func TestDeviceListMergesRealtimeSessions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityRefreshKeepsPlaybackState(t *testing.T) {
|
||||
tracker := NewSessionTrackerService(zap.NewNop())
|
||||
now := time.Date(2026, 6, 21, 11, 30, 0, 0, time.UTC)
|
||||
tracker.now = func() time.Time { return now }
|
||||
tracker.RecordPlayback(t.Context(), "u1", "viewer", "dev-1", "Apple TV", "Yamby", "10.0.0.8", "media-1", 123, 456, false)
|
||||
now = now.Add(time.Minute)
|
||||
tracker.RecordActivity(t.Context(), "u1", "viewer", "dev-1", "Apple TV", "Yamby", "10.0.0.8")
|
||||
|
||||
sessions := tracker.List(t.Context())
|
||||
if len(sessions) != 1 {
|
||||
t.Fatalf("sessions = %#v, want one", sessions)
|
||||
}
|
||||
if !sessions[0].IsPlaying || sessions[0].ItemID != "media-1" || sessions[0].PositionTicks != 123 || sessions[0].RuntimeTicks != 456 {
|
||||
t.Fatalf("activity refresh should keep playback state, got %#v", sessions[0])
|
||||
}
|
||||
if !sessions[0].LastActivityAt.Equal(now) {
|
||||
t.Fatalf("last activity = %v, want %v", sessions[0].LastActivityAt, now)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBotDevicesIncludesRealtimeSessionOnlyDevices(t *testing.T) {
|
||||
repos, bot := newBotTestService(t)
|
||||
user := model.User{Base: model.Base{ID: "u1"}, Username: "viewer", PasswordHash: "x", Role: "user", IsActive: true}
|
||||
if err := repos.User.Create(t.Context(), &user); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := repos.DB.Create(&model.TelegramBinding{TelegramUserID: 9103, ChatID: 9103, UserID: user.ID}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tracker := NewSessionTrackerService(zap.NewNop())
|
||||
now := time.Date(2026, 6, 21, 11, 0, 0, 0, time.UTC)
|
||||
tracker.now = func() time.Time { return now }
|
||||
tracker.RecordActivity(t.Context(), user.ID, user.Username, "dev-1", "Apple TV", "Yamby", "10.0.0.8")
|
||||
device := NewDeviceService(zap.NewNop(), repos)
|
||||
device.SetSessionTracker(tracker)
|
||||
bot.SetDeviceService(device)
|
||||
|
||||
reply := bot.replyDevices(t.Context(), &TelegramMessage{
|
||||
From: TelegramUser{ID: 9103, Username: "viewer"},
|
||||
Chat: TelegramChat{ID: 9103, Type: "private"},
|
||||
})
|
||||
|
||||
if !strings.Contains(reply.Text, "Apple TV / Yamby") || !strings.Contains(reply.Text, "在线") {
|
||||
t.Fatalf("reply should include realtime online device, got %q", reply.Text)
|
||||
}
|
||||
if !strings.Contains(reply.Text, "06-21 11:00") {
|
||||
t.Fatalf("reply should use realtime last seen time, got %q", reply.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealtimeRecentLoginProtectsCleanupCandidate(t *testing.T) {
|
||||
repos := newSessionTrackerTestRepos(t)
|
||||
now := time.Date(2026, 6, 21, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
Reference in New Issue
Block a user