//see DataDictionaryToWire private static ColDefResponse ToWire(SecurityGuard guard, TableDef source, ColDef c, IUser user) { bool isDBAssignedKey = source.PrimaryKeyColName == c.Name && source.DatabaseAssignsKey; return(new ColDefResponse { PermissionLevel = (int)guard.FinalLevel(null, source.Name, c.Name), AllowSort = c.AllowSort, ForeignKeyDatonTypeName = c.ForeignKeyDatonTypeName, LeftJoin = ToWire(c.LeftJoin), SelectBehavior = ToWire(c.SelectBehavior), ImageUrlColumName = c.Image?.UrlColumName, IsComputed = c.IsComputed || isDBAssignedKey, IsMainColumn = c.IsMainColumn, IsVisibleInDropdown = c.IsVisibleInDropdown, LengthValidationMessage = DataDictionary.ResolvePrompt(c.LengthValidationMessage, user, defaultValue: null), MaxLength = c.MaxLength, MaxNumberValue = c.MaxNumberValue, MinLength = c.MinLength, MinNumberValue = c.MinNumberValue, Name = CamelCasify(c.Name), Prompt = DataDictionary.ResolvePrompt(c.Prompt, user, c.Name), RangeValidationMessage = DataDictionary.ResolvePrompt(c.RangeValidationMessage, user, defaultValue: null), Regex = c.Regex, RegexValidationMessage = DataDictionary.ResolvePrompt(c.RegexValidationMessage, user, defaultValue: null), WireType = c.WireType }); }
public MultiSaver(Retroverse retroverse, IUser user, PersistonDiff[] diffs) { Retroverse = retroverse; User = user; Guard = new SecurityGuard(retroverse.DataDictionary, user); SaveItems = diffs.Select(d => new SaveItem { Diff = d }).ToArray(); }
/// <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 } } } }
//see DataDictionaryToWire private static TableDefResponse ToWire(SecurityGuard guard, TableDef source, IUser user, bool isCriteria) { if (source == null) { return(null); } var wire = new TableDefResponse() { Name = CamelCasify(source.Name), PermissionLevel = (int)guard.FinalLevel(null, source.Name, null), Cols = source.Cols.Select(c => ToWire(guard, source, c, user)).ToList(), PrimaryKeyColName = CamelCasify(source.PrimaryKeyColName), Prompt = DataDictionary.ResolvePrompt(source.Prompt, user, source.Name), IsCriteria = isCriteria, Children = source.Children?.Select(t => ToWire(guard, t, user, false)).ToList() }; return(wire); }
/// <summary> /// Convert the serializable portions of a data dictionary to a wire-ready structure. /// </summary> /// <param name="languageMessages">may be null; see Retroverse.LanguageMessages</param> public static DataDictionaryResponse DataDictionaryToWire(DataDictionary ddict, IUser user, Dictionary <string, Dictionary <string, string> > languageMessages) { var guard = new SecurityGuard(ddict, user); var datonWires = new List <DatonDefResponse>(); foreach (var name in ddict.DatonDefs.Keys) { var datondef = ddict.DatonDefs[name]; datonWires.Add(new DatonDefResponse { Name = name, IsPersiston = typeof(Persiston).IsAssignableFrom(datondef.Type), CriteriaDef = ToWire(guard, datondef.CriteriaDef, user, true), MainTableDef = ToWire(guard, datondef.MainTableDef, user, false), MultipleMainRows = datondef.MultipleMainRows }); } var messages = new Dictionary <string, string>(); Dictionary <string, string> dictByLanguage = null; languageMessages?.TryGetValue(user.LangCode, out dictByLanguage); foreach ((string code, string englishMessage) in Constants.EnglishMessages) { messages[code] = englishMessage; string overrideMessage = null; if (dictByLanguage?.TryGetValue(code, out overrideMessage) == true) { messages[code] = overrideMessage; } } return(new DataDictionaryResponse { DatonDefs = datonWires, MessageConstants = messages }); }
/// <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 }); }