11using Microsoft . Extensions . Hosting ;
22using Microsoft . Extensions . Logging ;
3+ using System . Threading . Channels ;
34using Telegram . Bot ;
45using Telegram . Bot . Exceptions ;
56using Telegram . Bot . Types ;
67
78namespace CubicBot . Telegram ;
89
9- public sealed partial class BotService ( ILogger < BotService > logger ) : BackgroundService
10+ public partial class BotService : IHostedService
1011{
11- protected override Task ExecuteAsync ( CancellationToken stoppingToken ) => RunBotAsync ( stoppingToken ) ;
12+ private readonly ILogger < BotService > _logger ;
13+ private readonly HttpClient _httpClient ;
14+ private readonly ChannelReader < Update > _updateReader ;
1215
13- private async Task RunBotAsync ( CancellationToken cancellationToken = default )
16+ public BotService ( ILogger < BotService > logger , HttpClient httpClient )
17+ {
18+ _logger = logger ;
19+ _httpClient = httpClient ;
20+ Channel < Update > updateChannel = Channel . CreateBounded < Update > ( new BoundedChannelOptions ( 64 )
21+ {
22+ SingleReader = true ,
23+ } ) ;
24+ _updateReader = updateChannel . Reader ;
25+ UpdateWriter = updateChannel . Writer ;
26+ }
27+
28+ public ChannelWriter < Update > UpdateWriter { get ; }
29+
30+ protected CancellationTokenSource ? Cts { get ; private set ; }
31+ private Task ? _saveDataTask ;
32+ private Task ? _handleUpdatesTask ;
33+
34+ public virtual Task StartAsync ( CancellationToken cancellationToken ) => StartBotAsync ( cancellationToken ) ;
35+
36+ protected async Task < ( TelegramBotClient , CancellationTokenSource ) > StartBotAsync ( CancellationToken cancellationToken = default )
1437 {
1538 Config config ;
1639
@@ -45,8 +68,7 @@ private async Task RunBotAsync(CancellationToken cancellationToken = default)
4568 {
4669 RetryCount = 7 ,
4770 } ;
48- using HttpClient httpClient = new ( ) ;
49- TelegramBotClient bot = new ( options , httpClient ) ;
71+ TelegramBotClient bot = new ( options , _httpClient ) ;
5072 User me ;
5173
5274 while ( true )
@@ -58,47 +80,43 @@ private async Task RunBotAsync(CancellationToken cancellationToken = default)
5880 }
5981 catch ( RequestException ex )
6082 {
61- logger . LogWarning ( ex , "Failed to get bot info, retrying in 30 seconds" ) ;
62- await Task . Delay ( 30000 , cancellationToken ) ;
83+ _logger . LogWarning ( ex , "Failed to get bot info, retrying in 30 seconds" ) ;
84+ await Task . Delay ( s_delayOnError , cancellationToken ) ;
6385 }
6486 }
6587
6688 if ( string . IsNullOrEmpty ( me . Username ) )
6789 throw new Exception ( "Bot username is null or empty." ) ;
6890
69- var updateHandler = new UpdateHandler ( me . Username , config , data , logger ) ;
91+ var updateHandler = new UpdateHandler ( me . Username , config , data , _logger , bot ) ;
7092
7193 while ( true )
7294 {
7395 try
7496 {
75- await updateHandler . RegisterCommandsAsync ( bot , cancellationToken ) ;
97+ await updateHandler . RegisterCommandsAsync ( cancellationToken ) ;
7698 break ;
7799 }
78100 catch ( RequestException ex )
79101 {
80- logger . LogWarning ( ex , "Failed to register commands, retrying in 30 seconds" ) ;
81- await Task . Delay ( 30000 , cancellationToken ) ;
102+ _logger . LogWarning ( ex , "Failed to register commands, retrying in 30 seconds" ) ;
103+ await Task . Delay ( s_delayOnError , cancellationToken ) ;
82104 }
83105 }
84106
85107 LogStartedBot ( me . Username , me . Id ) ;
86108
87- var saveDataTask = SaveDataHourlyAsync ( data , cancellationToken ) ;
109+ Cts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
110+ _saveDataTask = SaveDataHourlyAsync ( data , Cts . Token ) ;
111+ _handleUpdatesTask = updateHandler . RunAsync ( _updateReader , Cts . Token ) ;
88112
89- try
90- {
91- await updateHandler . RunAsync ( bot , cancellationToken ) ;
92- }
93- finally
94- {
95- await saveDataTask ;
96- }
113+ return ( bot , Cts ) ;
97114 }
98115
99116 [ LoggerMessage ( Level = LogLevel . Information , Message = "Started Telegram bot: @{BotUsername} ({BotId})" ) ]
100117 private partial void LogStartedBot ( string botUsername , long botId ) ;
101118
119+ private static readonly TimeSpan s_delayOnError = TimeSpan . FromSeconds ( 30 ) ;
102120 private static readonly TimeSpan s_saveDataInterval = TimeSpan . FromHours ( 1 ) ;
103121
104122 private async Task SaveDataHourlyAsync ( Data data , CancellationToken cancellationToken = default )
@@ -116,11 +134,36 @@ private async Task SaveDataHourlyAsync(Data data, CancellationToken cancellation
116134 try
117135 {
118136 await data . SaveAsync ( CancellationToken . None ) ;
119- logger . LogDebug ( "Saved data" ) ;
137+ _logger . LogDebug ( "Saved data" ) ;
120138 }
121139 catch ( Exception ex )
122140 {
123- logger . LogError ( ex , "Failed to save data" ) ;
141+ _logger . LogError ( ex , "Failed to save data" ) ;
142+ }
143+ }
144+ }
145+
146+ public virtual async Task StopAsync ( CancellationToken cancellationToken )
147+ {
148+ if ( Cts is null )
149+ {
150+ return ;
151+ }
152+
153+ try
154+ {
155+ Cts . Cancel ( ) ;
156+ }
157+ finally
158+ {
159+ if ( _saveDataTask is not null )
160+ {
161+ await _saveDataTask . WaitAsync ( cancellationToken ) . ConfigureAwait ( ConfigureAwaitOptions . SuppressThrowing ) ;
162+ }
163+
164+ if ( _handleUpdatesTask is not null )
165+ {
166+ await _handleUpdatesTask . WaitAsync ( cancellationToken ) . ConfigureAwait ( ConfigureAwaitOptions . SuppressThrowing ) ;
124167 }
125168 }
126169 }
0 commit comments