using System; using System.IO; using System.Collections.Generic; using System.Threading.Tasks; using System.Diagnostics; using System.Linq; using Nibriboard.Utilities; namespace Nibriboard.RippleSpace { public class RippleSpaceManager { /// /// The temporary directory in which we are strong out data. /// public string SourceDirectory { get; set; } /// /// The master list of planes that this PlaneManager is in charge of. /// public List Planes = new List(); /// /// The number of milliseconds between each maintenance run. /// public readonly int MaintenanceInternal = 5000; /// /// The number of milliseconds the last maintenance run took. /// public long LastMaintenanceDuration = 0; public int DefaultChunkSize { get; set; } = 512; /// /// The size of the last save, in bytes. /// Returns 0 if this RippleSpace hasn't been saved yet. /// /// The last size of the save file. public long LastSaveSize { get { if(!Directory.Exists(SourceDirectory)) return 0; return (new DirectoryInfo(SourceDirectory)) .GetFiles("*", SearchOption.AllDirectories) .Sum(file => file.Length); } } public RippleSpaceManager(string inSourceDirectory) { SourceDirectory = inSourceDirectory; // Make sure that the source directory exists if (!Directory.Exists(SourceDirectory)) { Directory.CreateDirectory(SourceDirectory); Log.WriteLine("[RippleSpace] New blank ripplespace initialised."); } } /// /// Gets the plane with the specified name from this RippleSpace. /// /// The plane name to retrieve. public Plane this[string planeName] { get { return GetById(planeName); } } /// /// Gets the plane with the specified name from this RippleSpace. /// /// The plane name to retrieve. /// The plane wwith the specified name. protected Plane GetById(string targetName) { foreach (Plane plane in Planes) { if (plane.Name == targetName) return plane; } return null; } /// /// Creates a new plane, adds it to this RippleSpaceManager, and then returns it. /// /// The settings for the new plane to create. /// The newly created plane. public Plane CreatePlane(PlaneInfo newPlaneInfo) { if(this[newPlaneInfo.Name] != null) throw new InvalidOperationException($"Error: A plane with the name '{newPlaneInfo.Name}' already exists in this RippleSpaceManager."); Log.WriteLine("[RippleSpace] Creating plane {0}", newPlaneInfo.Name); Plane newPlane = new Plane( newPlaneInfo, CalcPaths.PlaneDirectory(SourceDirectory, newPlaneInfo.Name) ); Planes.Add(newPlane); return newPlane; } public Plane GetByName(string targetPlaneName) { foreach(Plane plane in Planes) { if(plane.Name == targetPlaneName) return plane; } return null; } public async Task StartMaintenanceMonkey() { Log.WriteLine("[RippleSpace/Maintenance] Automated maintenance monkey created."); while (true) { Stopwatch maintenanceStopwatch = Stopwatch.StartNew(); foreach (Plane plane in Planes) await plane.PerformMaintenance(); LastMaintenanceDuration = maintenanceStopwatch.ElapsedMilliseconds; await Task.Delay(MaintenanceInternal); } } public async Task Save() { Stopwatch timer = Stopwatch.StartNew(); // Save the planes to disk List> planeSavers = new List>(); StreamWriter indexWriter = new StreamWriter(Path.Combine(SourceDirectory, "index.list")); foreach(Plane currentPlane in Planes) { // Add the plane to the index await indexWriter.WriteLineAsync(currentPlane.Name); // Ask the plane to save to the directory planeSavers.Add(currentPlane.Save()); } indexWriter.Close(); await Task.WhenAll(planeSavers); long totalBytesWritten = planeSavers.Sum((Task saver) => saver.Result); Log.WriteLine( "[Command/Save] Save complete - {0} written in {1}ms", Formatters.HumanSize(totalBytesWritten), timer.ElapsedMilliseconds ); 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(Path.Combine(rippleSpace.SourceDirectory, "index.list"))) { Log.WriteLine($"[Core] Warning: The ripplespace at {sourceDirectory} doesn't appear to contain an index file."); return rippleSpace; } Log.WriteLine("[Core] Importing planes"); Stopwatch timer = Stopwatch.StartNew(); StreamReader planeList = new StreamReader(Path.Combine(sourceDirectory, "index.list")); List> planeLoaders = new List>(); string nextPlaneName = string.Empty; while ((nextPlaneName = await planeList.ReadLineAsync()) != null) { string nextPlaneDirectory = CalcPaths.PlaneDirectory(sourceDirectory, nextPlaneName); if (!Directory.Exists(nextPlaneDirectory)) { Log.WriteLine($"[Core] Warning: Couldn't find listed plane {nextPlaneName} when loading ripplespace."); continue; } planeLoaders.Add(Plane.FromDirectory(nextPlaneDirectory)); } 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; } } }