Synchronizing calendars for gmail and some events.
This commit is contained in:
@@ -125,7 +125,7 @@ namespace Wino.Calendar.ViewModels
|
|||||||
Type = MailSynchronizationType.UpdateProfile
|
Type = MailSynchronizationType.UpdateProfile
|
||||||
};
|
};
|
||||||
|
|
||||||
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
var profileSynchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(new NewMailSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
||||||
|
|
||||||
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
||||||
|
|
||||||
@@ -141,11 +141,13 @@ namespace Wino.Calendar.ViewModels
|
|||||||
accountCreationDialog.State = AccountCreationDialogState.FetchingEvents;
|
accountCreationDialog.State = AccountCreationDialogState.FetchingEvents;
|
||||||
|
|
||||||
// Start synchronizing events.
|
// Start synchronizing events.
|
||||||
var eventsSyncOptions = new MailSynchronizationOptions()
|
var synchronizationOptions = new CalendarSynchronizationOptions()
|
||||||
{
|
{
|
||||||
AccountId = createdAccount.Id,
|
AccountId = createdAccount.Id,
|
||||||
Type = MailSynchronizationType.Events
|
Type = CalendarSynchronizationType.CalendarMetadata
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var synchronizationResponse = await WinoServerConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(new NewCalendarSynchronizationRequested(synchronizationOptions, SynchronizationSource.Client));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
@@ -8,13 +7,14 @@ using CommunityToolkit.Mvvm.Messaging;
|
|||||||
using Wino.Core.Domain.Collections;
|
using Wino.Core.Domain.Collections;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.MenuItems;
|
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Core.Extensions;
|
using Wino.Core.Extensions;
|
||||||
using Wino.Core.ViewModels;
|
using Wino.Core.ViewModels;
|
||||||
using Wino.Messaging.Client.Calendar;
|
using Wino.Messaging.Client.Calendar;
|
||||||
using Wino.Messaging.Client.Navigation;
|
using Wino.Messaging.Client.Navigation;
|
||||||
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
namespace Wino.Calendar.ViewModels
|
namespace Wino.Calendar.ViewModels
|
||||||
{
|
{
|
||||||
@@ -22,18 +22,17 @@ namespace Wino.Calendar.ViewModels
|
|||||||
IRecipient<VisibleDateRangeChangedMessage>,
|
IRecipient<VisibleDateRangeChangedMessage>,
|
||||||
IRecipient<CalendarEnableStatusChangedMessage>,
|
IRecipient<CalendarEnableStatusChangedMessage>,
|
||||||
IRecipient<CalendarInitializedMessage>,
|
IRecipient<CalendarInitializedMessage>,
|
||||||
IRecipient<NavigateManageAccountsRequested>
|
IRecipient<NavigateManageAccountsRequested>,
|
||||||
|
IRecipient<GoToCalendarDayMessage>
|
||||||
{
|
{
|
||||||
public event EventHandler<CalendarDisplayType> DisplayTypeChanged;
|
public event EventHandler<CalendarDisplayType> DisplayTypeChanged;
|
||||||
public IPreferencesService PreferencesService { get; }
|
public IPreferencesService PreferencesService { get; }
|
||||||
public IStatePersistanceService StatePersistenceService { get; }
|
public IStatePersistanceService StatePersistenceService { get; }
|
||||||
public INavigationService NavigationService { get; }
|
public INavigationService NavigationService { get; }
|
||||||
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
public IWinoServerConnectionManager ServerConnectionManager { get; }
|
||||||
public MenuItemCollection FooterItems { get; set; }
|
|
||||||
public MenuItemCollection MenuItems { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private IMenuItem _selectedMenuItem;
|
private int _selectedMenuItemIndex = -1;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool isCalendarEnabled;
|
private bool isCalendarEnabled;
|
||||||
@@ -62,10 +61,13 @@ namespace Wino.Calendar.ViewModels
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private int _selectedDateNavigationHeaderIndex;
|
private int _selectedDateNavigationHeaderIndex;
|
||||||
|
|
||||||
|
private readonly IAccountService _accountService;
|
||||||
|
|
||||||
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
|
public bool IsVerticalCalendar => StatePersistenceService.CalendarDisplayType == CalendarDisplayType.Month;
|
||||||
|
|
||||||
public AppShellViewModel(IPreferencesService preferencesService,
|
public AppShellViewModel(IPreferencesService preferencesService,
|
||||||
IStatePersistanceService statePersistanceService,
|
IStatePersistanceService statePersistanceService,
|
||||||
|
IAccountService accountService,
|
||||||
INavigationService navigationService,
|
INavigationService navigationService,
|
||||||
IWinoServerConnectionManager serverConnectionManager)
|
IWinoServerConnectionManager serverConnectionManager)
|
||||||
{
|
{
|
||||||
@@ -74,6 +76,7 @@ namespace Wino.Calendar.ViewModels
|
|||||||
PreferencesService = preferencesService;
|
PreferencesService = preferencesService;
|
||||||
|
|
||||||
StatePersistenceService = statePersistanceService;
|
StatePersistenceService = statePersistanceService;
|
||||||
|
_accountService = accountService;
|
||||||
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
StatePersistenceService.StatePropertyChanged += PrefefencesChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,20 +96,37 @@ namespace Wino.Calendar.ViewModels
|
|||||||
{
|
{
|
||||||
base.OnNavigatedTo(mode, parameters);
|
base.OnNavigatedTo(mode, parameters);
|
||||||
|
|
||||||
CreateFooterItems();
|
|
||||||
UpdateDateNavigationHeaderItems();
|
UpdateDateNavigationHeaderItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedMenuItemChanged(IMenuItem oldValue, IMenuItem newValue)
|
partial void OnSelectedMenuItemIndexChanged(int oldValue, int newValue)
|
||||||
{
|
{
|
||||||
if (newValue is SettingsItem)
|
switch (newValue)
|
||||||
{
|
{
|
||||||
NavigationService.Navigate(WinoPage.SettingsPage);
|
case -1:
|
||||||
|
NavigationService.Navigate(WinoPage.CalendarPage);
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
NavigationService.Navigate(WinoPage.AccountManagementPage);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
NavigationService.Navigate(WinoPage.SettingsPage);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (newValue is ManageAccountsMenuItem)
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Sync()
|
||||||
|
{
|
||||||
|
var t = new NewCalendarSynchronizationRequested(new CalendarSynchronizationOptions()
|
||||||
{
|
{
|
||||||
NavigationService.Navigate(WinoPage.AccountManagementPage);
|
AccountId = Guid.Parse("52fae547-0740-4aa3-8d51-519bd31278ca"),
|
||||||
}
|
Type = CalendarSynchronizationType.CalendarMetadata
|
||||||
|
}, SynchronizationSource.Client);
|
||||||
|
|
||||||
|
Messenger.Send(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -142,14 +162,6 @@ namespace Wino.Calendar.ViewModels
|
|||||||
return DateTime.Today.Date;
|
return DateTime.Today.Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDispatcherAssigned()
|
|
||||||
{
|
|
||||||
base.OnDispatcherAssigned();
|
|
||||||
|
|
||||||
MenuItems = new MenuItemCollection(Dispatcher);
|
|
||||||
FooterItems = new MenuItemCollection(Dispatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnPageLoaded()
|
public override void OnPageLoaded()
|
||||||
{
|
{
|
||||||
base.OnPageLoaded();
|
base.OnPageLoaded();
|
||||||
@@ -160,13 +172,6 @@ namespace Wino.Calendar.ViewModels
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateFooterItems()
|
|
||||||
{
|
|
||||||
FooterItems.Clear();
|
|
||||||
FooterItems.Add(new ManageAccountsMenuItem());
|
|
||||||
FooterItems.Add(new SettingsItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Commands
|
#region Commands
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -236,6 +241,8 @@ namespace Wino.Calendar.ViewModels
|
|||||||
// Calendar page is loaded and calendar is ready to recieve render requests.
|
// Calendar page is loaded and calendar is ready to recieve render requests.
|
||||||
public void Receive(CalendarInitializedMessage message) => Messenger.Send(new GoToCalendarDayMessage(DateTime.Now.Date));
|
public void Receive(CalendarInitializedMessage message) => Messenger.Send(new GoToCalendarDayMessage(DateTime.Now.Date));
|
||||||
|
|
||||||
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItem = FooterItems.FirstOrDefault(a => a is ManageAccountsMenuItem);
|
public void Receive(NavigateManageAccountsRequested message) => SelectedMenuItemIndex = 1;
|
||||||
|
|
||||||
|
public void Receive(GoToCalendarDayMessage message) => SelectedMenuItemIndex = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,62 +375,62 @@ namespace Wino.Calendar.ViewModels
|
|||||||
base.OnCalendarEventAdded(calendarItem);
|
base.OnCalendarEventAdded(calendarItem);
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
var eventDays = DayRanges.SelectMany(a => a.CalendarDays).Where(b => b.Period.Start.Date == calendarItem.StartTime.Date);
|
//var eventDays = DayRanges.SelectMany(a => a.CalendarDays).Where(b => b.Period.Start.Date == calendarItem.StartTime.Date);
|
||||||
|
|
||||||
var beforeAllDay = new CalendarItem(calendarItem.StartTime.Date.AddHours(0), calendarItem.StartTime.Date.AddMinutes(30))
|
//var beforeAllDay = new CalendarItem(calendarItem.StartTime.Date.AddHours(0), calendarItem.StartTime.Date.AddMinutes(30))
|
||||||
{
|
//{
|
||||||
Title = "kj"
|
// Title = "kj"
|
||||||
};
|
//};
|
||||||
|
|
||||||
var allday = new CalendarItem(calendarItem.StartTime.Date.AddHours(1), calendarItem.StartTime.Date.AddHours(10).AddMinutes(59))
|
//var allday = new CalendarItem(calendarItem.StartTime.Date.AddHours(1), calendarItem.StartTime.Date.AddHours(10).AddMinutes(59))
|
||||||
{
|
//{
|
||||||
Title = "All day"
|
// Title = "All day"
|
||||||
};
|
//};
|
||||||
|
|
||||||
var test = new CalendarItem(calendarItem.StartTime.Date.AddHours(4), calendarItem.StartTime.Date.AddHours(4).AddMinutes(30))
|
//var test = new CalendarItem(calendarItem.StartTime.Date.AddHours(4), calendarItem.StartTime.Date.AddHours(4).AddMinutes(30))
|
||||||
{
|
//{
|
||||||
Title = "test"
|
// Title = "test"
|
||||||
};
|
//};
|
||||||
|
|
||||||
var hour = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8))
|
//var hour = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8))
|
||||||
{
|
//{
|
||||||
Title = "1 h"
|
// Title = "1 h"
|
||||||
};
|
//};
|
||||||
|
|
||||||
var hourandhalf = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30))
|
//var hourandhalf = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30))
|
||||||
{
|
//{
|
||||||
Title = "1.5 h"
|
// Title = "1.5 h"
|
||||||
};
|
//};
|
||||||
var halfhour1 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(7).AddMinutes(30))
|
//var halfhour1 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7), calendarItem.StartTime.Date.AddHours(7).AddMinutes(30))
|
||||||
{
|
//{
|
||||||
Title = "30 min"
|
// Title = "30 min"
|
||||||
};
|
//};
|
||||||
|
|
||||||
var halfhour2 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7).AddMinutes(30), calendarItem.StartTime.Date.AddHours(8))
|
//var halfhour2 = new CalendarItem(calendarItem.StartTime.Date.AddHours(7).AddMinutes(30), calendarItem.StartTime.Date.AddHours(8))
|
||||||
{
|
//{
|
||||||
Title = "30 min"
|
// Title = "30 min"
|
||||||
};
|
//};
|
||||||
var halfhour3 = new CalendarItem(calendarItem.StartTime.Date.AddHours(8), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30))
|
//var halfhour3 = new CalendarItem(calendarItem.StartTime.Date.AddHours(8), calendarItem.StartTime.Date.AddHours(8).AddMinutes(30))
|
||||||
{
|
//{
|
||||||
Title = "30 min"
|
// Title = "30 min"
|
||||||
};
|
//};
|
||||||
|
|
||||||
foreach (var day in eventDays)
|
//foreach (var day in eventDays)
|
||||||
{
|
//{
|
||||||
await ExecuteUIThread(() =>
|
// await ExecuteUIThread(() =>
|
||||||
{
|
// {
|
||||||
day.Events.Add(beforeAllDay);
|
// day.Events.Add(beforeAllDay);
|
||||||
day.Events.Add(allday);
|
// day.Events.Add(allday);
|
||||||
day.Events.Add(hourandhalf);
|
// day.Events.Add(hourandhalf);
|
||||||
day.Events.Add(hour);
|
// day.Events.Add(hour);
|
||||||
day.Events.Add(halfhour1);
|
// day.Events.Add(halfhour1);
|
||||||
day.Events.Add(halfhour2);
|
// day.Events.Add(halfhour2);
|
||||||
day.Events.Add(halfhour3);
|
// day.Events.Add(halfhour3);
|
||||||
day.Events.Add(test);
|
// day.Events.Add(test);
|
||||||
});
|
// });
|
||||||
}
|
//}
|
||||||
|
|
||||||
return;
|
//return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ using Wino.Activation;
|
|||||||
using Wino.Calendar.Activation;
|
using Wino.Calendar.Activation;
|
||||||
using Wino.Calendar.Services;
|
using Wino.Calendar.Services;
|
||||||
using Wino.Calendar.ViewModels;
|
using Wino.Calendar.ViewModels;
|
||||||
|
using Wino.Core.Domain;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Exceptions;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Core.UWP;
|
using Wino.Core.UWP;
|
||||||
using Wino.Messaging.Client.Connection;
|
using Wino.Messaging.Client.Connection;
|
||||||
using Wino.Messaging.Server;
|
using Wino.Messaging.Server;
|
||||||
@@ -20,7 +24,7 @@ using Wino.Services;
|
|||||||
|
|
||||||
namespace Wino.Calendar
|
namespace Wino.Calendar
|
||||||
{
|
{
|
||||||
public sealed partial class App : WinoApplication, IRecipient<NewSynchronizationRequested>
|
public sealed partial class App : WinoApplication, IRecipient<NewCalendarSynchronizationRequested>
|
||||||
{
|
{
|
||||||
public override string AppCenterKey => "dfdad6ab-95f9-44cc-9112-45ec6730c49e";
|
public override string AppCenterKey => "dfdad6ab-95f9-44cc-9112-45ec6730c49e";
|
||||||
|
|
||||||
@@ -101,13 +105,6 @@ namespace Wino.Calendar
|
|||||||
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
|
protected override ActivationHandler<IActivatedEventArgs> GetDefaultActivationHandler()
|
||||||
=> new DefaultActivationHandler();
|
=> new DefaultActivationHandler();
|
||||||
|
|
||||||
public void Receive(NewSynchronizationRequested message)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
base.OnBackgroundActivated(args);
|
base.OnBackgroundActivated(args);
|
||||||
@@ -142,5 +139,20 @@ namespace Wino.Calendar
|
|||||||
|
|
||||||
AppServiceConnectionManager.Connection = null;
|
AppServiceConnectionManager.Connection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async void Receive(NewCalendarSynchronizationRequested message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<CalendarSynchronizationResult, NewCalendarSynchronizationRequested>(message);
|
||||||
|
synchronizationResultResponse.ThrowIfFailed();
|
||||||
|
}
|
||||||
|
catch (WinoServerException serverException)
|
||||||
|
{
|
||||||
|
var dialogService = Services.GetService<ICalendarDialogService>();
|
||||||
|
|
||||||
|
dialogService.InfoBarMessage(Translator.Info_SyncFailedTitle, serverException.Message, InfoBarMessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,9 @@ namespace Wino.Calendar.Controls
|
|||||||
|
|
||||||
var childMeasurement = _measurements[child.Item.Id];
|
var childMeasurement = _measurements[child.Item.Id];
|
||||||
|
|
||||||
double childHeight = Math.Max(0, GetChildHeight(child.Item.StartTime, child.Item.EndTime));
|
// TODO Math.Max(0, GetChildHeight(child.Item.StartTime, child.Item.EndTime));
|
||||||
|
// Recurring events may not have an end time. We need to calculate the height based on the start time and duration.
|
||||||
|
double childHeight = 50;
|
||||||
double childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
|
double childWidth = Math.Max(0, GetChildWidth(childMeasurement, finalSize.Width));
|
||||||
double childTop = Math.Max(0, GetChildTopMargin(child.Item.StartTime, availableHeight));
|
double childTop = Math.Max(0, GetChildTopMargin(child.Item.StartTime, availableHeight));
|
||||||
double childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
|
double childLeft = Math.Max(0, GetChildLeftMargin(childMeasurement, availableWidth));
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<SolidColorBrush x:Key="CalendarFieldSelectedBackgroundBrush">#121212</SolidColorBrush>
|
<SolidColorBrush x:Key="CalendarFieldSelectedBackgroundBrush">#121212</SolidColorBrush>
|
||||||
|
|
||||||
<SolidColorBrush x:Key="WinoCalendarViewBorderBrush">#3d3d3d</SolidColorBrush>
|
<SolidColorBrush x:Key="WinoCalendarViewBorderBrush">#3d3d3d</SolidColorBrush>
|
||||||
<SolidColorBrush x:Key="WinoCalendarViewVisibleDayBackgroundBrush">#30336b</SolidColorBrush>
|
<SolidColorBrush x:Key="WinoCalendarViewVisibleDayBackgroundBrush">#4b4b4b</SolidColorBrush>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ResourceDictionary.ThemeDictionaries>
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -4,7 +4,6 @@ using Microsoft.UI.Xaml.Controls;
|
|||||||
using Windows.UI.Xaml.Controls;
|
using Windows.UI.Xaml.Controls;
|
||||||
using Wino.Calendar.Args;
|
using Wino.Calendar.Args;
|
||||||
using Wino.Calendar.Views.Abstract;
|
using Wino.Calendar.Views.Abstract;
|
||||||
using Wino.Core.Domain.Models.Calendar;
|
|
||||||
using Wino.Messaging.Client.Calendar;
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
|
||||||
namespace Wino.Calendar.Views
|
namespace Wino.Calendar.Views
|
||||||
@@ -30,24 +29,15 @@ namespace Wino.Calendar.Views
|
|||||||
{
|
{
|
||||||
selectedDateTime = e.ClickedDate;
|
selectedDateTime = e.ClickedDate;
|
||||||
|
|
||||||
|
// TODO: Popup is not positioned well on daily view.
|
||||||
TeachingTipPositionerGrid.Width = e.CellSize.Width;
|
TeachingTipPositionerGrid.Width = e.CellSize.Width;
|
||||||
TeachingTipPositionerGrid.Height = e.CellSize.Height;
|
TeachingTipPositionerGrid.Height = e.CellSize.Height;
|
||||||
|
|
||||||
Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X);
|
Canvas.SetLeft(TeachingTipPositionerGrid, e.PositionerPoint.X);
|
||||||
Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y);
|
Canvas.SetTop(TeachingTipPositionerGrid, e.PositionerPoint.Y);
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new CalendarEventAdded(new CalendarItem(selectedDateTime.Value, default)));
|
// TODO: End time can be from settings.
|
||||||
|
// WeakReferenceMessenger.Default.Send(new CalendarEventAdded(new CalendarItem(selectedDateTime.Value, selectedDateTime.Value.AddMinutes(30))));
|
||||||
//var t = new Flyout()
|
|
||||||
//{
|
|
||||||
// Content = new TextBlock() { Text = "Create event" }
|
|
||||||
//};
|
|
||||||
|
|
||||||
//t.ShowAt(TeachingTipPositionerGrid, new FlyoutShowOptions()
|
|
||||||
//{
|
|
||||||
// ShowMode = FlyoutShowMode.Transient,
|
|
||||||
// Placement = FlyoutPlacementMode.Right
|
|
||||||
//});
|
|
||||||
|
|
||||||
NewEventTip.IsOpen = true;
|
NewEventTip.IsOpen = true;
|
||||||
}
|
}
|
||||||
@@ -71,7 +61,7 @@ namespace Wino.Calendar.Views
|
|||||||
var eventEndDate = selectedDateTime.Value.Add(EventTimePicker.Time);
|
var eventEndDate = selectedDateTime.Value.Add(EventTimePicker.Time);
|
||||||
|
|
||||||
// Create the event.
|
// Create the event.
|
||||||
WeakReferenceMessenger.Default.Send(new CalendarEventAdded(new CalendarItem(selectedDateTime.Value, eventEndDate)));
|
// WeakReferenceMessenger.Default.Send(new CalendarEventAdded(new CalendarItem(selectedDateTime.Value, eventEndDate)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ namespace Wino.Core.Domain.Entities.Calendar
|
|||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
public string RemoteCalendarId { get; set; }
|
||||||
|
public string SynchronizationDeltaToken { get; set; }
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string ColorHex { get; set; }
|
public string ColorHex { get; set; }
|
||||||
public string TimeZoneId { get; set; }
|
public string TimeZone { get; set; }
|
||||||
|
public bool IsPrimary { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using SQLite;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Entities.Calendar
|
|
||||||
{
|
|
||||||
public class Attendee
|
|
||||||
{
|
|
||||||
[PrimaryKey]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public Guid EventId { get; set; }
|
|
||||||
public Guid ContactId { get; set; }
|
|
||||||
public AttendeeStatus Status { get; set; }
|
|
||||||
public bool IsOrganizer { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
Wino.Core.Domain/Entities/Calendar/CalendarEventAttendee.cs
Normal file
20
Wino.Core.Domain/Entities/Calendar/CalendarEventAttendee.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using SQLite;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Entities.Calendar
|
||||||
|
{
|
||||||
|
// TODO: Connect to Contact store with Wino People.
|
||||||
|
public class CalendarEventAttendee
|
||||||
|
{
|
||||||
|
[PrimaryKey]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid CalendarItemId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public AttendeeStatus AttendenceStatus { get; set; }
|
||||||
|
public bool IsOrganizer { get; set; }
|
||||||
|
public bool IsOptionalAttendee { get; set; }
|
||||||
|
public string Comment { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,9 +14,8 @@ namespace Wino.Core.Domain.Entities.Calendar
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Location { get; set; }
|
public string Location { get; set; }
|
||||||
public DateTimeOffset StartTime { get; set; }
|
public DateTimeOffset StartTime { get; set; }
|
||||||
public DateTimeOffset EndTime { get; set; }
|
public int DurationInMinutes { get; set; }
|
||||||
public bool IsAllDay { get; set; }
|
public string Recurrence { get; set; }
|
||||||
public Guid? RecurrenceRuleId { get; set; }
|
|
||||||
public CalendarItemStatus Status { get; set; }
|
public CalendarItemStatus Status { get; set; }
|
||||||
public CalendarItemVisibility Visibility { get; set; }
|
public CalendarItemVisibility Visibility { get; set; }
|
||||||
public DateTimeOffset CreatedAt { get; set; }
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
@@ -24,6 +23,6 @@ namespace Wino.Core.Domain.Entities.Calendar
|
|||||||
public Guid CalendarId { get; set; }
|
public Guid CalendarId { get; set; }
|
||||||
|
|
||||||
[Ignore]
|
[Ignore]
|
||||||
public TimeRange Period => new TimeRange(StartTime.Date, EndTime.Date);
|
public TimeRange Period => new TimeRange(StartTime.Date, StartTime.Date.AddMinutes(DurationInMinutes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
using System;
|
|
||||||
using SQLite;
|
|
||||||
using Wino.Core.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Entities.Calendar
|
|
||||||
{
|
|
||||||
public class RecurrenceRule
|
|
||||||
{
|
|
||||||
[PrimaryKey]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
public Guid CalendarItemId { get; set; }
|
|
||||||
public CalendarItemRecurrenceFrequency Frequency { get; set; }
|
|
||||||
public int Interval { get; set; }
|
|
||||||
public string DaysOfWeek { get; set; }
|
|
||||||
public DateTimeOffset StartDate { get; set; }
|
|
||||||
public DateTimeOffset EndDate { get; set; }
|
|
||||||
public int Occurrences { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,7 @@ namespace Wino.Core.Domain.Entities.Shared
|
|||||||
public MailProviderType ProviderType { get; set; }
|
public MailProviderType ProviderType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For tracking change delta.
|
/// For tracking mail change delta.
|
||||||
/// Gmail : historyId
|
/// Gmail : historyId
|
||||||
/// Outlook: deltaToken
|
/// Outlook: deltaToken
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public enum AttendeeStatus
|
public enum AttendeeStatus
|
||||||
{
|
{
|
||||||
|
NeedsAction,
|
||||||
Accepted,
|
Accepted,
|
||||||
Tentative,
|
Tentative,
|
||||||
Declined
|
Declined
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
{
|
{
|
||||||
public enum CalendarItemVisibility
|
public enum CalendarItemVisibility
|
||||||
{
|
{
|
||||||
|
Default,
|
||||||
Public,
|
Public,
|
||||||
Private
|
Private,
|
||||||
|
Confidential
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
{
|
{
|
||||||
public enum CalendarSynchronizationType
|
public enum CalendarSynchronizationType
|
||||||
{
|
{
|
||||||
AllCalendars, // Sync all calendars.
|
ExecuteRequests, // Execute all requests in the queue.
|
||||||
SingleCalendar, // Sync only one calendar.
|
CalendarMetadata, // Sync calendar metadata.
|
||||||
|
CalendarEvents, // Sync all events for all calendars.
|
||||||
|
SingleCalendar, // Sync events for only specified calendars.
|
||||||
UpdateProfile // Update profile information only.
|
UpdateProfile // Update profile information only.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
string Title { get; }
|
string Title { get; }
|
||||||
Guid Id { get; }
|
Guid Id { get; }
|
||||||
DateTimeOffset StartTime { get; }
|
DateTimeOffset StartTime { get; }
|
||||||
DateTimeOffset EndTime { get; }
|
int DurationInMinutes { get; }
|
||||||
TimeRange Period { get; }
|
TimeRange Period { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Wino.Core.Domain/Interfaces/ICalendarService.cs
Normal file
18
Wino.Core.Domain/Interfaces/ICalendarService.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain.Interfaces
|
||||||
|
{
|
||||||
|
public interface ICalendarService
|
||||||
|
{
|
||||||
|
Task<List<AccountCalendar>> GetAccountCalendarsAsync(Guid accountId);
|
||||||
|
Task DeleteCalendarItemAsync(Guid calendarItemId);
|
||||||
|
|
||||||
|
Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||||
|
Task InsertAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||||
|
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||||
|
Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,5 +10,6 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
Task<List<AccountContact>> GetAddressInformationAsync(string queryText);
|
Task<List<AccountContact>> GetAddressInformationAsync(string queryText);
|
||||||
Task<AccountContact> GetAddressInformationByAddressAsync(string address);
|
Task<AccountContact> GetAddressInformationByAddressAsync(string address);
|
||||||
Task SaveAddressInformationAsync(MimeMessage message);
|
Task SaveAddressInformationAsync(MimeMessage message);
|
||||||
|
Task<AccountContact> CreateNewContactAsync(string address, string displayName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// <returns>Result summary of synchronization.</returns>
|
/// <returns>Result summary of synchronization.</returns>
|
||||||
Task<MailSynchronizationResult> SynchronizeMailsAsync(MailSynchronizationOptions options, CancellationToken cancellationToken = default);
|
Task<MailSynchronizationResult> SynchronizeMailsAsync(MailSynchronizationOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<CalendarSynchronizationResult> SynchronizeCalendarEventsAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downloads a single MIME message from the server and saves it to disk.
|
/// Downloads a single MIME message from the server and saves it to disk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Itenso.TimePeriod;
|
|
||||||
using Wino.Core.Domain.Interfaces;
|
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Models.Calendar
|
|
||||||
{
|
|
||||||
public class CalendarItem : ICalendarItem
|
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
public CalendarItem(DateTime startTime, DateTime endTime)
|
|
||||||
{
|
|
||||||
StartTime = startTime;
|
|
||||||
EndTime = endTime;
|
|
||||||
Period = new TimeRange(startTime, endTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTimeOffset StartTime { get; }
|
|
||||||
public DateTimeOffset EndTime { get; }
|
|
||||||
|
|
||||||
public Guid Id { get; } = Guid.NewGuid();
|
|
||||||
|
|
||||||
public TimeRange Period { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@ namespace Wino.Helpers
|
|||||||
{
|
{
|
||||||
private const string TwentyFourHourTimeFormat = "HH:mm";
|
private const string TwentyFourHourTimeFormat = "HH:mm";
|
||||||
private const string TwelveHourTimeFormat = "hh:mm tt";
|
private const string TwelveHourTimeFormat = "hh:mm tt";
|
||||||
|
|
||||||
#region Converters
|
#region Converters
|
||||||
|
|
||||||
public static Visibility ReverseBoolToVisibilityConverter(bool value) => value ? Visibility.Collapsed : Visibility.Visible;
|
public static Visibility ReverseBoolToVisibilityConverter(bool value) => value ? Visibility.Collapsed : Visibility.Visible;
|
||||||
@@ -40,6 +41,19 @@ namespace Wino.Helpers
|
|||||||
_ => InfoBarSeverity.Informational,
|
_ => InfoBarSeverity.Informational,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode NavigationViewDisplayModeConverter(SplitViewDisplayMode splitViewDisplayMode)
|
||||||
|
{
|
||||||
|
return splitViewDisplayMode switch
|
||||||
|
{
|
||||||
|
SplitViewDisplayMode.CompactOverlay => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Compact,
|
||||||
|
SplitViewDisplayMode.CompactInline => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal,
|
||||||
|
SplitViewDisplayMode.Overlay => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded,
|
||||||
|
SplitViewDisplayMode.Inline => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Expanded,
|
||||||
|
_ => Microsoft.UI.Xaml.Controls.NavigationViewDisplayMode.Minimal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static SolidColorBrush GetSolidColorBrushFromHex(string colorHex) => string.IsNullOrEmpty(colorHex) ? new SolidColorBrush(Colors.Transparent) : new SolidColorBrush(colorHex.ToColor());
|
public static SolidColorBrush GetSolidColorBrushFromHex(string colorHex) => string.IsNullOrEmpty(colorHex) ? new SolidColorBrush(Colors.Transparent) : new SolidColorBrush(colorHex.ToColor());
|
||||||
public static Visibility IsSelectionModeMultiple(ListViewSelectionMode mode) => mode == ListViewSelectionMode.Multiple ? Visibility.Visible : Visibility.Collapsed;
|
public static Visibility IsSelectionModeMultiple(ListViewSelectionMode mode) => mode == ListViewSelectionMode.Multiple ? Visibility.Visible : Visibility.Collapsed;
|
||||||
public static FontWeight GetFontWeightBySyncState(bool isSyncing) => isSyncing ? FontWeights.SemiBold : FontWeights.Normal;
|
public static FontWeight GetFontWeightBySyncState(bool isSyncing) => isSyncing ? FontWeights.SemiBold : FontWeights.Normal;
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
using Google.Apis.Calendar.v3.Data;
|
||||||
using Google.Apis.Gmail.v1.Data;
|
using Google.Apis.Gmail.v1.Data;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
@@ -13,10 +15,6 @@ namespace Wino.Core.Extensions
|
|||||||
{
|
{
|
||||||
public static class GoogleIntegratorExtensions
|
public static class GoogleIntegratorExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static string GetNormalizedLabelName(string labelName)
|
private static string GetNormalizedLabelName(string labelName)
|
||||||
{
|
{
|
||||||
// 1. Remove CATEGORY_ prefix.
|
// 1. Remove CATEGORY_ prefix.
|
||||||
@@ -168,5 +166,119 @@ namespace Wino.Core.Extensions
|
|||||||
IsVerified = a.VerificationStatus == "accepted" || a.IsDefault.GetValueOrDefault(),
|
IsVerified = a.VerificationStatus == "accepted" || a.IsDefault.GetValueOrDefault(),
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AccountCalendar AsCalendar(this CalendarListEntry calendarListEntry, Guid accountId)
|
||||||
|
{
|
||||||
|
return new AccountCalendar()
|
||||||
|
{
|
||||||
|
RemoteCalendarId = calendarListEntry.Id,
|
||||||
|
AccountId = accountId,
|
||||||
|
Name = calendarListEntry.Summary,
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
TimeZone = calendarListEntry.TimeZone,
|
||||||
|
IsPrimary = calendarListEntry.Primary.GetValueOrDefault(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the start DateTimeOffset of a Google Calendar Event.
|
||||||
|
/// Handles different date/time representations (date-only, date-time, recurring events).
|
||||||
|
/// Uses the DateTimeDateTimeOffset property for optimal performance and accuracy.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendarEvent">The Google Calendar Event object.</param>
|
||||||
|
/// <returns>The start DateTimeOffset of the event, or null if it cannot be determined.</returns>
|
||||||
|
public static DateTimeOffset? GetEventStartDateTimeOffset(this Event calendarEvent)
|
||||||
|
{
|
||||||
|
if (calendarEvent == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendarEvent.Start != null)
|
||||||
|
{
|
||||||
|
if (calendarEvent.Start.DateTimeDateTimeOffset != null)
|
||||||
|
{
|
||||||
|
return calendarEvent.Start.DateTimeDateTimeOffset; // Use the direct DateTimeOffset property!
|
||||||
|
}
|
||||||
|
else if (calendarEvent.Start.Date != null)
|
||||||
|
{
|
||||||
|
if (DateTime.TryParse(calendarEvent.Start.Date, out DateTime startDate))
|
||||||
|
{
|
||||||
|
// Date-only events are treated as UTC midnight
|
||||||
|
return new DateTimeOffset(startDate, TimeSpan.Zero);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Start time not found
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the duration of a Google Calendar Event in minutes.
|
||||||
|
/// Handles date-only and date-time events, but *does not* handle recurring events correctly.
|
||||||
|
/// For recurring events, this method will return the duration of the *first* instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="calendarEvent">The Google Calendar Event object.</param>
|
||||||
|
/// <returns>The duration of the event in minutes, or null if it cannot be determined.</returns>
|
||||||
|
public static int? GetEventDurationInMinutes(this Event calendarEvent)
|
||||||
|
{
|
||||||
|
if (calendarEvent == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset? start = calendarEvent.GetEventStartDateTimeOffset();
|
||||||
|
if (start == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset? end = null;
|
||||||
|
if (calendarEvent.End != null)
|
||||||
|
{
|
||||||
|
if (calendarEvent.End.DateTimeDateTimeOffset != null)
|
||||||
|
{
|
||||||
|
end = calendarEvent.End.DateTimeDateTimeOffset;
|
||||||
|
}
|
||||||
|
else if (calendarEvent.End.Date != null)
|
||||||
|
{
|
||||||
|
if (DateTime.TryParse(calendarEvent.End.Date, out DateTime endDate))
|
||||||
|
{
|
||||||
|
end = new DateTimeOffset(endDate, TimeSpan.Zero);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(end.Value - start.Value).TotalMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545.
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>___ separated lines.</returns>
|
||||||
|
public static string GetRecurrenceString(this Event calendarEvent)
|
||||||
|
{
|
||||||
|
if (calendarEvent == null || calendarEvent.Recurrence == null || !calendarEvent.Recurrence.Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join("___", calendarEvent.Recurrence);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Google.Apis.Calendar.v3.Data;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -32,28 +34,28 @@ namespace Wino.Core.Integration.Processors
|
|||||||
Task DeleteFolderAsync(Guid accountId, string remoteFolderId);
|
Task DeleteFolderAsync(Guid accountId, string remoteFolderId);
|
||||||
Task InsertFolderAsync(MailItemFolder folder);
|
Task InsertFolderAsync(MailItemFolder folder);
|
||||||
Task UpdateFolderAsync(MailItemFolder folder);
|
Task UpdateFolderAsync(MailItemFolder folder);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the list of folders that are available for account.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="accountId">Account id to get folders for.</param>
|
|
||||||
/// <returns>All folders.</returns>
|
|
||||||
Task<List<MailItemFolder>> GetLocalFoldersAsync(Guid accountId);
|
Task<List<MailItemFolder>> GetLocalFoldersAsync(Guid accountId);
|
||||||
|
|
||||||
|
|
||||||
Task<List<MailItemFolder>> GetSynchronizationFoldersAsync(MailSynchronizationOptions options);
|
Task<List<MailItemFolder>> GetSynchronizationFoldersAsync(MailSynchronizationOptions options);
|
||||||
|
|
||||||
Task<bool> MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId);
|
Task<bool> MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId);
|
||||||
Task UpdateFolderLastSyncDateAsync(Guid folderId);
|
Task UpdateFolderLastSyncDateAsync(Guid folderId);
|
||||||
|
|
||||||
Task<List<MailItemFolder>> GetExistingFoldersAsync(Guid accountId);
|
Task<List<MailItemFolder>> GetExistingFoldersAsync(Guid accountId);
|
||||||
Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases);
|
Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases);
|
||||||
|
|
||||||
|
// Calendar
|
||||||
|
Task<List<AccountCalendar>> GetAccountCalendarsAsync(Guid accountId);
|
||||||
|
|
||||||
|
Task DeleteCalendarItemAsync(Guid calendarItemId);
|
||||||
|
|
||||||
|
Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||||
|
Task InsertAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||||
|
Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IGmailChangeProcessor : IDefaultChangeProcessor
|
public interface IGmailChangeProcessor : IDefaultChangeProcessor
|
||||||
{
|
{
|
||||||
Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId);
|
Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId);
|
||||||
Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
|
Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
|
||||||
|
Task<CalendarItem> CreateCalendarItemAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IOutlookChangeProcessor : IDefaultChangeProcessor
|
public interface IOutlookChangeProcessor : IDefaultChangeProcessor
|
||||||
@@ -115,13 +117,15 @@ namespace Wino.Core.Integration.Processors
|
|||||||
public class DefaultChangeProcessor(IDatabaseService databaseService,
|
public class DefaultChangeProcessor(IDatabaseService databaseService,
|
||||||
IFolderService folderService,
|
IFolderService folderService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
|
ICalendarService calendarService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMimeFileService mimeFileService) : BaseDatabaseService(databaseService), IDefaultChangeProcessor
|
IMimeFileService mimeFileService) : BaseDatabaseService(databaseService), IDefaultChangeProcessor
|
||||||
{
|
{
|
||||||
protected IMailService MailService = mailService;
|
protected IMailService MailService = mailService;
|
||||||
|
protected ICalendarService CalendarService = calendarService;
|
||||||
protected IFolderService FolderService = folderService;
|
protected IFolderService FolderService = folderService;
|
||||||
protected IAccountService AccountService = accountService;
|
protected IAccountService AccountService = accountService;
|
||||||
|
|
||||||
private readonly IMimeFileService _mimeFileService = mimeFileService;
|
private readonly IMimeFileService _mimeFileService = mimeFileService;
|
||||||
|
|
||||||
public Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string synchronizationDeltaIdentifier)
|
public Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string synchronizationDeltaIdentifier)
|
||||||
@@ -179,5 +183,20 @@ namespace Wino.Core.Integration.Processors
|
|||||||
|
|
||||||
public Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases)
|
public Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases)
|
||||||
=> AccountService.UpdateRemoteAliasInformationAsync(account, remoteAccountAliases);
|
=> AccountService.UpdateRemoteAliasInformationAsync(account, remoteAccountAliases);
|
||||||
|
|
||||||
|
public Task<List<AccountCalendar>> GetAccountCalendarsAsync(Guid accountId)
|
||||||
|
=> CalendarService.GetAccountCalendarsAsync(accountId);
|
||||||
|
|
||||||
|
public Task DeleteCalendarItemAsync(Guid calendarItemId)
|
||||||
|
=> CalendarService.DeleteCalendarItemAsync(calendarItemId);
|
||||||
|
|
||||||
|
public Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar)
|
||||||
|
=> CalendarService.DeleteAccountCalendarAsync(accountCalendar);
|
||||||
|
|
||||||
|
public Task InsertAccountCalendarAsync(AccountCalendar accountCalendar)
|
||||||
|
=> CalendarService.InsertAccountCalendarAsync(accountCalendar);
|
||||||
|
|
||||||
|
public Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar)
|
||||||
|
=> CalendarService.UpdateAccountCalendarAsync(accountCalendar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,28 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Google.Apis.Calendar.v3.Data;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Extensions;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
|
using CalendarEventAttendee = Wino.Core.Domain.Entities.Calendar.CalendarEventAttendee;
|
||||||
|
using CalendarItem = Wino.Core.Domain.Entities.Calendar.CalendarItem;
|
||||||
|
|
||||||
namespace Wino.Core.Integration.Processors
|
namespace Wino.Core.Integration.Processors
|
||||||
{
|
{
|
||||||
public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcessor
|
public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcessor
|
||||||
{
|
{
|
||||||
public GmailChangeProcessor(IDatabaseService databaseService, IFolderService folderService, IMailService mailService, IAccountService accountService, IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, accountService, mimeFileService)
|
public GmailChangeProcessor(IDatabaseService databaseService,
|
||||||
|
IFolderService folderService,
|
||||||
|
IMailService mailService,
|
||||||
|
ICalendarService calendarService,
|
||||||
|
IAccountService accountService,
|
||||||
|
IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, calendarService, accountService, mimeFileService)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId)
|
public Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId)
|
||||||
@@ -16,5 +30,116 @@ namespace Wino.Core.Integration.Processors
|
|||||||
|
|
||||||
public Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId)
|
public Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId)
|
||||||
=> MailService.CreateAssignmentAsync(accountId, mailCopyId, remoteFolderId);
|
=> MailService.CreateAssignmentAsync(accountId, mailCopyId, remoteFolderId);
|
||||||
|
|
||||||
|
public async Task<CalendarItem> CreateCalendarItemAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount)
|
||||||
|
{
|
||||||
|
var calendarItem = new CalendarItem()
|
||||||
|
{
|
||||||
|
CalendarId = assignedCalendar.Id,
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
Description = calendarEvent.Description,
|
||||||
|
StartTime = GoogleIntegratorExtensions.GetEventStartDateTimeOffset(calendarEvent) ?? throw new Exception("Event without a start time."),
|
||||||
|
DurationInMinutes = GoogleIntegratorExtensions.GetEventDurationInMinutes(calendarEvent) ?? throw new Exception("Event without a duration."),
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Location = calendarEvent.Location,
|
||||||
|
Recurrence = GoogleIntegratorExtensions.GetRecurrenceString(calendarEvent),
|
||||||
|
Status = GetStatus(calendarEvent.Status),
|
||||||
|
Title = calendarEvent.Summary,
|
||||||
|
UpdatedAt = DateTimeOffset.UtcNow,
|
||||||
|
Visibility = GetVisibility(calendarEvent.Visibility),
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: There are some edge cases with cancellation here.
|
||||||
|
CalendarItemStatus GetStatus(string status)
|
||||||
|
{
|
||||||
|
return status switch
|
||||||
|
{
|
||||||
|
"confirmed" => CalendarItemStatus.Confirmed,
|
||||||
|
"tentative" => CalendarItemStatus.Tentative,
|
||||||
|
"cancelled" => CalendarItemStatus.Cancelled,
|
||||||
|
_ => CalendarItemStatus.Confirmed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
CalendarItemVisibility GetVisibility(string visibility)
|
||||||
|
{
|
||||||
|
/// Visibility of the event. Optional. Possible values are: - "default" - Uses the default visibility for
|
||||||
|
/// events on the calendar. This is the default value. - "public" - The event is public and event details are
|
||||||
|
/// visible to all readers of the calendar. - "private" - The event is private and only event attendees may
|
||||||
|
/// view event details. - "confidential" - The event is private. This value is provided for compatibility
|
||||||
|
/// reasons.
|
||||||
|
|
||||||
|
return visibility switch
|
||||||
|
{
|
||||||
|
"default" => CalendarItemVisibility.Default,
|
||||||
|
"public" => CalendarItemVisibility.Public,
|
||||||
|
"private" => CalendarItemVisibility.Private,
|
||||||
|
"confidential" => CalendarItemVisibility.Confidential,
|
||||||
|
_ => CalendarItemVisibility.Default
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attendees
|
||||||
|
var attendees = new List<CalendarEventAttendee>();
|
||||||
|
|
||||||
|
if (calendarEvent.Attendees == null)
|
||||||
|
{
|
||||||
|
// Self-only event.
|
||||||
|
|
||||||
|
attendees.Add(new CalendarEventAttendee()
|
||||||
|
{
|
||||||
|
CalendarItemId = calendarItem.Id,
|
||||||
|
IsOrganizer = true,
|
||||||
|
Email = organizerAccount.Address,
|
||||||
|
Name = organizerAccount.SenderName,
|
||||||
|
AttendenceStatus = AttendeeStatus.Accepted,
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
IsOptionalAttendee = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var attendee in calendarEvent.Attendees)
|
||||||
|
{
|
||||||
|
if (attendee.Self == true)
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(attendee.Email))
|
||||||
|
{
|
||||||
|
AttendeeStatus GetAttendenceStatus(string responseStatus)
|
||||||
|
{
|
||||||
|
return responseStatus switch
|
||||||
|
{
|
||||||
|
"accepted" => AttendeeStatus.Accepted,
|
||||||
|
"declined" => AttendeeStatus.Declined,
|
||||||
|
"tentative" => AttendeeStatus.Tentative,
|
||||||
|
"needsAction" => AttendeeStatus.NeedsAction,
|
||||||
|
_ => AttendeeStatus.NeedsAction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventAttendee = new CalendarEventAttendee()
|
||||||
|
{
|
||||||
|
CalendarItemId = calendarItem.Id,
|
||||||
|
IsOrganizer = attendee.Organizer ?? false,
|
||||||
|
Comment = attendee.Comment,
|
||||||
|
Email = attendee.Email,
|
||||||
|
Name = attendee.DisplayName,
|
||||||
|
AttendenceStatus = GetAttendenceStatus(attendee.ResponseStatus),
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
IsOptionalAttendee = attendee.Optional ?? false,
|
||||||
|
};
|
||||||
|
|
||||||
|
attendees.Add(eventAttendee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await CalendarService.CreateNewCalendarItemAsync(calendarItem, attendees);
|
||||||
|
|
||||||
|
return calendarItem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ namespace Wino.Core.Integration.Processors
|
|||||||
IFolderService folderService,
|
IFolderService folderService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, accountService, mimeFileService)
|
ICalendarService calendarService,
|
||||||
|
IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, calendarService, accountService, mimeFileService)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ namespace Wino.Core.Integration.Processors
|
|||||||
{
|
{
|
||||||
public class OutlookChangeProcessor(IDatabaseService databaseService,
|
public class OutlookChangeProcessor(IDatabaseService databaseService,
|
||||||
IFolderService folderService,
|
IFolderService folderService,
|
||||||
|
ICalendarService calendarService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, accountService, mimeFileService)
|
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, calendarService, accountService, mimeFileService)
|
||||||
, IOutlookChangeProcessor
|
, IOutlookChangeProcessor
|
||||||
{
|
{
|
||||||
public Task<bool> IsMailExistsAsync(string messageId)
|
public Task<bool> IsMailExistsAsync(string messageId)
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ namespace Wino.Core.Services
|
|||||||
Type = MailSynchronizationType.ExecuteRequests
|
Type = MailSynchronizationType.ExecuteRequests
|
||||||
};
|
};
|
||||||
|
|
||||||
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
|
WeakReferenceMessenger.Default.Send(new NewMailSynchronizationRequested(options, SynchronizationSource.Client));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureServerConnectedAsync()
|
private async Task EnsureServerConnectedAsync()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ using Microsoft.IdentityModel.Tokens;
|
|||||||
using MimeKit;
|
using MimeKit;
|
||||||
using MoreLinq;
|
using MoreLinq;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
@@ -33,6 +34,7 @@ using Wino.Core.Requests.Folder;
|
|||||||
using Wino.Core.Requests.Mail;
|
using Wino.Core.Requests.Mail;
|
||||||
using Wino.Messaging.UI;
|
using Wino.Messaging.UI;
|
||||||
using Wino.Services;
|
using Wino.Services;
|
||||||
|
using CalendarService = Google.Apis.Calendar.v3.CalendarService;
|
||||||
|
|
||||||
namespace Wino.Core.Synchronizers.Mail
|
namespace Wino.Core.Synchronizers.Mail
|
||||||
{
|
{
|
||||||
@@ -47,6 +49,7 @@ namespace Wino.Core.Synchronizers.Mail
|
|||||||
|
|
||||||
private readonly ConfigurableHttpClient _googleHttpClient;
|
private readonly ConfigurableHttpClient _googleHttpClient;
|
||||||
private readonly GmailService _gmailService;
|
private readonly GmailService _gmailService;
|
||||||
|
private readonly CalendarService _calendarService;
|
||||||
private readonly PeopleServiceService _peopleService;
|
private readonly PeopleServiceService _peopleService;
|
||||||
|
|
||||||
private readonly IGmailChangeProcessor _gmailChangeProcessor;
|
private readonly IGmailChangeProcessor _gmailChangeProcessor;
|
||||||
@@ -64,9 +67,10 @@ namespace Wino.Core.Synchronizers.Mail
|
|||||||
};
|
};
|
||||||
|
|
||||||
_googleHttpClient = new ConfigurableHttpClient(messageHandler);
|
_googleHttpClient = new ConfigurableHttpClient(messageHandler);
|
||||||
|
|
||||||
_gmailService = new GmailService(initializer);
|
_gmailService = new GmailService(initializer);
|
||||||
_peopleService = new PeopleServiceService(initializer);
|
_peopleService = new PeopleServiceService(initializer);
|
||||||
|
_calendarService = new CalendarService(initializer);
|
||||||
|
|
||||||
_gmailChangeProcessor = gmailChangeProcessor;
|
_gmailChangeProcessor = gmailChangeProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,109 +288,258 @@ namespace Wino.Core.Synchronizers.Mail
|
|||||||
return MailSynchronizationResult.Completed(unreadNewItems);
|
return MailSynchronizationResult.Completed(unreadNewItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<CalendarSynchronizationResult> SynchronizeCalendarEventsInternalAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default)
|
protected override async Task<CalendarSynchronizationResult> SynchronizeCalendarEventsInternalAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
_logger.Information("Internal calendar synchronization started for {Name}", Account.Name);
|
_logger.Information("Internal calendar synchronization started for {Name}", Account.Name);
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
await SynchronizeCalendarsAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
bool isInitialSync = string.IsNullOrEmpty(Account.SynchronizationDeltaIdentifier);
|
||||||
|
|
||||||
|
_logger.Debug("Is initial synchronization: {IsInitialSync}", isInitialSync);
|
||||||
|
|
||||||
|
var localCalendars = await _gmailChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// TODO: Better logging and exception handling.
|
||||||
|
foreach (var calendar in localCalendars)
|
||||||
|
{
|
||||||
|
var request = _calendarService.Events.List(calendar.RemoteCalendarId);
|
||||||
|
|
||||||
|
request.SingleEvents = false;
|
||||||
|
request.ShowDeleted = true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(calendar.SynchronizationDeltaToken))
|
||||||
|
{
|
||||||
|
// If a sync token is available, perform an incremental sync
|
||||||
|
request.SyncToken = calendar.SynchronizationDeltaToken;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If no sync token, perform an initial sync
|
||||||
|
// Fetch events from the past year
|
||||||
|
|
||||||
|
request.TimeMinDateTimeOffset = DateTimeOffset.UtcNow.AddYears(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
string nextPageToken;
|
||||||
|
string syncToken;
|
||||||
|
|
||||||
|
var allEvents = new List<Event>();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Execute the request
|
||||||
|
var events = await request.ExecuteAsync();
|
||||||
|
|
||||||
|
// Process the fetched events
|
||||||
|
if (events.Items != null)
|
||||||
|
{
|
||||||
|
allEvents.AddRange(events.Items);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next page token and sync token
|
||||||
|
nextPageToken = events.NextPageToken;
|
||||||
|
syncToken = events.NextSyncToken;
|
||||||
|
|
||||||
|
// Set the next page token for subsequent requests
|
||||||
|
request.PageToken = nextPageToken;
|
||||||
|
|
||||||
|
} while (!string.IsNullOrEmpty(nextPageToken));
|
||||||
|
|
||||||
|
calendar.SynchronizationDeltaToken = syncToken;
|
||||||
|
|
||||||
|
await _gmailChangeProcessor.UpdateAccountCalendarAsync(calendar).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var @event in allEvents)
|
||||||
|
{
|
||||||
|
// TODO: Exception handling for event processing.
|
||||||
|
await _gmailChangeProcessor.CreateCalendarItemAsync(@event, calendar, Account).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default)
|
private async Task SynchronizeCalendarsAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
var calendarListRequest = _calendarService.CalendarList.List();
|
||||||
|
var calendarListResponse = await calendarListRequest.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (calendarListResponse.Items == null)
|
||||||
{
|
{
|
||||||
var localFolders = await _gmailChangeProcessor.GetLocalFoldersAsync(Account.Id).ConfigureAwait(false);
|
_logger.Warning("No calendars found for {Name}", Account.Name);
|
||||||
var folderRequest = _gmailService.Users.Labels.List("me");
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var labelsResponse = await folderRequest.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
var localCalendars = await _gmailChangeProcessor.GetAccountCalendarsAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
if (labelsResponse.Labels == null)
|
List<AccountCalendar> insertedCalendars = new();
|
||||||
|
List<AccountCalendar> updatedCalendars = new();
|
||||||
|
List<AccountCalendar> deletedCalendars = new();
|
||||||
|
|
||||||
|
// 1. Handle deleted calendars.
|
||||||
|
|
||||||
|
foreach (var calendar in localCalendars)
|
||||||
|
{
|
||||||
|
var remoteCalendar = calendarListResponse.Items.FirstOrDefault(a => a.Id == calendar.RemoteCalendarId);
|
||||||
|
if (remoteCalendar == null)
|
||||||
{
|
{
|
||||||
_logger.Warning("No folders found for {Name}", Account.Name);
|
// Local calendar doesn't exists remotely. Delete local copy.
|
||||||
return;
|
|
||||||
|
await _gmailChangeProcessor.DeleteAccountCalendarAsync(calendar).ConfigureAwait(false);
|
||||||
|
deletedCalendars.Add(calendar);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<MailItemFolder> insertedFolders = new();
|
// Delete the deleted folders from local list.
|
||||||
List<MailItemFolder> updatedFolders = new();
|
deletedCalendars.ForEach(a => localCalendars.Remove(a));
|
||||||
List<MailItemFolder> deletedFolders = new();
|
|
||||||
|
|
||||||
// 1. Handle deleted labels.
|
// 2. Handle update/insert based on remote calendars.
|
||||||
|
foreach (var calendar in calendarListResponse.Items)
|
||||||
foreach (var localFolder in localFolders)
|
{
|
||||||
|
var existingLocalCalendar = localCalendars.FirstOrDefault(a => a.RemoteCalendarId == calendar.Id);
|
||||||
|
if (existingLocalCalendar == null)
|
||||||
{
|
{
|
||||||
// Category folder is virtual folder for Wino. Skip it.
|
// Insert new calendar.
|
||||||
if (localFolder.SpecialFolderType == SpecialFolderType.Category) continue;
|
var localCalendar = calendar.AsCalendar(Account.Id);
|
||||||
|
insertedCalendars.Add(localCalendar);
|
||||||
var remoteFolder = labelsResponse.Labels.FirstOrDefault(a => a.Id == localFolder.RemoteFolderId);
|
|
||||||
|
|
||||||
if (remoteFolder == null)
|
|
||||||
{
|
|
||||||
// Local folder doesn't exists remotely. Delete local copy.
|
|
||||||
await _gmailChangeProcessor.DeleteFolderAsync(Account.Id, localFolder.RemoteFolderId).ConfigureAwait(false);
|
|
||||||
|
|
||||||
deletedFolders.Add(localFolder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// Delete the deleted folders from local list.
|
|
||||||
deletedFolders.ForEach(a => localFolders.Remove(a));
|
|
||||||
|
|
||||||
// 2. Handle update/insert based on remote folders.
|
|
||||||
foreach (var remoteFolder in labelsResponse.Labels)
|
|
||||||
{
|
{
|
||||||
var existingLocalFolder = localFolders.FirstOrDefault(a => a.RemoteFolderId == remoteFolder.Id);
|
// Update existing calendar. Right now we only update the name.
|
||||||
|
if (ShouldUpdateCalendar(calendar, existingLocalCalendar))
|
||||||
if (existingLocalFolder == null)
|
|
||||||
{
|
{
|
||||||
// Insert new folder.
|
existingLocalCalendar.Name = calendar.Summary;
|
||||||
var localFolder = remoteFolder.GetLocalFolder(labelsResponse, Account.Id);
|
|
||||||
|
|
||||||
insertedFolders.Add(localFolder);
|
updatedCalendars.Add(existingLocalCalendar);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Update existing folder. Right now we only update the name.
|
// Remove it from the local folder list to skip additional calendar updates.
|
||||||
|
localCalendars.Remove(existingLocalCalendar);
|
||||||
// TODO: Moving folders around different parents. This is not supported right now.
|
|
||||||
// We will need more comphrensive folder update mechanism to support this.
|
|
||||||
|
|
||||||
if (ShouldUpdateFolder(remoteFolder, existingLocalFolder))
|
|
||||||
{
|
|
||||||
existingLocalFolder.FolderName = remoteFolder.Name;
|
|
||||||
existingLocalFolder.TextColorHex = remoteFolder.Color?.TextColor;
|
|
||||||
existingLocalFolder.BackgroundColorHex = remoteFolder.Color?.BackgroundColor;
|
|
||||||
|
|
||||||
updatedFolders.Add(existingLocalFolder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Remove it from the local folder list to skip additional folder updates.
|
|
||||||
localFolders.Remove(existingLocalFolder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.Process changes in order-> Insert, Update. Deleted ones are already processed.
|
|
||||||
|
|
||||||
foreach (var folder in insertedFolders)
|
|
||||||
{
|
|
||||||
await _gmailChangeProcessor.InsertFolderAsync(folder).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var folder in updatedFolders)
|
|
||||||
{
|
|
||||||
await _gmailChangeProcessor.UpdateFolderAsync(folder).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (insertedFolders.Any() || deletedFolders.Any() || updatedFolders.Any())
|
|
||||||
{
|
|
||||||
WeakReferenceMessenger.Default.Send(new AccountFolderConfigurationUpdated(Account.Id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
|
||||||
|
// 3.Process changes in order-> Insert, Update. Deleted ones are already processed.
|
||||||
|
foreach (var calendar in insertedCalendars)
|
||||||
{
|
{
|
||||||
throw;
|
await _gmailChangeProcessor.InsertAccountCalendarAsync(calendar).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var calendar in updatedCalendars)
|
||||||
|
{
|
||||||
|
await _gmailChangeProcessor.UpdateAccountCalendarAsync(calendar).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertedCalendars.Any() || deletedCalendars.Any() || updatedCalendars.Any())
|
||||||
|
{
|
||||||
|
// TODO: Notify calendar updates.
|
||||||
|
// WeakReferenceMessenger.Default.Send(new AccountFolderConfigurationUpdated(Account.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var localFolders = await _gmailChangeProcessor.GetLocalFoldersAsync(Account.Id).ConfigureAwait(false);
|
||||||
|
var folderRequest = _gmailService.Users.Labels.List("me");
|
||||||
|
|
||||||
|
var labelsResponse = await folderRequest.ExecuteAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (labelsResponse.Labels == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("No folders found for {Name}", Account.Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MailItemFolder> insertedFolders = new();
|
||||||
|
List<MailItemFolder> updatedFolders = new();
|
||||||
|
List<MailItemFolder> deletedFolders = new();
|
||||||
|
|
||||||
|
// 1. Handle deleted labels.
|
||||||
|
|
||||||
|
foreach (var localFolder in localFolders)
|
||||||
|
{
|
||||||
|
// Category folder is virtual folder for Wino. Skip it.
|
||||||
|
if (localFolder.SpecialFolderType == SpecialFolderType.Category) continue;
|
||||||
|
|
||||||
|
var remoteFolder = labelsResponse.Labels.FirstOrDefault(a => a.Id == localFolder.RemoteFolderId);
|
||||||
|
|
||||||
|
if (remoteFolder == null)
|
||||||
|
{
|
||||||
|
// Local folder doesn't exists remotely. Delete local copy.
|
||||||
|
await _gmailChangeProcessor.DeleteFolderAsync(Account.Id, localFolder.RemoteFolderId).ConfigureAwait(false);
|
||||||
|
|
||||||
|
deletedFolders.Add(localFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the deleted folders from local list.
|
||||||
|
deletedFolders.ForEach(a => localFolders.Remove(a));
|
||||||
|
|
||||||
|
// 2. Handle update/insert based on remote folders.
|
||||||
|
foreach (var remoteFolder in labelsResponse.Labels)
|
||||||
|
{
|
||||||
|
var existingLocalFolder = localFolders.FirstOrDefault(a => a.RemoteFolderId == remoteFolder.Id);
|
||||||
|
|
||||||
|
if (existingLocalFolder == null)
|
||||||
|
{
|
||||||
|
// Insert new folder.
|
||||||
|
var localFolder = remoteFolder.GetLocalFolder(labelsResponse, Account.Id);
|
||||||
|
|
||||||
|
insertedFolders.Add(localFolder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Update existing folder. Right now we only update the name.
|
||||||
|
|
||||||
|
// TODO: Moving folders around different parents. This is not supported right now.
|
||||||
|
// We will need more comphrensive folder update mechanism to support this.
|
||||||
|
|
||||||
|
if (ShouldUpdateFolder(remoteFolder, existingLocalFolder))
|
||||||
|
{
|
||||||
|
existingLocalFolder.FolderName = remoteFolder.Name;
|
||||||
|
existingLocalFolder.TextColorHex = remoteFolder.Color?.TextColor;
|
||||||
|
existingLocalFolder.BackgroundColorHex = remoteFolder.Color?.BackgroundColor;
|
||||||
|
|
||||||
|
updatedFolders.Add(existingLocalFolder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remove it from the local folder list to skip additional folder updates.
|
||||||
|
localFolders.Remove(existingLocalFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.Process changes in order-> Insert, Update. Deleted ones are already processed.
|
||||||
|
|
||||||
|
foreach (var folder in insertedFolders)
|
||||||
|
{
|
||||||
|
await _gmailChangeProcessor.InsertFolderAsync(folder).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var folder in updatedFolders)
|
||||||
|
{
|
||||||
|
await _gmailChangeProcessor.UpdateFolderAsync(folder).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertedFolders.Any() || deletedFolders.Any() || updatedFolders.Any())
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new AccountFolderConfigurationUpdated(Account.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldUpdateCalendar(CalendarListEntry calendarListEntry, AccountCalendar accountCalendar)
|
||||||
|
{
|
||||||
|
// TODO: Only calendar name is updated for now. We can add more checks here.
|
||||||
|
|
||||||
|
var remoteCalendarName = calendarListEntry.Summary;
|
||||||
|
var localCalendarName = accountCalendar.Name;
|
||||||
|
|
||||||
|
return !localCalendarName.Equals(remoteCalendarName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldUpdateFolder(Label remoteFolder, MailItemFolder existingLocalFolder)
|
private bool ShouldUpdateFolder(Label remoteFolder, MailItemFolder existingLocalFolder)
|
||||||
|
|||||||
@@ -257,6 +257,18 @@ namespace Wino.Core.Synchronizers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Batches network requests, executes them, and does the needed synchronization after the batch request execution.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">Synchronization options.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
|
/// <returns>Synchronization result that contains summary of the sync.</returns>
|
||||||
|
public Task<CalendarSynchronizationResult> SynchronizeCalendarEventsAsync(CalendarSynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// TODO: Execute requests for calendar events.
|
||||||
|
return SynchronizeCalendarEventsInternalAsync(options, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates unread item counts for some folders and account.
|
/// Updates unread item counts for some folders and account.
|
||||||
/// Sends a message that shell can pick up and update the UI.
|
/// Sends a message that shell can pick up and update the UI.
|
||||||
@@ -355,5 +367,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
<PackageReference Include="MimeKit" Version="4.9.0" />
|
<PackageReference Include="MimeKit" Version="4.9.0" />
|
||||||
<PackageReference Include="morelinq" Version="4.3.0" />
|
<PackageReference Include="morelinq" Version="4.3.0" />
|
||||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||||
|
<PackageReference Include="NodaTime" Version="3.2.0" />
|
||||||
<PackageReference Include="Serilog" Version="4.1.0" />
|
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ using Wino.Services;
|
|||||||
|
|
||||||
namespace Wino
|
namespace Wino
|
||||||
{
|
{
|
||||||
public sealed partial class App : WinoApplication, IRecipient<NewSynchronizationRequested>
|
public sealed partial class App : WinoApplication, IRecipient<NewMailSynchronizationRequested>
|
||||||
{
|
{
|
||||||
public override string AppCenterKey { get; } = "90deb1d0-a77f-47d0-8a6b-7eaf111c6b72";
|
public override string AppCenterKey { get; } = "90deb1d0-a77f-47d0-8a6b-7eaf111c6b72";
|
||||||
|
|
||||||
@@ -224,11 +224,11 @@ namespace Wino
|
|||||||
AppServiceConnectionManager.Connection = null;
|
AppServiceConnectionManager.Connection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void Receive(NewSynchronizationRequested message)
|
public async void Receive(NewMailSynchronizationRequested message)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<MailSynchronizationResult, NewSynchronizationRequested>(message);
|
var synchronizationResultResponse = await AppServiceConnectionManager.GetResponseAsync<MailSynchronizationResult, NewMailSynchronizationRequested>(message);
|
||||||
synchronizationResultResponse.ThrowIfFailed();
|
synchronizationResultResponse.ThrowIfFailed();
|
||||||
}
|
}
|
||||||
catch (WinoServerException serverException)
|
catch (WinoServerException serverException)
|
||||||
|
|||||||
10
Wino.Messages/Client/Calendar/CalendarEventMessages.cs
Normal file
10
Wino.Messages/Client/Calendar/CalendarEventMessages.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Messaging.Client.Calendar
|
||||||
|
{
|
||||||
|
public record CalendarListRefreshed(List<AccountCalendar> AccountCalendars);
|
||||||
|
public record CalendarListAdded(AccountCalendar AccountCalendar);
|
||||||
|
public record CalendarListUpdated(AccountCalendar AccountCalendar);
|
||||||
|
public record CalendarListDeleted(AccountCalendar AccountCalendar);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Messaging.Client.Calendar
|
||||||
|
{
|
||||||
|
public record CalendarItemAdded(CalendarItem CalendarItem);
|
||||||
|
public record CalendarItemUpdated(CalendarItem CalendarItem);
|
||||||
|
public record CalendarItemDeleted(CalendarItem CalendarItem);
|
||||||
|
}
|
||||||
@@ -5,8 +5,14 @@ using Wino.Core.Domain.Models.Synchronization;
|
|||||||
namespace Wino.Messaging.Server
|
namespace Wino.Messaging.Server
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Triggers a new synchronization if possible.
|
/// Triggers a new mail synchronization if possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Options">Options for synchronization.</param>
|
/// <param name="Options">Options for synchronization.</param>
|
||||||
public record NewSynchronizationRequested(MailSynchronizationOptions Options, SynchronizationSource Source) : IClientMessage;
|
public record NewMailSynchronizationRequested(MailSynchronizationOptions Options, SynchronizationSource Source) : IClientMessage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers a new calendar synchronization if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Options">Options for synchronization.</param>
|
||||||
|
public record NewCalendarSynchronizationRequested(CalendarSynchronizationOptions Options, SynchronizationSource Source) : IClientMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ namespace Wino.Server.Core
|
|||||||
{
|
{
|
||||||
return typeName switch
|
return typeName switch
|
||||||
{
|
{
|
||||||
nameof(NewSynchronizationRequested) => App.Current.Services.GetService<SynchronizationRequestHandler>(),
|
nameof(NewMailSynchronizationRequested) => App.Current.Services.GetService<MailSynchronizationRequestHandler>(),
|
||||||
|
nameof(NewCalendarSynchronizationRequested) => App.Current.Services.GetService<CalendarSynchronizationRequestHandler>(),
|
||||||
nameof(ServerRequestPackage) => App.Current.Services.GetService<UserActionRequestHandler>(),
|
nameof(ServerRequestPackage) => App.Current.Services.GetService<UserActionRequestHandler>(),
|
||||||
nameof(DownloadMissingMessageRequested) => App.Current.Services.GetService<SingleMimeDownloadHandler>(),
|
nameof(DownloadMissingMessageRequested) => App.Current.Services.GetService<SingleMimeDownloadHandler>(),
|
||||||
nameof(AuthorizationRequested) => App.Current.Services.GetService<AuthenticationHandler>(),
|
nameof(AuthorizationRequested) => App.Current.Services.GetService<AuthenticationHandler>(),
|
||||||
@@ -31,7 +32,8 @@ namespace Wino.Server.Core
|
|||||||
{
|
{
|
||||||
// Register all known handlers.
|
// Register all known handlers.
|
||||||
|
|
||||||
serviceCollection.AddTransient<SynchronizationRequestHandler>();
|
serviceCollection.AddTransient<MailSynchronizationRequestHandler>();
|
||||||
|
serviceCollection.AddTransient<CalendarSynchronizationRequestHandler>();
|
||||||
serviceCollection.AddTransient<UserActionRequestHandler>();
|
serviceCollection.AddTransient<UserActionRequestHandler>();
|
||||||
serviceCollection.AddTransient<SingleMimeDownloadHandler>();
|
serviceCollection.AddTransient<SingleMimeDownloadHandler>();
|
||||||
serviceCollection.AddTransient<AuthenticationHandler>();
|
serviceCollection.AddTransient<AuthenticationHandler>();
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Core.Domain.Models.Server;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
using Wino.Messaging.Server;
|
||||||
|
using Wino.Server.Core;
|
||||||
|
|
||||||
|
namespace Wino.Server.MessageHandlers
|
||||||
|
{
|
||||||
|
public class CalendarSynchronizationRequestHandler : ServerMessageHandler<NewCalendarSynchronizationRequested, CalendarSynchronizationResult>
|
||||||
|
{
|
||||||
|
public override WinoServerResponse<CalendarSynchronizationResult> FailureDefaultResponse(Exception ex)
|
||||||
|
=> WinoServerResponse<CalendarSynchronizationResult>.CreateErrorResponse(ex.Message);
|
||||||
|
|
||||||
|
private readonly ISynchronizerFactory _synchronizerFactory;
|
||||||
|
|
||||||
|
public CalendarSynchronizationRequestHandler(ISynchronizerFactory synchronizerFactory)
|
||||||
|
{
|
||||||
|
_synchronizerFactory = synchronizerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<WinoServerResponse<CalendarSynchronizationResult>> HandleAsync(NewCalendarSynchronizationRequested message, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var synchronizer = await _synchronizerFactory.GetAccountSynchronizerAsync(message.Options.AccountId);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var synchronizationResult = await synchronizer.SynchronizeCalendarEventsAsync(message.Options, cancellationToken);
|
||||||
|
|
||||||
|
return WinoServerResponse<CalendarSynchronizationResult>.CreateSuccessResponse(synchronizationResult);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,9 +14,9 @@ using Wino.Server.Core;
|
|||||||
namespace Wino.Server.MessageHandlers
|
namespace Wino.Server.MessageHandlers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handler for NewSynchronizationRequested from the client.
|
/// Handler for NewMailSynchronizationRequested from the client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SynchronizationRequestHandler : ServerMessageHandler<NewSynchronizationRequested, MailSynchronizationResult>
|
public class MailSynchronizationRequestHandler : ServerMessageHandler<NewMailSynchronizationRequested, MailSynchronizationResult>
|
||||||
{
|
{
|
||||||
public override WinoServerResponse<MailSynchronizationResult> FailureDefaultResponse(Exception ex)
|
public override WinoServerResponse<MailSynchronizationResult> FailureDefaultResponse(Exception ex)
|
||||||
=> WinoServerResponse<MailSynchronizationResult>.CreateErrorResponse(ex.Message);
|
=> WinoServerResponse<MailSynchronizationResult>.CreateErrorResponse(ex.Message);
|
||||||
@@ -25,7 +25,7 @@ namespace Wino.Server.MessageHandlers
|
|||||||
private readonly INotificationBuilder _notificationBuilder;
|
private readonly INotificationBuilder _notificationBuilder;
|
||||||
private readonly IFolderService _folderService;
|
private readonly IFolderService _folderService;
|
||||||
|
|
||||||
public SynchronizationRequestHandler(ISynchronizerFactory synchronizerFactory,
|
public MailSynchronizationRequestHandler(ISynchronizerFactory synchronizerFactory,
|
||||||
INotificationBuilder notificationBuilder,
|
INotificationBuilder notificationBuilder,
|
||||||
IFolderService folderService)
|
IFolderService folderService)
|
||||||
{
|
{
|
||||||
@@ -34,7 +34,7 @@ namespace Wino.Server.MessageHandlers
|
|||||||
_folderService = folderService;
|
_folderService = folderService;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<WinoServerResponse<MailSynchronizationResult>> HandleAsync(NewSynchronizationRequested message, CancellationToken cancellationToken = default)
|
protected override async Task<WinoServerResponse<MailSynchronizationResult>> HandleAsync(NewMailSynchronizationRequested message, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var synchronizer = await _synchronizerFactory.GetAccountSynchronizerAsync(message.Options.AccountId);
|
var synchronizer = await _synchronizerFactory.GetAccountSynchronizerAsync(message.Options.AccountId);
|
||||||
|
|
||||||
@@ -84,6 +84,10 @@ namespace Wino.Server
|
|||||||
|
|
||||||
private async void SynchronizationTimerTriggered(object sender, System.Timers.ElapsedEventArgs e)
|
private async void SynchronizationTimerTriggered(object sender, System.Timers.ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
|
// TODO: Disabled for calendar synchronization. Implement a separate timer for calendar synchronization.
|
||||||
|
// or completely separate contexts for both apps.
|
||||||
|
return;
|
||||||
|
|
||||||
// Send sync request for all accounts.
|
// Send sync request for all accounts.
|
||||||
|
|
||||||
var accounts = await _accountService.GetAccountsAsync();
|
var accounts = await _accountService.GetAccountsAsync();
|
||||||
@@ -96,7 +100,7 @@ namespace Wino.Server
|
|||||||
Type = MailSynchronizationType.InboxOnly,
|
Type = MailSynchronizationType.InboxOnly,
|
||||||
};
|
};
|
||||||
|
|
||||||
var request = new NewSynchronizationRequested(options, SynchronizationSource.Server);
|
var request = new NewMailSynchronizationRequested(options, SynchronizationSource.Server);
|
||||||
|
|
||||||
await ExecuteServerMessageSafeAsync(null, request);
|
await ExecuteServerMessageSafeAsync(null, request);
|
||||||
}
|
}
|
||||||
@@ -279,10 +283,15 @@ namespace Wino.Server
|
|||||||
{
|
{
|
||||||
switch (typeName)
|
switch (typeName)
|
||||||
{
|
{
|
||||||
case nameof(NewSynchronizationRequested):
|
case nameof(NewMailSynchronizationRequested):
|
||||||
Debug.WriteLine($"New synchronization requested.");
|
Debug.WriteLine($"New mail synchronization requested.");
|
||||||
|
|
||||||
await ExecuteServerMessageSafeAsync(args, JsonSerializer.Deserialize<NewSynchronizationRequested>(messageJson, _jsonSerializerOptions));
|
await ExecuteServerMessageSafeAsync(args, JsonSerializer.Deserialize<NewMailSynchronizationRequested>(messageJson, _jsonSerializerOptions));
|
||||||
|
break;
|
||||||
|
case nameof(NewCalendarSynchronizationRequested):
|
||||||
|
Debug.WriteLine($"New calendar synchronization requested.");
|
||||||
|
|
||||||
|
await ExecuteServerMessageSafeAsync(args, JsonSerializer.Deserialize<NewCalendarSynchronizationRequested>(messageJson, _jsonSerializerOptions));
|
||||||
break;
|
break;
|
||||||
case nameof(DownloadMissingMessageRequested):
|
case nameof(DownloadMissingMessageRequested):
|
||||||
Debug.WriteLine($"Download missing message requested.");
|
Debug.WriteLine($"Download missing message requested.");
|
||||||
|
|||||||
71
Wino.Services/CalendarService.cs
Normal file
71
Wino.Services/CalendarService.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using SqlKata;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
|
using Wino.Core.Domain.Interfaces;
|
||||||
|
using Wino.Messaging.Client.Calendar;
|
||||||
|
using Wino.Services.Extensions;
|
||||||
|
|
||||||
|
namespace Wino.Services
|
||||||
|
{
|
||||||
|
public class CalendarService : BaseDatabaseService, ICalendarService
|
||||||
|
{
|
||||||
|
public CalendarService(IDatabaseService databaseService) : base(databaseService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<AccountCalendar>> GetAccountCalendarsAsync(Guid accountId)
|
||||||
|
=> Connection.Table<AccountCalendar>().Where(x => x.AccountId == accountId).ToListAsync();
|
||||||
|
|
||||||
|
public async Task InsertAccountCalendarAsync(AccountCalendar accountCalendar)
|
||||||
|
{
|
||||||
|
await Connection.InsertAsync(accountCalendar);
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarListAdded(accountCalendar));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAccountCalendarAsync(AccountCalendar accountCalendar)
|
||||||
|
{
|
||||||
|
await Connection.UpdateAsync(accountCalendar);
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarListUpdated(accountCalendar));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAccountCalendarAsync(AccountCalendar accountCalendar)
|
||||||
|
{
|
||||||
|
var deleteCalendarItemsQuery = new Query()
|
||||||
|
.From(nameof(CalendarItem))
|
||||||
|
.Where(nameof(CalendarItem.CalendarId), accountCalendar.Id)
|
||||||
|
.Where(nameof(AccountCalendar.AccountId), accountCalendar.AccountId);
|
||||||
|
|
||||||
|
var rawQuery = deleteCalendarItemsQuery.GetRawQuery();
|
||||||
|
|
||||||
|
await Connection.ExecuteAsync(rawQuery);
|
||||||
|
await Connection.DeleteAsync(accountCalendar);
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarListDeleted(accountCalendar));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteCalendarItemAsync(Guid calendarItemId)
|
||||||
|
{
|
||||||
|
var calendarItem = await Connection.GetAsync<CalendarItem>(calendarItemId);
|
||||||
|
|
||||||
|
if (calendarItem == null) return;
|
||||||
|
|
||||||
|
await Connection.Table<CalendarItem>().DeleteAsync(x => x.Id == calendarItemId);
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new CalendarItemDeleted(calendarItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CreateNewCalendarItemAsync(CalendarItem calendarItem, List<CalendarEventAttendee> attendees)
|
||||||
|
{
|
||||||
|
return Connection.RunInTransactionAsync((conn) =>
|
||||||
|
{
|
||||||
|
conn.Insert(calendarItem);
|
||||||
|
conn.InsertAll(attendees);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,15 @@ namespace Wino.Services
|
|||||||
{
|
{
|
||||||
public ContactService(IDatabaseService databaseService) : base(databaseService) { }
|
public ContactService(IDatabaseService databaseService) : base(databaseService) { }
|
||||||
|
|
||||||
|
public async Task<AccountContact> CreateNewContactAsync(string address, string displayName)
|
||||||
|
{
|
||||||
|
var contact = new AccountContact() { Address = address, Name = displayName };
|
||||||
|
|
||||||
|
await Connection.InsertAsync(contact).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<List<AccountContact>> GetAddressInformationAsync(string queryText)
|
public Task<List<AccountContact>> GetAddressInformationAsync(string queryText)
|
||||||
{
|
{
|
||||||
if (queryText == null || queryText.Length < 2)
|
if (queryText == null || queryText.Length < 2)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
|
using Wino.Core.Domain.Entities.Calendar;
|
||||||
using Wino.Core.Domain.Entities.Mail;
|
using Wino.Core.Domain.Entities.Mail;
|
||||||
using Wino.Core.Domain.Entities.Shared;
|
using Wino.Core.Domain.Entities.Shared;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
@@ -52,7 +53,11 @@ namespace Wino.Services
|
|||||||
typeof(AccountSignature),
|
typeof(AccountSignature),
|
||||||
typeof(MergedInbox),
|
typeof(MergedInbox),
|
||||||
typeof(MailAccountPreferences),
|
typeof(MailAccountPreferences),
|
||||||
typeof(MailAccountAlias)
|
typeof(MailAccountAlias),
|
||||||
|
typeof(AccountCalendar),
|
||||||
|
typeof(CalendarEventAttendee),
|
||||||
|
typeof(CalendarItem),
|
||||||
|
typeof(Reminder)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace Wino.Services
|
|||||||
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
|
services.AddSingleton<ILaunchProtocolService, LaunchProtocolService>();
|
||||||
services.AddSingleton<IMimeFileService, MimeFileService>();
|
services.AddSingleton<IMimeFileService, MimeFileService>();
|
||||||
|
|
||||||
|
services.AddTransient<ICalendarService, CalendarService>();
|
||||||
services.AddTransient<IMailService, MailService>();
|
services.AddTransient<IMailService, MailService>();
|
||||||
services.AddTransient<IFolderService, FolderService>();
|
services.AddTransient<IFolderService, FolderService>();
|
||||||
services.AddTransient<IAccountService, AccountService>();
|
services.AddTransient<IAccountService, AccountService>();
|
||||||
|
|||||||
Reference in New Issue
Block a user