-
-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathBDictionary.cs
More file actions
262 lines (223 loc) · 9.51 KB
/
Copy pathBDictionary.cs
File metadata and controls
262 lines (223 loc) · 9.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace BencodeNET.Objects
{
/// <summary>
/// Represents a bencoded dictionary of <see cref="BString"/> keys and <see cref="IBObject"/> values.
/// </summary>
/// <remarks>
/// The underlying value is a <see cref="IDictionary{BString,IBObject}"/>.
/// </remarks>
public sealed class BDictionary : BObject<IDictionary<BString, IBObject>>, IDictionary<BString, IBObject>
{
/// <summary>
/// The underlying dictionary.
/// </summary>
public override IDictionary<BString, IBObject> Value { get; } = new SortedDictionary<BString, IBObject>();
/// <summary>
/// Creates an empty dictionary.
/// </summary>
public BDictionary()
{ }
/// <summary>
/// Creates a dictionary from key-value pairs.
/// </summary>
/// <param name="keyValuePairs"></param>
public BDictionary(IEnumerable<KeyValuePair<BString, IBObject>> keyValuePairs)
{
Value = new SortedDictionary<BString, IBObject>(keyValuePairs.ToDictionary(x => x.Key, x => x.Value));
}
/// <summary>
/// Creates a dictionary with an initial value of the supplied dictionary.
/// </summary>
/// <param name="dictionary"></param>
public BDictionary(IDictionary<BString, IBObject> dictionary)
{
Value = dictionary;
}
/// <summary>
/// Adds the specified key and value to the dictionary as <see cref="BString"/>.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(string key, string value) => Add(new BString(key), new BString(value));
/// <summary>
/// Adds the specified key and value to the dictionary as <see cref="BNumber"/>.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(string key, long value) => Add(new BString(key), new BNumber(value));
/// <summary>
/// Gets the value associated with the specified key and casts it as <typeparamref name="T"/>.
/// If the key does not exist or the value is not of the specified type null is returned.
/// </summary>
/// <typeparam name="T">The type to cast the value to.</typeparam>
/// <param name="key">The key to get the associated value of.</param>
/// <returns>The associated value of the specified key or null if the key does not exist.
/// If the value is not of the specified type null is returned as well.</returns>
public T Get<T>(BString key) where T : class, IBObject
{
return this[key] as T;
}
/// <summary>
/// Merges this instance with another <see cref="BDictionary"/>.
/// </summary>
/// <remarks>
/// By default existing keys are either overwritten (<see cref="BString"/> and <see cref="BNumber"/>) or merged if possible (<see cref="BList"/> and <see cref="BDictionary"/>).
/// This behavior can be changed with the <paramref name="existingKeyAction"/> parameter.
/// </remarks>
/// <param name="dictionary">The dictionary to merge into this instance.</param>
/// <param name="existingKeyAction">Decides how to handle the values of existing keys.</param>
public void MergeWith(BDictionary dictionary, ExistingKeyAction existingKeyAction = ExistingKeyAction.Merge)
{
foreach (var field in dictionary)
{
// Add non-existing key
if (!ContainsKey(field.Key))
{
Add(field);
continue;
}
if (existingKeyAction == ExistingKeyAction.Skip)
continue;
switch (field.Value)
{
// Replace strings and numbers
case BString _:
case BNumber _:
this[field.Key] = field.Value;
continue;
// Append list to existing list or replace other types
case BList newList:
{
var existingList = Get<BList>(field.Key);
if (existingList == null || existingKeyAction == ExistingKeyAction.Replace)
{
this[field.Key] = field.Value;
continue;
}
existingList.AddRange(newList);
continue;
}
// Merge dictionary with existing or replace other types
case BDictionary newDictionary:
{
var existingDictionary = Get<BDictionary>(field.Key);
if (existingDictionary == null || existingKeyAction == ExistingKeyAction.Replace)
{
this[field.Key] = field.Value;
continue;
}
existingDictionary.MergeWith(newDictionary);
break;
}
}
}
}
/// <inheritdoc/>
public override int GetSizeInBytes()
{
var size = 2;
foreach (var entry in this)
{
size += entry.Key.GetSizeInBytes() + entry.Value.GetSizeInBytes();
}
return size;
}
/// <inheritdoc/>
protected override void EncodeObject(Stream stream)
{
stream.Write('d');
foreach (var entry in this)
{
entry.Key.EncodeTo(stream);
entry.Value.EncodeTo(stream);
}
stream.Write('e');
}
/// <inheritdoc/>
protected override void EncodeObject(PipeWriter writer)
{
writer.WriteChar('d');
foreach (var entry in this)
{
entry.Key.EncodeTo(writer);
entry.Value.EncodeTo(writer);
}
writer.WriteChar('e');
}
/// <inheritdoc/>
protected override async ValueTask<FlushResult> EncodeObjectAsync(PipeWriter writer, CancellationToken cancellationToken)
{
writer.WriteChar('d');
foreach (var entry in this)
{
cancellationToken.ThrowIfCancellationRequested();
await entry.Key.EncodeToAsync(writer, cancellationToken).ConfigureAwait(false);
await entry.Value.EncodeToAsync(writer, cancellationToken).ConfigureAwait(false);
}
writer.WriteChar('e');
return await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
}
#region IDictionary<BString, IBObject> Members
#pragma warning disable 1591
public ICollection<BString> Keys => Value.Keys;
public ICollection<IBObject> Values => Value.Values;
public int Count => Value.Count;
public bool IsReadOnly => Value.IsReadOnly;
/// <summary>
/// Returns the value associated with the key or null if the key doesn't exist.
/// </summary>
public IBObject this[BString key]
{
get => ContainsKey(key) ? Value[key] : null;
set => Value[key] = value ?? throw new ArgumentNullException(nameof(value), "A null value cannot be added to a BDictionary");
}
public void Add(KeyValuePair<BString, IBObject> item)
{
if (item.Value == null) throw new ArgumentException("Must not contain a null value", nameof(item));
Value.Add(item);
}
public void Add(BString key, IBObject value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
Value.Add(key, value);
}
public void Clear() => Value.Clear();
public bool Contains(KeyValuePair<BString, IBObject> item) => Value.Contains(item);
public bool ContainsKey(BString key) => Value.ContainsKey(key);
public void CopyTo(KeyValuePair<BString, IBObject>[] array, int arrayIndex) => Value.CopyTo(array, arrayIndex);
public IEnumerator<KeyValuePair<BString, IBObject>> GetEnumerator() => Value.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public bool Remove(KeyValuePair<BString, IBObject> item) => Value.Remove(item);
public bool Remove(BString key) => Value.Remove(key);
public bool TryGetValue(BString key, out IBObject value) => Value.TryGetValue(key, out value);
#pragma warning restore 1591
#endregion
}
/// <summary>
/// Specifies the action to take when encountering an already existing key when merging two <see cref="BDictionary"/>.
/// </summary>
public enum ExistingKeyAction
{
/// <summary>
/// Merges the values of existing keys for <see cref="BList"/> and <see cref="BDictionary"/>.
/// Overwrites existing keys for <see cref="BString"/> and <see cref="BNumber"/>.
/// </summary>
Merge,
/// <summary>
/// Replaces the values of all existing keys.
/// </summary>
Replace,
/// <summary>
/// Leaves all existing keys as they were.
/// </summary>
Skip
}
}