Skip to content

Conversation

niklasweimann
Copy link
Member

Special Thanks to: https://github.com/HedgehogNSK

The main purpose of this PR is to add the ability to switch between different update streams. It allows users to switch between various streams, such as long polling, webhooks, or test streams, without needing to resubscribe to a new update manager.

Core changes:

IUpdateManager extended with the Set(IObservable) method.
UpdateManager has been split into two classes: UpdateDistributor and LongPollingUpdateTracker.
UpdateDistributor serves as a facade that implements IUpdateManager.
LongPollingUpdateTracker implements IObservable.
UpdateManager is no longer needed but has been retained as is.
The TelegramBot class has been extended with a Builder that allows setting IUpdateManager and IObservable.

HedgehogNSK and others added 4 commits January 27, 2025 19:13
UpdateDistributor implements IUpdateManager
UpdateTracker implements IObservable<Update>
Add TelegramBot.Builder
Extend IUpdateManager with Set(IObservable<Update>) method
Add Rx utility entities: CustomSubjects that works similar to Rx Subject
DisposableAction that  works as Rx Disposable.Create
Desc: It didn't apply tracker to update manager
@HedgehogNSK
Copy link
Contributor

Hi, there. 😃
Should I fix the issues that Sonar is flagging?

- Move UpdateDistributor._update initialization to constructor
- Add 'readonly' keyword to LongpollingUpdateTracker._observers
- Add 'readonly' keyword to UpdateDistributor._updateInfos
- Add 'sealed' keyword to UpdateTypeInfo
- Add 'sealed' keyword to DisposableAction
- Call Dispose method for LongpollingUpdateTracker._cancellationTokenSource
- Add class constraint to TOut parameter in CustomSubject class
HedgehogNSK and others added 6 commits March 10, 2025 23:03
…ing issues.

- Added Observable operators:
  - `DoOnDispose`: Executes an action when the observable is disposed.
  - `DoOnSubscribe`: Executes an action when a new observer subscribes.
  - `Select`: Applies a function to each item in the observable and returns the result of that function.
  - `Switch`: Switches to the latest observable sequence when the source emits a new value.
  - `Where`: Filters the observable sequence based on a predicate.

- Added `ReactiveProperty` for easier state management.
- Removed unnecessary `CustomSubject` and `UpdateSubject`.
- Fixed exception handling in `LongpollingUpdateTracker`.
Replaced `DoOnDispose` with `Finally` in `UpdateDistributor` to fix subscriber tracking.
@niklasweimann
Copy link
Member Author

@HedgehogNSK Two Issues here:

  1. Could you please fix the 9 issues that sonar cloud found?
  2. Cloud you please make sure that your Source Code matches the rest of the project? We use Rider for the development so when you use it too you should be able to see the issues. Otherwise please make sure your editor respects the .editorconfig of the project (I have not tested it with other IDEs but it should result in a similar style).

@HedgehogNSK
Copy link
Contributor

HedgehogNSK commented Mar 12, 2025

Ок. :) I need a bit of time, and I will fix the issues. ;)

@niklasweimann
Copy link
Member Author

Awesome 👍😎

@HedgehogNSK
Copy link
Contributor

I have a question about src/.../Utils/Rx/ReactiveProperty.cs link to file
@niklasweimann, could you please explain what Sonar actually wants me to fix:

  1. 'ReactiveProperty.Dispose()' should also call 'Dispose(true)'.:
    But my code contains the call already:
public void  Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
  1. Modify 'ReactiveProperty.~ReactiveProperty()' so that it calls 'Dispose(false)' and then returns.
    And my code containes this implementation as well:
ReactiveProperty() => Dispose(false);

I'm confused. 😕

@niklasweimann
Copy link
Member Author

Hey @HedgehogNSK,

The following implementation should meet the sonar cloud requirements:

~ReactiveProperty() => Dispose(false);

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool explicitDisposing)
    {
        if (IsDisposed)
        {
            return;
        }

        if (explicitDisposing)
        {
            OnCompleted();
            Interlocked.Exchange(ref _observers, Terminated);
        }

        IsDisposed = true;
    }

When looking at the SonarCloud issue you can click on "Why is this an issue?" and near the end is an example of how to implement disposable properly.

niklasweimann and others added 3 commits March 13, 2025 21:38
Feature/rework updatemanager Fixes for Sonar and Config issues for PR #71
- Fix IDisposable for ReactiveProperty
- Fix IDisposable for SwithObservable
Copy link

@r-ising r-ising requested a review from Copilot September 26, 2025 14:35
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR reworks the update manager system to enable switching between different update streams (long polling, webhooks, test streams) without requiring resubscription. The refactoring splits functionality into specialized components and introduces a builder pattern for flexible TelegramBot configuration.

  • Introduces new IObservable<Update> implementations with custom Rx operators for update stream management
  • Refactors update management into UpdateDistributor (facade) and LongPollingUpdateTracker (implementation) classes
  • Adds a TelegramBot builder pattern supporting custom update managers and trackers

Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Utils/Rx/*.cs Custom reactive extensions framework with observables, operators, and utility classes
TelegramBot.cs Updated constructor to use new UpdateDistributor with LongPollingUpdateTracker
TelegramBot.Builder.cs New builder class for configurable TelegramBot construction
UpdateDistributor.cs New facade implementing IUpdateManager with stream switching capabilities
LongpollingUpdateTracker.cs New IObservable implementation for long polling with setup interface
UpdateManager.cs Added placeholder Set method to maintain interface compatibility
IUpdateManager.cs Extended interface with Set method for tracker configuration
ITrackerSetup.cs New interface for configuring update type filtering

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(SwitchObserver));
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ObjectDisposedException should use the type name instead of nameof(SwitchObserver) since SwitchObserver is a nested class. Consider using GetType().Name or a constant string.

Suggested change
throw new ObjectDisposedException(nameof(SwitchObserver));
throw new ObjectDisposedException(GetType().Name);

Copilot uses AI. Check for mistakes.

{
ThrowIfDisposed();
_error = error;
Current = default;
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting Current to default(T) in OnError will incorrectly mark HasValue as true on line 17. This could lead to incorrect behavior where subscribers think there's a valid value after an error occurred.

Suggested change
Current = default;
_current = default;
HasValue = false;

Copilot uses AI. Check for mistakes.

catch (Exception exception)
{
OnException(exception);
throw;
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After calling OnException which clears all observers, throwing the exception will not be handled by any observer. The throw statement should be removed since OnException already notifies observers of the error.

Suggested change
throw;
// Exception already handled by notifying observers; do not rethrow.

Copilot uses AI. Check for mistakes.

Comment on lines +107 to +111
catch (TaskCanceledException)
{
_offset = null;
_cancellationTokenSource = new CancellationTokenSource();
}
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new CancellationTokenSource without disposing the previous one can lead to resource leaks. The old token source should be disposed before creating a new one.

Copilot uses AI. Check for mistakes.

Comment on lines +77 to +78
_trackedTypes = _updateInfos.Where(x => x.Value.Listeners != 0)
.Select(x => x.Key);
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _trackedTypes enumerable is evaluated at construction time when all Listeners are 0, resulting in an empty collection. This should be evaluated lazily or updated when listeners change to reflect the current state.

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants