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 { }
}
}
}