Skip to content

Commit 50283f6

Browse files
committed
StorageAPI: Added CollectionValue, DictionaryValue and added save & change time trackers
1 parent cec3b8e commit 50283f6

File tree

5 files changed

+685
-28
lines changed

5 files changed

+685
-28
lines changed

LabExtended/API/Images/ImageLoader.cs

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
using LabExtended.Core;
22
using LabExtended.Utilities;
3-
using LabExtended.Attributes;
3+
using LabExtended.Extensions;
4+
5+
using LabExtended.API.Images.Conversion;
46

57
using NorthwoodLib.Pools;
68

79
using System.Drawing;
810

9-
using LabExtended.API.Images.Conversion;
10-
using LabExtended.Extensions;
11-
1211
namespace LabExtended.API.Images;
1312

1413
/// <summary>
@@ -92,8 +91,55 @@ public static ImageFile Get(string name)
9291
/// <returns>true if the image was found</returns>
9392
public static bool TryGet(string name, out ImageFile file)
9493
=> LoadedImages.TryGetValue(name, out file);
94+
95+
/// <summary>
96+
/// Attempts to load an image file from the specified file path.
97+
/// </summary>
98+
/// <remarks>This method does not throw an exception if the file does not exist or if the file cannot be
99+
/// parsed. Instead, it returns <see langword="false"/> and sets <paramref name="file"/> to <see
100+
/// langword="null"/>.</remarks>
101+
/// <param name="filePath">The full path to the file to be loaded. The path must point to an existing file.</param>
102+
/// <param name="file">When this method returns, contains the loaded <see cref="ImageFile"/> object if the operation succeeds;
103+
/// otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param>
104+
/// <returns><see langword="true"/> if the file was successfully loaded and parsed into an <see cref="ImageFile"/> object;
105+
/// otherwise, <see langword="false"/>.</returns>
106+
public static bool TryLoad(string filePath, out ImageFile file)
107+
{
108+
file = null!;
109+
110+
if (!File.Exists(filePath))
111+
return false;
112+
113+
var data = File.ReadAllBytes(filePath);
114+
115+
file = ReadFile(data);
116+
117+
if (file != null)
118+
{
119+
file.Path = filePath;
120+
file.Name = Path.GetFileNameWithoutExtension(filePath);
121+
122+
return true;
123+
}
124+
125+
return false;
126+
}
95127

96-
internal static ImageFile ReadFile(byte[] data)
128+
/// <summary>
129+
/// Reads image data from a byte array and constructs an <see cref="ImageFile"/> object.
130+
/// </summary>
131+
/// <remarks>This method reads the image data, including its dimensions, frames, and pixel information,
132+
/// from the provided byte array. If <paramref name="readAdditionalData"/> is <see langword="true"/>, additional
133+
/// metadata is read using the <see cref="ToyStringImageConvertor.ReadImage"/> method. If <paramref
134+
/// name="convertFormats"/> is <see langword="true"/>, the image formats are converted using the <see
135+
/// cref="ImageFile.ConvertFormats"/> method.</remarks>
136+
/// <param name="data">The byte array containing the image data to be read. This cannot be null or empty.</param>
137+
/// <param name="readAdditionalData">A boolean value indicating whether additional metadata should be read from the image data. Defaults to <see
138+
/// langword="true"/>.</param>
139+
/// <param name="convertFormats">A boolean value indicating whether the image formats should be converted after reading. Defaults to <see
140+
/// langword="true"/>.</param>
141+
/// <returns>An <see cref="ImageFile"/> object representing the image data, including its frames, pixels, and metadata.</returns>
142+
public static ImageFile ReadFile(byte[] data, bool readAdditionalData = true, bool convertFormats = true)
97143
{
98144
var image = new ImageFile();
99145

@@ -160,10 +206,13 @@ internal static ImageFile ReadFile(byte[] data)
160206
image.Frames.Add(frame);
161207
}
162208

163-
ToyStringImageConvertor.ReadImage(image, reader);
209+
if (readAdditionalData)
210+
ToyStringImageConvertor.ReadImage(image, reader);
164211
}
165212

166-
image.ConvertFormats();
213+
if (convertFormats)
214+
image.ConvertFormats();
215+
167216
return image;
168217
}
169218

@@ -200,8 +249,6 @@ private static void OnCreated(object _, FileSystemEventArgs ev)
200249
#endif
201250

202251
LoadedImages.Add(name, image);
203-
204-
ApiLog.Debug("Image Loader", $"Loaded image &6{image.Name}&r");
205252
return image;
206253
}
207254
catch (Exception ex)
@@ -232,8 +279,6 @@ private static void OnDeleted(object _, FileSystemEventArgs ev)
232279
image.Value.Dispose();
233280

