From 3037b959208739de281c50b230044b69744cf13a Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 13 Sep 2019 16:02:01 -0400 Subject: [PATCH] metadata repository for table storage --- src/Core/Models/Data/DictionaryEntity.cs | 138 ++++++++++++++++++ src/Core/Repositories/IMetaDataRespository.cs | 14 ++ .../TableStorage/MetaDataRespository.cs | 97 ++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 src/Core/Models/Data/DictionaryEntity.cs create mode 100644 src/Core/Repositories/IMetaDataRespository.cs create mode 100644 src/Core/Repositories/TableStorage/MetaDataRespository.cs diff --git a/src/Core/Models/Data/DictionaryEntity.cs b/src/Core/Models/Data/DictionaryEntity.cs new file mode 100644 index 000000000..90c3b156e --- /dev/null +++ b/src/Core/Models/Data/DictionaryEntity.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Table; + +namespace Bit.Core.Models.Data +{ + public class DictionaryEntity : TableEntity, IDictionary + { + private IDictionary _properties = new Dictionary(); + + public ICollection Values => _properties.Values; + + public EntityProperty this[string key] + { + get => _properties[key]; + set => _properties[key] = value; + } + + public int Count => _properties.Count; + + public bool IsReadOnly => _properties.IsReadOnly; + + public ICollection Keys => _properties.Keys; + + public override void ReadEntity(IDictionary properties, + OperationContext operationContext) + { + _properties = properties; + } + + public override IDictionary WriteEntity(OperationContext operationContext) + { + return _properties; + } + + public void Add(string key, EntityProperty value) + { + _properties.Add(key, value); + } + + public void Add(string key, bool value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, byte[] value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, DateTime? value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, DateTimeOffset? value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, double value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, Guid value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, int value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, long value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(string key, string value) + { + _properties.Add(key, new EntityProperty(value)); + } + + public void Add(KeyValuePair item) + { + _properties.Add(item); + } + + public bool ContainsKey(string key) + { + return _properties.ContainsKey(key); + } + + public bool Remove(string key) + { + return _properties.Remove(key); + } + + public bool TryGetValue(string key, out EntityProperty value) + { + return _properties.TryGetValue(key, out value); + } + + public void Clear() + { + _properties.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return _properties.Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + _properties.CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return _properties.Remove(item); + } + + public IEnumerator> GetEnumerator() + { + return _properties.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _properties.GetEnumerator(); + } + } +} diff --git a/src/Core/Repositories/IMetaDataRespository.cs b/src/Core/Repositories/IMetaDataRespository.cs new file mode 100644 index 000000000..9ce3047f1 --- /dev/null +++ b/src/Core/Repositories/IMetaDataRespository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Bit.Core.Repositories +{ + public interface IMetaDataRespository + { + Task DeleteAsync(string id); + Task> GetAsync(string id); + Task GetAsync(string id, string prop); + Task UpsertAsync(string id, IDictionary dict); + Task UpsertAsync(string id, KeyValuePair keyValuePair); + } +} diff --git a/src/Core/Repositories/TableStorage/MetaDataRespository.cs b/src/Core/Repositories/TableStorage/MetaDataRespository.cs new file mode 100644 index 000000000..9b2d0d4ea --- /dev/null +++ b/src/Core/Repositories/TableStorage/MetaDataRespository.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Bit.Core.Models.Data; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Table; + +namespace Bit.Core.Repositories.TableStorage +{ + public class MetaDataRespository : IMetaDataRespository + { + private readonly CloudTable _table; + + public MetaDataRespository(GlobalSettings globalSettings) + : this(globalSettings.Events.ConnectionString) + { } + + public MetaDataRespository(string storageConnectionString) + { + var storageAccount = CloudStorageAccount.Parse(storageConnectionString); + var tableClient = storageAccount.CreateCloudTableClient(); + _table = tableClient.GetTableReference("metadata"); + } + + public async Task> GetAsync(string id) + { + var query = new TableQuery().Where( + TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, id)); + var queryResults = await _table.ExecuteQuerySegmentedAsync(query, null); + return queryResults.Results.FirstOrDefault()?.ToDictionary(d => d.Key, d => d.Value.StringValue); + } + + public async Task GetAsync(string id, string prop) + { + var dict = await GetAsync(id); + if(dict != null && dict.ContainsKey(prop)) + { + return dict[prop]; + } + return null; + } + + public async Task UpsertAsync(string id, KeyValuePair keyValuePair) + { + var query = new TableQuery().Where( + TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, id)); + var queryResults = await _table.ExecuteQuerySegmentedAsync(query, null); + var entity = queryResults.Results.FirstOrDefault(); + if(entity == null) + { + entity = new DictionaryEntity + { + PartitionKey = id + }; + } + if(entity.ContainsKey(keyValuePair.Key)) + { + entity.Remove(keyValuePair.Key); + } + entity.Add(keyValuePair.Key, keyValuePair.Value); + await _table.ExecuteAsync(TableOperation.InsertOrReplace(entity)); + } + + public async Task UpsertAsync(string id, IDictionary dict) + { + var entity = new DictionaryEntity + { + PartitionKey = id + }; + foreach(var item in dict) + { + entity.Add(item.Key, item.Value); + } + await _table.ExecuteAsync(TableOperation.InsertOrReplace(entity)); + } + + public async Task DeleteAsync(string id) + { + try + { + await _table.ExecuteAsync(TableOperation.Delete(new DictionaryEntity + { + PartitionKey = id, + ETag = "*" + })); + } + catch(StorageException e) + { + if(e.RequestInformation.HttpStatusCode != (int)HttpStatusCode.NotFound) + { + throw e; + } + } + } + } +}