From 99ac0634ad949eb340adedc4df476b93a2ee680a Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Thu, 20 Jul 2017 20:02:50 +0100
Subject: [PATCH] Reworkt he saving system a bit.

---
 Nibriboard/NibriboardServer.cs               |  2 +-
 Nibriboard/RippleSpace/Chunk.cs              | 15 ++++---
 Nibriboard/RippleSpace/Plane.cs              | 28 ++++++++++---
 Nibriboard/RippleSpace/RippleSpaceManager.cs | 42 ++++++++++++++++++--
 Nibriboard/Utilities/CalcPaths.cs            |  9 ++++-
 5 files changed, 81 insertions(+), 15 deletions(-)

diff --git a/Nibriboard/NibriboardServer.cs b/Nibriboard/NibriboardServer.cs
index 3dfd048..3571228 100644
--- a/Nibriboard/NibriboardServer.cs
+++ b/Nibriboard/NibriboardServer.cs
@@ -23,7 +23,7 @@ namespace Nibriboard
 		private HttpServer httpServer;
 
 		private ClientSettings clientSettings;
-		private RippleSpaceManager planeManager = new RippleSpaceManager();
+		private RippleSpaceManager planeManager = new RippleSpaceManager() { SourceFilename = "./test.ripplespace.tar.gz" };
 
 		private readonly CancellationTokenSource clientManagerCanceller = new CancellationTokenSource();
 		private NibriClientManager clientManager;
diff --git a/Nibriboard/RippleSpace/Chunk.cs b/Nibriboard/RippleSpace/Chunk.cs
index 9cc745e..63f1559 100644
--- a/Nibriboard/RippleSpace/Chunk.cs
+++ b/Nibriboard/RippleSpace/Chunk.cs
@@ -216,20 +216,25 @@ namespace Nibriboard.RippleSpace
 
 		public static async Task<Chunk> FromFile(Plane plane, string filename)
 		{
-			FileStream chunkSource = new FileStream(filename, FileMode.Open);
+			StreamReader chunkSource = new StreamReader(filename);
 			return await FromStream(plane, chunkSource);
 		}
-		public static async Task<Chunk> FromStream(Plane plane, Stream chunkSource)
+		public static async Task<Chunk> FromStream(Plane plane, StreamReader chunkSource)
 		{
-			Chunk loadedChunk = await BinaryIO.DeserialiseBinaryObject<Chunk>(chunkSource);
+			Chunk loadedChunk = JsonConvert.DeserializeObject<Chunk>(await chunkSource.ReadToEndAsync());
 			loadedChunk.plane = plane;
+			loadedChunk.OnChunkUpdate += plane.HandleChunkUpdate;
 
 			return loadedChunk;
 		}
 
-		public async Task SaveTo(Stream destination)
+		/// <summary>
+		/// Saves this chunk to the specified stream.
+		/// </summary>
+		/// <param name="destination">The destination stream to save the chunk to.</param>
+		public async Task SaveTo(StreamWriter destination)
 		{
-			throw new NotImplementedException("Error:  Chunk saving hasn't been implemented yet!");
+			await destination.WriteLineAsync(JsonConvert.SerializeObject(this));
 		}
 
 		public void OnDeserialization(object sender)
diff --git a/Nibriboard/RippleSpace/Plane.cs b/Nibriboard/RippleSpace/Plane.cs
index 46f9169..7ed0672 100644
--- a/Nibriboard/RippleSpace/Plane.cs
+++ b/Nibriboard/RippleSpace/Plane.cs
@@ -158,7 +158,7 @@ namespace Nibriboard.RippleSpace
 				loadedChunk = await Chunk.FromFile(this, chunkFilePath);
 			else // Ooooh! It's a _new_, never-before-seen one! Create a brand new chunk :D
 				loadedChunk = new Chunk(this, ChunkSize, chunkLocation);
-			loadedChunk.OnChunkUpdate += handleChunkUpdate;
+			loadedChunk.OnChunkUpdate += HandleChunkUpdate;
 			loadedChunkspace.Add(chunkLocation, loadedChunk);
 
 			return loadedChunk;
@@ -189,7 +189,11 @@ namespace Nibriboard.RippleSpace
 
 			Chunk chunk = loadedChunkspace[chunkLocation];
 			string chunkFilePath = Path.Combine(StorageDirectory, chunkLocation.AsFilename());
-			await chunk.SaveTo(File.OpenWrite(chunkFilePath));
+
+			using(StreamWriter chunkDestination = new StreamWriter(chunkFilePath))
+			{
+				await chunk.SaveTo(chunkDestination);
+			}
 		}
 
 		public async Task AddLine(DrawnLine newLine)
@@ -242,10 +246,24 @@ namespace Nibriboard.RippleSpace
 			}
 		}
 
