/// <summary> /// Notify clients of changed persistons. This should be called when any persiston changes, in case a client is subscribed to it. /// No action will be taken if the change didn't change the version, so it is performant to call this repeatedly, or if it might /// not have really changed. /// </summary> public void NotifyClientsOf(DataDictionary dbdef, Daton[] datons) { foreach (var cli in Sessions.Values) { bool thisClientChanged = false; foreach (var daton in datons) { //skip if this user doesn't need this daton if (!cli.Subscriptions.TryGetValue(daton.Key, out string version)) { continue; } if (version == daton.Version) { continue; //don't send if client already has the latest version } //remember we sent it cli.Subscriptions[daton.Key] = daton.Version; thisClientChanged = true; //hide cols by permissions var datondef = dbdef.FindDef(daton); var trimmedDaton = daton.Clone(datondef); var guard = new SecurityGuard(dbdef, cli.User); guard.HidePrivateParts(trimmedDaton); //queue it for sending lock (cli.DatonsToPush) { cli.DatonsToPush.Add(trimmedDaton); } } //send queued datons now if (thisClientChanged) { var completer = cli.LongPollingCompleter; //another thread could change cli.LongPollingCompleter, so access only through local var if (completer != null) { try { completer.SetResult(true); } catch { } //see note in LongPollingCompleted } } } }
/// <summary> /// Get a daton, from cache or load from database. The reutrn value is a shared instance so the caller may not modify it. /// For new unsaved persistons with -1 as the key, this will create the instance with default values. /// </summary> /// <param name="user">if null, the return value is a shared guaranteed complete daton; if user is provided, /// the return value may be a clone with some rows removed or columns set to null</param> /// <param name="forceCheckLatest">if true then checks database to ensure latest version even if it was cached</param> /// <returns>object with daton, or readable errors</returns> public async Task <RetroSql.LoadResult> GetDaton(DatonKey key, IUser user, bool forceCheckLatest = false) { //new persiston: return now if (key.IsNew) { var datondef2 = DataDictionary.FindDef(key); Daton newDaton = Utils.Construct(datondef2.Type) as Daton; Utils.FixTopLevelDefaultsInNewPersiston(datondef2, newDaton); newDaton.Key = key; if (datondef2.Initializer != null) { await datondef2.Initializer(newDaton); } return(new RetroSql.LoadResult { Daton = newDaton }); } //get from cache if possible, and optionally ignore cached version if it is not the latest string verifiedVersion = null; Daton daton = DatonCache.Get(key); if (forceCheckLatest && daton != null) { //viewons: always ignore cache; persistons: use cached only if known to be latest if (daton is Persiston) { verifiedVersion = LockManager.GetVersion(key); if (verifiedVersion != daton.Version) { daton = null; } } else { daton = null; } } //get from database if needed (and cache it), or abort var datondef = DataDictionary.FindDef(key); if (typeof(Persiston).IsAssignableFrom(datondef.Type) && (key is ViewonKey)) { throw new Exception("Persiston requested but key format is for viewon"); } if (typeof(Viewon).IsAssignableFrom(datondef.Type) && (key is PersistonKey)) { throw new Exception("Viewon requested but key format is for persiston"); } if (daton == null) { var sql = GetSqlInstance(key); RetroSql.LoadResult loadResult; using (var db = GetDbConnection(datondef.DatabaseNumber)) loadResult = await sql.Load(db, DataDictionary, user, key, ViewonPageSize); if (loadResult.Daton == null) { return(loadResult); } daton = loadResult.Daton; if (verifiedVersion == null && daton is Persiston) { verifiedVersion = LockManager.GetVersion(key); } daton.Version = verifiedVersion; DatonCache.Put(daton); Diagnostics.IncrementLoadCount(); } //enforce permissions on the user if (user != null) { daton = daton.Clone(datondef); var guard = new SecurityGuard(DataDictionary, user); guard.HidePrivateParts(daton); } return(new RetroSql.LoadResult { Daton = daton }); }