Skip to content

Commit aeea443

Browse files
committed
chain timeout added
1 parent bc78dd1 commit aeea443

File tree

9 files changed

+109
-23
lines changed

9 files changed

+109
-23
lines changed

Deployf.Botf/Attributes/Handle.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ public enum Handle
2121
/// Execute action before message go to routing and whole the botf
2222
/// </summary>
2323
BeforeAll,
24-
ClearState
24+
ClearState,
25+
ChainTimeout
2526
}

Deployf.Botf/BotController.cs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -490,19 +490,43 @@ public string FPath(string controller, string action, params object[] args)
490490

491491
#region chaining
492492

493-
public async Task<IUpdateContext> AwaitNextUpdate()
493+
public async Task<IUpdateContext> AwaitNextUpdate(Action onCanceled = null)
494494
{
495+
var options = ((BotfBot)Context.Bot).Options;
495496
var store = Context.Services.GetRequiredService<ChainStorage>();
496497
var tcs = new TaskCompletionSource<IUpdateContext>();
497498
store.Set(ChatId, new (tcs));
498-
return await tcs.Task;
499+
if(options.ChainTimeout.HasValue)
500+
{
501+
_ = Task.Run(async () =>
502+
{
503+
await Task.Delay(options.ChainTimeout.Value);
504+
if(tcs != null)
505+
{
506+
store.Clear(ChatId);
507+
tcs.SetCanceled();
508+
}
509+
});
510+
}
511+
512+
try
513+
{
514+
var context = await tcs.Task;
515+
tcs = null;
516+
return context;
517+
}
518+
catch(TaskCanceledException)
519+
{
520+
onCanceled?.Invoke();
521+
throw new ChainTimeoutException(onCanceled != null);
522+
}
499523
}
500524

