Moved functionality into the application root
Added a CachedByteArray class that will store compressed (disk-ready) images on the appropriate media
This commit is contained in:
parent
ea5e564352
commit
ecbc3978f1
@ -1,10 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace RBG_Server
|
namespace RBG_Server
|
||||||
{
|
{
|
||||||
@ -13,5 +18,194 @@ namespace RBG_Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
public GameServer BoardGameServer {get;}
|
||||||
|
public CommunicationHandler communicationHandler { get; private set;}
|
||||||
|
protected static readonly SolidColorBrush RED_BRUSH = new(Color.FromRgb(255, 0, 0));
|
||||||
|
protected static readonly SolidColorBrush BLUE_BRUSH = new(Color.FromRgb(0, 0, 255));
|
||||||
|
protected static readonly SolidColorBrush GREEN_BRUSH = new(Color.FromRgb(0, 255, 0));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the specified files from disk, creating mip maps as appropriate
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="selectedFiles"></param>
|
||||||
|
/// <param name="isSprite"></param>
|
||||||
|
/// <param name="spriteLimit"></param>
|
||||||
|
void LoadFiles(string[] selectedFiles, bool isSprite=false, int spriteLimit=128)
|
||||||
|
{
|
||||||
|
// Continue in parallel
|
||||||
|
_ = Parallel.ForEach(selectedFiles, (file) =>
|
||||||
|
{
|
||||||
|
LoadFile(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a singular file from disk, generating Mip maps as required
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
void LoadFile(string path, bool isSprite=true)
|
||||||
|
{
|
||||||
|
// Check if the file is a .gif, first.
|
||||||
|
using FileStream fs = File.OpenRead(path);
|
||||||
|
LoadStream(fs, path, isSprite);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadStream(Stream source, string path, bool isSprite)
|
||||||
|
{
|
||||||
|
long srcStart = source.Position;
|
||||||
|
byte[] vs = new byte[6]; // First 6 bytes is the gif specifier
|
||||||
|
source.Read(vs, 0, 6);
|
||||||
|
source.Position = srcStart;
|
||||||
|
string magicNumber = Encoding.ASCII.GetString(vs);
|
||||||
|
if (magicNumber == "GIF87a" || magicNumber == "GIF89a")
|
||||||
|
{
|
||||||
|
// Gif file, replace with a layered .png
|
||||||
|
GifBitmapDecoder decoder = new(source, BitmapCreateOptions.None, BitmapCacheOption.Default);
|
||||||
|
// Obtain each .gif frame
|
||||||
|
BitmapFrame[] frames = new BitmapFrame[decoder.Frames.Count];
|
||||||
|
decoder.Frames.CopyTo(frames, 0);
|
||||||
|
// Get frame sizes
|
||||||
|
int height = frames[0].PixelHeight;
|
||||||
|
int width = frames[0].PixelWidth;
|
||||||
|
// The lowest mip is static, using either the thumb or first frame
|
||||||
|
if (decoder.Thumbnail != null)
|
||||||
|
{
|
||||||
|
double scaleRate = 32 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(decoder.Thumbnail, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_low" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double scaleRate = 32 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(frames[0], scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_low" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
// The remaining mips are animated
|
||||||
|
if (width >= 128 || height >= 128)
|
||||||
|
{
|
||||||
|
double scaleRate = 128 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleAnimatedImage(frames, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_med" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 512 || height >= 512)
|
||||||
|
{
|
||||||
|
double scaleRate = 512 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleAnimatedImage(frames, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_high" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 512 || height >= 512)
|
||||||
|
{
|
||||||
|
// Just re-encode the gif to .png
|
||||||
|
PngBitmapEncoder encoder = new();
|
||||||
|
foreach (BitmapFrame frame in frames)
|
||||||
|
{
|
||||||
|
encoder.Frames.Add(frame);
|
||||||
|
}
|
||||||
|
MemoryStream ms = new();
|
||||||
|
encoder.Save(ms);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_raw" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Regular image file
|
||||||
|
// Create the bitmap to get the current intrinsics
|
||||||
|
BitmapImage bitmapImage = new();
|
||||||
|
bitmapImage.BeginInit();
|
||||||
|
bitmapImage.StreamSource = source;
|
||||||
|
bitmapImage.CacheOption = BitmapCacheOption.None;
|
||||||
|
bitmapImage.EndInit();
|
||||||
|
int height = bitmapImage.PixelHeight;
|
||||||
|
int width = bitmapImage.PixelWidth;
|
||||||
|
|
||||||
|
// Behaviour depends on the required image
|
||||||
|
if (isSprite)
|
||||||
|
{
|
||||||
|
// Sprites should have a low mip-map, and the medium
|
||||||
|
// Low = 32*32
|
||||||
|
// Medium = 128*128
|
||||||
|
// High = 512*512 or image size if less than 1024
|
||||||
|
if (width >= 32 || height >= 32)
|
||||||
|
{
|
||||||
|
double scaleRate = 32 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(bitmapImage, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_low" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 128 || height >= 128)
|
||||||
|
{
|
||||||
|
double scaleRate = 128 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(bitmapImage, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_med" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 512 || height >= 512)
|
||||||
|
{
|
||||||
|
double scaleRate = 512 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(bitmapImage, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_high" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 512 || height >= 512)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new();
|
||||||
|
source.CopyTo(ms);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_raw" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Non-sprites should have a low mip-map, and the medium, but at a larger size than the sprites
|
||||||
|
// Low = 128*128
|
||||||
|
// Medium = 512*512
|
||||||
|
// High = 2048*2048
|
||||||
|
if (width >= 128 || height >= 128)
|
||||||
|
{
|
||||||
|
double scaleRate = 128 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(bitmapImage, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_low" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 512 || height >= 512)
|
||||||
|
{
|
||||||
|
double scaleRate = 512 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(bitmapImage, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_med" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 2048 || height >= 2048)
|
||||||
|
{
|
||||||
|
double scaleRate = 2048 / (double)Math.Max(height, width);
|
||||||
|
MemoryStream ms = ScaleImage(bitmapImage, scaleRate);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_high" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
if (width >= 2048 || height >= 2048)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new();
|
||||||
|
source.CopyTo(ms);
|
||||||
|
_ = communicationHandler.ImageCollection.TryAdd(Path.GetFileNameWithoutExtension(path) + "_mip_raw" + Path.GetExtension(path), new CachedByteArray(ms.ToArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryStream ScaleImage(BitmapSource source, double scaleRate)
|
||||||
|
{
|
||||||
|
TransformedBitmap modified = new(source, new ScaleTransform(source.PixelWidth * scaleRate, source.PixelHeight * scaleRate));
|
||||||
|
PngBitmapEncoder encoder = new();
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(modified));
|
||||||
|
MemoryStream ms = new();
|
||||||
|
encoder.Save(ms);
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryStream ScaleAnimatedImage(BitmapFrame[] frames, double scaleRate)
|
||||||
|
{
|
||||||
|
PngBitmapEncoder encoder = new();
|
||||||
|
foreach (BitmapFrame frame in frames)
|
||||||
|
{
|
||||||
|
TransformedBitmap modified = new(frame, new ScaleTransform(frame.PixelWidth * scaleRate, frame.PixelHeight * scaleRate));
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(modified));
|
||||||
|
}
|
||||||
|
MemoryStream ms = new();
|
||||||
|
encoder.Save(ms);
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
240
RBG_Server.Core/CachedByteArray.cs
Normal file
240
RBG_Server.Core/CachedByteArray.cs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace RBG_Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stores data in either memory or disk, depending on the required behaviour of the system, namely, if the
|
||||||
|
/// byte array exceeds the size of the Memory Limit (objects can be required to stay in RAM)
|
||||||
|
/// </summary>
|
||||||
|
public struct CachedByteArray : IDisposable
|
||||||
|
{
|
||||||
|
// Static event, so that every instance of CachedByteArray is affected by a change in memory limit
|
||||||
|
private static event EventHandler MemoryLimitModifiedEvent;
|
||||||
|
// Properties to keep track of the actual allocated cache amounts
|
||||||
|
private static long memoryAllocationTotal = 0;
|
||||||
|
private static long memoryAllocationRAM = 0;
|
||||||
|
// static fields
|
||||||
|
private static int memoryLimit = 1024 * 1024 * 1; // Start with a memory limit of 1 MB
|
||||||
|
|
||||||
|
private static readonly string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Remote Board Game\\Cache Objects");
|
||||||
|
// static properties
|
||||||
|
public static long MemoryAllocationRAM { get { return memoryAllocationRAM; } }
|
||||||
|
public static long MemoryAllocationTotal { get { return memoryAllocationTotal; } }
|
||||||
|
public static long MemoryAllocationDisk { get { return memoryAllocationTotal - memoryAllocationRAM; } }
|
||||||
|
|
||||||
|
private SemaphoreSlim accessSem;
|
||||||
|
|
||||||
|
public bool IsInRam { get; private set; }
|
||||||
|
|
||||||
|
// struct data
|
||||||
|
private bool useOnlyRAM;
|
||||||
|
|
||||||
|
private Stream dataStream;
|
||||||
|
|
||||||
|
private string dataHashCode;
|
||||||
|
public bool UseOnlyRAM { get => useOnlyRAM; set => useOnlyRAM = value; }
|
||||||
|
// Properties from the array itself
|
||||||
|
public long Length => dataStream.Length;
|
||||||
|
/// <summary>
|
||||||
|
/// Set the memory limit for keeping data in RAM, as opposed to storing on disk.
|
||||||
|
/// Will raise events if modified, so all instances will be refactored to disk if appropriate
|
||||||
|
/// </summary>
|
||||||
|
public static int MemoryLimit
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return memoryLimit;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
memoryLimit = value;
|
||||||
|
MemoryLimitModifiedEvent?.Invoke(null, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CachedByteArray(byte[] b) : this(b, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CachedByteArray(byte[] b, bool useOnlyRAM)
|
||||||
|
{
|
||||||
|
accessSem = new SemaphoreSlim(0,1);
|
||||||
|
this.useOnlyRAM = useOnlyRAM;
|
||||||
|
dataHashCode = GenerateDataHash(b);
|
||||||
|
if (!useOnlyRAM && b.Length > MemoryLimit)
|
||||||
|
{
|
||||||
|
// Save to a file; load the data
|
||||||
|
string path = Path.Combine(folderPath, dataHashCode);
|
||||||
|
dataStream = CopyIntoFile(b, path);
|
||||||
|
IsInRam = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataStream = new MemoryStream(b);
|
||||||
|
memoryAllocationRAM += dataStream.Length;
|
||||||
|
IsInRam = true;
|
||||||
|
}
|
||||||
|
dataStream.Position = 0;
|
||||||
|
memoryAllocationTotal += dataStream.Length;
|
||||||
|
// Signal the semaphore is ready
|
||||||
|
accessSem.Release();
|
||||||
|
// Listen to changes in the memory limit
|
||||||
|
MemoryLimitModifiedEvent += CachedByteArray_memoryLimitModifiedEvent;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a SHA256 hash of the data in the array, and returns it in a safe string format
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="byteData"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static string GenerateDataHash(byte[] byteData)
|
||||||
|
{
|
||||||
|
using SHA256 dataHash = SHA256.Create();
|
||||||
|
byte[] hashCode = dataHash.ComputeHash(byteData);
|
||||||
|
string generatedHashCode = "";
|
||||||
|
foreach (byte hashByte in hashCode)
|
||||||
|
{
|
||||||
|
generatedHashCode += $"{hashByte:X2}";
|
||||||
|
}
|
||||||
|
return generatedHashCode;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the given <see cref="byte[]"/> into the file at <paramref name="filePath"/>, opening the stream, and returning it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="byteData"></param>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static Stream CopyIntoFile(byte[] byteData, string filePath)
|
||||||
|
{
|
||||||
|
FileStream s = File.OpenWrite(filePath);
|
||||||
|
foreach (byte b in byteData)
|
||||||
|
{
|
||||||
|
s.WriteByte(b);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CachedByteArray_memoryLimitModifiedEvent(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!useOnlyRAM)
|
||||||
|
{
|
||||||
|
if (dataStream.Length > MemoryLimit && IsInRam)
|
||||||
|
{
|
||||||
|
// Memory stream exceeds the limit; create an FS and copy
|
||||||
|
accessSem.Wait();
|
||||||
|
// Get the underlying byte[]
|
||||||
|
byte[] dataBytes = ((MemoryStream)dataStream).ToArray();
|
||||||
|
// Generate the hashcode from the data
|
||||||
|
dataHashCode = GenerateDataHash(dataBytes);
|
||||||
|
// Create the file, and copy the data
|
||||||
|
dataStream = CopyIntoFile(dataBytes, Path.Combine(folderPath, dataHashCode));
|
||||||
|
// Disregard the allocation from RAM
|
||||||
|
memoryAllocationRAM -= dataStream.Length;
|
||||||
|
accessSem.Release();
|
||||||
|
}
|
||||||
|
else if (dataStream.Length <= MemoryLimit && !IsInRam)
|
||||||
|
{
|
||||||
|
// Data stream is no larger than the limit, but the file is saved on disk; load to RAM instead
|
||||||
|
// Obtain a lock on the stream
|
||||||
|
accessSem.Wait();
|
||||||
|
// Copy data to memory
|
||||||
|
dataStream.Position = 0;
|
||||||
|
MemoryStream ms = new();
|
||||||
|
dataStream.CopyTo(ms);
|
||||||
|
// Increase the amount noted in RAM
|
||||||
|
memoryAllocationRAM += ms.Length;
|
||||||
|
// Close the FileStream
|
||||||
|
dataStream.Close();
|
||||||
|
// Delete the file
|
||||||
|
DeleteFile(Path.Combine(folderPath, dataHashCode));
|
||||||
|
// Set the datastream to the memory location
|
||||||
|
dataStream = ms;
|
||||||
|
accessSem.Release();
|
||||||
|
// Check the loaded data matches the hashcode we already have
|
||||||
|
string loadedHashCode = GenerateDataHash(ms.ToArray());
|
||||||
|
if (loadedHashCode != dataHashCode)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Data was modified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, the data is already in the correct spot, so we don't need to do anything else
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static void DeleteFile(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static implicit operator byte[](CachedByteArray c)
|
||||||
|
{
|
||||||
|
byte[] dataBytes = new byte[c.dataStream.Length];
|
||||||
|
c.accessSem.Wait();
|
||||||
|
if (c.IsInRam)
|
||||||
|
{
|
||||||
|
// We can use the underlying array from the MemoryStream
|
||||||
|
dataBytes = (c.dataStream as MemoryStream).ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ensure we read from the beginning of the stream
|
||||||
|
c.dataStream.Position = 0;
|
||||||
|
c.dataStream.Read(dataBytes, 0, dataBytes.Length);
|
||||||
|
}
|
||||||
|
c.accessSem.Release();
|
||||||
|
return dataBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static explicit operator CachedByteArray(byte[] b) => new CachedByteArray(b);
|
||||||
|
/// <summary>
|
||||||
|
/// Array index operator, allows the CachedByteArray to act more completely as a byte[]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public byte this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int b;
|
||||||
|
// Aquire the semaphore, so that the read is effectively atomic (ensures the read is performed on the expected data)
|
||||||
|
accessSem.Wait();
|
||||||
|
dataStream.Position = index;
|
||||||
|
b = dataStream.ReadByte();
|
||||||
|
accessSem.Release();
|
||||||
|
if (b == -1)
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException(index.ToString());
|
||||||
|
}
|
||||||
|
return (byte)b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
accessSem.Wait();
|
||||||
|
// Remove the event listener
|
||||||
|
MemoryLimitModifiedEvent -= CachedByteArray_memoryLimitModifiedEvent;
|
||||||
|
// Remove the allocation from the counter
|
||||||
|
memoryAllocationTotal -= dataStream.Length;
|
||||||
|
memoryAllocationRAM -= dataStream.Length;
|
||||||
|
// If the FileStream is opened, close and dispose it
|
||||||
|
dataStream?.Dispose();
|
||||||
|
// If the file is on disk, remove it
|
||||||
|
DeleteFile(Path.Combine(folderPath, dataHashCode));
|
||||||
|
// Release all resources obtained by the semaphore
|
||||||
|
accessSem.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
RBG_Server.Core/CommunicationHandler.cs
Normal file
24
RBG_Server.Core/CommunicationHandler.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace RBG_Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains all communication data, from both the client's and server's perspective (should be able to switch between each mode as necessary)
|
||||||
|
/// </summary>
|
||||||
|
public class CommunicationHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Image data is stored in memory in a dictionary collection. Each byte[] represents an image file, compressed according to its file type;
|
||||||
|
/// which helps save space in memory. Uses a CachedByteArray; essentially a normal byte array but automatically stored on disk if it is larger
|
||||||
|
/// than a set size.
|
||||||
|
/// This limit can be changed at any time
|
||||||
|
/// </summary>
|
||||||
|
public ConcurrentDictionary<string, CachedByteArray> ImageCollection { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user