Explorar o código

implement feedbag start/end transaction

Mike hai 5 meses
pai
achega
7ae7f0c533

+ 17 - 3
foodgroup/feedbag.go

@@ -374,9 +374,23 @@ func (s FeedbagService) DeleteItem(ctx context.Context, instance *state.SessionI
 	return nil, nil
 }
 
-// StartCluster exists to capture the SNAC input in unit tests to verify it's
-// correctly unmarshalled.
-func (s FeedbagService) StartCluster(context.Context, wire.SNACFrame, wire.SNAC_0x13_0x11_FeedbagStartCluster) {
+// StartCluster signals the beginning of a batch of feedbag operations that clients should
+// process together to prevent UI flicker during rapid updates. It transmits the start message
+// to other session instances.
+func (s FeedbagService) StartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster) {
+	s.messageRelayer.RelayToOtherInstances(ctx, instance, wire.SNACMessage{
+		Frame: inFrame,
+		Body:  inBody,
+	})
+}
+
+// EndCluster signals the completion of a batched feedbag operation group. It transmits the end
+// message to other session instances.
+func (s FeedbagService) EndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) {
+	s.messageRelayer.RelayToOtherInstances(ctx, instance, wire.SNACMessage{
+		Frame: inFrame,
+		Body:  wire.SNAC_0x13_0x12_FeedbagEndCluster{},
+	})
 }
 
 // Use sends a user the contents of their buddy list. It's invoked at sign-on

+ 45 - 0
foodgroup/feedbag_test.go

@@ -2158,3 +2158,48 @@ func TestFeedbagBuddyPref(t *testing.T) {
 		})
 	}
 }
+
+func TestFeedbagService_StartCluster(t *testing.T) {
+	instance := newTestInstance("me")
+	inFrame := wire.SNACFrame{
+		FoodGroup: wire.Feedbag,
+		SubGroup:  wire.FeedbagStartCluster,
+		RequestID: 1234,
+	}
+	inBody := wire.SNAC_0x13_0x11_FeedbagStartCluster{
+		TLVRestBlock: wire.TLVRestBlock{
+			TLVList: wire.TLVList{
+				wire.NewTLVBE(0x01, uint16(100)),
+			},
+		},
+	}
+
+	messageRelayer := newMockMessageRelayer(t)
+	messageRelayer.EXPECT().
+		RelayToOtherInstances(matchContext(), instance, wire.SNACMessage{
+			Frame: inFrame,
+			Body:  inBody,
+		})
+
+	svc := NewFeedbagService(slog.Default(), messageRelayer, nil, nil, nil, nil)
+	svc.StartCluster(context.Background(), instance, inFrame, inBody)
+}
+
+func TestFeedbagService_EndCluster(t *testing.T) {
+	instance := newTestInstance("me")
+	inFrame := wire.SNACFrame{
+		FoodGroup: wire.Feedbag,
+		SubGroup:  wire.FeedbagEndCluster,
+		RequestID: 1234,
+	}
+
+	messageRelayer := newMockMessageRelayer(t)
+	messageRelayer.EXPECT().
+		RelayToOtherInstances(matchContext(), instance, wire.SNACMessage{
+			Frame: inFrame,
+			Body:  wire.SNAC_0x13_0x12_FeedbagEndCluster{},
+		})
+
+	svc := NewFeedbagService(slog.Default(), messageRelayer, nil, nil, nil, nil)
+	svc.EndCluster(context.Background(), instance, inFrame)
+}

+ 4 - 3
server/oscar/handler.go

@@ -336,17 +336,18 @@ func (rt Handler) FeedbagDeleteItem(ctx context.Context, instance *state.Session
 	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
 }
 
-func (rt Handler) FeedbagStartCluster(ctx context.Context, _ *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, _ ResponseWriter) error {
+func (rt Handler) FeedbagStartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, _ ResponseWriter) error {
 	inBody := wire.SNAC_0x13_0x11_FeedbagStartCluster{}
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	rt.FeedbagService.StartCluster(ctx, inFrame, inBody)
+	rt.FeedbagService.StartCluster(ctx, instance, inFrame, inBody)
 	rt.LogRequest(ctx, inFrame, inBody)
 	return nil
 }
 
-func (rt Handler) FeedbagEndCluster(ctx context.Context, _ *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, _ ResponseWriter) error {
+func (rt Handler) FeedbagEndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
+	rt.FeedbagService.EndCluster(ctx, instance, inFrame)
 	rt.LogRequest(ctx, inFrame, nil)
 	return nil
 }

+ 4 - 1
server/oscar/handler_test.go

@@ -1616,6 +1616,9 @@ func TestHandler_FeedbagEndCluster(t *testing.T) {
 			}
 
 			svc := newMockFeedbagService(t)