-		public void Save(Stream destination)
+		public async Task Save(Stream destination)
 		{
+			// Save all the chunks to disk
+			List<Task> chunkSavers = new List<Task>();
+			foreach(KeyValuePair<ChunkReference, Chunk> loadedChunkItem in loadedChunkspace)
+			{
+				// 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
+				using(StreamWriter chunkDestination = new StreamWriter(chunkDestinationFilename))
+				{
+					chunkSavers.Add(loadedChunkItem.Value.SaveTo(chunkDestination));
+				}
+			}
+			await Task.WhenAll(chunkSavers);
 
-
+			// Pack the chunks into an nplane file
 			WriterOptions packingOptions = new WriterOptions(CompressionType.GZip);
 
 			IEnumerable<string> chunkFiles = Directory.GetFiles(StorageDirectory);
@@ -264,7 +282,7 @@ namespace Nibriboard.RippleSpace
 		/// </summary>
 		/// <param name="sender">The chunk responsible for the update.</param>
 		/// <param name="eventArgs">The event arguments associated with the chunk update.</param>
-		protected void handleChunkUpdate(object sender, ChunkUpdateEventArgs eventArgs)
+		public void HandleChunkUpdate(object sender, ChunkUpdateEventArgs eventArgs)
 		{
 			Chunk updatingChunk = sender as Chunk;
 			if(updatingChunk == null)
diff --git a/Nibriboard/RippleSpace/RippleSpaceManager.cs b/Nibriboard/RippleSpace/RippleSpaceManager.cs
index 249bd5f..d6551f6 100644
--- a/Nibriboard/RippleSpace/RippleSpaceManager.cs
+++ b/Nibriboard/RippleSpace/RippleSpaceManager.cs
@@ -6,15 +6,23 @@ using System.Diagnostics;
 using System.Linq;
 using SharpCompress.Readers;
 using Nibriboard.Utilities;
+using SharpCompress.Writers;
+using SharpCompress.Common;
 
 namespace Nibriboard.RippleSpace
 {
 	public class RippleSpaceManager
 	{
+		/// <summary>
+		/// The filename from which this ripplespace was loaded, and the filename to which it should be saved again.
+		/// </summary>
+		/// <value>The source filename.</value>
+		public string SourceFilename { get; set; }
+		
 		/// <summary>
 		/// The temporary directory in which we are currently storing our unpacked planes temporarily.
 		/// </summary>
-		public string UnpackedDirectory;
+		public string UnpackedDirectory { get; set; }
 
 		/// <summary>
 		/// The master list of planes that this PlaneManager is in charge of. 
@@ -38,11 +46,15 @@ namespace Nibriboard.RippleSpace
 			// Create a temporary directory in which to store our unpacked planes
 			UnpackedDirectory = Path.GetTempFileName();
 			File.Delete(UnpackedDirectory);
-			UnpackedDirectory += "/";
+			UnpackedDirectory = Path.GetDirectoryName(UnpackedDirectory) + "/ripplespace-" + Path.GetFileName(UnpackedDirectory) + "/";
 			Directory.CreateDirectory(UnpackedDirectory);
 
 			Log.WriteLine("[RippleSpace] New blank ripplespace initialised.");
 		}
+		~RippleSpaceManager()
+		{
+			Directory.Delete(UnpackedDirectory, true);
+		}
 
 		/// <summary>
 		/// Gets the plane with the specified name from this RippleSpace.
@@ -110,7 +122,30 @@ namespace Nibriboard.RippleSpace
 
 		public async Task Save()
 		{
-			throw new NotImplementedException();
+			// Save the planes to disk
+			List<Task> planeSavers = new List<Task>();
+			foreach(Plane item in Planes)
+			{
+				// 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));
+
+				// Ask the plane to save to the directory
+				planeSavers.Add(item.Save(File.OpenWrite(planeSavePath)));
+			}
+			await Task.WhenAll(planeSavers);
+
+			// Pack the planes into the ripplespace archive
+			Stream destination = File.OpenWrite(SourceFilename);
+			string[] planeFiles = Directory.GetFiles(UnpackedDirectory, "*.nplane.tar.gz", SearchOption.TopDirectoryOnly);
+
+			using(IWriter rippleSpacePacker = WriterFactory.Open(destination, ArchiveType.Tar, new WriterOptions(CompressionType.GZip)))
+			{
+				foreach(string planeFilename in planeFiles)
+				{
+					rippleSpacePacker.Write(Path.GetFileName(planeFilename), planeFilename);
+				}
+			}
 		}
 
 		public async Task<RippleSpaceManager> FromFile(string filename)
@@ -119,6 +154,7 @@ namespace Nibriboard.RippleSpace
 				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))
diff --git a/Nibriboard/Utilities/CalcPaths.cs b/Nibriboard/Utilities/CalcPaths.cs
index 06ed16d..0bc8126 100644
--- a/Nibriboard/Utilities/CalcPaths.cs
+++ b/Nibriboard/Utilities/CalcPaths.cs
@@ -1,4 +1,5 @@
 ﻿using System;
+using Nibriboard.RippleSpace;
 
 namespace Nibriboard.Utilities
 {
@@ -34,7 +35,13 @@ namespace Nibriboard.Utilities
 		/// <returns>The path to the packed plane file.</returns>
 		public static string UnpackedPlaneFile(string unpackingDir, string planeName)
 		{
-			return $"{unpackingDir}/{planeName}.nplane";
+			return $"{unpackingDir}/{planeName}.nplane.tar.gz";
+		}
+
+
+		public static string ChunkFilepath(string planeStorageDirectory, ChunkReference chunkRef)
+		{
+			return $"{planeStorageDirectory}/{chunkRef.AsFilename()}";
 		}
 	}
 }
