536 lines
24 KiB
C#
536 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
using gehGassi.Dto;
|
|
using gehGassi.Dto.Messages;
|
|
using gehGassiApp.Core.Data;
|
|
using gehGassiApp.Core.Helper;
|
|
using gehGassiApp.Core.Interfaces;
|
|
using gehGassiApp.Core.Interfaces.Synchronization;
|
|
using gehGassiApp.Domain.Common;
|
|
using gehGassiApp.Domain.Lookup;
|
|
using gehGassiApp.Domain.Messages;
|
|
using gehGassiApp.Domain.Users;
|
|
|
|
namespace gehGassiApp.Core.Services
|
|
{
|
|
/// <summary>
|
|
/// Schnittstellenbeschreibung für einen Service der die Verwaltung von Konversationen ermöglicht
|
|
/// </summary>
|
|
public class ConversationService : ServiceBase<Conversation>, IConversationService
|
|
{
|
|
private readonly ICommunicationService _communicationService;
|
|
private readonly ISyncInfoPullService _pullService;
|
|
private readonly ISyncInfoPushService _pushService;
|
|
private readonly IRepository<AppUser> _appUserRepository;
|
|
|
|
/// <summary>
|
|
/// Erstellt eine Instanz
|
|
/// </summary>
|
|
/// <param name="unitOfWork">Instanz eines IUnitOfWork</param>
|
|
/// <param name="communicationService">Instanz eines ICommunicationService</param>
|
|
/// <param name="syncInfoPullService">Instanz eines ISyncInfoPullService</param>
|
|
/// <param name="syncInfoPushService">Instanz eines ISyncInfoPushService</param>
|
|
public ConversationService(IUnitOfWork unitOfWork, ICommunicationService communicationService, ISyncInfoPullService syncInfoPullService, ISyncInfoPushService syncInfoPushService) : base(unitOfWork)
|
|
{
|
|
_communicationService = communicationService;
|
|
_pullService = syncInfoPullService;
|
|
_pushService = syncInfoPushService;
|
|
_appUserRepository = unitOfWork.GetRepository<AppUser>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt eine Entität anhand der eindeutigen Id zurück
|
|
/// </summary>
|
|
/// <param name="id">Id der Entität</param>
|
|
/// <param name="noTracking">Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten</param>
|
|
/// <returns>Entität oder null, wenn nicht gefunden</returns>
|
|
public override Conversation Get(object id, bool noTracking = true)
|
|
{
|
|
return Repository.SingleOrDefault(c => c.Id == id.ToString(), noTracking);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt eine Entität anhand der eindeutigen Id zurück
|
|
/// </summary>
|
|
/// <param name="id">Id der Entität</param>
|
|
/// <param name="noTracking">Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten</param>
|
|
/// <returns>Entität oder null, wenn nicht gefunden</returns>
|
|
public override async Task<Conversation> GetAsync(object id, bool noTracking = true)
|
|
{
|
|
return await Repository.SingleOrDefaultAsync(c => c.Id == id.ToString(), noTracking).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Anlegen einer Konversation
|
|
/// </summary>
|
|
/// <param name="senderId">Sender, falls online gesucht werden muss</param>
|
|
/// <param name="receipient">Empfänger nach welcher gesucht wird</param>
|
|
/// <param name="accessToken">Aktuelles Accesstoken</param>
|
|
/// <param name="token">CancellationToken</param>
|
|
/// <returns>Konversation</returns>
|
|
public async Task<CommunicationResult<Conversation>> CreateAsync(string senderId, AppUserLookup receipient, string accessToken, CancellationToken token)
|
|
{
|
|
var result = new CommunicationResult<Conversation>()
|
|
{
|
|
Success = false,
|
|
};
|
|
|
|
//Zuerst versuchen den Datensatz online anzulegen
|
|
var conversationId = Guid.NewGuid().ToString("N");
|
|
|
|
var isConnected = await _communicationService.IsConnected();
|
|
if (isConnected)
|
|
{
|
|
var createResult = await _communicationService.CreateConversationAsync(conversationId, senderId, receipient.Id, accessToken, token);
|
|
if (createResult.Success)
|
|
{
|
|
if (createResult.Value.Status == CreateStatus.Success || createResult.Value.Status == CreateStatus.Exists)
|
|
{
|
|
var createResponse = createResult.Value;
|
|
Repository.Add(createResponse.Value);
|
|
await CommitAsync();
|
|
|
|
result.Success = true;
|
|
result.Value = createResponse.Value;
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
//TODO: Achtung, man kann nur eine Konversation anlegen wenn man online ist
|
|
//else
|
|
//{
|
|
// //In der DB anlegen und Delta erstellen
|
|
// var conversation = new Conversation
|
|
// {
|
|
// Id = conversationId,
|
|
// Recipient = receipient.Id,
|
|
// Created = DateTimeOffset.UtcNow,
|
|
// ReceipientType = receipient.Type,
|
|
// UpdatedAt = DateTimeOffset.UtcNow,
|
|
// RecipientPhoto = receipient.Photo,
|
|
// RecipientName = receipient.FullName,
|
|
// RecipientShort = receipient.Initials
|
|
// };
|
|
|
|
// Repository.Add(conversation);
|
|
// await CommitAsync().ConfigureAwait(false);
|
|
|
|
// await _pushService.AddAsync(nameof(Conversation), conversation.Id, SyncOperation.Create, conversation).ConfigureAwait(false);
|
|
|
|
// return conversation;
|
|
//}
|
|
result.ErrorCode = CommunicationErrors.ServerNoConnection;
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Anlegen einer Konversation
|
|
/// </summary>
|
|
/// <param name="senderId">Sender, falls online gesucht werden muss</param>
|
|
/// <param name="receipientId">Id des Emprängers</param>
|
|
/// <param name="accessToken">Aktuelles Accesstoken</param>
|
|
/// <param name="token">CancellationToken</param>
|
|
/// <returns>CommunicationResult</returns>
|
|
public async Task<CommunicationResult<Conversation>> CreateAsync(string senderId, string receipientId, string accessToken, CancellationToken token)
|
|
{
|
|
var result = new CommunicationResult<Conversation>()
|
|
{
|
|
Success = false,
|
|
};
|
|
|
|
//Zuerst versuchen den Datensatz online anzulegen
|
|
var conversationId = Guid.NewGuid().ToString("N");
|
|
|
|
var isConnected = await _communicationService.IsConnected();
|
|
if (isConnected)
|
|
{
|
|
var createResult = await _communicationService.CreateConversationAsync(conversationId, senderId, receipientId, accessToken, token);
|
|
if (createResult.Success)
|
|
{
|
|
if (createResult.Value.Status == CreateStatus.Success || createResult.Value.Status == CreateStatus.Exists)
|
|
{
|
|
var createResponse = createResult.Value;
|
|
Repository.Add(createResponse.Value);
|
|
await CommitAsync();
|
|
|
|
result.Success = true;
|
|
result.Value = createResponse.Value;
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
result.ErrorCode = CommunicationErrors.ServerNoConnection;
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt eine Liste der Konversationen zurück
|
|
/// </summary>
|
|
/// <param name="accessToken">Aktuelles Accesstoken</param>
|
|
/// <param name="token">CancellationToken</param>
|
|
/// <param name="senderId">Id des Senders</param>
|
|
/// <returns>Liste Konversatinen</returns>
|
|
public async Task<List<Conversation>> GetConversationsAsync(string senderId, string accessToken, CancellationToken token)
|
|
{
|
|
//Zuerst DB
|
|
var localConversations = (await Repository.FindAsync(c => c.Deleted == false).ConfigureAwait(false)).ToList();
|
|
if (localConversations.Any())
|
|
return localConversations.OrderByDescending(c => c.UpdatedAt).ToList();
|
|
else
|
|
{
|
|
//Online...
|
|
var isConnected = await _communicationService.IsConnected();
|
|
//isConnected = false;
|
|
if (isConnected)
|
|
{
|
|
var conversationsResult = await _communicationService.GetConversationsAsync(senderId, null, accessToken, token);
|
|
if (conversationsResult.Success)
|
|
{
|
|
var result = await HandleChangesAsync(conversationsResult.Value);
|
|
if (result.LastUpdate != null)
|
|
await _pullService.AddOrUpddateAsync(nameof(Conversation), result.LastUpdate.Value).ConfigureAwait(false);
|
|
return conversationsResult.Value.Where(c => c.Deleted == false).OrderByDescending(c => c.UpdatedAt).ToList();
|
|
}
|
|
}
|
|
}
|
|
//Keine Daten online, keine offline
|
|
return new List<Conversation>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt eine Liste der Konversationen zurück
|
|
/// </summary>
|
|
/// <param name="filter">Filter</param>
|
|
/// <param name="accessToken">Aktuelles Accesstoken</param>
|
|
/// <param name="token">CancellationToken</param>
|
|
/// <param name="senderId">Id des Senders</param>
|
|
/// <returns>Liste Konversatinen</returns>
|
|
public async Task<List<Conversation>> GetConversationsAsync(string senderId, string filter, string accessToken, CancellationToken token)
|
|
{
|
|
//Online...
|
|
var isConnected = await _communicationService.IsConnected();
|
|
//isConnected = false;
|
|
if (isConnected)
|
|
{
|
|
var conversationsResult = await _communicationService.GetConversationsWithFilterAsync(senderId, filter, accessToken, token);
|
|
if (conversationsResult.Success)
|
|
{
|
|
var result = await HandleChangesAsync(conversationsResult.Value);
|
|
if (result.LastUpdate != null)
|
|
await _pullService.AddOrUpddateAsync(nameof(Conversation), result.LastUpdate.Value).ConfigureAwait(false);
|
|
|
|
//Wir müssen tricksen und nun die Conversationen von der DB holen, damit auch Daten wie letzte Nachricht usw. dabei ist...
|
|
var conversationsList = new List<Conversation>();
|
|
foreach (var conversation in conversationsResult.Value)
|
|
{
|
|
var localConversation = await Repository.FirstOrDefaultAsync(c => c.Id == conversation.Id).ConfigureAwait(false);
|
|
if (localConversation != null)
|
|
conversationsList.Add(localConversation);
|
|
}
|
|
|
|
return conversationsList.Where(c => c.Deleted == false).OrderByDescending(c => c.UpdatedAt).ToList();
|
|
}
|
|
}
|
|
return new List<Conversation>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt eine Konversation zurück welche ein bestimmtes Ziel hat
|
|
/// </summary>
|
|
/// <param name="senderId">Sender, falls online gesucht werden muss</param>
|
|
/// <param name="receipientId">EmpfängerId nach welcher gesucht wird</param>
|
|
/// <param name="accessToken">Aktuelles Accesstoken</param>
|
|
/// <param name="token">CancellationToken</param>
|
|
/// <returns></returns>
|
|
public async Task<Conversation> GetByReceipientAsync(string senderId, string receipientId, string accessToken, CancellationToken token)
|
|
{
|
|
//Zuerst DB
|
|
var localConversation = await Repository.FirstOrDefaultAsync(c => c.Recipient == receipientId).ConfigureAwait(false);
|
|
if (localConversation != null)
|
|
{
|
|
if (localConversation.Deleted)
|
|
{
|
|
localConversation.Deleted = false;
|
|
Repository.Update(localConversation);
|
|
await CommitAsync();
|
|
}
|
|
|
|
return localConversation;
|
|
}
|
|
|
|
//Nun online versuchen
|
|
var isConnected = await _communicationService.IsConnected();
|
|
//isConnected = false;
|
|
if (isConnected)
|
|
{
|
|
var conversationsResult = await _communicationService.GetConversationByReceipientAsync(senderId, receipientId, accessToken, token);
|
|
if (conversationsResult.Success)
|
|
{
|
|
var list = new List<Conversation>();
|
|
list.Add(conversationsResult.Value);
|
|
var result = await HandleChangesAsync(list);
|
|
if (result.LastUpdate != null)
|
|
await _pullService.AddOrUpddateAsync(nameof(Conversation), result.LastUpdate.Value).ConfigureAwait(false);
|
|
return conversationsResult.Value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setzen der letzten Nachricht für eine Konversation
|
|
/// </summary>
|
|
/// <param name="conversationId">Id der Konversation</param>
|
|
/// <param name="messageId">Id der letzten Nachricht</param>
|
|
/// <param name="messagePreview">Vorschautext der letzten Nachricht</param>
|
|
/// <param name="lastUpdate">Datum letztes Update</param>
|
|
/// <returns>Task</returns>
|
|
public async Task SetLastMessageAsync(string conversationId, string messageId, string messagePreview, DateTimeOffset lastUpdate)
|
|
{
|
|
var conversation = await Repository.FirstOrDefaultAsync(c => c.Id == conversationId, false).ConfigureAwait(false);
|
|
if (conversation != null)
|
|
{
|
|
conversation.LastMessageId = messageId;
|
|
conversation.LastMessagePreview = messagePreview.Ellipsis(100);
|
|
conversation.UpdatedAt = lastUpdate;
|
|
Repository.Update(conversation);
|
|
await CommitAsync().ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt zurück ob es Konversationen mit ungelesenen nachrichten gibt
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<bool> HasUnreadAsync()
|
|
{
|
|
var unread = await Repository.CountAsync(c => c.HasUnreadMessages == true).ConfigureAwait(false);
|
|
return unread > 0;
|
|
}
|
|
|
|
#region Pull-Push Implementation
|
|
|
|
/// <summary>
|
|
/// Holen der letzten Daten vom Server und Synchronisieren mit den lokalen Daten
|
|
/// </summary>
|
|
/// <param name="language">Sprache</param>
|
|
/// <param name="accessToken">Aktuelles Accesstoken</param>
|
|
/// <param name="token">CancellationToken</param>
|
|
/// <returns>Task</returns>
|
|
async Task<SyncResult<Conversation>> ISyncPushPullService<Conversation>.PullAsync(string language, string accessToken, CancellationToken token)
|
|
{
|
|
var result = new SyncResult<Conversation>();
|
|
|
|
var isConnected = await _communicationService.IsConnected();
|
|
|
|
if (isConnected)
|
|
{
|
|
//Zuerst lezte Aktivität holen...
|
|
DateTimeOffset? lastUpdate = null;
|
|
var lastSyncInfo = await _pullService.GetAsync(nameof(Conversation));
|
|
if (lastSyncInfo != null)
|
|
lastUpdate = lastSyncInfo.LastUpdate;
|
|
|
|
var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false);
|
|
|
|
var conversationsResult = await _communicationService.GetConversationsAsync(user.Id, lastUpdate, accessToken, token);
|
|
if (conversationsResult.Success)
|
|
{
|
|
result = await HandleChangesAsync(conversationsResult.Value);
|
|
if (result.LastUpdate != null)
|
|
await _pullService.AddOrUpddateAsync(nameof(Conversation), result.LastUpdate.Value).ConfigureAwait(false);
|
|
}
|
|
|
|
result.LastUpdate ??= lastUpdate;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Holen der Daten vom lokalen Speicher die noch nicht synchronisiert wurden und senden an den Server
|
|
/// </summary>
|
|
/// <param name="accessToken">Aktuelles Accesstoken</param>
|
|
/// <param name="token">CancellationToken</param>
|
|
/// <returns>Task</returns>
|
|
async Task<SyncResult<Conversation>> ISyncPushPullService<Conversation>.PushAsync(string accessToken, CancellationToken token)
|
|
{
|
|
var result = new SyncResult<Conversation>();
|
|
|
|
if (await _pushService.HasOpenAsync(nameof(Conversation)) > 0)
|
|
{
|
|
var deltas = await _pushService.GetAllAsync(nameof(Conversation));
|
|
if (deltas.Any())
|
|
{
|
|
var isConnected = await _communicationService.IsConnected();
|
|
|
|
if (!isConnected) return result;
|
|
|
|
var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false);
|
|
|
|
deltas = deltas.OrderBy(c => c.DateTime).ToList();
|
|
foreach (var syncInfoPush in deltas)
|
|
{
|
|
//Löschen einer Konversation geben wir nicht weiter... Update gibt es eigentlich auch keines
|
|
if (syncInfoPush.Operation == SyncOperation.Delete || syncInfoPush.Operation == SyncOperation.Edit)
|
|
{
|
|
await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false);
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(syncInfoPush.Value))
|
|
{
|
|
var conversation = JsonSerializer.Deserialize<Conversation>(syncInfoPush.Value, new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
|
var model = new CreateConversationDto()
|
|
{
|
|
Id = conversation.Id,
|
|
SenderId = user.Id,
|
|
ReceiverId = conversation.Recipient
|
|
};
|
|
|
|
var createResult = await _communicationService.CreateConversationAsync(conversation.Id, user.Id, conversation.Recipient, accessToken, token);
|
|
if (createResult.Success)
|
|
{
|
|
if (createResult.Value.Status == CreateStatus.Exists)
|
|
{
|
|
//Neue Konversation anlegen alte löschen.
|
|
//Deltas der alten Nachrichten werden am Server ignoriert...
|
|
|
|
var newConversation = createResult.Value.Value;
|
|
Repository.Add(newConversation);
|
|
await CommitAsync().ConfigureAwait(false);
|
|
|
|
var existingConversation = await Repository.FirstOrDefaultAsync(c => c.Id == conversation.Id, false).ConfigureAwait(false);
|
|
Remove(existingConversation);
|
|
await CommitAsync().ConfigureAwait(false);
|
|
|
|
}
|
|
await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false);
|
|
}
|
|
else
|
|
{
|
|
//Fehler, weg damit
|
|
await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false);
|
|
}
|
|
//Else ist nichts tun, konnte nicht übertragen werden.
|
|
}
|
|
else
|
|
{
|
|
await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine(ex.Message);
|
|
await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Es gibt hier kein Push!
|
|
await Task.Delay(1);
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private
|
|
|
|
/// <summary>
|
|
/// Behandeln der Liste von Konversationen wenn welche vom Online-Store geholt werden.
|
|
/// </summary>
|
|
/// <param name="conversations">Liste der Konversationen</param>
|
|
/// <returns>Task</returns>
|
|
private async Task<SyncResult<Conversation>> HandleChangesAsync(List<Conversation> conversations)
|
|
{
|
|
var result = new SyncResult<Conversation>();
|
|
|
|
foreach (var conversation in conversations)
|
|
{
|
|
var localConversation = await Repository.FirstOrDefaultAsync(c => c.Id == conversation.Id, false).ConfigureAwait(false);
|
|
if (localConversation != null && localConversation.UpdatedAt < conversation.UpdatedAt)
|
|
{
|
|
if (localConversation.UpdatedAt >= conversation.UpdatedAt)
|
|
{
|
|
if (result.LastUpdate == null || result.LastUpdate < localConversation.UpdatedAt)
|
|
result.LastUpdate = localConversation.UpdatedAt;
|
|
continue;
|
|
}
|
|
|
|
var deleted = localConversation.Deleted == false && conversation.Deleted;
|
|
|
|
localConversation.Version = conversation.Version;
|
|
localConversation.UpdatedAt = conversation.UpdatedAt;
|
|
localConversation.Deleted = conversation.Deleted;
|
|
localConversation.Recipient = conversation.Recipient;
|
|
localConversation.ReceipientType = conversation.ReceipientType;
|
|
localConversation.RecipientName = conversation.RecipientName;
|
|
localConversation.RecipientShort = conversation.RecipientShort;
|
|
localConversation.RecipientPhoto = conversation.RecipientPhoto;
|
|
|
|
if (result.LastUpdate == null || result.LastUpdate < localConversation.UpdatedAt)
|
|
result.LastUpdate = localConversation.UpdatedAt;
|
|
if (!deleted)
|
|
result.Updated.Add(localConversation);
|
|
else
|
|
result.Deleted.Add(localConversation);
|
|
Repository.Update(localConversation);
|
|
}
|
|
|
|
if (localConversation == null)
|
|
{
|
|
localConversation = new Conversation()
|
|
{
|
|
Id = conversation.Id,
|
|
Version = conversation.Version,
|
|
UpdatedAt = conversation.UpdatedAt,
|
|
Deleted = conversation.Deleted,
|
|
Recipient = conversation.Recipient,
|
|
ReceipientType = conversation.ReceipientType,
|
|
RecipientName = conversation.RecipientName,
|
|
RecipientShort = conversation.RecipientShort,
|
|
RecipientPhoto = conversation.RecipientPhoto,
|
|
Created = conversation.Created
|
|
};
|
|
|
|
Repository.Add(localConversation);
|
|
|
|
if (result.LastUpdate == null || result.LastUpdate < localConversation.UpdatedAt)
|
|
result.LastUpdate = localConversation.UpdatedAt;
|
|
result.Added.Add(localConversation);
|
|
}
|
|
}
|
|
|
|
if (result.HasChanges)
|
|
{
|
|
try
|
|
{
|
|
await CommitAsync().ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var err = ex.Message;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
}
|
|
}
|