using System; using System.Collections.Generic; using System.IO; using System.Linq; using Newtonsoft.Json; using StarfaceOutlookSync.Models; namespace StarfaceOutlookSync.Services { /// /// Verteilt Konflikt-Hinweise ueber das gemeinsame Verzeichnis an alle /// Arbeitsplaetze. Wer einen echten Feld-Konflikt aufloest, legt eine Notiz /// ab; andere Clients zeigen beim naechsten Sync die Notizen zu Kontakten, /// die sie selbst gemappt haben (per StarfaceId). Bereits gezeigte Notizen /// merkt sich jeder Client lokal, damit sie nicht doppelt erscheinen. /// public class ConflictNotifier { private static readonly TimeSpan Ttl = TimeSpan.FromDays(7); private readonly string _seenFile; public ConflictNotifier() { var dir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StarfaceOutlookSync"); try { Directory.CreateDirectory(dir); } catch { } _seenFile = Path.Combine(dir, "seen-conflicts.json"); } private static string ConflictsDir(string sharedDir) => Path.Combine(sharedDir, "conflicts"); /// Legt fuer jeden Konflikt eine Notiz im gemeinsamen Verzeichnis ab. public void Write(string sharedDir, IEnumerable conflicts) { if (string.IsNullOrWhiteSpace(sharedDir)) return; var list = conflicts?.ToList(); if (list == null || list.Count == 0) return; try { var dir = ConflictsDir(sharedDir); Directory.CreateDirectory(dir); var host = Environment.MachineName; var user = Environment.UserName; var ts = DateTime.UtcNow.ToString("o"); foreach (var c in list) { var notice = new ConflictNotice { Id = Guid.NewGuid().ToString("N"), ByHost = host, ByUser = user, TimestampUtc = ts, StarfaceId = c.StarfaceId, ContactName = c.ContactName, Field = c.Field, FieldKey = c.FieldKey, OutlookValue = c.OutlookValue, StarfaceValue = c.StarfaceValue, Winner = c.Winner }; try { File.WriteAllText(Path.Combine(dir, notice.Id + ".json"), JsonConvert.SerializeObject(notice, Formatting.Indented)); } catch { } } } catch { } } /// /// Liefert ungesehene Konflikt-Notizen, die Kontakte dieses Clients /// betreffen und nicht von ihm selbst stammen. Es werden nur die Notizen /// zurueckgegeben, bei denen der EIGENE aktuelle Feldwert vom uebernommenen /// (Gewinner-)Wert abweicht - wer den Wert ohnehin schon hat, wird nicht /// benachrichtigt. myContacts bildet StarfaceId -> eigener Kontaktstand ab. /// Markiert verarbeitete Notizen als gesehen und raeumt veraltete auf. /// public List GetPending(string sharedDir, IDictionary myContacts) { var result = new List(); if (string.IsNullOrWhiteSpace(sharedDir)) return result; var dir = ConflictsDir(sharedDir); string[] files; try { if (!Directory.Exists(dir)) return result; files = Directory.GetFiles(dir, "*.json"); } catch { return result; } var seen = LoadSeen(); var host = Environment.MachineName; var existingIds = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var f in files) { ConflictNotice n = null; try { n = JsonConvert.DeserializeObject(File.ReadAllText(f)); } catch { } if (n == null || string.IsNullOrEmpty(n.Id)) continue; // Veraltete Notizen entfernen. if (DateTime.TryParse(n.TimestampUtc, out var ts) && DateTime.UtcNow - ts.ToUniversalTime() > Ttl) { try { File.Delete(f); } catch { } continue; } existingIds.Add(n.Id); if (seen.Contains(n.Id)) continue; // schon gezeigt if (string.Equals(n.ByHost, host, StringComparison.OrdinalIgnoreCase)) { seen.Add(n.Id); // selbst erzeugt -> nicht erneut anzeigen continue; } // Habe ich diesen Kontakt ueberhaupt? UnifiedContact mine = null; bool haveContact = !string.IsNullOrEmpty(n.StarfaceId) && myContacts != null && myContacts.TryGetValue(n.StarfaceId, out mine); if (!haveContact) continue; // betrifft mich nicht (kein seen -> evtl. spaeter relevant) // Bin ich wirklich betroffen? Nur wenn mein aktueller Feldwert vom // uebernommenen Wert abweicht. Wer den Gewinner-Wert schon hat, wird // nicht benachrichtigt. if (mine != null && !string.IsNullOrEmpty(n.FieldKey)) { var winnerValue = string.Equals(n.Winner, "Outlook", StringComparison.OrdinalIgnoreCase) ? n.OutlookValue : n.StarfaceValue; var myValue = ContactMerger.GetValue(mine, n.FieldKey); if (ContactMerger.ValuesEqual(n.FieldKey, myValue, winnerValue)) { seen.Add(n.Id); // nicht betroffen -> als erledigt merken continue; } } result.Add(n); seen.Add(n.Id); } // Gesehen-Liste auf noch vorhandene Notizen eindampfen. seen.IntersectWith(existingIds); SaveSeen(seen); return result; } private HashSet LoadSeen() { try { if (File.Exists(_seenFile)) return new HashSet( JsonConvert.DeserializeObject>(File.ReadAllText(_seenFile)) ?? new List(), StringComparer.OrdinalIgnoreCase); } catch { } return new HashSet(StringComparer.OrdinalIgnoreCase); } private void SaveSeen(HashSet seen) { try { File.WriteAllText(_seenFile, JsonConvert.SerializeObject(seen.ToList())); } catch { } } } }