using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using DTS.Common.Interface.Tags; using DTS.Common.Utilities.Logging; // ReSharper disable InconsistentNaming // ReSharper disable once CheckNamespace namespace DTS.Common.Classes.Tags { public class TagsInstance { private static TagsInstance _tagsInstance; public static TagsInstance GetTagsInstance(TagsGetDelegate tagsGet) { if (null == _tagsInstance) { _tagsInstance = new TagsInstance(tagsGet); } return _tagsInstance; } private static readonly object LOCK_OBJECT = new object(); public TagsInstance(TagsGetDelegate tagsGet) { _tagsLookup = new Dictionary(); UpdateList(tagsGet); } /// /// holds a cached collection of tags. This collection however is currently only populated on startup /// and not updated except explicitly when a user adds a tag /// // ReSharper disable once RedundantDefaultMemberInitializer private readonly Dictionary _tagsLookup = null; /// /// Adds a tag if not present in memory cache to db /// /// /// public static bool AddTag(string tagText, GetSqlCommandDelegate getSqlCommand, TagsGetDelegate tagsGet, TagsGetIdDelegate tagsGetId, TagsInsertDelegate tagsInsert) { if (string.IsNullOrEmpty(tagText)) { return false; } // is it already in the dictionary? var tags = GetTagsInstance(tagsGet); if (tags.ContainsTag(tagText)) return false; tags.Commit(new Tag(tagText, Tag.INVALID_ID), tagsGet, tagsGetId, tagsInsert); return true; } /// /// Changes the ID of a tag during database migration /// /// /// public static bool MigrateTag(string tagText, GetSqlCommandDelegate getSqlCommand, TagsGetDelegate tagsGet, TagsGetIdDelegate tagsGetId, TagsInsertDelegate tagsInsert) { if (string.IsNullOrEmpty(tagText)) { return false; } GetTagsInstance(tagsGet).Commit(new Tag(tagText, Tag.INVALID_ID), tagsGet, tagsGetId, tagsInsert); return true; } /// /// commits a tag to db if doesn't already exist in db /// /// private void Commit(Tag tag, TagsGetDelegate tagsGet, TagsGetIdDelegate tagsGetId, TagsInsertDelegate tagsInsert) { try { if (-1 == GetIDFromTagText(tag.Text, tagsGet, tagsGetId)) { Insert(tag, tagsInsert); } else { UpdateAll(tag, tagsGetId); } lock (LOCK_OBJECT) { _tagsLookup[tag.Text] = tag; } } catch (Exception ex) { APILogger.Log(ex); } } private void UpdateAll(Tag tag, TagsGetIdDelegate tagsGetId) { //nothing to do currently? (we don't let you rename or edit, or obsolete, or delete ...) tag.ID = GetTagIdFromText(tag.Text, tagsGetId); } public delegate ulong TagAssignmentsGet(TagTypes? tagType, out ITagAssignment[] records); public delegate SqlCommand GetSqlCommandDelegate(bool bNewConnection); public delegate ulong TagAssignmentsDelete(TagTypes objectType, int objectId); public delegate ulong TagAssignmentsInsert(ITagAssignment tagAssignment); public delegate ulong TagsGetIdDelegate(string text, out int? id); public delegate ulong TagsGetDelegate(int? id, out ITag[] tags); public delegate ulong TagsInsertDelegate(ref ITag tag); /// /// inserts a tag into the db /// /// private void Insert(Tag tag, TagsInsertDelegate tagsInsert) { var itag = (ITag)tag; _ = tagsInsert(ref itag); } /// /// retrieves a string text associated with an ID FROM CACHED copies /// /// /// private string GetTagTextFromId(int id) { lock (LOCK_OBJECT) { if (_tagsLookup == null) return null; var e = _tagsLookup.GetEnumerator(); while (e.MoveNext()) { if (e.Current.Value.ID != id) continue; var returnText = e.Current.Value.Text; e.Dispose(); return returnText; } } return null; } /// /// Gets an ID for a given tag text FROM DB /// returns InvalidID if not found /// /// /// private int GetTagIdFromText(string text, TagsGetIdDelegate tagsGetId) { var hr = tagsGetId(text, out var id); if( 0 != hr || null == id) { return Tag.INVALID_ID; } return (int)id; } /// /// returns true if a given tag text is contained in cached in memory tags /// /// /// public bool ContainsTag(string text) { lock (LOCK_OBJECT) { return _tagsLookup.ContainsKey(text); } } /// /// adds multiple tags at once /// note that tags will have their start trimmed before commiting /// /// /// public static bool[] AddRange(string[] tagText, GetSqlCommandDelegate getSqlCommand, TagsGetDelegate tagsGet, TagsGetIdDelegate tagsGetId, TagsInsertDelegate tagsInsert) { List rv = new List(); if (null == tagText || 0 == tagText.Length) { return null; } foreach (string s in tagText) { var tag = s.TrimStart(); rv.Add(AddTag(tag, getSqlCommand, tagsGet, tagsGetId, tagsInsert)); } return rv.ToArray(); } /// /// gets an ID for a given tag text FROM DB /// /// /// public static int GetIDFromTagText(string tagText, TagsGetDelegate tagsGet, TagsGetIdDelegate tagsGetId) { return GetTagsInstance(tagsGet).GetTagIdFromText(tagText, tagsGetId); } /// /// gets an array of ids given an array of tag texts /// /// /// public static int[] GetIDsFromTagText(string[] tagText, TagsGetDelegate tagsGet, TagsGetIdDelegate tagsGetId) { if (null == tagText || 0 == tagText.Length) { return null; } return tagText.Select(s => s.TrimStart()).Select(text => GetIDFromTagText(text, tagsGet, tagsGetId)) .Where(id => id != Tag.INVALID_ID).ToArray(); } /// /// returns a string for a given id from memory cache /// returns null if it doesn't exist or is an invalid id /// /// /// public static string GetTagTextFromID(int tagID, TagsGetDelegate tagsGet) { if (0 > tagID || tagID == Tag.INVALID_ID) { // Not a valid ID return null; } return GetTagsInstance(tagsGet).GetTagTextFromId(tagID); } /// /// returns an array of tag text given an array of tag ids. /// skips invalid tags or tag text /// /// /// public static string[] GetTagTextFromIDs(int[] tagId, TagsGetDelegate tagsGet) { if (null == tagId || 0 == tagId.Length) { return new string [0]; } return tagId.Where(i => i != Tag.INVALID_ID).Select(i => GetTagTextFromID(i,tagsGet)).Where(tag => !string.IsNullOrWhiteSpace(tag)).ToArray(); } /// /// retrieves all tags and updates the cached dictionary of tags /// public void UpdateList(TagsGetDelegate tagsGet) { var hr = tagsGet(null, out var tags); if( 0 == hr && null != tags && tags.Any()) { lock (LOCK_OBJECT) { _tagsLookup.Clear(); foreach( var tag in tags) { _tagsLookup[tag.Text] = tag; } } } } } }