using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using gehGassi.Dto; using gehGassi.Dto.DogWalkers; using gehGassi.Dto.Walks; using gehGassiApp.Core.Data; using gehGassiApp.Core.Interfaces; using gehGassiApp.Core.Interfaces.Synchronization; using gehGassiApp.Core.Mapper; using gehGassiApp.Core.Resources; using gehGassiApp.Domain.Common; using gehGassiApp.Domain.Messages; using gehGassiApp.Domain.News; using gehGassiApp.Domain.Users; using gehGassiApp.Domain.Walkers; namespace gehGassiApp.Core.Services { /// /// Service der die Verfügbarkeit eines Walkers für Walks je wochentag ermöglicht /// public class WalkingTimeService : ServiceBase, IWalkingTimeService { 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 WalkingTimeService(IUnitOfWork unitOfWork, ICommunicationService communicationService, ISyncInfoPullService syncInfoPullService, ISyncInfoPushService syncInfoPushService) : base(unitOfWork) { _communicationService = communicationService; _pullService = syncInfoPullService; _pushService = syncInfoPushService; _appUserRepository = unitOfWork.GetRepository(); } public override WalkingTime Get(object id, bool noTracking = true) { throw new NotImplementedException(); } public override async Task GetAsync(object id, bool noTracking = true) { await Task.Delay(1); throw new NotImplementedException(); } /// /// Gibt eine Liste aller WalkingTimes eines DogWalkers zurück /// /// Id des DogWalkers /// Unbedingt online versuchen /// Aktuelles Accesstoken /// CancellationToken /// CommunicationResult public async Task>> GetAllAsync(string dogWalkerId, bool forceOnline, string accessToken, CancellationToken token) { var result = new CommunicationResult>() { Value = new List() }; if (!forceOnline) { var walkingTimes = await Repository.FindAsync(c => c.DogWalkerId == dogWalkerId && c.Deleted == false).ConfigureAwait(false); if (walkingTimes != null && walkingTimes.Any()) { result.Value = walkingTimes.OrderBy(c => c.Day).ThenBy(c => c.Start).ToList(); result.Success = true; return result; } } var isConnected = await _communicationService.IsConnected(); if (isConnected) { var walkingTimesResult = await _communicationService.GetAllWalkingTimesAsync(dogWalkerId, accessToken, token); if (walkingTimesResult.Success) { var changeResult = await HandleChangesAsync(walkingTimesResult.Value); if (changeResult.LastUpdate != null) await _pullService.AddOrUpddateAsync(nameof(WalkingTime), changeResult.LastUpdate.Value).ConfigureAwait(false); return walkingTimesResult; } } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// Hinzufügen einer WalkingTime /// /// Id des Dogwalkers /// Wochentag /// Startzeit /// Endzeit /// Aktiv /// Aktuelles Accesstoken /// CancellationToken /// WalkingTime public async Task AddAsync(string dogWalkerId, DayOfWeek day, TimeSpan start, TimeSpan end, bool enabled, string accessToken, CancellationToken token) { var walkingTimeId = Guid.NewGuid().ToString("N"); var walkingTime = new WalkingTime() { Id = walkingTimeId, UpdatedAt = DateTimeOffset.UtcNow, DogWalkerId = dogWalkerId, Day = day, Start = start, End = end, Enabled = enabled, Deleted = false }; Repository.Add(walkingTime); await CommitAsync(); //Jetzt die WalkingTime an der Server übertragen oder ein Delta erstellen var isConnected = await _communicationService.IsConnected(); var createDelta = true; if (isConnected) { var walkingTimeDto = walkingTime.ToDto(); var createResult = await _communicationService.CreateWalkingTimeAsync(walkingTimeDto, accessToken, token); if (createResult.Success) createDelta = false; } if (createDelta) { await _pushService.AddAsync(nameof(WalkingTime), walkingTime.Id, SyncOperation.Create, walkingTime).ConfigureAwait(false); } return walkingTime; } /// /// Aktualisieren einer WalkingTime /// /// WalkingTime /// Aktuelles Accesstoken /// CancellationToken /// True wenn erfolgreich, false sonst public async Task UpdateAsync(WalkingTime walkingTime, string accessToken, CancellationToken token) { var localWalkingTime = await Repository.FirstOrDefaultAsync(c => c.Id == walkingTime.Id, false).ConfigureAwait(false); if (localWalkingTime != null && !localWalkingTime.Deleted) { bool hasChanges = localWalkingTime.Day != walkingTime.Day || localWalkingTime.Start != walkingTime.Start || localWalkingTime.End != walkingTime.End || localWalkingTime.Enabled != walkingTime.Enabled; if (hasChanges) { localWalkingTime.Day = walkingTime.Day; localWalkingTime.Start = walkingTime.Start; localWalkingTime.End = walkingTime.End; localWalkingTime.Enabled = walkingTime.Enabled; localWalkingTime.UpdatedAt = DateTimeOffset.UtcNow; Repository.Update(localWalkingTime); await CommitAsync().ConfigureAwait(false); var isConnected = await _communicationService.IsConnected(); var createDelta = true; if (isConnected) { var walkingTimeDto = localWalkingTime.ToDto(); var updateResult = await _communicationService.UpdateWalkingTimeAsync(walkingTimeDto, accessToken, token); if (updateResult.Success && updateResult.Value) createDelta = false; } if (createDelta) { await _pushService.AddAsync(nameof(WalkingTime), localWalkingTime.Id, SyncOperation.Edit, localWalkingTime).ConfigureAwait(false); } } return true; } return false; } /// /// Echtes Löschen einer WalkingTime /// /// Id der WalkingTime /// Id des AppBenutzers /// Aktuelles Accesstoken /// CancellationToken /// True wenn erfolgreich, false sonst public async Task DeleteAsync(string walkingTimeId, string appUserId, string accessToken, CancellationToken token) { var localWalkingTime = await Repository.FirstOrDefaultAsync(c => c.Id == walkingTimeId, false).ConfigureAwait(false); if (localWalkingTime != null && localWalkingTime.DogWalkerId == appUserId) { Repository.Remove(localWalkingTime); await CommitAsync().ConfigureAwait(false); var walkingTimeDto = localWalkingTime.ToDto(); var isConnected = await _communicationService.IsConnected(); var createDelta = true; if (isConnected) { var deleteResult = await _communicationService.DeleteWalkingTimeAsync(walkingTimeDto, appUserId, accessToken, token); if (deleteResult.Success && deleteResult.Value) createDelta = false; } if (createDelta) { await _pushService.AddAsync(nameof(WalkingTime), localWalkingTime.Id, SyncOperation.Delete, localWalkingTime).ConfigureAwait(false); } return true; } return false; } #region Pull-Push Implementation /// /// Holen der letzten Daten vom Server und Synchronisieren mit den lokalen Daten /// /// Sprache /// Aktuelles Accesstoken /// CancellationToken /// Task public async Task> PullAsync(string language, string accessToken, CancellationToken token) { var result = new SyncResult(); var isConnected = await _communicationService.IsConnected(); if (isConnected) { var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false); //Zuerst lezte Aktivität holen... DateTimeOffset? lastUpdate = null; var lastSyncInfo = await _pullService.GetAsync(nameof(WalkingTime)); if (lastSyncInfo != null) lastUpdate = lastSyncInfo.LastUpdate; var requestResult = await _communicationService.GetWalkingTimesForSyncAsync(user.Id, lastUpdate, accessToken, token); if (requestResult.Success) { result = await HandleChangesAsync(requestResult.Value); if (result.LastUpdate != null) await _pullService.AddOrUpddateAsync(nameof(WalkingTime), 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 public async Task> PushAsync(string accessToken, CancellationToken token) { var result = new SyncResult(); if (await _pushService.HasOpenAsync(nameof(WalkingTime)) > 0) { var deltas = await _pushService.GetAllAsync(nameof(WalkingTime)); if (deltas.Any()) { var user = await _appUserRepository.FirstOrDefaultAsync(c => c.Id != "", false).ConfigureAwait(false); deltas = deltas.OrderBy(c => c.DateTime).ToList(); var isConnected = await _communicationService.IsConnected(); if (!isConnected) return result; foreach (var syncInfoPush in deltas) { try { if (!string.IsNullOrWhiteSpace(syncInfoPush.Value)) { var request = JsonSerializer.Deserialize(syncInfoPush.Value, new JsonSerializerOptions(JsonSerializerDefaults.Web)); if (syncInfoPush.Operation == SyncOperation.Create) { var createDto = request.ToDto(); var createResult = await _communicationService.CreateWalkingTimeAsync(createDto, accessToken, token); if (createResult.Success && createResult.Value.Status != CreateStatus.Error) { await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } //TODO: Was tun wenn Fehler? } else if (syncInfoPush.Operation == SyncOperation.Edit) { var updateDto = request.ToDto(); var updateResult = await _communicationService.UpdateWalkingTimeAsync(updateDto, accessToken, token); if (updateResult.Success && updateResult.Value) await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); //TODO: Was tun wenn Fehler? } else { var deleteDto = request.ToDto(); var deleteResult = await _communicationService.DeleteWalkingTimeAsync(deleteDto, user.Id, accessToken, token); if (deleteResult.Success && deleteResult.Value) await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); //TODO: Was tun wenn Fehler? } } else { await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); await _pushService.RemoveAsync(syncInfoPush.Id).ConfigureAwait(false); } } //Else ist nichts tun, konnte nicht übertragen werden. } } return result; } #endregion #region Private /// /// Behandeln der Liste von öffentlichern Anfragen wenn welche vom Online-Store geholt werden. /// /// Liste der anfragen /// Task private async Task> HandleChangesAsync(List walkingTimes) { var result = new SyncResult(); //Je Rasse durchgehen ob was gemacht werden soll foreach (var walkingTime in walkingTimes) { var localWalkingTime = await Repository.FirstOrDefaultAsync(c => c.Id == walkingTime.Id, false); if (localWalkingTime != null && localWalkingTime.UpdatedAt < walkingTime.UpdatedAt) { if (localWalkingTime.UpdatedAt >= walkingTime.UpdatedAt) { if (result.LastUpdate == null || result.LastUpdate < localWalkingTime.UpdatedAt) result.LastUpdate = localWalkingTime.UpdatedAt; continue; } var deleted = localWalkingTime.Deleted == false && walkingTime.Deleted; localWalkingTime.Version = walkingTime.Version; localWalkingTime.UpdatedAt = walkingTime.UpdatedAt; localWalkingTime.Deleted = walkingTime.Deleted; localWalkingTime.DogWalkerId = walkingTime.DogWalkerId; localWalkingTime.Day = walkingTime.Day; localWalkingTime.Start = walkingTime.Start; localWalkingTime.End = walkingTime.End; localWalkingTime.Enabled = walkingTime.Enabled; if (result.LastUpdate == null || result.LastUpdate < localWalkingTime.UpdatedAt) result.LastUpdate = localWalkingTime.UpdatedAt; if (!deleted) result.Updated.Add(localWalkingTime); else result.Deleted.Add(localWalkingTime); Repository.Update(localWalkingTime); } if (localWalkingTime == null) { var localWalkingTimeToAdd = new WalkingTime() { Id = walkingTime.Id, Version = walkingTime.Version, UpdatedAt = walkingTime.UpdatedAt, Deleted = walkingTime.Deleted, DogWalkerId = walkingTime.DogWalkerId, Day = walkingTime.Day, Start = walkingTime.Start, End = walkingTime.End, Enabled = walkingTime.Enabled }; Repository.Add(localWalkingTimeToAdd); if (result.LastUpdate == null || result.LastUpdate < localWalkingTimeToAdd.UpdatedAt) result.LastUpdate = localWalkingTimeToAdd.UpdatedAt; result.Added.Add(localWalkingTimeToAdd); } } if (result.HasChanges) { try { await CommitAsync().ConfigureAwait(false); } catch (Exception ex) { var err = ex.Message; } } return result; } #endregion } }