501-
public async Task<string> AwaitText()
525+
public async Task<string> AwaitText(Action onCanceled = null)
502526
{
503527
while (true)
504528
{
505-
var update = await AwaitNextUpdate();
529+
var update = await AwaitNextUpdate(onCanceled);
506530
if (update.Update.Type != UpdateType.Message)
507531
{
508532
continue;
@@ -512,11 +536,11 @@ public async Task<string> AwaitText()
512536
}
513537
}
514538

515-
public async Task<string> AwaitQuery()
539+
public async Task<string> AwaitQuery(Action onCanceled = null)
516540
{
517541
while (true)
518542
{
519-
var update = await AwaitNextUpdate();
543+
var update = await AwaitNextUpdate(onCanceled);
520544
if (update.Update.Type != UpdateType.CallbackQuery)
521545
{
522546
continue;

Deployf.Botf/Middlewares/BotControllersChainMiddleware.cs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,51 @@ public class BotControllersChainMiddleware : IUpdateHandler
66
{
77
readonly ILogger<BotControllersChainMiddleware> _log;
88
readonly ChainStorage _chainStorage;
9+
readonly BotControllersInvoker _invoker;
10+
readonly BotControllerHandlers _handlers;
911

10-
public BotControllersChainMiddleware(ILogger<BotControllersChainMiddleware> log, ChainStorage chainStorage)
12+
public BotControllersChainMiddleware(ILogger<BotControllersChainMiddleware> log, ChainStorage chainStorage, BotControllersInvoker invoker, BotControllerHandlers handlers)
1113
{
1214
_log = log;
1315
_chainStorage = chainStorage;
16+
_invoker = invoker;
17+
_handlers = handlers;
1418
}
1519

1620
public async Task HandleAsync(IUpdateContext context, UpdateDelegate next, CancellationToken cancellationToken)
1721
{
18-
var id = context.GetSafeChatId();
19-
if (id != null)
22+
try
2023
{
21-
var chain = _chainStorage.Get(id.Value);
22-
if(chain != null)
24+
var id = context.GetSafeChatId();
25+
if (id != null)
2326
{
24-
_log.LogTrace("Found chain for user {userId}, triggered continue execution of chain", id.Value);
25-
_chainStorage.Clear(id.Value);
26-
if (chain.Synchronizator != null)
27+
var chain = _chainStorage.Get(id.Value);
28+
if (chain != null)
2729
{
28-
chain.Synchronizator.SetResult(context);
30+
_log.LogTrace("Found chain for user {userId}, triggered continue execution of chain", id.Value);
31+
_chainStorage.Clear(id.Value);
32+
if (chain.Synchronizator != null)
33+
{
34+
chain.Synchronizator.SetResult(context);
35+
}
36+
}
37+
else
38+
{
39+
await next(context, cancellationToken);
2940
}
3041
}
3142
else
3243
{
3344
await next(context, cancellationToken);
3445
}
3546
}
36-
else
47+
catch(ChainTimeoutException e)
3748
{
38-
await next(context, cancellationToken);
49+
_log.LogDebug("Chain timeout reached for chat {chatId}", context.GetChatId());
50+
if (!e.Handled && _handlers.TryGetValue(Handle.ChainTimeout, out var controller))
51+
{
52+
await _invoker.Invoke(context, cancellationToken, controller);
53+
}
3954
}
4055
}
4156
}

Deployf.Botf/System/BotfOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public string? Username
2020
public bool HandleOnlyMentionedInGroups { get; set; }
2121
public string ApiBaseUrl { get; set; }
2222
public bool AutoCleanReplyKeyboard { get; set; }
23-
23+
public TimeSpan? ChainTimeout { get; set; } = TimeSpan.FromHours(1);
2424
public bool UseWebhooks => !string.IsNullOrEmpty(WebhookUrl);
2525
public string? WebhookPath
2626
{

Deployf.Botf/System/ChainStorage.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Telegram.Bot.Framework.Abstractions;
1+
using System.Collections.Concurrent;
2+
using Telegram.Bot.Framework.Abstractions;
23

34
namespace Deployf.Botf;
45

@@ -8,7 +9,7 @@ public class ChainStorage
89

910
public ChainStorage()
1011
{
11-
_chains = new Dictionary<long, ChainItem?>();
12+
_chains = new ConcurrentDictionary<long, ChainItem?>();
1213
}
1314

1415
public ChainItem? Get(long id)
@@ -19,7 +20,11 @@ public ChainStorage()
1920

2021
public void Clear(long id)
2122
{
22-
_chains[id] = null;
23+
if(_chains.ContainsKey(id))
24+
{
25+
_chains[id] = null;
26+
_chains.Remove(id);
27+
}
2328
}
2429

2530
public void Set(long id, ChainItem item)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Deployf.Botf;
2+
3+
public class ChainTimeoutException : Exception
4+
{
5+
public readonly bool Handled;
6+
7+
public ChainTimeoutException(bool handled)
8+
{
9+
Handled = handled;
10+
}
11+
}

Deployf.Botf/System/ConnectionString.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ public static BotfOptions Parse(string value)
7272
options.AutoCleanReplyKeyboard = autoclean;
7373
}
7474
break;
75+
case "chain_timeout":
76+
options.ChainTimeout = cortage[1].TryParseTimeSpan();
77+
break;
7578
}
7679
}
7780

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace Deployf.Botf;
2+
3+
public static class TimeSpanFormatExtensions
4+
{
5+
public static TimeSpan? TryParseTimeSpan(this string format)
6+
{
7+
if(format == "-1")
8+
{
9+
return null;
10+
}
11+
12+
if (TimeSpan.TryParse(format, out var result))
13+
{
14+
return result;
15+
}
16+
else
17+
{
18+
throw new FormatException("TimeSpan wrong format");
19+
}
20+
}
21+
}

Examples/Deployf.Botf.ChainedExample/Program.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public async Task Start()
1818
{
1919
await Send($"Hi! What is your name?");
2020

21-
var name = await AwaitText();
21+
var name = await AwaitText(() => Send("Use /start to try again"));
2222
await Send($"Hi, {name}! Where are you from?");
2323

2424
var place = await AwaitText();
@@ -59,4 +59,10 @@ public async Task Ex(Exception e)
5959
Push("Error");
6060
}
6161
}
62+
63+
[On(Handle.ChainTimeout)]
64+
public async Task ChainTimeout()
65+
{
66+
PushL("timeout");
67+
}
6268
}

0 commit comments

Comments
 (0)