/
GoogleCalendarWriter.cs
110 lines (93 loc) · 4.16 KB
/
GoogleCalendarWriter.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
using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Services;
namespace TimetableCalendarGenerator;
public class GoogleCalendarWriter(string email, string serviceAccountKey) : ICalendarWriter, IDisposable
{
private const string CalendarId = "primary";
private const string AppName = "makecal";
private const string EventColour = "5";
private const string DutyEventColour = "8";
private const string MeetingEventColour = "2";
private static readonly Event.ExtendedPropertiesData EventProperties = new()
{
Private__ = new Dictionary<string, string> { { AppName, "true" } }
};
private static readonly EventComparer<Event> Comparer = new(e => e.Start?.DateTimeDateTimeOffset, e => e.End?.DateTimeDateTimeOffset, e => e.Summary, e => e.Location);
private readonly CalendarService _service = GetCalendarService(serviceAccountKey, email);
private bool _disposedValue;
public async Task WriteAsync(IList<CalendarEvent> events)
{
var existingEvents = await GetExistingEventsAsync();
var expectedEvents = events.Select(o => new Event
{
Summary = o.Title,
Location = o.Location,
Start = new EventDateTime { DateTimeDateTimeOffset = o.Start },
End = new EventDateTime { DateTimeDateTimeOffset = o.End }
}).ToList();
var removedEvents = existingEvents.Except(expectedEvents, Comparer);
var duplicateEvents = existingEvents.GroupBy(o => o, Comparer).Where(g => g.Count() > 1).SelectMany(g => g.Skip(1));
var eventsToDelete = removedEvents.Union(duplicateEvents, Comparer).OrderBy(o => o.Start?.DateTimeDateTimeOffset);
await DeleteEventsAsync(eventsToDelete);
await AddEventsAsync(expectedEvents.Except(existingEvents, Comparer).OrderBy(o => o.Start?.DateTimeDateTimeOffset));
}
private static CalendarService GetCalendarService(string serviceAccountKey, string email)
{
var credential = GoogleCredential.FromJson(serviceAccountKey).CreateScoped(CalendarService.Scope.Calendar).CreateWithUser(email);
return new CalendarService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = AppName
});
}
private async Task<IList<Event>> GetExistingEventsAsync()
{
var listRequest = _service.Events.List(CalendarId);
listRequest.PrivateExtendedProperty = $"{AppName}=true";
listRequest.Fields = "items(id,summary,location,start(dateTime),end(dateTime)),nextPageToken";
return await listRequest.FetchAllWithRetryAsync(after: DateTime.Today);
}
private async Task DeleteEventsAsync(IEnumerable<Event> events)
{
var deleteBatch = new GoogleUnlimitedBatch(_service);
foreach (var ev in events)
{
deleteBatch.Queue(_service.Events.Delete(CalendarId, ev.Id));
}
await deleteBatch.ExecuteWithRetryAsync();
}
private async Task AddEventsAsync(IEnumerable<Event> events)
{
var insertBatch = new GoogleUnlimitedBatch(_service);
foreach (var ev in events)
{
var isDuty = ev.Summary.Contains("duty", StringComparison.OrdinalIgnoreCase) || ev.Summary.Contains("duties", StringComparison.OrdinalIgnoreCase);
var isMeeting = ev.Summary.Contains("meet", StringComparison.OrdinalIgnoreCase) || ev.Summary.Contains("line management", StringComparison.OrdinalIgnoreCase)
|| ev.Summary.Contains("brief", StringComparison.OrdinalIgnoreCase) || ev.Summary.Contains("mentor", StringComparison.OrdinalIgnoreCase)
|| ev.Summary.Contains("lmm", StringComparison.OrdinalIgnoreCase);
ev.ColorId = isDuty ? DutyEventColour : (isMeeting ? MeetingEventColour : EventColour);
ev.Reminders = new Event.RemindersData { UseDefault = isDuty };
ev.ExtendedProperties = EventProperties;
var insertRequest = _service.Events.Insert(ev, CalendarId);
insertRequest.Fields = "id";
insertBatch.Queue(insertRequest);
}
await insertBatch.ExecuteWithRetryAsync();
}
protected virtual void Dispose(bool disposing)
{
if (_disposedValue) return;
if (disposing)
{
_service?.Dispose();
}
_disposedValue = true;
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}