-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCalendar.cs
256 lines (203 loc) · 9.58 KB
/
Calendar.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
using Microsoft.Graph;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ExchangeGraphTool
{
public class Calendar
{
private const int MaxBatchSize = 20; // Batch max size = 20 at present with Graph API
private int _batchSize = MaxBatchSize;
private readonly GraphServiceClient _client;
public int BatchSize
{
get => _batchSize;
set => Math.Min(value, MaxBatchSize);
}
public Calendar(GraphServiceClient client)
{
_client = client;
}
/// <summary>
/// Create sample events in each of the specified mailbox calendars, with random number of events up to maxEventsPerMailbox
/// </summary>
/// <param name="mailboxes"></param>
/// <param name="maxEventsPerMailbox">Max number of events to create per mailbox</param>
/// <returns></returns>
public async Task CreateSampleEvents(IEnumerable<string> mailboxes, int maxEventsPerMailbox, string transactionId, CancellationToken token = default)
{
if (mailboxes == null || !mailboxes.Any())
return;
// TODO: batches work in parallel on the server but per mailbox there's a 4 request concurrent limit.
// Group into 4s, or spread across batches ensuring no more than 4 of same mailbox in a batch?
maxEventsPerMailbox = Math.Min(maxEventsPerMailbox, 4);
int eventNum = 1;
DateTime start = DateTime.UtcNow;
for (int numRuns = 0; numRuns < 10; numRuns++)
{
Console.WriteLine($"Create events in {mailboxes.Count()} mailboxes starting from {start}");
eventNum = await CreateEvents(mailboxes, start, maxEventsPerMailbox, transactionId, eventNum, token);
start = start.AddHours(2);
}
}
private async Task<int> CreateEvents(IEnumerable<string> mailboxes, DateTime start, int maxEventsPerMailbox, string transactionId, int eventNum, CancellationToken token)
{
var rnd = new Random();
var requests = new List<BatchRequestStep>();
foreach (var mailbox in mailboxes)
{
DateTime nextStart = start;
int numEvents = rnd.Next(0, maxEventsPerMailbox + 1);
//Console.WriteLine($"{mailbox} - Creating {numEvents} events");
for (int n = 1; n <= numEvents; n++)
{
string stepId = Guid.NewGuid().ToString();
requests.Add(new BatchRequestStep(stepId, BuildCreateEventRequest(mailbox, $"Event {eventNum}", nextStart, 15, $"{transactionId}_{stepId}")));
nextStart = nextStart.AddMinutes(30);
eventNum++;
}
}
var batches = requests.Batch(BatchSize).ToList();
Console.WriteLine($"Sending {requests.Count} requests in {batches.Count} batches");
int requestNum = 1;
foreach (var batch in batches)
{
var batchRequest = new BatchRequestContent(batch.ToArray());
Console.WriteLine($"Batch - {requestNum}");
var batchResponse = await _client
.Batch
.Request()
.PostAsync(batchRequest, token);
var responses = await batchResponse.GetResponsesAsync();
foreach (var response in responses)
{
if (!response.Value.IsSuccessStatusCode)
{
var request = batchRequest.BatchRequestSteps[response.Key].Request;
string requestUri = request.RequestUri.ToString();
string requestContent = await request.Content.ReadAsStringAsync();
string content = await response.Value.Content.ReadAsStringAsync();
Console.WriteLine($"Request failed: {requestUri} - {requestContent}{Environment.NewLine}{(int)response.Value.StatusCode} : {response.Value.ReasonPhrase} - {content}");
}
}
requestNum++;
}
return eventNum;
}
/// <summary>
/// Find sample events created based on transaction ID
/// </summary>
/// <param name="mailboxes"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<IDictionary<string, IList<Event>>> FindEvents(IEnumerable<string> mailboxes, string transactionId, CancellationToken token = default)
{
if (mailboxes == null || !mailboxes.Any())
return ImmutableDictionary<string, IList<Event>>.Empty;
var batches = mailboxes.Batch(BatchSize);
var events = new Dictionary<string, IList<Event>>();
foreach (var batch in batches)
{
var batchSteps = from mailbox in batch
select new BatchRequestStep(mailbox, BuildFindEventsRequest(mailbox).GetHttpRequestMessage());
var batchRequest = new BatchRequestContent(batchSteps.ToArray());
var batchResponse = await _client
.Batch
.Request()
.PostAsync(batchRequest, token);
foreach (var mailbox in batch)
{
try
{
var collectionPage = await batchResponse.GetResponseByIdAsync<UserEventsCollectionResponse>(mailbox);
List<Event> responseEvents = collectionPage.Value.Where(e => string.IsNullOrEmpty(transactionId) || (e.TransactionId != null && e.TransactionId.StartsWith(transactionId))).ToList();
if (responseEvents.Any())
events.Add(mailbox, responseEvents);
}
catch (ServiceException ex)
{
Console.WriteLine($"Error with mailbox: {mailbox}: {ex.Message}");
}
}
}
return events;
}
/// <summary>
/// Delete the specified events
/// </summary>
/// <param name="events"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task DeleteEvents(IDictionary<string, IList<Event>> eventLists, CancellationToken token = default)
{
if (eventLists == null || !eventLists.Any())
return;
var requests = (from l in eventLists
from e in l.Value
select new BatchRequestStep(e.ICalUId, BuildDeleteEventRequest(l.Key, e.Id))).ToList();
// TODO: this may fail if there are more than 4 events in a mailbox due to concurrent limits per mailbox (4) as the batch will run
// concurrently on the server.
var batches = requests.Batch(BatchSize).ToList();
Console.WriteLine($"Sending {requests.Count} requests in {batches.Count} batches");
foreach (var batch in batches)
{
var batchRequest = new BatchRequestContent(batch.ToArray());
await _client
.Batch
.Request()
.PostAsync(batchRequest, token);
}
}
private HttpRequestMessage BuildCreateEventRequest(string mailbox, string subject, DateTime startUtc, int duration, string transactionId)
{
var newEvent = new Event
{
TransactionId = transactionId,
Subject = subject,
Start = new DateTimeTimeZone
{
DateTime = startUtc.ToString("yyyy-MM-dd HH:mm:ss"),
TimeZone = "UTC"
},
End = new DateTimeTimeZone
{
DateTime = startUtc.AddMinutes(duration).ToString("yyyy-MM-dd HH:mm:ss"),
TimeZone = "UTC"
}
};
var jsonEvent = _client.HttpProvider.Serializer.SerializeAsJsonContent(newEvent);
var addEventRequest = _client
.Users[mailbox]
.Events
.Request()
.Header("Prefer", $"outlook.timezone=\"UTC\"")
.GetHttpRequestMessage();
addEventRequest.Method = HttpMethod.Post;
addEventRequest.Content = jsonEvent;
return addEventRequest;
}
private IUserEventsCollectionRequest BuildFindEventsRequest(string mailbox)
{
return _client
.Users[mailbox]
.Events
.Request()
.Top(99999)
.OrderBy("start/dateTime");
}
private HttpRequestMessage BuildDeleteEventRequest(string mailbox, string eventId)
{
var deleteEventRequest = _client
.Users[mailbox]
.Events[eventId]
.Request()
.GetHttpRequestMessage();
deleteEventRequest.Method = HttpMethod.Delete;
return deleteEventRequest;
}
}
}