+			svc.EXPECT().
+				EndCluster(mock.Anything, mock.Anything, input.Frame)
+
 			h := Handler{
 				FeedbagService: svc,
 				RouteLogger: middleware.RouteLogger{
@@ -2075,7 +2078,7 @@ func TestHandler_FeedbagStartCluster(t *testing.T) {
 
 			svc := newMockFeedbagService(t)
 			svc.EXPECT().
-				StartCluster(mock.Anything, input.Frame, input.Body)
+				StartCluster(mock.Anything, mock.Anything, input.Frame, input.Body)
 
 			h := Handler{
 				FeedbagService: svc,

+ 68 - 10
server/oscar/mock_feedbag_service_test.go

@@ -119,6 +119,58 @@ func (_c *mockFeedbagService_DeleteItem_Call) RunAndReturn(run func(ctx context.
 	return _c
 }
 
+// EndCluster provides a mock function for the type mockFeedbagService
+func (_mock *mockFeedbagService) EndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) {
+	_mock.Called(ctx, instance, inFrame)
+	return
+}
+
+// mockFeedbagService_EndCluster_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EndCluster'
+type mockFeedbagService_EndCluster_Call struct {
+	*mock.Call
+}
+
+// EndCluster is a helper method to define mock.On call
+//   - ctx context.Context
+//   - instance *state.SessionInstance
+//   - inFrame wire.SNACFrame
+func (_e *mockFeedbagService_Expecter) EndCluster(ctx interface{}, instance interface{}, inFrame interface{}) *mockFeedbagService_EndCluster_Call {
+	return &mockFeedbagService_EndCluster_Call{Call: _e.mock.On("EndCluster", ctx, instance, inFrame)}
+}
+
+func (_c *mockFeedbagService_EndCluster_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame)) *mockFeedbagService_EndCluster_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		var arg0 context.Context
+		if args[0] != nil {
+			arg0 = args[0].(context.Context)
+		}
+		var arg1 *state.SessionInstance
+		if args[1] != nil {
+			arg1 = args[1].(*state.SessionInstance)
+		}
+		var arg2 wire.SNACFrame
+		if args[2] != nil {
+			arg2 = args[2].(wire.SNACFrame)
+		}
+		run(
+			arg0,
+			arg1,
+			arg2,
+		)
+	})
+	return _c
+}
+
+func (_c *mockFeedbagService_EndCluster_Call) Return() *mockFeedbagService_EndCluster_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *mockFeedbagService_EndCluster_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame)) *mockFeedbagService_EndCluster_Call {
+	_c.Run(run)
+	return _c
+}
+
 // Query provides a mock function for the type mockFeedbagService
 func (_mock *mockFeedbagService) Query(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) (wire.SNACMessage, error) {
 	ret := _mock.Called(ctx, instance, inFrame)
@@ -396,8 +448,8 @@ func (_c *mockFeedbagService_RightsQuery_Call) RunAndReturn(run func(ctx context
 }
 
 // StartCluster provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) StartCluster(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster) {
-	_mock.Called(ctx, inFrame, inBody)
+func (_mock *mockFeedbagService) StartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster) {
+	_mock.Called(ctx, instance, inFrame, inBody)
 	return
 }
 
@@ -408,30 +460,36 @@ type mockFeedbagService_StartCluster_Call struct {
 
 // StartCluster is a helper method to define mock.On call
 //   - ctx context.Context
+//   - instance *state.SessionInstance
 //   - inFrame wire.SNACFrame
 //   - inBody wire.SNAC_0x13_0x11_FeedbagStartCluster
-func (_e *mockFeedbagService_Expecter) StartCluster(ctx interface{}, inFrame interface{}, inBody interface{}) *mockFeedbagService_StartCluster_Call {
-	return &mockFeedbagService_StartCluster_Call{Call: _e.mock.On("StartCluster", ctx, inFrame, inBody)}
+func (_e *mockFeedbagService_Expecter) StartCluster(ctx interface{}, instance interface{}, inFrame interface{}, inBody interface{}) *mockFeedbagService_StartCluster_Call {
+	return &mockFeedbagService_StartCluster_Call{Call: _e.mock.On("StartCluster", ctx, instance, inFrame, inBody)}
 }
 
-func (_c *mockFeedbagService_StartCluster_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)) *mockFeedbagService_StartCluster_Call {
+func (_c *mockFeedbagService_StartCluster_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)) *mockFeedbagService_StartCluster_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		var arg0 context.Context
 		if args[0] != nil {
 			arg0 = args[0].(context.Context)
 		}
-		var arg1 wire.SNACFrame
+		var arg1 *state.SessionInstance
 		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
+			arg1 = args[1].(*state.SessionInstance)
 		}
-		var arg2 wire.SNAC_0x13_0x11_FeedbagStartCluster
+		var arg2 wire.SNACFrame
 		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x13_0x11_FeedbagStartCluster)
+			arg2 = args[2].(wire.SNACFrame)
+		}
+		var arg3 wire.SNAC_0x13_0x11_FeedbagStartCluster
+		if args[3] != nil {
+			arg3 = args[3].(wire.SNAC_0x13_0x11_FeedbagStartCluster)
 		}
 		run(
 			arg0,
 			arg1,
 			arg2,
+			arg3,
 		)
 	})
 	return _c
@@ -442,7 +500,7 @@ func (_c *mockFeedbagService_StartCluster_Call) Return() *mockFeedbagService_Sta
 	return _c
 }
 
-func (_c *mockFeedbagService_StartCluster_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)) *mockFeedbagService_StartCluster_Call {
+func (_c *mockFeedbagService_StartCluster_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)) *mockFeedbagService_StartCluster_Call {
 	_c.Run(run)
 	return _c
 }

+ 2 - 1
server/oscar/types.go

@@ -95,7 +95,8 @@ type FeedbagService interface {
 	QueryIfModified(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified) (wire.SNACMessage, error)
 	RespondAuthorizeToHost(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost) error
 	RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
-	StartCluster(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)
+	StartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)
+	EndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame)
 	UpsertItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, items []wire.FeedbagItem) (*wire.SNACMessage, error)
 	Use(ctx context.Context, instance *state.SessionInstance) error
 }

+ 3 - 0
wire/snacs.go

@@ -1702,6 +1702,9 @@ type SNAC_0x13_0x11_FeedbagStartCluster struct {
 	TLVRestBlock
 }
 
+type SNAC_0x13_0x12_FeedbagEndCluster struct {
+}
+
 type SNAC_0x13_0x18_FeedbagRequestAuthorizationToHost struct {
 	ScreenName string `oscar:"len_prefix=uint8"`
 	Reason     string `oscar:"len_prefix=uint16"`