234281
removedImages.Add(image.Key);
235-
236-
ApiLog.Debug("Image Loader", $"Removed image &6{image.Value.Name}&r");
237282
}
238283
}
239284

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
using Mirror;
2+
3+
using System.Collections;
4+
using System.Collections.ObjectModel;
5+
6+
namespace LabExtended.Core.Storage
7+
{
8+
/// <summary>
9+
/// Represents a collection of items of type <typeparamref name="T"/> that supports read-only access, modification
10+
/// tracking, and common collection operations.
11+
/// </summary>
12+
/// <remarks>This class provides a wrapper around a <see cref="List{T}"/> with additional functionality,
13+
/// such as: <list type="bullet"> <item>Exposing the collection as a read-only view through the <see
14+
/// cref="Collection"/> property.</item> <item>Tracking modifications to the collection via the <see
15+
/// cref="StorageValue.IsDirty"/> property.</item> <item>Implementing common collection interfaces, including <see
16+
/// cref="IList{T}"/>, <see cref="ICollection{T}"/>, and <see cref="IReadOnlyList{T}"/>.</item> </list> The
17+
/// collection is not thread-safe and must be synchronized externally if accessed concurrently.</remarks>
18+
/// <typeparam name="T">The type of elements in the collection.</typeparam>
19+
public class CollectionValue<T> : StorageValue,
20+
21+
IList<T>,
22+
ICollection<T>,
23+
IReadOnlyList<T>
24+
{
25+
private List<T> collection;
26+
27+
/// <summary>
28+
/// Gets a read-only collection of items of type <typeparamref name="T"/>.
29+
/// </summary>
30+
/// <remarks>This property provides a thread-safe way to access the collection. Any attempt to
31+
/// modify the collection will result in a runtime exception.</remarks>
32+
public ReadOnlyCollection<T> Collection { get; }
33+
34+
/// <summary>
35+
/// Gets the number of elements contained in the collection.
36+
/// </summary>
37+
public int Count => collection.Count;
38+
39+
/// <summary>
40+
/// Gets a value indicating whether the collection is read-only.
41+
/// </summary>
42+
public bool IsReadOnly => false;
43+
44+
/// <summary>
45+
/// Gets or sets the element at the specified index in the collection.
46+
/// </summary>
47+
/// <param name="index">The zero-based index of the element to get or set.</param>
48+
/// <returns></returns>
49+
public T this[int index]
50+
{
51+
get
52+
{
53+
return collection[index];
54+
}
55+
set
56+
{
57+
collection[index] = value;
58+
59+
IsDirty = true;
60+
}
61+
}
62+
63+
/// <summary>
64+
/// Initializes a new instance of the <see cref="CollectionValue{T}"/> class with an optional initial capacity.
65+
/// </summary>
66+
/// <param name="size">The initial number of elements that the underlying collection can contain. Defaults to 0.</param>
67+
public CollectionValue(int size = 0)
68+
: this(new List<T>(size)) { }
69+
70+
/// <summary>
71+
/// Initializes a new instance of the <see cref="CollectionValue{T}"/> class with the specified collection.
72+
/// </summary>
73+
/// <remarks>If the provided collection is a <see cref="List{T}"/>, it is used directly.
74+
/// Otherwise, the collection is copied into a new list. The resulting collection is exposed as a read-only
75+
/// collection through the <c>Collection</c> property.</remarks>
76+
/// <param name="collection">The collection of items to initialize the instance with. Cannot be <see langword="null"/>.</param>
77+
/// <exception cref="ArgumentNullException">Thrown if <paramref name="collection"/> is <see langword="null"/>.</exception>
78+
public CollectionValue(IEnumerable<T> collection)
79+
{
80+
if (collection is null)
81+
throw new ArgumentNullException(nameof(collection));
82+
83+
if (collection is List<T> list)
84+
{
85+
this.collection = list;
86+
}
87+
else
88+
{
89+
this.collection = new(collection);
90+
}
91+
92+
Collection = this.collection.AsReadOnly();
93+
}
94+
95+
/// <summary>
96+
/// Determines the zero-based index of the first occurrence of the specified item in the collection.
97+
/// </summary>
98+
/// <param name="item">The item to locate in the collection.</param>
99+
/// <returns>The zero-based index of the first occurrence of <paramref name="item"/> in the collection, or -1 if the
100+
/// item is not found.</returns>
101+
public int IndexOf(T item)
102+
=> collection.IndexOf(item);
103+
104+
/// <summary>
105+
/// Determines whether the collection contains the specified item.
106+
/// </summary>
107+
/// <remarks>The method uses the default equality comparer to determine item equality.</remarks>
108+
/// <param name="item">The item to locate in the collection.</param>
109+
/// <returns><see langword="true"/> if the specified item is found in the collection; otherwise, <see langword="false"/>.</returns>
110+
public bool Contains(T item)
111+
=> collection.Contains(item);
112+
113+
/// <summary>
114+
/// Copies the elements of the collection to the specified array, starting at the specified index.
115+
/// </summary>
116+
/// <param name="array">The one-dimensional array that is the destination of the elements copied from the collection. The array must
117+
/// have zero-based indexing.</param>
118+
/// <param name="arrayIndex">The zero-based index in the destination array at which copying begins.</param>
119+
public void CopyTo(T[] array, int arrayIndex)
120+
=> collection.CopyTo(array, arrayIndex);
121+
122+
/// <summary>
123+
/// Returns an enumerator that iterates through the collection.
124+
/// </summary>
125+
/// <remarks>The enumerator provides a simple way to iterate over the elements in the
126+
/// collection.</remarks>
127+
/// <returns>An <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.</returns>
128+
public IEnumerator<T> GetEnumerator()
129+
=> collection.GetEnumerator();
130+
131+
/// <summary>
132+
/// Returns an enumerator that iterates through the collection.
133+
/// </summary>
134+
/// <remarks>This method is an explicit implementation of the <see
135+
/// cref="IEnumerable.GetEnumerator"/> method and provides support for non-generic iteration over the
136+
/// collection.</remarks>
137+
/// <returns>An <see cref="IEnumerator"/> that can be used to iterate through the collection.</returns>
138+
IEnumerator IEnumerable.GetEnumerator()
139+
=> collection.GetEnumerator();
140+
141+
/// <summary>
142+
/// Inserts an item into the collection at the specified index.
143+
/// </summary>
144+
/// <remarks>After the item is inserted, the collection is marked as modified.</remarks>
145+
/// <param name="index">The zero-based index at which the item should be inserted.</param>
146+
/// <param name="item">The item to insert into the collection.</param>
147+
public void Insert(int index, T item)
148+
{
149+
collection.Insert(index, item);
150+
151+
IsDirty = true;
152+
}
153+
154+
/// <summary>
155+
/// Adds the specified item to the collection and marks the collection as modified.
156+
/// </summary>
157+
/// <param name="item">The item to add to the collection. Cannot be null.</param>
158+
public void Add(T item)
159+
{
160+
collection.Add(item);
161+
162+
IsDirty = true;
163+
}
164+
165+
/// <summary>
166+
/// Removes the specified item from the collection.
167+
/// </summary>
168+
/// <remarks>After a successful removal, the <c>IsDirty</c> property is set to <see
169+
/// langword="true"/> to indicate that the collection's state has changed.</remarks>
170+
/// <param name="item">The item to remove from the collection.</param>
171+
/// <returns><see langword="true"/> if the item was successfully removed from the collection; otherwise, <see
172+
/// langword="false"/> if the item was not found in the collection.</returns>
173+
public bool Remove(T item)
174+
{
175+
if (collection.Remove(item))
176+
{
177+
IsDirty = true;
178+
return true;
179+
}
180+
181+
return false;
182+
}
183+
184+
/// <summary>
185+
/// Removes the element at the specified index from the collection.
186+
/// </summary>
187+
/// <remarks>After the element is removed, the collection is marked as modified by setting the
188+
/// <see cref="StorageValue.IsDirty"/> property to <see langword="true"/>.</remarks>
189+
/// <param name="index">The zero-based index of the element to remove.</param>
190+
public void RemoveAt(int index)
191+
{
192+
collection.RemoveAt(index);
193+
194+
IsDirty = true;
195+
}
196+
197+
/// <summary>
198+
/// Clears all items from the collection and marks the state as modified.
199+
/// </summary>
200+
/// <remarks>If the collection is already empty, this method has no effect. After clearing, the
201+
/// <see cref="StorageValue.IsDirty"/> property is set to <see langword="true"/>.</remarks>
202+
public void Clear()
203+
{
204+
if (collection.Count > 0)
205+
{
206+
collection.Clear();
207+
208+
IsDirty = true;
209+
}
210+
}
211+
212+
/// <inheritdoc/>
213+
public override void ReadValue(NetworkReader reader)
214+
{
215+
if (Reader<T>.read is not null)
216+
{
217+
if (collection.Count > 0)
218+
collection.Clear();
219+
220+
var count = reader.ReadInt();
221+
222+
for (var i = 0; i < count; i++)
223+
{
224+
var item = Reader<T>.read(reader);
225+
226+
collection.Add(item);
227+
}
228+
}
229+
}
230+
231+
/// <inheritdoc/>
232+
public override void WriteValue(NetworkWriter writer)
233+
{
234+
if (Writer<T>.write is not null)
235+
{
236+
writer.WriteInt(collection.Count);
237+
238+
foreach (var item in collection)
239+
Writer<T>.write(writer, item);
240+
}
241+
}
242+
243+
/// <inheritdoc/>
244+
public override string ToString()
245+
{
246+
return string.Concat(
247+
"CollectionValue{", typeof(T).Name, "} [Count = ", Count.ToString(), "]");
248+
}
249+
}
250+
}

0 commit comments

Comments
 (0)