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
{
///
/// Schnittstellenbeschreibung für einen Service der die Verwaltung von Konversationen ermöglicht
///
public class ConversationService : ServiceBase, IConversationService
{
private readonly ICommunicationService _communicationService;
private readonly ISyncInfoPullService _pullService;
private readonly ISyncInfoPushService _pushService;
private readonly IRepository _appUserRepository;
///
/// Erstellt eine Instanz
///
/// Instanz eines IUnitOfWork
/// Instanz eines ICommunicationService
/// Instanz eines ISyncInfoPullService
/// Instanz eines ISyncInfoPushService
public ConversationService(IUnitOfWork unitOfWork, ICommunicationService communicationService, ISyncInfoPullService syncInfoPullService, ISyncInfoPushService syncInfoPushService) : base(unitOfWork)
{
_communicationService = communicationService;
_pullService = syncInfoPullService;
_pushService = syncInfoPushService;
_appUserRepository = unitOfWork.GetRepository();
}
///
/// Gibt eine Entität anhand der eindeutigen Id zurück
///
/// Id der Entität
/// Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten
/// Entität oder null, wenn nicht gefunden
public override Conversation Get(object id, bool noTracking = true)
{
return Repository.SingleOrDefault(c => c.Id == id.ToString(), noTracking);
}
///
/// Gibt eine Entität anhand der eindeutigen Id zurück
///
/// Id der Entität
/// Gibt an ob NoTRacking verwendet werden soll. Es werden keine Entitäten im EF-Speicher gehalten
/// Entität oder null, wenn nicht gefunden
public override async Task GetAsync(object id, bool noTracking = true)
{
return await Repository.SingleOrDefaultAsync(c => c.Id == id.ToString(), noTracking).ConfigureAwait(false);
}
///
/// Anlegen einer Konversation
///
/// Sender, falls online gesucht werden muss
/// Empfänger nach welcher gesucht wird
/// Aktuelles Accesstoken
/// CancellationToken
/// Konversation
public async Task> CreateAsync(string senderId, AppUserLookup receipient, string accessToken, CancellationToken token)
{
var result = new CommunicationResult()
{
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;
}
///
/// Anlegen einer Konversation
///
/// Sender, falls online gesucht werden muss
/// Id des Emprängers
/// Aktuelles Accesstoken
/// CancellationToken
/// CommunicationResult
public async Task> CreateAsync(string senderId, string receipientId, string accessToken, CancellationToken token)
{
var result = new CommunicationResult()
{
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;
}
///
/// Gibt eine Liste der Konversationen zurück
///
/// Aktuelles Accesstoken
/// CancellationToken
/// Id des Senders
/// Liste Konversatinen
public async Task> 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();
}
///
/// Gibt eine Liste der Konversationen zurück
///
/// Filter
/// Aktuelles Accesstoken
/// CancellationToken
/// Id des Senders
/// Liste Konversatinen
public async Task> 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();
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();
}
///
/// Gibt eine Konversation zurück welche ein bestimmtes Ziel hat
///
/// Sender, falls online gesucht werden muss
/// EmpfängerId nach welcher gesucht wird
/// Aktuelles Accesstoken
/// CancellationToken
///
public async Task 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();
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;
}
///
/// Setzen der letzten Nachricht für eine Konversation
///
/// Id der Konversation
/// Id der letzten Nachricht
/// Vorschautext der letzten Nachricht
/// Datum letztes Update
/// Task
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);
}
}
///
/// Gibt zurück ob es Konversationen mit ungelesenen nachrichten gibt
///
///
public async Task HasUnreadAsync()
{
var unread = await Repository.CountAsync(c => c.HasUnreadMessages == true).ConfigureAwait(false);
return unread > 0;
}
#region Pull-Push Implementation
///
/// Holen der letzten Daten vom Server und Synchronisieren mit den lokalen Daten
///
/// Sprache
/// Aktuelles Accesstoken
/// CancellationToken
/// Task
async Task> ISyncPushPullService.PullAsync(string language, string accessToken, CancellationToken token)
{
var result = new SyncResult();
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;
}
///
/// Holen der Daten vom lokalen Speicher die noch nicht synchronisiert wurden und senden an den Server
///
/// Aktuelles Accesstoken
/// CancellationToken
/// Task
async Task> ISyncPushPullService.PushAsync(string accessToken, CancellationToken token)
{
var result = new SyncResult();
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(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
///
/// Behandeln der Liste von Konversationen wenn welche vom Online-Store geholt werden.
///
/// Liste der Konversationen
/// Task
private async Task> HandleChangesAsync(List conversations)
{
var result = new SyncResult();
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
}
}