using System.Diagnostics; using System.Reflection.Metadata; using gehGassi.Dto; using gehGassi.Dto.Common; using gehGassi.Dto.News; using gehGassiApp.Core.Data; using gehGassiApp.Core.Interfaces; using gehGassiApp.Core.Interfaces.Synchronization; using gehGassiApp.Core.Resources; using gehGassiApp.Domain.Common; using gehGassiApp.Domain.News; namespace gehGassiApp.Core.Services { /// /// Service der alle Funktionen rund um News zur Verfügung stellt /// public class NewsService : ServiceBase, INewsService { private readonly ICommunicationService _communicationService; private readonly ILocationService _locationService; private readonly ISyncInfoPullService _syncInfoPullService; /// /// Erstellt eine Instanz /// /// Instanz eines IUnitOfWork /// Instanz eines ICommunicationService /// Instanz eines ILocationService /// Instanz eines ISyncInfoPullService public NewsService(IUnitOfWork unitOfWork, ICommunicationService communicationService, ILocationService locationService, ISyncInfoPullService syncInfoPullService) : base(unitOfWork) { _communicationService = communicationService; _locationService = locationService; _syncInfoPullService = syncInfoPullService; } /// /// Gibt eine Liste der aktuellen News Kategorien zurück. /// Entweder von der DB oder vom Server /// /// Aktuelles Accesstoken /// Gewünschte Sprache /// CancellationToken /// Liste der News Kategorien public async Task> GetCategoriesAsync(string accessToken, string language, CancellationToken token) { //Zuerst DB var localCategories = await Repository.FindAsync(c => c.Language == language && c.OnlineStatus == OnlineStatus.Online, noTracking: true).ConfigureAwait(false); if (localCategories != null && localCategories.Any()) return localCategories.Where(c => c.OnlineStatus == OnlineStatus.Online && c.Deleted == false).OrderBy(c => c.Name).ToList(); else { //Dann wenn lokal nicht verfügbar immer online... var isConnected = await _communicationService.IsConnected(); //isConnected = false; if (isConnected) { var categoriesResult = await _communicationService.GetNewsCategoriesAsync(null, language, accessToken, token); if (categoriesResult.Success) { var result = await HandleChangesAsync(categoriesResult.Value); if (result.LastUpdate != null) await _syncInfoPullService.AddOrUpddateAsync(nameof(NewsCategory), result.LastUpdate.Value).ConfigureAwait(false); return categoriesResult.Value.Where(c => c.OnlineStatus == OnlineStatus.Online && c.Deleted == false).OrderBy(c => c.Name).ToList(); } } } //Keine Daten online, keine offline return new List(); } /// /// Abrufen von News. Berücksichtigt auch den Standort. /// /// Id der Kategorie. Leer wenn ohne gewünscht /// Aktuelle Position /// Wie viele News sollen abgerufen werden? /// Wie viele News sollen ausgelassen werden? /// Akteuller Modus der App /// Aktuelles Accesstoken /// Gewünschte Sprache /// CancellationToken /// Liste der News public async Task>> GetNewsAsync(string categoryId, LocationDto location, int take, int skip, AppMode appMode, string language, string accessToken, CancellationToken token) { var result = new ListCommunicationResult>() { Value = new List() }; var query = new NewsQueryDto { Location = location, CategoryId = categoryId, Language = language, AppMode = (AppModeDto)appMode, Take = take, Skip = skip }; var isConnected = await _communicationService.IsConnected(); if (isConnected) { var newsResult = await _communicationService.GetNewsAsync(query, accessToken, token); if (newsResult.Success) return newsResult; } else { result.Success = false; result.ErrorCode = CommunicationErrors.ServerNoConnection; result.ErrorMessage = Errors.Server_NoConnection; } return result; } /// /// 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 NewsCategory 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); } #region Pull-Push Implementation /// /// 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(); //Es gibt hier kein Push! await Task.Delay(1); return result; } /// /// 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) { //Zuerst lezte Aktivität holen... DateTimeOffset? lastUpdate = null; var lastSyncInfo = await _syncInfoPullService.GetAsync(nameof(NewsCategory)); if (lastSyncInfo != null) lastUpdate = lastSyncInfo.LastUpdate; var ctsQuery = new CancellationTokenSource(Common.Constants.PushPullTimeout); var categoriesResult = await _communicationService.GetNewsCategoriesAsync(lastUpdate, language, accessToken, token); if (categoriesResult.Success) { result = await HandleChangesAsync(categoriesResult.Value); if(result.LastUpdate != null) await _syncInfoPullService.AddOrUpddateAsync(nameof(NewsCategory), result.LastUpdate.Value).ConfigureAwait(false); } result.LastUpdate ??= lastUpdate; } return result; } #endregion #region Private /// /// Behandeln der Liste von News-Kategorien wenn welche vom Online-Store geholt werden. /// /// Liste der Kategorien /// Task private async Task> HandleChangesAsync(List categories) { var result = new SyncResult(); //Je Kategorie durchgehen ob was gemacht werden soll foreach (var newsCategory in categories) { var localCategory = await Repository.FirstOrDefaultAsync(c => c.Id == newsCategory.Id, false); if (localCategory != null && localCategory.UpdatedAt < newsCategory.UpdatedAt) { if (localCategory.UpdatedAt >= newsCategory.UpdatedAt) { if (result.LastUpdate == null || result.LastUpdate < localCategory.UpdatedAt) result.LastUpdate = localCategory.UpdatedAt; continue; } var deleted = localCategory.Deleted == false && newsCategory.Deleted; localCategory.Version = newsCategory.Version; localCategory.UpdatedAt = newsCategory.UpdatedAt; localCategory.Deleted = newsCategory.Deleted; localCategory.Language = newsCategory.Language; localCategory.Name = newsCategory.Name; localCategory.Description = newsCategory.Description; localCategory.Image = newsCategory.Image; localCategory.ImageLanguage = newsCategory.ImageLanguage; localCategory.OnlineStatus = newsCategory.OnlineStatus; localCategory.Created = newsCategory.Created; if(result.LastUpdate == null || result.LastUpdate < localCategory.UpdatedAt) result.LastUpdate = localCategory.UpdatedAt; if(!deleted) result.Updated.Add(localCategory); else result.Deleted.Add(localCategory); Repository.Update(localCategory); } if (localCategory == null) { localCategory = new NewsCategory { Id = newsCategory.Id, Version = newsCategory.Version, UpdatedAt = newsCategory.UpdatedAt, Deleted = newsCategory.Deleted, Language = newsCategory.Language, Name = newsCategory.Name, Description = newsCategory.Description, Image = newsCategory.Image, ImageLanguage = newsCategory.ImageLanguage, OnlineStatus = newsCategory.OnlineStatus, Created = newsCategory.Created }; Repository.Add(localCategory); if (result.LastUpdate == null || result.LastUpdate < localCategory.UpdatedAt) result.LastUpdate = localCategory.UpdatedAt; result.Added.Add(localCategory); } } if (result.HasChanges) { try { await CommitAsync().ConfigureAwait(false); } catch (Exception ex) { var err = ex.Message; } } return result; } #endregion } }