diff --git a/Nibriboard/CommandConsole.cs b/Nibriboard/CommandConsole.cs
index 680a0df..81f26e1 100644
--- a/Nibriboard/CommandConsole.cs
+++ b/Nibriboard/CommandConsole.cs
@@ -1,10 +1,12 @@
using System;
+using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Nibriboard.Client;
using Nibriboard.RippleSpace;
+using Nibriboard.Utilities;
namespace Nibriboard
{
@@ -74,9 +76,12 @@ namespace Nibriboard
break;
case "save":
await destination.WriteAsync("Saving ripple space - ");
- await server.PlaneManager.Save();
+ Stopwatch timer = Stopwatch.StartNew();
+ long bytesWritten = await server.PlaneManager.Save();
+ long msTaken = timer.ElapsedMilliseconds;
await destination.WriteLineAsync("done.");
- await destination.WriteLineAsync($"Save is now {BytesToString(server.PlaneManager.LastSaveFileSize)} in size.");
+ await destination.WriteLineAsync($"{Formatters.HumanSize(bytesWritten)} written in {msTaken}ms.");
+ await destination.WriteLineAsync($"Save is now {Formatters.HumanSize(server.PlaneManager.LastSaveSize)} in size.");
break;
case "plane":
if(commandParts.Length < 2) {
@@ -194,16 +199,5 @@ namespace Nibriboard
break;
}
}
-
- public static string BytesToString(long byteCount)
- {
- string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; // Longs run out around EB
- if(byteCount == 0)
- return "0" + suf[0];
- long bytes = Math.Abs(byteCount);
- int place = (int)Math.Floor(Math.Log(bytes, 1024));
- double num = Math.Round(bytes / Math.Pow(1024, place), 1);
- return (Math.Sign(byteCount) * num).ToString() + suf[place];
- }
}
}
diff --git a/Nibriboard/Nibriboard.csproj b/Nibriboard/Nibriboard.csproj
index 6f0bbac..fbbcc54 100644
--- a/Nibriboard/Nibriboard.csproj
+++ b/Nibriboard/Nibriboard.csproj
@@ -144,6 +144,7 @@
+
@@ -179,10 +180,7 @@
-
-
-
-
+
diff --git a/Nibriboard/NibriboardServer.cs b/Nibriboard/NibriboardServer.cs
index d54723f..9bcd40a 100644
--- a/Nibriboard/NibriboardServer.cs
+++ b/Nibriboard/NibriboardServer.cs
@@ -46,22 +46,20 @@ namespace Nibriboard
public readonly int CommandPort = 31587;
public readonly int Port = 31586;
- public RippleSpaceManager PlaneManager;
- public NibriboardApp AppServer;
+ public readonly RippleSpaceManager PlaneManager;
+ public readonly NibriboardApp AppServer;
public NibriboardServer(string pathToRippleSpace, int inPort = 31586)
{
Port = inPort;
- // Load the specified packed ripple space file if it exists - otherwise save it to disk
- if(File.Exists(pathToRippleSpace))
- {
- PlaneManager = RippleSpaceManager.FromFile(pathToRippleSpace).Result;
+ // Load the specified ripple space if it exists - otherwise save it to disk
+ if(Directory.Exists(pathToRippleSpace)) {
+ PlaneManager = RippleSpaceManager.FromDirectory(pathToRippleSpace).Result;
}
- else
- {
+ else {
Log.WriteLine("[NibriboardServer] Couldn't find packed ripple space at {0} - creating new ripple space instead.", pathToRippleSpace);
- PlaneManager = new RippleSpaceManager() { SourceFilename = pathToRippleSpace };
+ PlaneManager = new RippleSpaceManager(pathToRippleSpace);
}
clientSettings = new ClientSettings() {
diff --git a/Nibriboard/Program.cs b/Nibriboard/Program.cs
index 120d0c5..bcd0bf4 100644
--- a/Nibriboard/Program.cs
+++ b/Nibriboard/Program.cs
@@ -10,7 +10,7 @@ namespace Nibriboard
{
public static void Main(string[] args)
{
- string packedRippleSpaceFile = "./default.ripplespace.zip";
+ string packedRippleSpaceFile = "./default-ripplespace";
for(int i = 0; i < args.Length; i++)
{
diff --git a/Nibriboard/RippleSpace/Chunk.cs b/Nibriboard/RippleSpace/Chunk.cs
index b3d2ce5..34fa5ab 100644
--- a/Nibriboard/RippleSpace/Chunk.cs
+++ b/Nibriboard/RippleSpace/Chunk.cs
@@ -7,6 +7,11 @@ using System.Runtime.Serialization;
using Newtonsoft.Json;
using Nibriboard.Utilities;
+using SharpCompress.Archives;
+using SharpCompress.Readers;
+using SharpCompress.Common;
+using SharpCompress.Compressors.BZip2;
+using SharpCompress.Compressors;
namespace Nibriboard.RippleSpace
{
@@ -90,6 +95,14 @@ namespace Nibriboard.RippleSpace
///
public DateTime TimeLastAccessed { get; private set; } = DateTime.Now;
+ ///
+ /// Whether this chunk is currently empty or not.
+ ///
+ public bool IsEmpty {
+ get {
+ return lines.Count == 0;
+ }
+ }
///
/// Whether this is a primary chunk.
/// Primary chunks are always loaded.
@@ -225,13 +238,16 @@ namespace Nibriboard.RippleSpace
public static async Task FromFile(Plane plane, string filename)
{
- StreamReader chunkSource = new StreamReader(filename);
+ Stream chunkSource = File.OpenRead(filename);
return await FromStream(plane, chunkSource);
}
- public static async Task FromStream(Plane plane, StreamReader chunkSource)
+ public static async Task FromStream(Plane plane, Stream chunkSource)
{
+ BZip2Stream decompressor = new BZip2Stream(chunkSource, CompressionMode.Decompress);
+ StreamReader decompressedSource = new StreamReader(decompressor);
+
Chunk loadedChunk = JsonConvert.DeserializeObject(
- await chunkSource.ReadToEndAsync(),
+ await decompressedSource.ReadToEndAsync(),
new JsonSerializerSettings() {
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
@@ -256,9 +272,13 @@ namespace Nibriboard.RippleSpace
/// Saves this chunk to the specified stream.
///
/// The destination stream to save the chunk to.
- public async Task SaveTo(StreamWriter destination)
+ public async Task SaveTo(Stream destination)
{
- await destination.WriteLineAsync(JsonConvert.SerializeObject(this));
+ BZip2Stream compressor = new BZip2Stream(destination, CompressionMode.Compress);
+ StreamWriter destWriter = new StreamWriter(compressor) { AutoFlush = true };
+
+ await destWriter.WriteLineAsync(JsonConvert.SerializeObject(this));
+ compressor.Close();
destination.Close();
}
diff --git a/Nibriboard/RippleSpace/ChunkReference.cs b/Nibriboard/RippleSpace/ChunkReference.cs
index c1b2cac..0631a0a 100644
--- a/Nibriboard/RippleSpace/ChunkReference.cs
+++ b/Nibriboard/RippleSpace/ChunkReference.cs
@@ -15,6 +15,28 @@ namespace Nibriboard.RippleSpace
///
public class ChunkReference : Reference
{
+ ///
+ /// The region size to use.
+ /// Regions are used when saving and loading to avoid too many files being stored in a
+ /// single directory.
+ ///
+ public static int RegionSize = 32;
+
+ ///
+ /// Converts this ChunkReference into a RegionReference.
+ /// A RegionReference is used when saving, to work out which folder it should go in.
+ /// The size of the regions used is determined by the property.
+ ///
+ public ChunkReference RegionReference {
+ get {
+ return new ChunkReference(
+ Plane,
+ (int)Math.Floor((float)X / (float)RegionSize),
+ (int)Math.Floor((float)Y / (float)RegionSize)
+ );
+ }
+ }
+
public ChunkReference(Plane inPlane, int inX, int inY) : base(inPlane, inX, inY)
{
@@ -54,12 +76,12 @@ namespace Nibriboard.RippleSpace
);
}
- public string AsFilename()
+ public string AsFilepath()
{
- return $"{Plane.Name}-{X},{Y}.chunk";
+ return Path.Combine($"Region_{RegionReference.X},{RegionReference.Y}", $"{X},{Y}.chunk");
}
- public override int GetHashCode ()
+ public override int GetHashCode()
{
return $"({Plane.Name})+{X}+{Y}".GetHashCode();
}
diff --git a/Nibriboard/RippleSpace/Plane.cs b/Nibriboard/RippleSpace/Plane.cs
index 8d2e8b3..8ff6c09 100644
--- a/Nibriboard/RippleSpace/Plane.cs
+++ b/Nibriboard/RippleSpace/Plane.cs
@@ -179,7 +179,7 @@ namespace Nibriboard.RippleSpace
// Uh-oh! The chunk isn't loaded at moment. Load it quick & then
// return it fast.
- string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
+ string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilepath());
Chunk loadedChunk;
if(File.Exists(chunkFilePath)) // If the chunk exists on disk, load it
loadedChunk = await Chunk.FromFile(this, chunkFilePath);
@@ -210,7 +210,7 @@ namespace Nibriboard.RippleSpace
if(loadedChunkspace.ContainsKey(chunkLocation))
return true;
- string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
+ string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilepath());
if(File.Exists(chunkFilePath))
return true;
@@ -220,16 +220,19 @@ namespace Nibriboard.RippleSpace
public async Task SaveChunk(ChunkReference chunkLocation)
{
// It doesn't exist, so we can't save it :P
- if(!loadedChunkspace.ContainsKey(chunkLocation))
+ if (!loadedChunkspace.ContainsKey(chunkLocation))
return;
Chunk chunk = loadedChunkspace[chunkLocation];
- string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
- using(StreamWriter chunkDestination = new StreamWriter(chunkFilePath))
- {
+ // If it's empty, then there's no point in saving it
+ if (chunk.IsEmpty)
+ return;
+
+ string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilepath());
+
+ using (Stream chunkDestination = File.Open(chunkFilePath, FileMode.OpenOrCreate))
await chunk.SaveTo(chunkDestination);
- }
}
public async Task AddLine(DrawnLine newLine)
@@ -279,7 +282,7 @@ namespace Nibriboard.RippleSpace
// This chunk has been inactive for a while - let's serialise it and save it to disk
Stream chunkSerializationSink = new FileStream(
- Path.Combine(StorageDirectory, chunkEntry.Key.AsFilename()),
+ Path.Combine(StorageDirectory, chunkEntry.Key.AsFilepath()),
FileMode.Create,
FileAccess.Write,
FileShare.None
@@ -292,7 +295,7 @@ namespace Nibriboard.RippleSpace
}
}
- public async Task Save(Stream destination)
+ public async Task Save()
{
// Save all the chunks to disk
List chunkSavers = new List();
@@ -301,32 +304,31 @@ namespace Nibriboard.RippleSpace
// Figure out where to put the chunk and create the relevant directories
string chunkDestinationFilename = CalcPaths.ChunkFilepath(StorageDirectory, loadedChunkItem.Key);
Directory.CreateDirectory(Path.GetDirectoryName(chunkDestinationFilename));
- // Ask the chunk to save itself
- StreamWriter chunkDestination = new StreamWriter(chunkDestinationFilename);
+ // Ask the chunk to save itself, but only if it isn't empty
+ if (loadedChunkItem.Value.IsEmpty)
+ continue;
+
+ Stream chunkDestination = File.Open(chunkDestinationFilename, FileMode.OpenOrCreate);
chunkSavers.Add(loadedChunkItem.Value.SaveTo(chunkDestination));
}
await Task.WhenAll(chunkSavers);
// Save the plane information
- StreamWriter planeInfoWriter = new StreamWriter(CalcPaths.UnpackedPlaneIndex(StorageDirectory));
+ StreamWriter planeInfoWriter = new StreamWriter(CalcPaths.PlaneIndex(StorageDirectory));
await planeInfoWriter.WriteLineAsync(JsonConvert.SerializeObject(Info));
planeInfoWriter.Close();
- // Pack the chunks & plane information into an nplane file
- WriterOptions packingOptions = new WriterOptions(CompressionType.Deflate);
-
- IEnumerable chunkFiles = Directory.GetFiles(StorageDirectory.TrimEnd("/".ToCharArray()));
- using(IWriter packer = WriterFactory.Open(destination, ArchiveType.Zip, packingOptions))
+ // Calculate the total number bytes written
+ long totalSize = 0;
+ foreach (KeyValuePair loadedChunkItem in loadedChunkspace)
{
- packer.Write("plane-index.json", CalcPaths.UnpackedPlaneIndex(StorageDirectory));
-
- foreach(string nextChunkFile in chunkFiles)
- {
- packer.Write($"{Name}/{Path.GetFileName(nextChunkFile)}", nextChunkFile);
- }
+ string destFilename = CalcPaths.ChunkFilepath(StorageDirectory, loadedChunkItem.Key);
+ if (!File.Exists(destFilename)) // Don't assume that the file exists - it might be an empty chunk
+ continue;
+ totalSize += (new FileInfo(destFilename)).Length;
}
- destination.Flush();
- destination.Close();
+
+ return totalSize;
}
///
@@ -351,38 +353,21 @@ namespace Nibriboard.RippleSpace
}
///
- /// Loads a plane form a given nplane file.
+ /// Loads a plane from a given nplane file.
///
- /// The name of the plane to load.
- /// The directory to which the plane should be unpacked.
- /// The path to the nplane file to load.
- /// Whether the source file should be deleted once the plane has been loaded.
+ /// The directory from which the plane should be loaded.
/// The loaded plane.
- public static async Task FromFile(string planeName, string storageDirectoryRoot, string sourceFilename, bool deleteSource)
+ public static async Task FromDirectory(string planeDirectory)
{
- string targetUnpackingPath = CalcPaths.UnpackedPlaneDir(storageDirectoryRoot, planeName);
-
- // Unpack the plane to the temporary directory
- using(Stream sourceStream = File.OpenRead(sourceFilename))
- using(IReader unpacker = ReaderFactory.Open(sourceStream))
- {
- Directory.CreateDirectory(targetUnpackingPath);
- unpacker.WriteAllToDirectory(targetUnpackingPath);
- }
-
PlaneInfo planeInfo = JsonConvert.DeserializeObject(
- File.ReadAllText(CalcPaths.UnpackedPlaneIndex(targetUnpackingPath))
+ File.ReadAllText(CalcPaths.PlaneIndex(planeDirectory))
);
- planeInfo.Name = planeName;
- Plane loadedPlane = new Plane(planeInfo, targetUnpackingPath);
+ Plane loadedPlane = new Plane(planeInfo, planeDirectory);
- // Load the primary chunks from disk inot the plane
+ // Load the primary chunks into the plane
await loadedPlane.LoadPrimaryChunks();
- if(deleteSource)
- File.Delete(sourceFilename);
-
return loadedPlane;
}
}
diff --git a/Nibriboard/RippleSpace/RippleSpaceManager.cs b/Nibriboard/RippleSpace/RippleSpaceManager.cs
index d327437..a237446 100644
--- a/Nibriboard/RippleSpace/RippleSpaceManager.cs
+++ b/Nibriboard/RippleSpace/RippleSpaceManager.cs
@@ -13,16 +13,10 @@ namespace Nibriboard.RippleSpace
{
public class RippleSpaceManager
{
- ///
- /// The filename from which this ripplespace was loaded, and the filename to which it should be saved again.
- ///
- /// The source filename.
- public string SourceFilename { get; set; }
-
///
/// The temporary directory in which we are currently storing our unpacked planes temporarily.
///
- public string UnpackedDirectory { get; set; }
+ public string SourceDirectory { get; set; }
///
/// The master list of planes that this PlaneManager is in charge of.
@@ -46,28 +40,31 @@ namespace Nibriboard.RippleSpace
/// Returns 0 if this RippleSpace hasn't been saved yet.
///
/// The last size of the save file.
- public long LastSaveFileSize {
+ public long LastSaveSize {
get {
- if(!File.Exists(SourceFilename))
+ if(!Directory.Exists(SourceDirectory))
return 0;
-
- return (new FileInfo(SourceFilename)).Length;
+
+ return (new DirectoryInfo(SourceDirectory))
+ .GetFiles("*", SearchOption.AllDirectories)
+ .Sum(file => file.Length);
}
}
- public RippleSpaceManager()
+ public RippleSpaceManager(string inSourceDirectory)
{
- // Create a temporary directory in which to store our unpacked planes
- UnpackedDirectory = Path.GetTempFileName();
- File.Delete(UnpackedDirectory);
- UnpackedDirectory = Path.GetDirectoryName(UnpackedDirectory) + "/ripplespace-" + Path.GetFileName(UnpackedDirectory) + "/";
- Directory.CreateDirectory(UnpackedDirectory);
+ SourceDirectory = inSourceDirectory;
- Log.WriteLine("[RippleSpace] New blank ripplespace initialised.");
+ // Make sure that the source directory exists
+ if (!Directory.Exists(SourceDirectory)) {
+ Directory.CreateDirectory(SourceDirectory);
+
+ Log.WriteLine("[RippleSpace] New blank ripplespace initialised.");
+ }
}
~RippleSpaceManager()
{
- Directory.Delete(UnpackedDirectory, true);
+ Directory.Delete(SourceDirectory, true);
}
///
@@ -110,7 +107,7 @@ namespace Nibriboard.RippleSpace
Plane newPlane = new Plane(
newPlaneInfo,
- CalcPaths.UnpackedPlaneDir(UnpackedDirectory, newPlaneInfo.Name)
+ CalcPaths.PlaneDirectory(SourceDirectory, newPlaneInfo.Name)
);
Planes.Add(newPlane);
return newPlane;
@@ -142,88 +139,72 @@ namespace Nibriboard.RippleSpace
}
}
- public async Task Save()
+ public async Task Save()
{
Stopwatch timer = Stopwatch.StartNew();
// Save the planes to disk
- List planeSavers = new List();
- StreamWriter indexWriter = new StreamWriter(UnpackedDirectory + "index.list");
- foreach(Plane item in Planes)
+ List> planeSavers = new List>();
+ StreamWriter indexWriter = new StreamWriter(SourceDirectory + "index.list");
+ foreach(Plane currentPlane in Planes)
{
// Add the plane to the index
- await indexWriter.WriteLineAsync(item.Name);
-
- // Figure out where the plane should save itself to and create the appropriate directories
- string planeSavePath = CalcPaths.UnpackedPlaneFile(UnpackedDirectory, item.Name);
- Directory.CreateDirectory(Path.GetDirectoryName(planeSavePath));
+ await indexWriter.WriteLineAsync(currentPlane.Name);
// Ask the plane to save to the directory
- planeSavers.Add(item.Save(File.OpenWrite(planeSavePath)));
+ planeSavers.Add(currentPlane.Save());
}
indexWriter.Close();
await Task.WhenAll(planeSavers);
+ long totalBytesWritten = planeSavers.Sum((Task saver) => saver.Result);
- // Pack the planes into the ripplespace archive
- Stream destination = File.OpenWrite(SourceFilename);
- string[] planeFiles = Directory.GetFiles(UnpackedDirectory, "*.nplane.zip", SearchOption.TopDirectoryOnly);
-
- using(IWriter rippleSpacePacker = WriterFactory.Open(destination, ArchiveType.Zip, new WriterOptions(CompressionType.Deflate)))
- {
- rippleSpacePacker.Write("index.list", UnpackedDirectory + "index.list");
- foreach(string planeFilename in planeFiles)
- {
- rippleSpacePacker.Write(Path.GetFileName(planeFilename), planeFilename);
- }
- }
- destination.Close();
-
- Log.WriteLine("[Command/Save] Save complete in {0}ms", timer.ElapsedMilliseconds);
- }
-
- public static async Task FromFile(string filename)
- {
- if(!File.Exists(filename))
- throw new FileNotFoundException($"Error: Couldn't find the packed ripplespace at {filename}");
-
- RippleSpaceManager rippleSpace = new RippleSpaceManager();
- rippleSpace.SourceFilename = filename;
-
- using(Stream packedRippleSpaceStream = File.OpenRead(filename))
- using(IReader rippleSpaceUnpacker = ReaderFactory.Open(packedRippleSpaceStream))
- {
- Log.WriteLine($"[Core] Unpacking ripplespace packed with {rippleSpaceUnpacker.ArchiveType} from {filename}.");
- rippleSpaceUnpacker.WriteAllToDirectory(rippleSpace.UnpackedDirectory);
- }
- Log.WriteLine("[Core] done!");
-
- if(!File.Exists(rippleSpace.UnpackedDirectory + "index.list"))
- throw new InvalidDataException($"Error: The packed ripplespace at {filename} doesn't appear to contain an index file.");
-
- Log.WriteLine("[Core] Importing planes");
-
- StreamReader planes = new StreamReader(rippleSpace.UnpackedDirectory + "index.list");
- List> planeReaders = new List>();
- string nextPlane;
- int planeCount = 0;
- while((nextPlane = await planes.ReadLineAsync()) != null)
- {
- planeReaders.Add(Plane.FromFile(
- planeName: nextPlane,
- storageDirectoryRoot: rippleSpace.UnpackedDirectory,
- sourceFilename: CalcPaths.UnpackedPlaneFile(rippleSpace.UnpackedDirectory, nextPlane),
- deleteSource: true
- ));
- planeCount++;
- }
- await Task.WhenAll(planeReaders);
-
- rippleSpace.Planes.AddRange(
- planeReaders.Select((Task planeReader) => planeReader.Result)
+ Log.WriteLine(
+ "[Command/Save] Save complete - {0} written in {1}ms",
+ Formatters.HumanSize(totalBytesWritten),
+ timer.ElapsedMilliseconds
);
- Log.WriteLine("[Core] done! {0} planes loaded.", planeCount);
+ return totalBytesWritten;
+ }
+
+ public static async Task FromDirectory(string sourceDirectory)
+ {
+
+ RippleSpaceManager rippleSpace = new RippleSpaceManager(sourceDirectory);
+
+ if (!Directory.Exists(sourceDirectory))
+ {
+ Log.WriteLine($"[Core] Creating new ripplespace in {sourceDirectory}.");
+ return rippleSpace;
+ }
+
+ Log.WriteLine($"[Core] Loading ripplespace from {sourceDirectory}.");
+
+ // Load the planes in
+ if (!File.Exists(rippleSpace.SourceDirectory + "index.list"))
+ throw new InvalidDataException($"Error: The ripplespace at {sourceDirectory} doesn't appear to contain an index file.");
+
+ Log.WriteLine("[Core] Importing planes");
+ Stopwatch timer = Stopwatch.StartNew();
+
+ StreamReader planeList = new StreamReader(sourceDirectory + "index.list");
+
+ List> planeLoaders = new List>();
+ string nextPlaneName = string.Empty;
+ while ((nextPlaneName = await planeList.ReadLineAsync()) != null)
+ {
+ planeLoaders.Add(Plane.FromDirectory(CalcPaths.PlaneDirectory(sourceDirectory, nextPlaneName)));
+ }
+ await Task.WhenAll(planeLoaders);
+
+
+ rippleSpace.Planes.AddRange(
+ planeLoaders.Select((Task planeLoader) => planeLoader.Result)
+ );
+
+ long msTaken = timer.ElapsedMilliseconds;
+ Log.WriteLine($"[Core] done! {rippleSpace.Planes.Count} plane{(rippleSpace.Planes.Count != 1?"s":"")} loaded in {msTaken}ms.");
return rippleSpace;
}
diff --git a/Nibriboard/Utilities/CalcPaths.cs b/Nibriboard/Utilities/CalcPaths.cs
index 44ae3de..4fc55da 100644
--- a/Nibriboard/Utilities/CalcPaths.cs
+++ b/Nibriboard/Utilities/CalcPaths.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using Nibriboard.RippleSpace;
namespace Nibriboard.Utilities
@@ -8,40 +9,29 @@ namespace Nibriboard.Utilities
///
/// Returns the directory in which a plane's data should be unpacked to.
///
- /// The root directory to which everything is going to be unpacked.
+ /// The root directory to which everything is going to be unpacked.
/// The name of the plane that will be unpacked.
/// The directory to which a plane should unpack it's data to.
- public static string UnpackedPlaneDir(string unpackingRoot, string planeName)
+ public static string PlaneDirectory(string storageRoot, string planeName)
{
- string result = $"{unpackingRoot}Planes/{planeName}/";
+ string result = Path.Combine(storageRoot, "Planes", planeName);
return result;
}
///
/// Returns the path to the plane index file given a directory that a plane has been unpacked to.
///
- /// The directory to which a plane's data has been unpacked.
+ /// The directory to which a plane's data has been unpacked.
/// The path to the plane index file.
- public static string UnpackedPlaneIndex(string unpackingPlaneDir)
+ public static string PlaneIndex(string planeDirectory)
{
- return $"{unpackingPlaneDir}/plane-index.json";
- }
-
- ///
- /// Calculates the path to a packed plane file.
- ///
- /// The directory to which the nplane files were unpacked.
- /// The name of the plane to fetch the filepath for.
- /// The path to the packed plane file.
- public static string UnpackedPlaneFile(string unpackingDir, string planeName)
- {
- return $"{unpackingDir}{planeName}.nplane.zip";
+ return Path.Combine(planeDirectory, "plane-index.json");
}
public static string ChunkFilepath(string planeStorageDirectory, ChunkReference chunkRef)
{
- return $"{planeStorageDirectory}{chunkRef.AsFilename()}";
+ return Path.Combine(planeStorageDirectory, chunkRef.AsFilepath());
}
}
}
diff --git a/Nibriboard/Utilities/Formatters.cs b/Nibriboard/Utilities/Formatters.cs
new file mode 100644
index 0000000..7b3a910
--- /dev/null
+++ b/Nibriboard/Utilities/Formatters.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Nibriboard.Utilities
+{
+ public static class Formatters
+ {
+ public static string HumanSize(long byteCount)
+ {
+ string[] suf = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" }; // longs run out around EiB
+ if (byteCount == 0)
+ return "0" + suf[0];
+ long bytes = Math.Abs(byteCount);
+ int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
+ double num = Math.Round(bytes / Math.Pow(1024, place), 1);
+ return (Math.Sign(byteCount) * num).ToString() + suf[place];
+ }
+ }
+}