/// Die reinen Entscheidungsfunktionen des Abgleichs — ohne Netz, ohne /// Datenbank, ohne Plattform-Kanäle. /// /// Sie liegen bewusst außerhalb von `SyncService`: was hier steht, lässt sich /// mit einer Handvoll Mengen prüfen statt mit einem halben Server. library; /// Wie viele Favoriten je Lauf höchstens gepusht werden. /// /// Der Rest kommt im nächsten Lauf dran. Die Pushes sind idempotent, ein /// Teilausfall heilt sich dadurch von selbst. const int maxFavoritenPushes = 200; /// Die cloudIds, die zum Server gepusht werden müssen: `lokal \ server`. /// /// [lokaleFavoriten] sind lokale Song-IDs, [cloudIdVon] bildet sie auf ihre /// cloudId ab. Titel ohne cloudId kennt der Server nicht — sie tauchen in /// keiner Richtung im Abgleich auf. List zuPushendeFavoriten({ required Set lokaleFavoriten, required Map cloudIdVon, required Set amServer, int deckel = maxFavoritenPushes, }) { final offen = []; for (final songId in lokaleFavoriten) { final cloudId = cloudIdVon[songId]; if (cloudId == null) continue; if (amServer.contains(cloudId)) continue; offen.add(cloudId); if (offen.length >= deckel) break; } return offen; } /// Die lokalen Song-IDs, die aus dem Server-Stand als Favorit dazukommen: /// `server \ lokal`. /// /// Eine cloudId ohne lokalen Titel (Download fehlgeschlagen, noch nicht /// geladen) wird **übersprungen, nicht gelöscht**: ein Favorit ohne Song wäre /// über den Join unsichtbar, würde aber weiter mitgeschleppt. List lokalZuSetzendeFavoriten({ required Set amServer, required Map songIdVonCloudId, required Set lokaleFavoriten, }) { final offen = []; for (final cloudId in amServer) { final songId = songIdVonCloudId[cloudId]; if (songId == null) continue; if (lokaleFavoriten.contains(songId)) continue; offen.add(songId); } return offen; } /// Ob der „Was ist neu"-Bericht fällig ist. /// /// `null` heißt Neuinstallation, Abmeldung oder gelöschte App-Daten — dann ist /// er **nicht** fällig, sonst begrüßt ein frisch eingerichtetes Gerät den /// Nutzer mit „Willkommen zurück! 325 neue Songs". `sollAutoSync` entscheidet /// bei `null` bewusst umgekehrt und ist hier **kein** Vorbild. bool berichtFaellig(DateTime? letzterErfolg, DateTime jetzt) => letzterErfolg != null && jetzt.difference(letzterErfolg) >= const Duration(hours: 24);