From 253d087651eff7a6432bdcfd09435bf6b6db9b8c Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 28 Mar 2016 14:31:17 +0200 Subject: [PATCH 01/48] Path handling types --- .../Persistence/GlobalPathTest.cs | 71 +++++++ .../Persistence/VolumePathTest.cs | 65 ++++++ src/kOS.Safe.Test/kOS.Safe.Test.csproj | 3 + .../Exceptions/KOSInvalidPathException.cs | 21 ++ src/kOS.Safe/Persistence/GlobalPath.cs | 148 ++++++++++++++ src/kOS.Safe/Persistence/VolumePath.cs | 192 ++++++++++++++++++ src/kOS.Safe/kOS.Safe.csproj | 3 + 7 files changed, 503 insertions(+) create mode 100644 src/kOS.Safe.Test/Persistence/GlobalPathTest.cs create mode 100644 src/kOS.Safe.Test/Persistence/VolumePathTest.cs create mode 100644 src/kOS.Safe/Exceptions/KOSInvalidPathException.cs create mode 100644 src/kOS.Safe/Persistence/GlobalPath.cs create mode 100644 src/kOS.Safe/Persistence/VolumePath.cs diff --git a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs new file mode 100644 index 000000000..1234c9591 --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs @@ -0,0 +1,71 @@ +using System; +using NUnit.Framework; +using kOS.Safe.Persistence; + +namespace kOS.Safe.Test.Persistence +{ + [TestFixture] + public class GlobalPathTest + { + [Test] + public void CanReturnParent() + { + GlobalPath path = GlobalPath.FromString("othervolume:/level1/level2"); + Assert.AreEqual("othervolume", path.VolumeId); + Assert.AreEqual(2, path.Depth); + Assert.AreEqual(2, path.Length); + } + + [Test] + public void CanIdentifyParents() + { + GlobalPath path = GlobalPath.FromString("othervolume:/level1/level2"); + GlobalPath parent1 = GlobalPath.FromString("othervolume:"); + GlobalPath parent2 = GlobalPath.FromString("othervolume:/level1"); + GlobalPath notParent1 = GlobalPath.FromString("othervolume:/sthelse"); + GlobalPath notParent2 = GlobalPath.FromString("othervolume:/level1/level2/level3"); + GlobalPath notParent3 = GlobalPath.FromString("othervolume2:/level1/level2"); + + Assert.IsTrue(parent1.IsParent(path)); + Assert.IsTrue(parent2.IsParent(path)); + Assert.IsFalse(path.IsParent(path)); + Assert.IsFalse(notParent1.IsParent(path)); + Assert.IsFalse(notParent2.IsParent(path)); + Assert.IsFalse(notParent3.IsParent(path)); + } + + [Test] + public void CanHandleVolumeNames() + { + GlobalPath path = GlobalPath.FromString("othervolume:/level1/level2"); + Assert.AreEqual("othervolume", path.VolumeId); + Assert.AreEqual(2, path.Depth); + Assert.AreEqual(2, path.Length); + } + + [Test] + public void CanHandleVolumeIds() + { + GlobalPath path = GlobalPath.FromString("1:level1/level2"); + Assert.AreEqual(1, path.VolumeId); + Assert.AreEqual(2, path.Depth); + Assert.AreEqual(2, path.Length); + } + + [Test] + public void CanHandleJustVolumeName() + { + GlobalPath path = GlobalPath.FromString("othervolume:"); + Assert.AreEqual("othervolume", path.VolumeId); + Assert.AreEqual(0, path.Depth); + Assert.AreEqual(0, path.Length); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanHandleGlobalPathWithLessThanZeroDepth() + { + GlobalPath.FromString("othervolume:/test/../../"); + } + } +} \ No newline at end of file diff --git a/src/kOS.Safe.Test/Persistence/VolumePathTest.cs b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs new file mode 100644 index 000000000..ad774a9a5 --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs @@ -0,0 +1,65 @@ +using System; +using NUnit.Framework; +using kOS.Safe.Persistence; + +namespace kOS.Safe.Test.Persistence +{ + [TestFixture] + public class VolumePathTest + { + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanHandleEmptyPath() + { + VolumePath.FromString(""); + } + + [Test] + public void CanHandleRootPath() + { + VolumePath path = VolumePath.FromString("/"); + Assert.AreEqual(0, path.Length); + Assert.AreEqual(0, path.Depth); + } + + [Test] + public void CanHandleSimplePath() + { + VolumePath path = VolumePath.FromString("/identifier"); + Assert.AreEqual(1, path.Length); + Assert.AreEqual(1, path.Depth); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanHandleAbsolutePathWithParent() + { + VolumePath parent = VolumePath.FromString("/parent"); + VolumePath.FromString("/identifier", parent); + } + + [Test] + public void CanHandleTwoDots() + { + VolumePath parent = VolumePath.FromString("/parent/deeper/and_deeper"); + VolumePath path = VolumePath.FromString("../../", parent); + Assert.AreEqual(1, path.Depth); + Assert.AreEqual(1, path.Length); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanHandlePathsThatPointOutside1() + { + VolumePath.FromString("/.."); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanHandlePathsThatPointOutside2() + { + VolumePath.FromString("/../test/test/test"); + } + } +} + diff --git a/src/kOS.Safe.Test/kOS.Safe.Test.csproj b/src/kOS.Safe.Test/kOS.Safe.Test.csproj index 39a76c1a2..7f1610539 100644 --- a/src/kOS.Safe.Test/kOS.Safe.Test.csproj +++ b/src/kOS.Safe.Test/kOS.Safe.Test.csproj @@ -73,6 +73,8 @@ + + @@ -94,6 +96,7 @@ + diff --git a/src/kOS.Safe/Exceptions/KOSInvalidPathException.cs b/src/kOS.Safe/Exceptions/KOSInvalidPathException.cs new file mode 100644 index 000000000..9701ab187 --- /dev/null +++ b/src/kOS.Safe/Exceptions/KOSInvalidPathException.cs @@ -0,0 +1,21 @@ +using System; +using kOS.Safe.Exceptions; + +namespace kOS.Safe +{ + public class KOSInvalidPathException : KOSException + { + private string pathString; + + public KOSInvalidPathException(string message, string pathString) : base(message + " (" + pathString + ")") + { + this.pathString = pathString; + } + + public new string VerboseMessage + { + get { return base.Message + ": '" + pathString + "'. This error occurred while trying to access a kOS Volume"; } + } + } +} + diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs new file mode 100644 index 000000000..a869e863d --- /dev/null +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -0,0 +1,148 @@ +using System; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using kOS.Safe.Persistence; +using kOS.Safe.Exceptions; +using System.Linq; + +namespace kOS.Safe.Persistence +{ + /// + /// Represents the location of a directory or a file inside a kOS. It contains a volumeId and a VolumePath. + /// + /// + public class GlobalPath : VolumePath + { + private const string CurrentDirectoryPath = "."; + public const string VolumeIdentifierRegexString = @"\A(?[\w\.]+):(?.*)\Z"; + private static Regex volumeIdentifierRegex = new Regex(VolumeIdentifierRegexString); + + public static new GlobalPath EMPTY = new GlobalPath("$$empty$$", VolumePath.EMPTY); + + public object VolumeId { get; private set; } + + private GlobalPath(object volumeId, VolumePath path) : this(volumeId, new List(path.Segments)) + { + + } + + private GlobalPath(object volumeId, List segments) : base(new List(segments)) + { + VolumeId = ValidateVolumeId(volumeId); + } + + private static object ValidateVolumeId(object volumeId) + { + if (!(volumeId is int || volumeId is string) || (volumeId is string && String.IsNullOrEmpty(volumeId as string))) + { + throw new KOSException("Invalid volumeId: '" + volumeId + "'"); + } + + int result; + if (volumeId is string && int.TryParse(volumeId as string, out result)) + { + volumeId = result; + } + + return volumeId; + } + + public static bool HasVolumeId(string pathString) + { + return volumeIdentifierRegex.Match(pathString).Success; + } + + public new GlobalPath GetParent() + { + if (Depth < 1) + { + throw new KOSException("This path does not have a parent"); + } + + return new GlobalPath(VolumeId, new List(Segments.Take(Segments.Count - 1))); + } + + public bool IsParent(GlobalPath path) + { + return VolumeId.Equals(path.VolumeId) && base.IsParent(path); + } + + public static GlobalPath FromVolumePath(VolumePath volumePath, Volume volume) + { + return new GlobalPath(volume.Name, new List(volumePath.Segments)); + } + + /// + /// Create a GlobalPath from a base path and a relative path. + /// + /// GlobalPath that represents the new path. + /// Path string relative to basePath. + /// Base path. + public static GlobalPath FromStringAndBase(string pathString, GlobalPath basePath) + { + if (IsAbsolute(pathString)) + { + throw new KOSInvalidPathException("Relative path expected", pathString); + } + + if (pathString.Equals(CurrentDirectoryPath)) + { + return basePath; + } + + List mergedSegments = new List(); + mergedSegments.AddRange(basePath.Segments); + mergedSegments.AddRange(GetSegmentsFromString(pathString)); + + return new GlobalPath(basePath.VolumeId, mergedSegments); + } + + /// + /// Creates a GlobalPath from string. + /// + /// Path string, must have the following format: volumeId:[/]segment1[/furthersegments]* + /// Path string. + public static new GlobalPath FromString(string pathString) + { + string volumeName = null; + Match match = volumeIdentifierRegex.Match(pathString); + + if (match.Success) { + volumeName = match.Groups["id"].Captures[0].Value; + pathString = match.Groups["rest"].Captures[0].Value; + } else { + throw new KOSInvalidPathException("GlobalPath should contain a volumeId", pathString); + } + + if (!pathString.StartsWith(PathSeparator.ToString())) + { + pathString = PathSeparator + pathString; + } + + VolumePath path = VolumePath.FromString(pathString); + return new GlobalPath(volumeName, path); + } + + public override int GetHashCode() + { + return 13 * VolumeId.GetHashCode() + base.GetHashCode(); + } + + public override bool Equals(object other) + { + GlobalPath otherPath = other as GlobalPath; + + if (otherPath == null) + { + return false; + } + + return VolumeId.Equals(otherPath.VolumeId) && Segments.SequenceEqual(otherPath.Segments); + } + + public override string ToString() + { + return VolumeId + ":" + base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs new file mode 100644 index 000000000..97f68cb8f --- /dev/null +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -0,0 +1,192 @@ +using System; +using System.Text.RegularExpressions; +using System.Linq; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using kOS.Safe.Exceptions; + +namespace kOS.Safe.Persistence +{ + /// + /// Represents the location of a directory or a file inside a volume. + /// + public class VolumePath + { + public static VolumePath EMPTY = new VolumePath(); + + public const char PathSeparator = '/'; + public const string UpSegment = ".."; + public const int MaxSegmentLength = 255; + public const string SegmentRegexString = @"\A[^/]+\Z"; + private static readonly Regex segmentRegex = new Regex(SegmentRegexString); + + /// + /// Number of segments in the path. + /// + public int Length { + get { + return Segments.Count; + } + } + + /// + /// Depth of the path. Same as Length if the path does not contain any '..'. + /// + public int Depth { + get { + int upSegments = Segments.Count(s => s.Equals(UpSegment)); + return Length - 2 * upSegments; + } + } + + /// + /// Gets a value indicating whether this points outside of this volume. + /// + public bool PointsOutside { + get { + return Segments.Count > 0 && Segments[0].Equals(UpSegment); + } + } + + /// + /// Gets the name. + /// + public string Name { + get { + return Segments.Count > 0 ? Segments.Last() : null; + } + } + + /// + /// Gets this path's segments. + /// + public List Segments { get; private set; } + + public static bool IsValidSegment(string segment) + { + return segment.Length > 0 && segment.Length <= MaxSegmentLength && segmentRegex.IsMatch(segment); + } + + /// + /// Determines if a string represents an absolute path. + /// + public static Boolean IsAbsolute(String pathString) + { + return pathString.StartsWith(PathSeparator.ToString()); + } + + public static VolumePath FromString(string pathString, VolumePath basePath) + { + if (IsAbsolute(pathString)) + { + throw new KOSInvalidPathException("Relative path expected", pathString); + } + + List mergedSegments = new List(); + mergedSegments.AddRange(basePath.Segments); + mergedSegments.AddRange(GetSegmentsFromString(pathString)); + + return new VolumePath(mergedSegments); + } + + public static VolumePath FromString(string pathString) + { + if (!pathString.StartsWith(PathSeparator.ToString())) + { + throw new KOSInvalidPathException("Absolute path expected", pathString); + } + + return new VolumePath(GetSegmentsFromString(pathString)); + } + + protected static List GetSegmentsFromString(string pathString) + { + IEnumerable segments = pathString.Split(PathSeparator).Where((s) => !String.IsNullOrEmpty(s)); + + foreach (string segment in segments) + { + if (!IsValidSegment(segment)) + { + throw new KOSInvalidPathException("Invalid path segment: '" + segment + "'", pathString); + } + } + + return new List(segments); + } + + private VolumePath() + { + this.Segments = new List(); + } + + protected VolumePath(List segments) + { + this.Segments = segments; + + Canonicalize(); + } + + private void Canonicalize() + { + List newSegments = new List(); + + for (int i = 0; i < Segments.Count; i++) + { + if (Segments[i].Equals(UpSegment) && newSegments.Count != 0 && !newSegments.Last().Equals(UpSegment)) + { + newSegments.RemoveAt(newSegments.Count() - 1); + } + else + { + newSegments.Add(Segments[i]); + } + } + + Segments = newSegments; + + if (PointsOutside) + { + throw new KOSInvalidPathException("This path points to something outside of volume", ToString()); + } + } + + public VolumePath GetParent() + { + if (Depth < 1) + { + throw new KOSException("This path does not have a parent"); + } + + return new VolumePath(new List(Segments.Take(Segments.Count - 1))); + } + + public bool IsParent(VolumePath path) + { + return path.Segments.Count > Segments.Count && path.Segments.GetRange(0, Segments.Count).SequenceEqual(Segments); + } + + public override int GetHashCode() + { + return Segments.Aggregate(1, (i, s) => i + s.GetHashCode()); + } + + public override bool Equals(object other) + { + VolumePath otherPath = other as VolumePath; + + if (otherPath == null) + { + return false; + } + + return Segments.SequenceEqual(otherPath.Segments); + } + + public override string ToString() + { + return "/" + String.Join("/", Segments.ToArray()); + } + + } +} + diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index 6aeffe018..76d4a58ba 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -240,6 +240,9 @@ + + + From d9a2917768df7c23a9dd4c145f3fd98d7f8cc5b9 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 4 Apr 2016 20:20:13 +0200 Subject: [PATCH 02/48] Add directory support for Archive and Harddisk --- src/kOS.Safe.Test/Persistence/ArchiveTest.cs | 42 +++ src/kOS.Safe.Test/Persistence/HarddiskTest.cs | 31 ++ src/kOS.Safe.Test/Persistence/VolumeTest.cs | 244 ++++++++++++++++ src/kOS.Safe.Test/kOS.Safe.Test.csproj | 3 + src/kOS.Safe/Compilation/CodePart.cs | 15 +- src/kOS.Safe/Compilation/CompiledObject.cs | 17 +- src/kOS.Safe/Compilation/KS/Context.cs | 5 +- src/kOS.Safe/Compilation/KS/KSScript.cs | 9 +- src/kOS.Safe/Compilation/Opcode.cs | 3 +- src/kOS.Safe/Compilation/ProgramBuilder.cs | 2 +- src/kOS.Safe/Compilation/Script.cs | 7 +- .../Exceptions/KOSPersistenceException.cs | 6 +- src/kOS.Safe/Execution/CPU.cs | 17 +- src/kOS.Safe/Execution/ProgramContext.cs | 2 +- src/kOS.Safe/Module/IProcessor.cs | 8 +- src/kOS.Safe/Persistence/Archive.cs | 177 +++++------ src/kOS.Safe/Persistence/ArchiveDirectory.cs | 56 ++++ .../ArchiveFile.cs | 9 +- src/kOS.Safe/Persistence/FileContent.cs | 14 +- src/kOS.Safe/Persistence/Harddisk.cs | 126 +++----- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 196 +++++++++++++ .../HarddiskFile.cs | 15 +- src/kOS.Safe/Persistence/IVolumeManager.cs | 14 + src/kOS.Safe/Persistence/Volume.cs | 131 ++++++--- src/kOS.Safe/Persistence/VolumeDirectory.cs | 35 +++ .../VolumeFile.cs | 30 +- src/kOS.Safe/Persistence/VolumeItem.cs | 57 ++++ src/kOS.Safe/Persistence/VolumeManager.cs | 69 ++++- src/kOS.Safe/Screen/ITermWindow.cs | 2 +- src/kOS.Safe/Serialization/JSONFormatter.cs | 6 +- src/kOS.Safe/Utilities/Debug.cs | 3 +- src/kOS.Safe/kOS.Safe.csproj | 12 +- .../AddOns/RemoteTech/RemoteTechArchive.cs | 5 + .../AddOns/RemoteTech/RemoteTechFactory.cs | 3 +- src/kOS/Factories/StandardFactory.cs | 3 +- src/kOS/Function/BuildList.cs | 2 +- src/kOS/Function/Misc.cs | 103 ++++--- src/kOS/Function/Persistence.cs | 275 ++++++++++-------- src/kOS/Function/PrintList.cs | 28 +- src/kOS/KSPLogger.cs | 55 +--- src/kOS/Module/kOSProcessor.cs | 24 +- src/kOS/Persistence/PersistenceExtensions.cs | 63 +++- src/kOS/Screen/Interpreter.cs | 5 +- src/kOS/Screen/KOSTextEditPopup.cs | 47 +-- src/kOS/Screen/TermWindow.cs | 4 +- 45 files changed, 1395 insertions(+), 585 deletions(-) create mode 100644 src/kOS.Safe.Test/Persistence/ArchiveTest.cs create mode 100644 src/kOS.Safe.Test/Persistence/HarddiskTest.cs create mode 100644 src/kOS.Safe.Test/Persistence/VolumeTest.cs create mode 100644 src/kOS.Safe/Persistence/ArchiveDirectory.cs rename src/kOS.Safe/{Encapsulation => Persistence}/ArchiveFile.cs (88%) create mode 100644 src/kOS.Safe/Persistence/HarddiskDirectory.cs rename src/kOS.Safe/{Encapsulation => Persistence}/HarddiskFile.cs (57%) create mode 100644 src/kOS.Safe/Persistence/VolumeDirectory.cs rename src/kOS.Safe/{Encapsulation => Persistence}/VolumeFile.cs (66%) create mode 100644 src/kOS.Safe/Persistence/VolumeItem.cs diff --git a/src/kOS.Safe.Test/Persistence/ArchiveTest.cs b/src/kOS.Safe.Test/Persistence/ArchiveTest.cs new file mode 100644 index 000000000..25dcef652 --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/ArchiveTest.cs @@ -0,0 +1,42 @@ +using System; +using NUnit.Framework; +using System.IO; +using kOS.Safe.Persistence; +using kOS.Safe.Utilities; + +namespace kOS.Safe.Test.Persistence +{ + [TestFixture] + public class ArchiveTest : VolumeTest + { + public const string KosTestDirectory = "kos_archive_tests"; + + protected override int ExpectedCapacity { + get { + return Volume.INFINITE_CAPACITY; + } + } + + protected override bool ExpectedRenameable { + get { + return false; + } + } + + protected string testPath = Path.Combine(Path.GetTempPath(), KosTestDirectory); + + [SetUp] + public void Setup() + { + if (Directory.Exists(testPath)) + { + Directory.Delete(testPath, true); + } + + Directory.CreateDirectory(testPath); + + TestVolume = new Archive(testPath); + } + } +} + diff --git a/src/kOS.Safe.Test/Persistence/HarddiskTest.cs b/src/kOS.Safe.Test/Persistence/HarddiskTest.cs new file mode 100644 index 000000000..1c17b96f1 --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/HarddiskTest.cs @@ -0,0 +1,31 @@ +using System; +using NUnit.Framework; +using kOS.Safe.Persistence; + +namespace kOS.Safe.Test.Persistence +{ + [TestFixture] + public class HarddiskTest : VolumeTest + { + + protected override int ExpectedCapacity { + get { + return 5000; + } + } + + protected override bool ExpectedRenameable { + get { + return true; + } + } + + [SetUp] + public void Setup() + { + TestVolume = new Harddisk(ExpectedCapacity); + } + + } +} + diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs new file mode 100644 index 000000000..21ccbdf8b --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -0,0 +1,244 @@ +using System; +using NUnit.Framework; +using System.IO; +using kOS.Safe.Persistence; +using kOS.Safe.Utilities; +using kOS.Safe.Exceptions; +using System.Text; + +namespace kOS.Safe.Test +{ + public abstract class VolumeTest + { + public Volume TestVolume { get; set; } + protected abstract int ExpectedCapacity { get; } + protected abstract bool ExpectedRenameable { get; } + + [SetUp] + public void SetupLogger() + { + SafeHouse.Logger = new TestLogger(); + } + + + [Test] + public void CanReturnCapacity() + { + Assert.AreEqual(ExpectedCapacity, TestVolume.Capacity); + } + + [Test] + public void CanReturnRenameable() + { + Assert.AreEqual(ExpectedRenameable, TestVolume.Renameable); + } + + [Test] + public void CanCreateDirectories() + { + string dir1 = "/testdir", dir2 = "/abc", dir3 = "/abc2", dir4 = "/abc/subdirectory"; + Assert.AreEqual(0, TestVolume.Root.List().Count); + + TestVolume.CreateDirectory(VolumePath.FromString(dir1)); + TestVolume.CreateDirectory(VolumePath.FromString(dir2)); + TestVolume.CreateDirectory(VolumePath.FromString(dir3)); + TestVolume.CreateDirectory(VolumePath.FromString(dir4)); + + Assert.AreEqual(3, TestVolume.Root.List().Count); + Assert.AreEqual(dir2, TestVolume.Root.List()["abc"].Path.ToString()); + Assert.AreEqual(dir3, TestVolume.Root.List()["abc2"].Path.ToString()); + Assert.AreEqual(dir1, TestVolume.Root.List()["testdir"].Path.ToString()); + + Assert.AreEqual(1, (TestVolume.Root.List()["abc"] as VolumeDirectory).List().Values.Count); + Assert.AreEqual(dir4, (TestVolume.Root.List()["abc"] as VolumeDirectory).List()["subdirectory"].Path.ToString()); + } + + [Test] + public void CanCreateSubdirectories() + { + string parent1 = "/parent1", parent2 = "/parent2"; + string dir1 = parent1 + "/sub1", dir2 = parent1 + "/sub2", dir3 = parent2 + "/sub3"; + Assert.AreEqual(0, TestVolume.Root.List().Count); + + TestVolume.CreateDirectory(VolumePath.FromString(dir1)); + TestVolume.CreateDirectory(VolumePath.FromString(dir2)); + TestVolume.CreateDirectory(VolumePath.FromString(dir3)); + + Assert.AreEqual(2, TestVolume.Root.List().Count); + Assert.AreEqual(parent1, TestVolume.Root.List()["parent1"].Path.ToString()); + Assert.AreEqual(parent2, TestVolume.Root.List()["parent2"].Path.ToString()); + + VolumeDirectory dir = TestVolume.Open(VolumePath.FromString(parent1)) as VolumeDirectory; + Assert.AreEqual(2, dir.List().Count); + Assert.AreEqual(dir1, dir.List()["sub1"].Path.ToString()); + Assert.AreEqual(dir2, dir.List()["sub2"].Path.ToString()); + } + + [Test] + public void CanCreateDirectoryOverExistingDirectory() + { + string parent = "/parent1"; + string dir = parent + "/sub1"; + + TestVolume.CreateDirectory(VolumePath.FromString(dir)); + TestVolume.CreateDirectory(VolumePath.FromString(dir)); + } + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenCreatingDirectoryOverFile() + { + string parent1 = "/parent1"; + string file1 = parent1 + "/sub1"; + + TestVolume.CreateFile(VolumePath.FromString(file1)); + TestVolume.CreateDirectory(VolumePath.FromString(file1)); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanFailWhenCreatingDirectoryWithNegativeDepth() + { + string dir = "/../test"; + + TestVolume.CreateDirectory(VolumePath.FromString(dir)); + } + + [Test] + public void CanDeleteDirectories() + { + string parent1 = "/parent1", parent2 = "/parent2"; + string dir1 = parent1 + "/sub1", dir2 = parent1 + "/sub2", dir3 = parent2 + "/sub3"; + + TestVolume.CreateDirectory(VolumePath.FromString(dir1)); + TestVolume.CreateDirectory(VolumePath.FromString(dir2)); + TestVolume.CreateDirectory(VolumePath.FromString(dir3)); + + VolumeDirectory parent = TestVolume.Open(VolumePath.FromString(parent1)) as VolumeDirectory; + + TestVolume.Delete(VolumePath.FromString(dir1)); + Assert.AreEqual(1, parent.List().Count); + Assert.AreEqual(dir2, parent.List()["sub2"].Path.ToString()); + + TestVolume.Delete(VolumePath.FromString(parent2)); + Assert.AreEqual(1, TestVolume.Root.List().Count); + Assert.AreEqual(parent1, TestVolume.Root.List()["parent1"].Path.ToString()); + } + + [Test] + public void CanDeleteNonExistingDirectories() + { + VolumePath path = VolumePath.FromString("/abc"); + TestVolume.CreateDirectory(path); + TestVolume.Delete(path); + TestVolume.Delete(path); + } + + [Test] + public void CanCreateFiles() + { + string parent1 = "/parent1", parent2 = "/parent2"; + string file1 = parent1 + "/sub1", file2 = parent1 + "/sub2", file3 = parent2 + "/sub3"; + + TestVolume.CreateFile(VolumePath.FromString(file1)); + TestVolume.CreateFile(VolumePath.FromString(file2)); + TestVolume.CreateFile(VolumePath.FromString(file3)); + + Assert.AreEqual(2, TestVolume.Root.List().Count); + Assert.AreEqual(parent1, TestVolume.Root.List()["parent1"].Path.ToString()); + Assert.AreEqual(parent2, TestVolume.Root.List()["parent2"].Path.ToString()); + + VolumeDirectory dir = TestVolume.Open(VolumePath.FromString(parent1)) as VolumeDirectory; + Assert.AreEqual(2, dir.List().Count); + Assert.AreEqual(file1, dir.List()["sub1"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["sub1"]); + Assert.AreEqual(file2, dir.List()["sub2"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["sub2"]); + + dir = TestVolume.Open(VolumePath.FromString(parent2)) as VolumeDirectory; + Assert.AreEqual(1, dir.List().Count); + Assert.AreEqual(file3, dir.List()["sub3"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["sub3"]); + } + + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenCreatingFileOverDirectory() + { + string parent1 = "/parent1"; + string file1 = parent1 + "/sub1"; + + TestVolume.CreateDirectory(VolumePath.FromString(file1)); + TestVolume.CreateFile(VolumePath.FromString(file1)); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanFailWhenCreatingFileWithNegativeDepth() + { + string dir = "/../test"; + + TestVolume.CreateFile(VolumePath.FromString(dir)); + } + + [Test] + public void CanReadAndWriteFiles() + { + string dir = "/content_parent/content_test"; + string content = "some test content!@#$;\n\rtaenstałąż"; + int contentLength = Encoding.UTF8.GetBytes(content).Length; + + VolumeFile volumeFile = TestVolume.CreateFile(VolumePath.FromString(dir)); + + Assert.AreEqual(0, volumeFile.ReadAll().Bytes.Length); + Assert.AreEqual("", volumeFile.ReadAll().String); + + Assert.IsTrue(volumeFile.Write(content)); + Assert.AreEqual(FileCategory.ASCII, volumeFile.ReadAll().Category); + + Assert.AreEqual(contentLength, TestVolume.Size); + + if (ExpectedCapacity != Volume.INFINITE_CAPACITY) + { + Assert.AreEqual(ExpectedCapacity - contentLength, TestVolume.FreeSpace); + } else + { + Assert.AreEqual(Volume.INFINITE_CAPACITY, TestVolume.FreeSpace); + } + + Assert.AreEqual(contentLength, volumeFile.Size); + Assert.AreEqual(content, volumeFile.ReadAll().String); + } + + [Test] + public void CanDeleteFiles() + { + string parent1 = "/parent1"; + string file1 = parent1 + "/sub1", file2 = parent1 + "/sub2"; + + TestVolume.CreateFile(VolumePath.FromString(file1)); + TestVolume.CreateFile(VolumePath.FromString(file2)); + + TestVolume.Delete(VolumePath.FromString(file1)); + + VolumeDirectory dir = TestVolume.Open(VolumePath.FromString(parent1)) as VolumeDirectory; + Assert.AreEqual(1, dir.List().Count); + Assert.AreEqual(file2, dir.List()["sub2"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["sub2"]); + } + + [Test] + public void CanDeleteNonExistingFiles() + { + VolumePath path = VolumePath.FromString("/abc"); + TestVolume.CreateFile(path); + + // Delete the file twice + TestVolume.Delete(path); + TestVolume.Delete(path); + } + + } +} + diff --git a/src/kOS.Safe.Test/kOS.Safe.Test.csproj b/src/kOS.Safe.Test/kOS.Safe.Test.csproj index 7f1610539..f7f978990 100644 --- a/src/kOS.Safe.Test/kOS.Safe.Test.csproj +++ b/src/kOS.Safe.Test/kOS.Safe.Test.csproj @@ -75,6 +75,9 @@ + + + diff --git a/src/kOS.Safe/Compilation/CodePart.cs b/src/kOS.Safe/Compilation/CodePart.cs index bd05b7dbe..a1c040748 100644 --- a/src/kOS.Safe/Compilation/CodePart.cs +++ b/src/kOS.Safe/Compilation/CodePart.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System; +using kOS.Safe.Persistence; namespace kOS.Safe.Compilation { public class CodePart { - public CodePart(string fromFile = "") + public CodePart() { FunctionsCode = new List(); InitializationCode = new List(); @@ -25,18 +26,18 @@ public List MergeSections() return mergedCode; } - public void AssignSourceName(string sourceName) + public void AssignSourceName(GlobalPath filePath) { - AssignSourceNameToSection(sourceName.ToLower(), FunctionsCode); - AssignSourceNameToSection(sourceName.ToLower(), InitializationCode); - AssignSourceNameToSection(sourceName.ToLower(), MainCode); + AssignSourcePathToSection(filePath, FunctionsCode); + AssignSourcePathToSection(filePath, InitializationCode); + AssignSourcePathToSection(filePath, MainCode); } - private void AssignSourceNameToSection(string sourceName, IEnumerable section) + private void AssignSourcePathToSection(GlobalPath filePath, IEnumerable section) { foreach (Opcode opcode in section) { - opcode.SourceName = string.Intern(sourceName.ToLower()); // Intern ensures we don't waste space storing the filename again and again per opcode. + opcode.SourcePath = filePath; } } diff --git a/src/kOS.Safe/Compilation/CompiledObject.cs b/src/kOS.Safe/Compilation/CompiledObject.cs index 157628015..ec7f7f9e0 100644 --- a/src/kOS.Safe/Compilation/CompiledObject.cs +++ b/src/kOS.Safe/Compilation/CompiledObject.cs @@ -5,6 +5,7 @@ using System.Linq; using kOS.Safe.Encapsulation; using kOS.Safe.Execution; +using kOS.Safe.Persistence; namespace kOS.Safe.Compilation { @@ -469,11 +470,11 @@ private static void AddByteChunkToArgumentPack(byte[] appendMe) /// /// Given a packed representation of the program, load it back into program form: /// - /// name of file (with preceeding "volume/") that the program came from, for runtime error reporting. + /// path of file (with preceeding "volume:") that the program came from, for runtime error reporting. /// prepend this string to all labels in this program. /// the file itself in ony big binary array. /// - public static List UnPack(string filePath, string prefix, byte[] content) + public static List UnPack(GlobalPath path, string prefix, byte[] content) { var program = new List(); var reader = new BinaryReader(new MemoryStream(content)); @@ -507,7 +508,7 @@ public static List UnPack(string filePath, string prefix, byte[] conte { case (byte)'F': // new CodePart's always start with the function header: - var nextPart = new CodePart(filePath); + var nextPart = new CodePart(); program.Add(nextPart); // If this is the very first code we've ever encountered in the file, remember its position: if (codeStart == 0) @@ -528,7 +529,7 @@ public static List UnPack(string filePath, string prefix, byte[] conte } reader.Close(); - PostReadProcessing(program, filePath, prefix, lineMap); + PostReadProcessing(program, path, prefix, lineMap); return program; } @@ -578,10 +579,10 @@ private static Dictionary ReadArgumentPack(BinaryReader reader, out /// proper values. /// /// recently built program parts to re-assign. - /// name of file (with preceeding volume/) that the compiled code came from, for rutime error reporting purposes. + /// path of file (with preceeding volume:) that the compiled code came from, for rutime error reporting purposes. /// a string to prepend to the labels in the program. /// describes the mapping of line numbers to code locations. - private static void PostReadProcessing(IEnumerable program, string filePath, string prefix, DebugLineMap lineMap) + private static void PostReadProcessing(IEnumerable program, GlobalPath path, string prefix, DebugLineMap lineMap) { //TODO:prefix is never used. SortedList lineLookup = lineMap.BuildInverseLookup(); @@ -627,8 +628,8 @@ private static void PostReadProcessing(IEnumerable program, string fil else // Not every opcode came from a source line - so if it's skipped over, assign it to bogus value. op.SourceLine = -1; - - op.SourceName = string.IsNullOrEmpty(op.SourceName) ? filePath : String.Empty; + + op.SourcePath = (op.SourcePath == null || op.SourcePath == GlobalPath.EMPTY) ? path : GlobalPath.EMPTY; } } diff --git a/src/kOS.Safe/Compilation/KS/Context.cs b/src/kOS.Safe/Compilation/KS/Context.cs index c3a6b2051..9be6d9511 100644 --- a/src/kOS.Safe/Compilation/KS/Context.cs +++ b/src/kOS.Safe/Compilation/KS/Context.cs @@ -1,4 +1,5 @@ using System; +using kOS.Safe.Persistence; namespace kOS.Safe.Compilation.KS { @@ -9,7 +10,7 @@ public class Context public SubprogramCollection Subprograms { get; private set; } public int NumCompilesSoFar {get; set;} public int LabelIndex { get; set; } - public string LastSourceName { get; set; } + public GlobalPath LastSourcePath { get; set; } // This has to live inside context because of the fact that more than one program // can be compiled into the same memory space. If it was reset to zero by the @@ -24,7 +25,7 @@ public Context() Triggers = new TriggerCollection(); Subprograms = new SubprogramCollection(); LabelIndex = 0; - LastSourceName = ""; + LastSourcePath = GlobalPath.EMPTY; MaxScopeIdSoFar = 0; NumCompilesSoFar = 0; } diff --git a/src/kOS.Safe/Compilation/KS/KSScript.cs b/src/kOS.Safe/Compilation/KS/KSScript.cs index d40622836..4fee5b92b 100644 --- a/src/kOS.Safe/Compilation/KS/KSScript.cs +++ b/src/kOS.Safe/Compilation/KS/KSScript.cs @@ -1,5 +1,6 @@ using kOS.Safe.Exceptions; using System.Collections.Generic; +using kOS.Safe.Persistence; namespace kOS.Safe.Compilation.KS { @@ -17,7 +18,7 @@ public KSScript() contexts = new Dictionary(); } - public override List Compile(string filePath, int startLineNum, string scriptText, string contextId, CompilerOptions options) + public override List Compile(GlobalPath filePath, int startLineNum, string scriptText, string contextId, CompilerOptions options) { var parts = new List(); ParseTree parseTree = parser.Parse(scriptText); @@ -84,12 +85,12 @@ private void LoadContext(string contextId) } } - private void AssignSourceId(IEnumerable parts, string fileName) + private void AssignSourceId(IEnumerable parts, GlobalPath filePath) { - currentContext.LastSourceName = fileName; + currentContext.LastSourcePath = filePath; foreach (CodePart part in parts) { - part.AssignSourceName(currentContext.LastSourceName); + part.AssignSourceName(currentContext.LastSourcePath); } } diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 2ac9c01ea..9aa8aa5c6 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -7,6 +7,7 @@ using kOS.Safe.Execution; using kOS.Safe.Exceptions; using kOS.Safe.Utilities; +using kOS.Safe.Persistence; namespace kOS.Safe.Compilation { @@ -231,7 +232,7 @@ public abstract class Opcode public string Label {get{return label;} set {label = value;} } public virtual string DestinationLabel {get;set;} - public string SourceName; + public GlobalPath SourcePath; public short SourceLine { get; set; } // line number in the source code that this was compiled from. public short SourceColumn { get; set; } // column number of the token nearest the cause of this Opcode. diff --git a/src/kOS.Safe/Compilation/ProgramBuilder.cs b/src/kOS.Safe/Compilation/ProgramBuilder.cs index f5e2629ec..70f791546 100644 --- a/src/kOS.Safe/Compilation/ProgramBuilder.cs +++ b/src/kOS.Safe/Compilation/ProgramBuilder.cs @@ -163,7 +163,7 @@ private void ReplaceLabels(List program) } else newOp = new OpcodePush(destinationIndex); - newOp.SourceName = opcode.SourceName; + newOp.SourcePath = opcode.SourcePath; newOp.SourceLine = opcode.SourceLine; newOp.SourceColumn = opcode.SourceColumn; newOp.Label = opcode.Label; diff --git a/src/kOS.Safe/Compilation/Script.cs b/src/kOS.Safe/Compilation/Script.cs index 0606f02b4..6b0654b87 100644 --- a/src/kOS.Safe/Compilation/Script.cs +++ b/src/kOS.Safe/Compilation/Script.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using kOS.Safe.Persistence; namespace kOS.Safe.Compilation { @@ -23,7 +24,7 @@ protected Script() /// corresponds to line (what) of the more global something, for reporting numbers on errors. /// The text to be compiled. /// The CodeParts made from the scriptText - public virtual List Compile(string filePath, int startLineNum, string scriptText) + public virtual List Compile(GlobalPath filePath, int startLineNum, string scriptText) { return Compile(filePath, startLineNum, scriptText, string.Empty); } @@ -41,7 +42,7 @@ public virtual List Compile(string filePath, int startLineNum, string /// The text to be compiled. /// The name of the runtime context (i.e. "interpreter"). /// The CodeParts made from the scriptText - public virtual List Compile(string filePath, int startLineNum, string scriptText, string contextId) + public virtual List Compile(GlobalPath filePath, int startLineNum, string scriptText, string contextId) { return Compile(filePath, startLineNum, scriptText, contextId, new CompilerOptions()); } @@ -60,7 +61,7 @@ public virtual List Compile(string filePath, int startLineNum, string /// The name of the runtime context (i.e. "interpreter"). /// settings for the compile /// The CodeParts made from the scriptText - public abstract List Compile(string filePath, int startLineNum, string scriptText, string contextId, CompilerOptions options); + public abstract List Compile(GlobalPath filePath, int startLineNum, string scriptText, string contextId, CompilerOptions options); public abstract void ClearContext(string contextId); diff --git a/src/kOS.Safe/Exceptions/KOSPersistenceException.cs b/src/kOS.Safe/Exceptions/KOSPersistenceException.cs index 10f48c6e7..f7a888ad5 100644 --- a/src/kOS.Safe/Exceptions/KOSPersistenceException.cs +++ b/src/kOS.Safe/Exceptions/KOSPersistenceException.cs @@ -4,7 +4,11 @@ namespace kOS.Safe.Exceptions { public class KOSPersistenceException : Exception { - public KOSPersistenceException(string message):base(message) + public KOSPersistenceException(string message) : base(message) + { + } + + public KOSPersistenceException(string message, Exception cause) : base(message, cause) { } diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index e580167d5..d4ce221b9 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text; using Debug = kOS.Safe.Utilities.Debug; +using kOS.Safe.Persistence; namespace kOS.Safe.Execution { @@ -120,15 +121,14 @@ public void Boot() if (!shared.Processor.CheckCanBoot()) return; - string filename = shared.Processor.BootFilename; + VolumePath path = shared.Processor.BootFilePath; // Check to make sure the boot file name is valid, and then that the boot file exists. - if (string.IsNullOrEmpty(filename)) { SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); } - else if (filename.Equals("None", StringComparison.InvariantCultureIgnoreCase)) { SafeHouse.Logger.Log("Boot file name is \"None\", skipping boot script"); } - else if (shared.VolumeMgr.CurrentVolume.Open(filename) == null) { SafeHouse.Logger.Log(string.Format("Boot file \"{0}\" is missing, skipping boot script", filename)); } + if (path == null) { SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); } + else if (shared.VolumeMgr.CurrentVolume.Open(path) == null) { SafeHouse.Logger.Log(string.Format("Boot file \"{0}\" is missing, skipping boot script", path)); } else { var bootContext = "program"; - string bootCommand = string.Format("run {0}.", filename); + string bootCommand = string.Format("run {0}.", path); var options = new CompilerOptions { @@ -138,8 +138,7 @@ public void Boot() }; shared.ScriptHandler.ClearContext(bootContext); - List parts = shared.ScriptHandler.Compile( - "sys:boot", 1, bootCommand, bootContext, options); + List parts = shared.ScriptHandler.Compile(GlobalPath.FromString("sysboot:"), 1, bootCommand, bootContext, options); IProgramContext programContext = SwitchToProgramContext(); programContext.Silent = true; @@ -1291,10 +1290,10 @@ private void SkipCurrentInstructionId() { if (currentContext.InstructionPointer >= (currentContext.Program.Count - 1)) return; - string currentSourceName = currentContext.Program[currentContext.InstructionPointer].SourceName; + GlobalPath currentSourcePath = currentContext.Program[currentContext.InstructionPointer].SourcePath; while (currentContext.InstructionPointer < currentContext.Program.Count && - currentContext.Program[currentContext.InstructionPointer].SourceName == currentSourceName) + currentContext.Program[currentContext.InstructionPointer].SourcePath == currentSourcePath) { currentContext.InstructionPointer++; } diff --git a/src/kOS.Safe/Execution/ProgramContext.cs b/src/kOS.Safe/Execution/ProgramContext.cs index 5b7c9205a..7e83385f9 100644 --- a/src/kOS.Safe/Execution/ProgramContext.cs +++ b/src/kOS.Safe/Execution/ProgramContext.cs @@ -221,7 +221,7 @@ public List GetCodeFragment(int start, int stop, bool doProfile = false) { string thisLine = string.Format( formatStr, - Program[index].SourceName, + Program[index].SourcePath, Program[index].SourceLine, Program[index].SourceColumn, index, diff --git a/src/kOS.Safe/Module/IProcessor.cs b/src/kOS.Safe/Module/IProcessor.cs index 808604b79..6db85a47f 100644 --- a/src/kOS.Safe/Module/IProcessor.cs +++ b/src/kOS.Safe/Module/IProcessor.cs @@ -1,9 +1,15 @@ +using kOS.Safe.Persistence; + namespace kOS.Safe.Module { public interface IProcessor { void SetMode(ProcessorModes newProcessorMode); - string BootFilename { get; set; } + + /// + /// Gets or sets the boot file path. Has to be a valid path or null. + /// + VolumePath BootFilePath { get; set; } bool CheckCanBoot(); string Tag { get; } diff --git a/src/kOS.Safe/Persistence/Archive.cs b/src/kOS.Safe/Persistence/Archive.cs index c9bffcd5b..3cf385683 100644 --- a/src/kOS.Safe/Persistence/Archive.cs +++ b/src/kOS.Safe/Persistence/Archive.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using kOS.Safe.Exceptions; namespace kOS.Safe.Persistence { @@ -11,138 +12,137 @@ namespace kOS.Safe.Persistence public class Archive : Volume { public const string ArchiveName = "Archive"; + public ArchiveDirectory RootArchiveDirectory { get; private set; } - private static string ArchiveFolder - { - get { return SafeHouse.ArchiveFolder; } + private static string ArchiveFolder { get; set; } + + public override VolumeDirectory Root { + get { + return RootArchiveDirectory; + } } - public Archive() + public Archive(string archiveFolder) { - Directory.CreateDirectory(ArchiveFolder); + ArchiveFolder = archiveFolder; + CreateArchiveDirectory(); Renameable = false; - Name = ArchiveName; + InitializeName(ArchiveName); + + RootArchiveDirectory = new ArchiveDirectory(this, VolumePath.EMPTY); } - public override float RequiredPower() + private void CreateArchiveDirectory() { - const int MULTIPLIER = 5; - const float POWER_REQUIRED = BASE_POWER * MULTIPLIER; - - return POWER_REQUIRED; + Directory.CreateDirectory(ArchiveFolder); } - public override VolumeFile Open(string name, bool ksmDefault = false) + public string GetArchivePath(VolumePath path) { - try - { - var fileInfo = FileSearch(name, ksmDefault); - if (fileInfo == null) - { - return null; - } - - return new ArchiveFile(fileInfo); - } - catch (Exception e) + if (path.PointsOutside) { - SafeHouse.Logger.Log(e); - return null; + throw new KOSInvalidPathException("Path refers to parent directory", path.ToString()); } - } - public override bool Delete(string name) - { - var fullPath = FileSearch(name); - if (fullPath == null) + string mergedPath = ArchiveFolder; + + foreach (string segment in path.Segments) { - return false; + mergedPath = Path.Combine(mergedPath, segment); } - File.Delete(fullPath.FullName); - return true; + + return mergedPath; } - public override bool RenameFile(string name, string newName) + public override VolumeItem Open(VolumePath path, bool ksmDefault = false) { try { - var fullSourcePath = FileSearch(name); - if (fullSourcePath == null) - { - return false; - } + var fileSystemInfo = Search(path, ksmDefault); - string destinationPath = string.Format(ArchiveFolder + newName); - if (!Path.HasExtension(newName)) + if (fileSystemInfo == null) { + return null; + } else if (fileSystemInfo is FileInfo) { - destinationPath += fullSourcePath.Extension; + VolumePath filePath = VolumePath.FromString(fileSystemInfo.FullName.Substring(ArchiveFolder.Length)); + return new ArchiveFile(this, fileSystemInfo as FileInfo, filePath); + } else { + // we can use 'path' here, default extensions are not added to directories + return new ArchiveDirectory(this, path); } - - File.Move(fullSourcePath.FullName, destinationPath); - return true; } - catch (Exception) + catch (Exception e) { - return false; + throw new KOSPersistenceException("Could not open path: " + path, e); } } - public override VolumeFile Create(string name) + public override VolumeDirectory CreateDirectory(VolumePath path) { - string filePath = Path.Combine(ArchiveFolder, name); - if (File.Exists(filePath)) - { - throw new KOSFileException("File already exists: " + name); - } + string archivePath = GetArchivePath(path); - using (File.Create(filePath)) + try { - // Do Nothing + Directory.CreateDirectory(archivePath); + } catch (IOException) + { + throw new KOSPersistenceException("Already exists: " + path); } - return new ArchiveFile(FileSearch(name)); + return new ArchiveDirectory(this, path); } - public override VolumeFile Save(string name, FileContent content) + public override VolumeFile CreateFile(VolumePath path) { - Directory.CreateDirectory(ArchiveFolder); + string archivePath = GetArchivePath(path); - byte[] fileBody = ConvertToWindowsNewlines(content.Bytes); + Directory.CreateDirectory(GetArchivePath(path.GetParent())); - using (var outfile = new BinaryWriter(File.Open(Path.Combine(ArchiveFolder, name), FileMode.Create))) + try { + File.Create(archivePath).Dispose(); + } catch (UnauthorizedAccessException) { - outfile.Write(fileBody); + throw new KOSPersistenceException("Could not create file: " + path); } - return new ArchiveFile(FileSearch(name)); + return Open(path) as VolumeFile; } - public override Dictionary FileList + public override bool Exists(VolumePath path, bool ksmDefault = false) { - get - { - var listFiles = Directory.GetFiles(ArchiveFolder); - var filterHid = listFiles.Where(f => (File.GetAttributes(f) & FileAttributes.Hidden) != 0); - var filterSys = listFiles.Where(f => (File.GetAttributes(f) & FileAttributes.System) != 0); - - var visFiles = listFiles.Except(filterSys).Except(filterHid); - var kosFiles = visFiles.Except(Directory.GetFiles(ArchiveFolder, ".*")); - return kosFiles.Select(file => new FileInfo(file)).Select(sysFileInfo => new ArchiveFile(sysFileInfo)). - Cast().ToDictionary(f => f.Name, f => f); - } + return Search(path, ksmDefault) != null; } - public override long Size + public override bool Delete(VolumePath path, bool ksmDefault = false) { - get + var fileSystemInfo = Search(path, ksmDefault); + + if (fileSystemInfo == null) { - return FileList.Values.Sum(i => i.Size); + return false; + } else if (fileSystemInfo is FileInfo) + { + File.Delete(fileSystemInfo.FullName); + } else + { + Directory.Delete(fileSystemInfo.FullName, true); } + + return true; } - public override bool Exists(string name) + public override VolumeFile Save(VolumePath path, FileContent content) { - return FileSearch(name) != null; + Directory.CreateDirectory(ArchiveFolder); + + byte[] fileBody = ConvertToWindowsNewlines(content.Bytes); + + using (var outfile = new BinaryWriter(File.Open(GetArchivePath(path), FileMode.Create))) + { + outfile.Write(fileBody); + } + + return Open(path) as VolumeFile; } public static byte[] ConvertToWindowsNewlines(byte[] bytes) @@ -175,19 +175,34 @@ public static byte[] ConvertFromWindowsNewlines(byte[] bytes) return bytes; } + public override float RequiredPower() + { + const int MULTIPLIER = 5; + const float POWER_REQUIRED = BASE_POWER * MULTIPLIER; + + return POWER_REQUIRED; + } + /// /// Get the file from the OS. /// /// filename to look for /// if true, it prefers to use the KSM filename over the KS. The default is to prefer KS. /// the full fileinfo of the filename if found - private FileInfo FileSearch(string name, bool ksmDefault = false) + private FileSystemInfo Search(VolumePath volumePath, bool ksmDefault) { - var path = Path.Combine(ArchiveFolder, name); + var path = GetArchivePath(volumePath); + + if (Directory.Exists(path)) + { + return new DirectoryInfo(path); + } + if (File.Exists(path)) { return new FileInfo(path); } + var kerboscriptFile = new FileInfo(PersistenceUtilities.CookedFilename(path, KERBOSCRIPT_EXTENSION, true)); var kosMlFile = new FileInfo(PersistenceUtilities.CookedFilename(path, KOS_MACHINELANGUAGE_EXTENSION, true)); diff --git a/src/kOS.Safe/Persistence/ArchiveDirectory.cs b/src/kOS.Safe/Persistence/ArchiveDirectory.cs new file mode 100644 index 000000000..05bb00a1a --- /dev/null +++ b/src/kOS.Safe/Persistence/ArchiveDirectory.cs @@ -0,0 +1,56 @@ +using System; +using kOS.Safe.Persistence; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using kOS.Safe.Encapsulation; + +namespace kOS.Safe.Persistence +{ + public class ArchiveDirectory : VolumeDirectory + { + private Archive archive; + private string archivePath; + + public ArchiveDirectory(Archive archive, VolumePath path) : base(archive, path) + { + this.archive = archive; + this.archivePath = archive.GetArchivePath(path); + } + + public override IDictionary List() + { + string[] files = Directory.GetFiles(archivePath); + var filterHid = files.Where(f => (File.GetAttributes(f) & FileAttributes.Hidden) != 0); + var filterSys = files.Where(f => (File.GetAttributes(f) & FileAttributes.System) != 0); + var visFiles = files.Except(filterSys).Except(filterHid).ToArray(); + string[] directories = Directory.GetDirectories(archivePath); + + Array.Sort(directories); + Array.Sort(visFiles); + + var result = new Dictionary(); + + foreach (string directory in directories) + { + string directoryName = System.IO.Path.GetFileName(directory); + result.Add(directoryName, new ArchiveDirectory(archive, VolumePath.FromString(directoryName, Path))); + } + + foreach (string file in visFiles) + { + string fileName = System.IO.Path.GetFileName(file); + result.Add(fileName, new ArchiveFile(archive, new FileInfo(file), VolumePath.FromString(fileName, Path))); + } + + return result; + } + + public override int Size { + get { + return List().Values.Aggregate(0, (acc, x) => acc + x.Size); + } + } + } +} + diff --git a/src/kOS.Safe/Encapsulation/ArchiveFile.cs b/src/kOS.Safe/Persistence/ArchiveFile.cs similarity index 88% rename from src/kOS.Safe/Encapsulation/ArchiveFile.cs rename to src/kOS.Safe/Persistence/ArchiveFile.cs index 77361d3cf..4fdd52a66 100644 --- a/src/kOS.Safe/Encapsulation/ArchiveFile.cs +++ b/src/kOS.Safe/Persistence/ArchiveFile.cs @@ -1,16 +1,17 @@ -using kOS.Safe.Persistence; -using System; +using System; using System.IO; -namespace kOS.Safe.Encapsulation +namespace kOS.Safe.Persistence { [kOS.Safe.Utilities.KOSNomenclature("VolumeFile", KOSToCSharp = false)] public class ArchiveFile : VolumeFile { private readonly FileInfo fileInfo; + public override int Size { get { fileInfo.Refresh(); return (int)fileInfo.Length; } } - public ArchiveFile(FileInfo fileInfo) : base(fileInfo.Name) + public ArchiveFile(Archive archive, FileInfo fileInfo, VolumePath path) + : base(archive, path) { this.fileInfo = fileInfo; } diff --git a/src/kOS.Safe/Persistence/FileContent.cs b/src/kOS.Safe/Persistence/FileContent.cs index 90f5b181f..a35ae36ed 100644 --- a/src/kOS.Safe/Persistence/FileContent.cs +++ b/src/kOS.Safe/Persistence/FileContent.cs @@ -15,8 +15,8 @@ namespace kOS.Safe.Persistence public class FileContent : SerializableStructure, IEnumerable { private static readonly Encoding fileEncoding = Encoding.UTF8; - private const string DUMP_CONTENT = "content"; - public const string NEW_LINE = "\n"; + private const string DumpContent = "content"; + public const string NewLine = "\n"; public byte[] Bytes { get; private set; } public string String { get { return fileEncoding.GetString(Bytes); } } @@ -57,14 +57,14 @@ private void InitializeSuffixes() public override Dump Dump() { - Dump dump = new Dump { { DUMP_CONTENT, PersistenceUtilities.EncodeBase64(Bytes) } }; + Dump dump = new Dump { { DumpContent, PersistenceUtilities.EncodeBase64(Bytes) } }; return dump; } public override void LoadDump(Dump dump) { - string contentString = dump[DUMP_CONTENT] as string; + string contentString = dump[DumpContent] as string; if (contentString == null) { @@ -74,9 +74,9 @@ public override void LoadDump(Dump dump) Bytes = PersistenceUtilities.DecodeBase64ToBinary(contentString); } - public List AsParts(string name, string prefix) + public List AsParts(GlobalPath path, string prefix) { - return CompiledObject.UnPack(name, prefix, Bytes); + return CompiledObject.UnPack(path, prefix, Bytes); } public static byte[] EncodeString(string content) @@ -104,7 +104,7 @@ public void Write(byte[] contentToWrite) public void WriteLn(string content) { - Write(content + NEW_LINE); + Write(content + NewLine); } public void Clear() diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index 42b102be6..3f505eeaf 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -3,141 +3,85 @@ using System; using System.Collections.Generic; using System.Linq; +using kOS.Safe.Exceptions; namespace kOS.Safe.Persistence { [kOS.Safe.Utilities.KOSNomenclature("LocalVolume")] public sealed class Harddisk : Volume { - private readonly Dictionary files; + public HarddiskDirectory RootHarddiskDirectory { get; set; } - public override Dictionary FileList - { - get - { - return files.ToDictionary(arg => arg.Key, arg => (VolumeFile)new HarddiskFile(this, arg.Key)); - } - } - - public override long Size - { + public override VolumeDirectory Root { get { - return files.Values.Sum(x => x.Size); + return RootHarddiskDirectory; } } public Harddisk(int size) { Capacity = size; - files = new Dictionary(StringComparer.OrdinalIgnoreCase); + RootHarddiskDirectory = new HarddiskDirectory(this, VolumePath.EMPTY); } - public FileContent GetFileContent(string name) + private HarddiskDirectory ParentDirectoryForPath(VolumePath path, bool create = false) { - if (!files.ContainsKey(name)) + HarddiskDirectory directory = RootHarddiskDirectory; + if (path.Depth > 1) { - throw new KOSFileException("File does not exist: " + name); + directory = RootHarddiskDirectory.GetSubdirectory(path.GetParent(), create); } - return files[name]; + return directory; } - public override VolumeFile Open(string name, bool ksmDefault = false) + public override VolumeItem Open(VolumePath path, bool ksmDefault = false) { - return FileSearch(name, ksmDefault); - } + HarddiskDirectory directory = ParentDirectoryForPath(path); - public override bool Delete(string name) - { - var fullPath = FileSearch(name); - if (fullPath == null) - { - return false; - } - return files.Remove(fullPath.Name); + return directory.Open(path.Name, ksmDefault); } - public override bool RenameFile(string name, string newName) + public override VolumeDirectory CreateDirectory(VolumePath path) { - VolumeFile file = Open(name); - if (file != null) - { - // Add the original file content under the new name - files.Add(newName, files[file.Name]); - // Then remove the old file content under the old name - files.Remove(file.Name); - return true; - } - return false; + HarddiskDirectory directory = ParentDirectoryForPath(path, true); + + return directory.CreateDirectory(path.Name); } - public override VolumeFile Create(string name) + public override VolumeFile CreateFile(VolumePath path) { - SafeHouse.Logger.Log("Creating file on harddisk " + name); + HarddiskDirectory directory = ParentDirectoryForPath(path, true); - if (files.ContainsKey(name)) - { - throw new KOSFileException("File already exists: " + name); - } - - files[name] = new FileContent(); - - SafeHouse.Logger.Log("Created file on harddisk " + name); - - return new HarddiskFile(this, name); + return directory.CreateFile(path.Name); } - public override VolumeFile Save(string name, FileContent content) + public override bool Exists(VolumePath path, bool ksmDefault = false) { - if (!IsRoomFor(name, content)) - { - return null; - } + HarddiskDirectory directory = ParentDirectoryForPath(path); - files[name] = content; - - return new HarddiskFile(this, name); + return directory.Exists(path.Name, ksmDefault); } - public override bool Exists(string name) + public override bool Delete(VolumePath path, bool ksmDefault = false) { - return FileSearch(name) != null; + HarddiskDirectory directory = ParentDirectoryForPath(path); + + return directory.Delete(path.Name, ksmDefault); } - private VolumeFile FileSearch(string name, bool ksmDefault = false) + public override VolumeFile Save(VolumePath path, FileContent content) { - if (files.ContainsKey(name)) - { - return new HarddiskFile(this, name); - } - else + if (!IsRoomFor(path, content)) { - var kerboscriptFilename = PersistenceUtilities.CookedFilename(name, KERBOSCRIPT_EXTENSION, true); - var kosMlFilename = PersistenceUtilities.CookedFilename(name, KOS_MACHINELANGUAGE_EXTENSION, true); - bool kerboscriptFileExists = files.ContainsKey(kerboscriptFilename); - bool kosMlFileExists = files.ContainsKey(kosMlFilename); - if (kerboscriptFileExists && kosMlFileExists) - { - if (ksmDefault) - { - return new HarddiskFile(this, kosMlFilename); - } - else - { - return new HarddiskFile(this, kerboscriptFilename); - } - } - if (kerboscriptFileExists) - { - return new HarddiskFile(this, kerboscriptFilename); - } - if (kosMlFileExists) - { - return new HarddiskFile(this, kosMlFilename); - } + return null; } - return null; + + HarddiskDirectory directory = ParentDirectoryForPath(path); + directory.CreateFile(path.Name, content); + + return Open(path) as VolumeFile; } } } diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs new file mode 100644 index 000000000..0365ce752 --- /dev/null +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using kOS.Safe.Persistence; +using System.Linq; +using kOS.Safe.Exceptions; +using kOS.Safe.Encapsulation; +using System.Collections; + +namespace kOS.Safe.Persistence +{ + public class HarddiskDirectory : VolumeDirectory, IEnumerable + { + private Dictionary items; + + public HarddiskDirectory(Harddisk harddisk, VolumePath path) : base(harddisk, path) + { + items = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + } + + public VolumeItem Open(string name, bool ksmDefault = false) + { + return Search(name, ksmDefault); + } + + public FileContent GetFileContent(string name) + { + if (!items.ContainsKey(name) || !(items[name] is FileContent)) + { + throw new KOSFileException("File does not exist: " + name); + } + + return items[name] as FileContent; + } + + public HarddiskFile CreateFile(string name) + { + return CreateFile(name, new FileContent()); + } + + public HarddiskFile CreateFile(string name, FileContent fileContent) + { + try { + items.Add(name, new FileContent(fileContent.Bytes.Clone() as byte[])); + } catch (ArgumentException) + { + throw new KOSPersistenceException("Already exists: " + name); + } + + return new HarddiskFile(this, name); + } + + public HarddiskDirectory CreateDirectory(string name) + { + try + { + return CreateDirectory(name, new HarddiskDirectory(Volume as Harddisk, VolumePath.FromString(name, Path))); + } catch (KOSPersistenceException e) + { + if (items[name] is HarddiskDirectory) + { + return items[name] as HarddiskDirectory; + } else + { + throw e; + } + } + } + + public HarddiskDirectory CreateDirectory(string name, HarddiskDirectory directory) + { + try + { + items.Add(name, directory); + } catch (ArgumentException) + { + throw new KOSPersistenceException("Already exists: " + name); + } + + return directory; + } + + public bool Exists(string name, bool ksmDefault) + { + return Search(name) != null; + } + + public bool Delete(string name, bool ksmDefault) + { + return items.Remove(name); + } + + public IEnumerator GetEnumerator() + { + return List().Values.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public HarddiskDirectory GetSubdirectory(VolumePath path, bool create = false) + { + if (Path.Equals(path)) + { + return this; + } + + if (!Path.IsParent(path)) + { + throw new KOSException("This directory does not contain that path: " + path.ToString()); + } + + string subdirectory = path.Segments[Path.Segments.Count]; + + if (!items.ContainsKey(subdirectory)) + { + if (create) + { + CreateDirectory(subdirectory); + } else + { + return null; + } + } + + HarddiskDirectory directory = items[subdirectory] as HarddiskDirectory; + + if (directory == null) + { + throw new KOSException("Subdirectory does not exist: " + path.ToString()); + } + + return directory; + } + + public override IDictionary List() + { + var result = new Dictionary(); + + foreach (var pair in items) + { + if (pair.Value is HarddiskDirectory) + { + result.Add(pair.Key, pair.Value as HarddiskDirectory); + } + else + { + result.Add(pair.Key, new HarddiskFile(this, pair.Key)); + } + } + + return result; + } + + public override int Size { + get { + return List().Aggregate(0, (acc, x) => acc + x.Value.Size); + } + } + + + private VolumeItem Search(string name, bool ksmDefault = false) + { + object item = items.ContainsKey(name) ? items[name] : null; + if (item is byte[]) + { + return new HarddiskFile(this, name); + } else if (item is HarddiskDirectory) + { + return item as HarddiskDirectory; + } + else + { + var kerboscriptFilename = PersistenceUtilities.CookedFilename(name, Volume.KERBOSCRIPT_EXTENSION, true); + var kosMlFilename = PersistenceUtilities.CookedFilename(name, Volume.KOS_MACHINELANGUAGE_EXTENSION, true); + bool kerboscriptFileExists = items.ContainsKey(kerboscriptFilename) && items[kerboscriptFilename] is FileContent; + bool kosMlFileExists = items.ContainsKey(kosMlFilename) && items[kosMlFilename] is FileContent; + if (kerboscriptFileExists && kosMlFileExists) + { + return ksmDefault ? new HarddiskFile(this, kosMlFilename) : new HarddiskFile(this, kerboscriptFilename); + } + if (kerboscriptFileExists) + { + return new HarddiskFile(this, kerboscriptFilename); + } + if (kosMlFileExists) + { + return new HarddiskFile(this, kosMlFilename); + } + } + return null; + } + } +} + diff --git a/src/kOS.Safe/Encapsulation/HarddiskFile.cs b/src/kOS.Safe/Persistence/HarddiskFile.cs similarity index 57% rename from src/kOS.Safe/Encapsulation/HarddiskFile.cs rename to src/kOS.Safe/Persistence/HarddiskFile.cs index 7ad813093..13745efe5 100644 --- a/src/kOS.Safe/Encapsulation/HarddiskFile.cs +++ b/src/kOS.Safe/Persistence/HarddiskFile.cs @@ -1,22 +1,21 @@ -using kOS.Safe.Persistence; - -namespace kOS.Safe.Encapsulation +namespace kOS.Safe.Persistence { [kOS.Safe.Utilities.KOSNomenclature("VolumeFile", KOSToCSharp = false)] public class HarddiskFile : VolumeFile { - private readonly Harddisk harddisk; + private readonly HarddiskDirectory hardiskDirectory; public override int Size { get { return ReadAll().Size; } } - public HarddiskFile(Harddisk harddisk, string name) : base(name) + public HarddiskFile(HarddiskDirectory harddiskDirectory, string name) : base(harddiskDirectory.Volume as Harddisk, + VolumePath.FromString(name, harddiskDirectory.Path)) { - this.harddisk = harddisk; + this.hardiskDirectory = harddiskDirectory; } private FileContent GetFileContent() { - return harddisk.GetFileContent(Name); + return hardiskDirectory.GetFileContent(Name); } public override FileContent ReadAll() @@ -26,7 +25,7 @@ public override FileContent ReadAll() public override bool Write(byte[] content) { - if (harddisk.FreeSpace <= content.Length) return false; + if ((hardiskDirectory.Volume as Harddisk).FreeSpace <= content.Length) return false; GetFileContent().Write(content); return true; diff --git a/src/kOS.Safe/Persistence/IVolumeManager.cs b/src/kOS.Safe/Persistence/IVolumeManager.cs index 2a4cf5f51..cb51dcba5 100644 --- a/src/kOS.Safe/Persistence/IVolumeManager.cs +++ b/src/kOS.Safe/Persistence/IVolumeManager.cs @@ -6,12 +6,14 @@ public interface IVolumeManager { Dictionary Volumes { get; } Volume CurrentVolume { get; } + VolumeDirectory CurrentDirectory { get; set; } float CurrentRequiredPower { get; } bool VolumeIsCurrent(Volume volume); int GetVolumeId(Volume volume); Volume GetVolume(object volumeId); Volume GetVolume(string name); Volume GetVolume(int id); + Volume GetVolumeFromPath(GlobalPath path); void Add(Volume volume); void Remove(string name); void Remove(int id); @@ -19,6 +21,18 @@ public interface IVolumeManager void UpdateVolumes(List attachedVolumes); string GetVolumeBestIdentifier(Volume volume); + /// + /// This creates a proper, absolute GlobalPath from the given string (which is assumed to come from the user). + /// This handles absolute paths (for example 'volume:/some/path'), paths relative to current volume ('/some/path') + /// and paths relative to current directory ('../some/path', 'some/path'). + /// + /// Relative paths need current volume and current directory for resolution, that's why this method is part of this + /// interface. + /// + /// GlobalPath instance + /// Path string. + GlobalPath GlobalPathFromString(string pathString); + /// /// Like GetVolumeBestIdentifier, but without the extra string formatting. /// diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index 17bf11402..d7056e8b6 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -2,6 +2,7 @@ using System.Linq; using kOS.Safe.Encapsulation; using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Exceptions; namespace kOS.Safe.Persistence { @@ -15,16 +16,33 @@ public abstract class Volume : Structure protected const float BASE_POWER = 0.04f; public const int INFINITE_CAPACITY = -1; - public abstract Dictionary FileList { get; } - public string Name { get; set; } + private string name; + + public abstract VolumeDirectory Root { get; } public long Capacity { get; protected set; } - public abstract long Size { get; } public long FreeSpace { get { return Capacity == INFINITE_CAPACITY ? INFINITE_CAPACITY : Capacity - Size; } } + public long Size { + get { + return Root.Size; + } + } public bool Renameable { get; protected set; } + public string Name { + get { + return name; + } + set { + if (Renameable) { + name = value; + } else { + throw new KOSException("Volume name can't be changed"); + } + } + } protected Volume() { @@ -34,48 +52,94 @@ protected Volume() InitializeVolumeSuffixes(); } + protected void InitializeName(string name) + { + this.name = name; + } + + public VolumeItem Open(string pathString, bool ksmDefault = false) + { + return Open(VolumePath.FromString(pathString), ksmDefault); + } + /// /// Get a file given its name /// /// filename to get. if it has no filename extension, one will be guessed at, ".ks" usually. /// in the scenario where there is no filename extension, do we prefer the .ksm over the .ks? The default is to prefer .ks /// the file - public abstract VolumeFile Open(string name, bool ksmDefault = false); + public abstract VolumeItem Open(VolumePath path, bool ksmDefault = false); - public abstract VolumeFile Create(string name); + public VolumeDirectory CreateDirectory(string pathString) + { + return CreateDirectory(VolumePath.FromString(pathString)); + } - public abstract bool Exists(string name); + public abstract VolumeDirectory CreateDirectory(VolumePath path); - public VolumeFile OpenOrCreate(string name, bool ksmDefault = false) + public VolumeFile CreateFile(string pathString) { - var volumeFile = Open(name, ksmDefault); + return CreateFile(VolumePath.FromString(pathString)); + } + + public abstract VolumeFile CreateFile(VolumePath path); - if (volumeFile != null) + public VolumeDirectory OpenOrCreateDirectory(VolumePath path) + { + VolumeDirectory directory = Open(path) as VolumeDirectory; + + if (directory == null) { - return volumeFile; + directory = CreateDirectory(path); } - return Create(name); + return directory; } - public abstract bool Delete(string name); + public VolumeFile OpenOrCreateFile(VolumePath path, bool ksmDefault = false) + { + VolumeFile file = Open(path, ksmDefault) as VolumeFile; - public abstract bool RenameFile(string name, string newName); + if (file == null) + { + file = CreateFile(path); + } - //public abstract bool AppendToFile(string name, string textToAppend); + return file; + } + + public bool Exists(string pathString, bool ksmDefault = false) + { + return Exists(VolumePath.FromString(pathString), ksmDefault); + } - //public abstract bool AppendToFile(string name, byte[] bytesToAppend); + public abstract bool Exists(VolumePath path, bool ksmDefault = false); + + public bool Delete(string pathString, bool ksmDefault = false) + { + return Delete(VolumePath.FromString(pathString), ksmDefault); + } + + public abstract bool Delete(VolumePath path, bool ksmDefault = false); + //public abstract void Move(VolumePath oldPath, VolumePath newPath); public VolumeFile Save(VolumeFile volumeFile) { - return Save(volumeFile.Name, volumeFile.ReadAll()); + return Save(volumeFile.Path, volumeFile.ReadAll()); } - public abstract VolumeFile Save(string name, FileContent content); + public abstract VolumeFile Save(VolumePath path, FileContent content); - public bool IsRoomFor(string name, FileContent fileContent) + public bool IsRoomFor(VolumePath path, FileContent fileContent) { - VolumeFile existingFile = Open(name); + VolumeItem existing = Open(path); + + if (existing is VolumeDirectory) + { + throw new KOSPersistenceException("'" + path + "' is a directory"); + } + + VolumeFile existingFile = existing as VolumeFile; int usedByThisFile = 0; @@ -95,6 +159,11 @@ public virtual float RequiredPower() return powerRequired; } + public Lexicon ListAsLexicon() + { + return Root.ListAsLexicon(); + } + public override string ToString() { return "Volume( " + Name + ", " + Capacity + ")"; @@ -108,22 +177,12 @@ private void InitializeVolumeSuffixes() AddSuffix("RENAMEABLE" , new Suffix(() => Renameable)); AddSuffix("POWERREQUIREMENT" , new Suffix(() => RequiredPower())); - AddSuffix("EXISTS" , new OneArgsSuffix(name => Exists(name))); - AddSuffix("FILES" , new Suffix(BuildFileLexicon)); - AddSuffix("CREATE" , new OneArgsSuffix(name => Create(name))); - AddSuffix("OPEN" , new OneArgsSuffix(name => Open(name))); - AddSuffix("DELETE" , new OneArgsSuffix(name => Delete(name))); - } - - private Lexicon BuildFileLexicon() - { - return new Lexicon(FileList.ToDictionary(item => FromPrimitiveWithAssert(item.Key), item => (Structure) item.Value)); - } - - - private int FileInfoComparer(VolumeFile a, VolumeFile b) - { - return string.CompareOrdinal(a.Name, b.Name); + AddSuffix("EXISTS" , new OneArgsSuffix(path => Exists(path))); + AddSuffix("FILES" , new Suffix(ListAsLexicon)); + AddSuffix("CREATE" , new OneArgsSuffix(path => CreateFile(path))); + AddSuffix("CREATEDIR" , new OneArgsSuffix(path => CreateDirectory(path))); + AddSuffix("OPEN" , new OneArgsSuffix(path => Open(path))); + AddSuffix("DELETE" , new OneArgsSuffix(path => Delete(path))); } - } + } } diff --git a/src/kOS.Safe/Persistence/VolumeDirectory.cs b/src/kOS.Safe/Persistence/VolumeDirectory.cs new file mode 100644 index 000000000..add0d3def --- /dev/null +++ b/src/kOS.Safe/Persistence/VolumeDirectory.cs @@ -0,0 +1,35 @@ +using System; +using kOS.Safe.Persistence; +using System.Collections.Generic; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; + +namespace kOS.Safe +{ + public abstract class VolumeDirectory : VolumeItem + { + public VolumeDirectory(Volume volume, VolumePath path) : base(volume, path) + { + InitializeSuffixes(); + } + + public Lexicon ListAsLexicon() + { + Lexicon result = new Lexicon(); + + foreach (KeyValuePair entry in List()) + { + result.Add(new StringValue(entry.Key), entry.Value); + } + + return result; + } + + public abstract IDictionary List(); + + private void InitializeSuffixes() + { + AddSuffix("LIST", new Suffix(ListAsLexicon)); + } + } +} diff --git a/src/kOS.Safe/Encapsulation/VolumeFile.cs b/src/kOS.Safe/Persistence/VolumeFile.cs similarity index 66% rename from src/kOS.Safe/Encapsulation/VolumeFile.cs rename to src/kOS.Safe/Persistence/VolumeFile.cs index 5def238bb..9fe1f685b 100644 --- a/src/kOS.Safe/Encapsulation/VolumeFile.cs +++ b/src/kOS.Safe/Persistence/VolumeFile.cs @@ -1,31 +1,15 @@ using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Exceptions; using System.Linq; -using kOS.Safe.Persistence; +using kOS.Safe.Encapsulation; -namespace kOS.Safe.Encapsulation +namespace kOS.Safe.Persistence { [kOS.Safe.Utilities.KOSNomenclature("VolumeFile")] - public abstract class VolumeFile : Structure + public abstract class VolumeFile : VolumeItem { - public string Name { get; private set; } - - public abstract int Size { get; } - - public string Extension - { - get - { - var fileParts = Name.Split('.'); - - return fileParts.Length > 1 ? fileParts.Last() : string.Empty; - } - } - - protected VolumeFile(string name) + protected VolumeFile(Volume volume, VolumePath path) : base(volume, path) { - Name = name; - InitializeSuffixes(); } @@ -40,7 +24,7 @@ public bool Write(string content) public bool WriteLn(string content) { - return Write(content + FileContent.NEW_LINE); + return Write(content + FileContent.NewLine); } public abstract void Clear(); @@ -52,10 +36,6 @@ public override string ToString() private void InitializeSuffixes() { - AddSuffix("NAME", new Suffix(() => Name)); - AddSuffix("SIZE", new Suffix(() => new ScalarIntValue(Size))); - AddSuffix("EXTENSION", new Suffix(() => Extension)); - AddSuffix("READALL", new Suffix(ReadAll)); AddSuffix("WRITE", new OneArgsSuffix(str => WriteObject(str))); AddSuffix("WRITELN", new OneArgsSuffix(str => new BooleanValue(WriteLn(str)))); diff --git a/src/kOS.Safe/Persistence/VolumeItem.cs b/src/kOS.Safe/Persistence/VolumeItem.cs new file mode 100644 index 000000000..ea3033fd2 --- /dev/null +++ b/src/kOS.Safe/Persistence/VolumeItem.cs @@ -0,0 +1,57 @@ +using System; +using kOS.Safe.Persistence; +using System.Linq; +using kOS.Safe.Encapsulation; +using kOS.Safe.Encapsulation.Suffixes; + +namespace kOS.Safe.Persistence +{ + public abstract class VolumeItem : Structure + { + public Volume Volume { get; set; } + public VolumePath Path { get; set; } + + public string Name + { + get + { + return Path.Name; + } + } + + public string Extension { + get + { + var fileParts = Name.Split('.'); + return fileParts.Count() > 1 ? fileParts.Last() : string.Empty; + } + } + + public VolumeItem(Volume volume, VolumePath path) + { + Volume = volume; + Path = path; + } + + public VolumeItem(Volume volume, VolumePath parentPath, String name) + { + Volume = volume; + Path = VolumePath.FromString(name, parentPath); + } + + private void InitializeSuffixes() + { + AddSuffix("NAME", new Suffix(() => Name)); + AddSuffix("SIZE", new Suffix(() => new ScalarIntValue(Size))); + AddSuffix("EXTENSION", new Suffix(() => Extension)); + } + + public override string ToString() + { + return Path.ToString(); + } + + public abstract int Size { get; } + } +} + diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index cd8bf0522..2f88bd954 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -1,29 +1,39 @@ using System; using System.Collections.Generic; using kOS.Safe.Encapsulation; +using kOS.Safe.Exceptions; namespace kOS.Safe.Persistence { public class VolumeManager : IVolumeManager { private readonly Dictionary volumes; - private Volume currentVolume; + public virtual Volume CurrentVolume { get; private set; } + public VolumeDirectory CurrentDirectory { get + { + return CurrentDirectory; + } + set { + CurrentDirectory = value; + CurrentVolume = value.Volume; + } + } private int lastId; public Dictionary Volumes { get { return volumes; } } - public virtual Volume CurrentVolume { get { return currentVolume; } } public float CurrentRequiredPower { get; private set; } public VolumeManager() { volumes = new Dictionary(); - currentVolume = null; + CurrentVolume = null; + CurrentDirectory = null; } public bool VolumeIsCurrent(Volume volume) { - return volume == currentVolume; + return volume == CurrentVolume; } private int GetVolumeId(string name) @@ -98,9 +108,9 @@ public void Add(Volume volume) { volumes.Add(lastId++, volume); - if (currentVolume == null) + if (CurrentVolume == null) { - currentVolume = volumes[0]; + CurrentVolume = volumes[0]; UpdateRequiredPower(); } } @@ -119,16 +129,16 @@ public void Remove(int id) { volumes.Remove(id); - if (currentVolume == volume) + if (CurrentVolume == volume) { if (volumes.Count > 0) { - currentVolume = volumes[0]; + CurrentVolume = volumes[0]; UpdateRequiredPower(); } else { - currentVolume = null; + CurrentVolume = null; } } } @@ -136,11 +146,9 @@ public void Remove(int id) public void SwitchTo(Volume volume) { - if (volume != null) - { - currentVolume = volume; - UpdateRequiredPower(); - } + CurrentVolume = volume; + CurrentDirectory = volume.Root; + UpdateRequiredPower(); } public void UpdateVolumes(List attachedVolumes) @@ -188,9 +196,40 @@ public string GetVolumeRawIdentifier(Volume volume) return !string.IsNullOrEmpty(volume.Name) ? volume.Name : id.ToString(); } + // Handles global, absolute and relative paths + public GlobalPath GlobalPathFromString(string pathString) + { + if (GlobalPath.HasVolumeId(pathString)) + { + return GlobalPath.FromString(pathString); + } else + { + if (GlobalPath.IsAbsolute(pathString)) + { + return GlobalPath.FromVolumePath(VolumePath.FromString(pathString), CurrentVolume); + } else + { + return GlobalPath.FromStringAndBase(pathString, GlobalPath.FromVolumePath(CurrentDirectory.Path, CurrentVolume)); + } + } + + } + + public Volume GetVolumeFromPath(GlobalPath path) + { + Volume volume = GetVolume(path.VolumeId); + + if (volume == null) + { + throw new KOSPersistenceException("Volume not found: " + path.VolumeId); + } + + return volume; + } + private void UpdateRequiredPower() { - CurrentRequiredPower = (float)Math.Round(currentVolume.RequiredPower(), 4); + CurrentRequiredPower = (float)Math.Round(CurrentVolume.RequiredPower(), 4); } } } diff --git a/src/kOS.Safe/Screen/ITermWindow.cs b/src/kOS.Safe/Screen/ITermWindow.cs index e73799385..d494d4a13 100644 --- a/src/kOS.Safe/Screen/ITermWindow.cs +++ b/src/kOS.Safe/Screen/ITermWindow.cs @@ -4,7 +4,7 @@ namespace kOS.Safe.Screen { public interface ITermWindow { - void OpenPopupEditor( Volume v, string fName ); + void OpenPopupEditor(Volume v, GlobalPath path); void Open(); void Close(); void Toggle(); diff --git a/src/kOS.Safe/Serialization/JSONFormatter.cs b/src/kOS.Safe/Serialization/JSONFormatter.cs index 1af19e7bc..07d74f71e 100644 --- a/src/kOS.Safe/Serialization/JSONFormatter.cs +++ b/src/kOS.Safe/Serialization/JSONFormatter.cs @@ -112,7 +112,7 @@ public static string FormatJson(string str) sb.Append(ch); if (!quoted) { - sb.Append(FileContent.NEW_LINE); + sb.Append(FileContent.NewLine); Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING)); } break; @@ -120,7 +120,7 @@ public static string FormatJson(string str) case ']': if (!quoted) { - sb.Append(FileContent.NEW_LINE); + sb.Append(FileContent.NewLine); Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING)); } sb.Append(ch); @@ -138,7 +138,7 @@ public static string FormatJson(string str) sb.Append(ch); if (!quoted) { - sb.Append(FileContent.NEW_LINE); + sb.Append(FileContent.NewLine); Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING)); } break; diff --git a/src/kOS.Safe/Utilities/Debug.cs b/src/kOS.Safe/Utilities/Debug.cs index 614c97ab9..9eb0ad1f4 100644 --- a/src/kOS.Safe/Utilities/Debug.cs +++ b/src/kOS.Safe/Utilities/Debug.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Runtime.Serialization; using kOS.Safe.Compilation; +using kOS.Safe.Persistence; namespace kOS.Safe.Utilities { @@ -80,7 +81,7 @@ public static string GetCodeFragment(List codes) for (int index = 0; index < codes.Count; index++) { codeFragment.Add(string.Format(FORMAT_STR, - codes[index].SourceName ?? "null", + codes[index].SourcePath ?? GlobalPath.EMPTY, codes[index].SourceLine, codes[index].SourceColumn , index, diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index 76d4a58ba..dff782c68 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -37,6 +37,8 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + 4 + false @@ -230,9 +232,6 @@ - - - @@ -243,6 +242,13 @@ + + + + + + + diff --git a/src/kOS/AddOns/RemoteTech/RemoteTechArchive.cs b/src/kOS/AddOns/RemoteTech/RemoteTechArchive.cs index ee27a5a5b..f01231618 100644 --- a/src/kOS/AddOns/RemoteTech/RemoteTechArchive.cs +++ b/src/kOS/AddOns/RemoteTech/RemoteTechArchive.cs @@ -5,6 +5,11 @@ namespace kOS.AddOns.RemoteTech [kOS.Safe.Utilities.KOSNomenclature("RTArchive")] public class RemoteTechArchive : Archive { + public RemoteTechArchive(string archiveFolder) : base(archiveFolder) + { + + } + public bool CheckRange(Vessel vessel) { if (vessel == null) diff --git a/src/kOS/AddOns/RemoteTech/RemoteTechFactory.cs b/src/kOS/AddOns/RemoteTech/RemoteTechFactory.cs index f34e5c07e..675975ad2 100644 --- a/src/kOS/AddOns/RemoteTech/RemoteTechFactory.cs +++ b/src/kOS/AddOns/RemoteTech/RemoteTechFactory.cs @@ -2,6 +2,7 @@ using kOS.Safe.Persistence; using kOS.Safe.Screen; using kOS.Communication; +using kOS.Safe.Utilities; namespace kOS.AddOns.RemoteTech { @@ -14,7 +15,7 @@ public IInterpreter CreateInterpreter(SharedObjects shared) public Archive CreateArchive() { - return new RemoteTechArchive(); + return new RemoteTechArchive(SafeHouse.ArchiveFolder); } public IVolumeManager CreateVolumeManager(SharedObjects sharedObjects) diff --git a/src/kOS/Factories/StandardFactory.cs b/src/kOS/Factories/StandardFactory.cs index 8b9474897..79250afb5 100644 --- a/src/kOS/Factories/StandardFactory.cs +++ b/src/kOS/Factories/StandardFactory.cs @@ -2,6 +2,7 @@ using kOS.Safe.Screen; using kOS.Screen; using kOS.Communication; +using kOS.Safe.Utilities; namespace kOS.Factories { @@ -14,7 +15,7 @@ public IInterpreter CreateInterpreter(SharedObjects shared) public Archive CreateArchive() { - return new Archive(); + return new Archive(SafeHouse.ArchiveFolder); } public IVolumeManager CreateVolumeManager(SharedObjects sharedObjects) diff --git a/src/kOS/Function/BuildList.cs b/src/kOS/Function/BuildList.cs index ff14d71a4..29b8f3332 100644 --- a/src/kOS/Function/BuildList.cs +++ b/src/kOS/Function/BuildList.cs @@ -40,7 +40,7 @@ public override void Execute(SharedObjects shared) list = shared.Vessel.PartList(listType, shared); break; case "files": - list = ListValue.CreateList(shared.VolumeMgr.CurrentVolume.FileList.Values.ToList()); + list = ListValue.CreateList(shared.VolumeMgr.CurrentVolume.ListAsLexicon().Values.ToList()); break; case "volumes": list = ListValue.CreateList(shared.VolumeMgr.Volumes.Values.ToList()); diff --git a/src/kOS/Function/Misc.cs b/src/kOS/Function/Misc.cs index 61a1ca81b..40028959c 100644 --- a/src/kOS/Function/Misc.cs +++ b/src/kOS/Function/Misc.cs @@ -15,6 +15,7 @@ using kOS.Safe.Compilation.KS; using kOS.Safe.Encapsulation; using KSP.UI.Screens; +using kOS.Safe; namespace kOS.Function { @@ -146,7 +147,7 @@ public override void Execute(SharedObjects shared) // run() is strange. It needs two levels of args - the args to itself, and the args it is meant to // pass on to the program it's invoking. First, these are the args to run itself: object volumeId = PopValueAssert(shared, true); - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); // Now the args it is going to be passing on to the program: @@ -157,10 +158,15 @@ public override void Execute(SharedObjects shared) AssertArgBottomAndConsume(shared); if (shared.VolumeMgr == null) return; - if (shared.VolumeMgr.CurrentVolume == null) throw new Exception("Volume not found"); - VolumeFile file = shared.VolumeMgr.CurrentVolume.Open(fileName, true); - if (file == null) throw new Exception(string.Format("File '{0}' not found", fileName)); + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + VolumeFile volumeFile = volume.Open(path) as VolumeFile; + + FileContent content = volumeFile != null ? volumeFile.ReadAll() : null; + + if (content == null) throw new Exception(string.Format("File '{0}' not found", path)); + if (shared.ScriptHandler == null) return; if (volumeId != null) @@ -170,9 +176,9 @@ public override void Execute(SharedObjects shared) { if (shared.ProcessorMgr != null) { - string filePath = string.Format("{0}/{1}", shared.VolumeMgr.GetVolumeRawIdentifier(targetVolume), fileName); var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager }; - List parts = shared.ScriptHandler.Compile(filePath, 1, file.ReadAll().String, "program", options); + + List parts = shared.ScriptHandler.Compile(path, 1, volumeFile.ReadAll().String, "program", options); var builder = new ProgramBuilder(); builder.AddRange(parts); List program = builder.BuildProgram(); @@ -189,22 +195,21 @@ public override void Execute(SharedObjects shared) // clear the "program" compilation context shared.Cpu.StartCompileStopwatch(); shared.ScriptHandler.ClearContext("program"); - string filePath = shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume) + "/" + fileName; + //string filePath = shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume) + "/" + fileName; var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager }; var programContext = ((CPU)shared.Cpu).SwitchToProgramContext(); List codeParts; - FileContent content = file.ReadAll(); if (content.Category == FileCategory.KSM) { string prefix = programContext.Program.Count.ToString(); - codeParts = content.AsParts(fileName, prefix); + codeParts = content.AsParts(path, prefix); } else { try { - codeParts = shared.ScriptHandler.Compile(filePath, 1, content.String, "program", options); + codeParts = shared.ScriptHandler.Compile(path, 1, content.String, "program", options); } catch (Exception) { @@ -241,7 +246,7 @@ public override void Execute(SharedObjects shared) { bool defaultOutput = false; bool justCompiling = false; // is this load() happening to compile, or to run? - string fileNameOut = null; + string outPathString = null; object topStack = PopValueAssert(shared, true); // null if there's no output file (output file means compile, not run). if (topStack != null) { @@ -250,30 +255,36 @@ public override void Execute(SharedObjects shared) if (outputArg.Equals("-default-compile-out-")) defaultOutput = true; else - fileNameOut = PersistenceUtilities.CookedFilename(outputArg, Volume.KOS_MACHINELANGUAGE_EXTENSION); + outPathString = outputArg; } - string fileName = null; + string pathString = null; topStack = PopValueAssert(shared, true); if (topStack != null) - fileName = topStack.ToString(); + pathString = topStack.ToString(); AssertArgBottomAndConsume(shared); - if (fileName == null) + if (pathString == null) throw new KOSFileException("No filename to load was given."); - VolumeFile file = shared.VolumeMgr.CurrentVolume.Open(fileName, !justCompiling); // if running, look for KSM first. If compiling look for KS first. - if (file == null) throw new KOSFileException(string.Format("Can't find file '{0}'.", fileName)); - fileName = file.Name; // just in case GetByName picked an extension that changed it. + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + VolumeFile file = volume.Open(path) as VolumeFile; // if running, look for KSM first. If compiling look for KS first. + if (file == null) throw new KOSFileException(string.Format("Can't find file '{0}'.", path)); + string fileName = file.Name; // just in case Get picked an extension that changed it. + FileContent fileContent = file.ReadAll(); // filename is now guaranteed to have an extension. To make default output name, replace the extension with KSM: if (defaultOutput) - fileNameOut = fileName.Substring(0, fileName.LastIndexOf('.')) + "." + Volume.KOS_MACHINELANGUAGE_EXTENSION; + outPathString = fileName.Substring(0, fileName.LastIndexOf('.')) + "." + Volume.KOS_MACHINELANGUAGE_EXTENSION; + + GlobalPath outPath = shared.VolumeMgr.GlobalPathFromString(outPathString); - if (fileNameOut != null && fileName == fileNameOut) - throw new KOSFileException("Input and output filenames must differ."); + if (path.Equals(outPath)) + throw new KOSFileException("Input and output paths must differ."); if (shared.VolumeMgr == null) return; if (shared.VolumeMgr.CurrentVolume == null) throw new KOSFileException("Volume not found"); @@ -282,14 +293,13 @@ public override void Execute(SharedObjects shared) { shared.Cpu.StartCompileStopwatch(); var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager }; - string filePath = shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume) + "/" + fileName; // add this program to the address space of the parent program, // or to a file to save: if (justCompiling) { - List compileParts = shared.ScriptHandler.Compile(filePath, 1, fileContent.String, string.Empty, options); - VolumeFile volumeFile = shared.VolumeMgr.CurrentVolume.Save(fileNameOut, new FileContent(compileParts)); - if (volumeFile == null) + List compileParts = shared.ScriptHandler.Compile(path, 1, fileContent.String, String.Empty, options); + VolumeFile written = volume.Save(outPath, new FileContent(compileParts)); + if (written == null) { throw new KOSFileException("Can't save compiled file: not enough space or access forbidden"); } @@ -301,11 +311,11 @@ public override void Execute(SharedObjects shared) if (fileContent.Category == FileCategory.KSM) { string prefix = programContext.Program.Count.ToString(); - parts = fileContent.AsParts(filePath, prefix); + parts = fileContent.AsParts(path, prefix); } else { - parts = shared.ScriptHandler.Compile(filePath, 1, fileContent.String, "program", options); + parts = shared.ScriptHandler.Compile(path, 1, fileContent.String, "program", options); } int programAddress = programContext.AddObjectParts(parts); // push the entry point address of the new program onto the stack @@ -343,26 +353,31 @@ public class FunctionLogFile : FunctionBase { public override void Execute(SharedObjects shared) { - string fileName = PopValueAssert(shared, true).ToString(); - string expressionResult = PopValueAssert(shared).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); + string toAppend = PopValueAssert(shared).ToString(); AssertArgBottomAndConsume(shared); if (shared.VolumeMgr != null) { - Volume volume = shared.VolumeMgr.CurrentVolume; - if (volume != null) - { - VolumeFile volumeFile = volume.OpenOrCreate(fileName); - - if (volumeFile == null || !volumeFile.WriteLn(expressionResult)) - { - throw new KOSFileException("Can't append to file: not enough space or access forbidden"); - } + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + VolumeItem volumeItem = volume.Open(path) as VolumeFile; + VolumeFile volumeFile = null; + + if (volumeItem == null) { + volumeFile = volume.CreateFile(path); + } else if (volumeItem is VolumeDirectory) { + throw new KOSFileException("Can't append to file: path points to a directory"); + } else { + volumeFile = volumeItem as VolumeFile; } - else + + if (!volumeFile.WriteLn(toAppend)) { - throw new KOSFileException("Volume not found"); + throw new KOSFileException("Can't append to file: not enough space or access forbidden"); } + } } } @@ -424,7 +439,7 @@ public override void Execute(SharedObjects shared) ReturnValue = sb.ToString(); } } - + [Function("warpto")] public class WarpTo : FunctionBase { @@ -446,7 +461,7 @@ public override void Execute(SharedObjects shared) TimeWarp.fetch.WarpTo(ut); } } - + [Function("processor")] public class FunctionProcessor : FunctionBase { @@ -514,7 +529,7 @@ public override void Execute(SharedObjects shared) AssertArgBottomAndConsume(shared); } } - + [Function("makebuiltindelegate")] public class MakeBuiltinDelegate : FunctionBase { @@ -522,7 +537,7 @@ public override void Execute(SharedObjects shared) { string name = PopValueAssert(shared).ToString(); AssertArgBottomAndConsume(shared); - + ReturnValue = new BuiltinDelegate(shared.Cpu, name); } } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 262f156a5..be92eaa69 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -6,6 +6,9 @@ using kOS.Safe.Utilities; using kOS.Serialization; using System; +using KSP.IO; +using kOS.Safe; +using kOS.Safe.Exceptions; namespace kOS.Function { @@ -37,129 +40,125 @@ public class FunctionEdit : FunctionBase { public override void Execute(SharedObjects shared) { - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - if (shared.VolumeMgr != null) - { - Volume vol = shared.VolumeMgr.CurrentVolume; - var volumeFile = vol.OpenOrCreate(fileName); - shared.Window.OpenPopupEditor(vol, volumeFile.Name); - } + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume vol = shared.VolumeMgr.GetVolumeFromPath(path); + shared.Window.OpenPopupEditor(vol, path); + } } - [Function("copy")] - public class FunctionCopy : FunctionBase + [Function("cd")] + public class FunctionCd : FunctionBase { public override void Execute(SharedObjects shared) { - object volumeId = PopValueAssert(shared, true); - string direction = PopValueAssert(shared).ToString(); - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - SafeHouse.Logger.Log(string.Format("FunctionCopy: Volume: {0} Direction: {1} Filename: {2}", volumeId, direction, fileName)); + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); - if (shared.VolumeMgr != null) + VolumeDirectory directory = volume.Open(path) as VolumeDirectory; + + if (directory == null) { - Volume origin; - Volume destination; + throw new KOSException("Invalid directory: " + pathString); + } - if (direction == "from") - { - origin = volumeId is Volume ? volumeId as Volume : shared.VolumeMgr.GetVolume(volumeId); - destination = shared.VolumeMgr.CurrentVolume; - } - else + shared.VolumeMgr.CurrentDirectory = directory; + } + } + + public abstract class FunctionWithCopy : FunctionBase + { + protected void Copy(IVolumeManager volumeManager, GlobalPath sourcePath, GlobalPath destinationPath) + { + Volume sourceVolume = volumeManager.GetVolumeFromPath(sourcePath); + Volume destinationVolume = volumeManager.GetVolumeFromPath(destinationPath); + + VolumeItem source = sourceVolume.Open(sourcePath); + VolumeItem destination = destinationVolume.Open(sourcePath); + + if (source == null) + { + throw new KOSPersistenceException("Path does not exist: " + sourcePath); + } + + if (source is VolumeDirectory) + { + if (destination is VolumeFile) { - origin = shared.VolumeMgr.CurrentVolume; - destination = volumeId is Volume ? volumeId as Volume : shared.VolumeMgr.GetVolume(volumeId); + throw new KOSPersistenceException("Can't copy directory into a file"); } - if (origin != null && destination != null) + CopyDirectory(source as VolumeDirectory, destination as VolumeDirectory); + } else + { + if (destination is VolumeFile || destination == null) { - if (origin == destination) - { - throw new Exception("Cannot copy from a volume to the same volume."); - } - - VolumeFile file = origin.Open(fileName); - if (file != null) - { - if (destination.Save(file.Name, file.ReadAll()) == null) - { - throw new Exception("File copy failed"); - } - } - else - { - throw new Exception(string.Format("File '{0}' not found", fileName)); - } - } - else + CopyFile(source as VolumeFile, destinationPath); + } else { - throw new Exception(string.Format("Volume {0} not found", volumeId)); + CopyFileToDirectory(source as VolumeFile, destination as VolumeDirectory); } } } + + protected void CopyDirectory(VolumeDirectory volumeDirectory, VolumeDirectory volumeDirectory2) + { + throw new NotImplementedException(); + } + + protected void CopyFile(VolumeFile volumeFile, GlobalPath destinationPath) + { + throw new NotImplementedException(); + } + + protected void CopyFileToDirectory(VolumeFile volumeFile, VolumeDirectory volumeDirectory) + { + throw new NotImplementedException(); + } } - [Function("rename")] - public class FunctionRename : FunctionBase + [Function("copy")] + public class FunctionCopy : FunctionWithCopy { public override void Execute(SharedObjects shared) { - string newName = PopValueAssert(shared, true).ToString(); - // old file name or, when we're renaming a volume, the old volume name or Volume instance - object volumeIdOrOldName = PopValueAssert(shared, true); - string objectToRename = PopValueAssert(shared).ToString(); + string destinationPathString = PopValueAssert(shared, true).ToString(); + string sourcePathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - if (shared.VolumeMgr != null) - { - if (objectToRename == "file") - { - Volume volume = shared.VolumeMgr.CurrentVolume; - if (volume != null) - { - if (volume.Open(newName) == null) - { - if (!volume.RenameFile(volumeIdOrOldName.ToString(), newName)) - { - throw new Exception(string.Format("File '{0}' not found", volumeIdOrOldName)); - } - } - else - { - throw new Exception(string.Format("File '{0}' already exists.", newName)); - } - } - else - { - throw new Exception("Volume not found"); - } - } - else - { - Volume volume = volumeIdOrOldName is Volume ? volumeIdOrOldName as Volume : shared.VolumeMgr.GetVolume(volumeIdOrOldName); - if (volume != null) - { - if (volume.Renameable) - { - volume.Name = newName; - } - else - { - throw new Exception("Volume cannot be renamed"); - } - } - else - { - throw new Exception("Volume not found"); - } - } - } + SafeHouse.Logger.Log(string.Format("FunctionCopy: {0} {1}", sourcePathString, destinationPathString)); + + GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); + GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); + + Copy(shared.VolumeMgr, sourcePath, destinationPath); + } + } + + [Function("move")] + public class FunctionMove : FunctionWithCopy + { + public override void Execute(SharedObjects shared) + { + string destinationPathString = PopValueAssert(shared, true).ToString(); + string sourcePathString = PopValueAssert(shared, true).ToString(); + AssertArgBottomAndConsume(shared); + + SafeHouse.Logger.Log(string.Format("FunctionCopy: {0} {1}", sourcePathString, destinationPathString)); + + GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); + GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); + + Copy(shared.VolumeMgr, sourcePath, destinationPath); + + Volume sourceVolume = shared.VolumeMgr.GetVolumeFromPath(sourcePath); + sourceVolume.Delete(sourcePath); } } @@ -168,25 +167,16 @@ public class FunctionDelete : FunctionBase { public override void Execute(SharedObjects shared) { - object volumeId = PopValueAssert(shared, true); - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - if (shared.VolumeMgr != null) - { - Volume volume = volumeId != null ? (volumeId is Volume ? volumeId as Volume : shared.VolumeMgr.GetVolume(volumeId)) : shared.VolumeMgr.CurrentVolume; + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + volume.Delete(path); - if (volume != null) - { - if (!volume.Delete(fileName)) - { - throw new Exception(string.Format("File '{0}' not found", fileName)); - } - } - else - { - throw new Exception("Volume not found"); - } + if (!volume.Delete(path)) + { + throw new Exception(string.Format("Could not remove '{0}'", path)); } } } @@ -196,7 +186,7 @@ public class FunctionWriteJson : FunctionBase { public override void Execute(SharedObjects shared) { - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); SerializableStructure serialized = PopValueAssert(shared, true) as SerializableStructure; AssertArgBottomAndConsume(shared); @@ -209,10 +199,10 @@ public override void Execute(SharedObjects shared) FileContent fileContent = new FileContent(serializedString); - if (shared.VolumeMgr != null) - { - shared.VolumeMgr.CurrentVolume.Save(fileName, fileContent); - } + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + ReturnValue = volume.Save(path, fileContent); } } @@ -221,14 +211,17 @@ public class FunctionReadJson : FunctionBase { public override void Execute(SharedObjects shared) { - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - VolumeFile volumeFile = shared.VolumeMgr.CurrentVolume.Open(fileName); + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + VolumeFile volumeFile = volume.Open(pathString) as VolumeFile; if (volumeFile == null) { - throw new KOSException("File does not exist: " + fileName); + throw new KOSException("File does not exist: " + path); } Structure read = new SerializationMgr(shared).Deserialize(volumeFile.ReadAll().String, JsonFormatter.ReaderInstance) as SerializableStructure; @@ -241,10 +234,13 @@ public class FunctionExists : FunctionBase { public override void Execute(SharedObjects shared) { - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - ReturnValue = shared.VolumeMgr.CurrentVolume.Exists(fileName); + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + ReturnValue = volume.Exists(path); } } @@ -253,17 +249,20 @@ public class FunctionOpen : FunctionBase { public override void Execute(SharedObjects shared) { - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - VolumeFile volumeFile = shared.VolumeMgr.CurrentVolume.Open(fileName); + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); - if (volumeFile == null) + VolumeItem volumeItem = volume.Open(path); + + if (volumeItem == null) { - throw new KOSException("File does not exist: " + fileName); + throw new KOSException("File or directory does not exist: " + path); } - ReturnValue = volumeFile; + ReturnValue = volumeItem; } } @@ -272,12 +271,32 @@ public class FunctionCreate : FunctionBase { public override void Execute(SharedObjects shared) { - string fileName = PopValueAssert(shared, true).ToString(); + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - VolumeFile volumeFile = shared.VolumeMgr.CurrentVolume.Create(fileName); + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + VolumeFile volumeFile = volume.CreateFile(path); ReturnValue = volumeFile; } } + + [Function("createdir")] + public class FunctionCreateDirectory : FunctionBase + { + public override void Execute(SharedObjects shared) + { + string pathString = PopValueAssert(shared, true).ToString(); + AssertArgBottomAndConsume(shared); + + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + VolumeDirectory volumeDirectory = volume.CreateDirectory(path); + + ReturnValue = volumeDirectory; + } + } } \ No newline at end of file diff --git a/src/kOS/Function/PrintList.cs b/src/kOS/Function/PrintList.cs index f22c770e1..8fe7e3508 100644 --- a/src/kOS/Function/PrintList.cs +++ b/src/kOS/Function/PrintList.cs @@ -10,6 +10,8 @@ using System.Text; using kOS.Utilities; using Math = System.Math; +using System.Linq; +using kOS.Safe; namespace kOS.Function { @@ -85,23 +87,23 @@ private kList GetFileList(Safe.SharedObjects shared) list.AddColumn("Name", 30, ColumnAlignment.Left); list.AddColumn("Size", 7, ColumnAlignment.Right); - if (shared.VolumeMgr != null) - { - Volume volume = shared.VolumeMgr.CurrentVolume; - if (volume != null) - { - list.Title = "Volume " + shared.VolumeMgr.GetVolumeBestIdentifier(volume); + list.Title = shared.VolumeMgr.CurrentDirectory.Path.ToString(); - foreach (KeyValuePair pair in volume.FileList) - { - list.AddItem(pair.Key, pair.Value.Size); - } + IOrderedEnumerable items = shared.VolumeMgr.CurrentDirectory.ListAsLexicon().Values.Cast().OrderBy(i => i.Name); - long freeSpace = volume.FreeSpace; - list.Footer = "Free space remaining: " + (freeSpace != Volume.INFINITE_CAPACITY ? freeSpace.ToString() : " infinite"); - } + foreach (VolumeDirectory info in items.OfType()) + { + list.AddItem(info.Name, ""); } + foreach (VolumeFile info in items.OfType()) + { + list.AddItem(info.Name, info.Size); + } + + long freeSpace = shared.VolumeMgr.CurrentVolume.FreeSpace; + list.Footer = "Free space remaining: " + (freeSpace != Volume.INFINITE_CAPACITY ? freeSpace.ToString() : " infinite"); + return list; } diff --git a/src/kOS/KSPLogger.cs b/src/kOS/KSPLogger.cs index ae52875f6..20b4ff174 100644 --- a/src/kOS/KSPLogger.cs +++ b/src/kOS/KSPLogger.cs @@ -6,6 +6,7 @@ using kOS.Safe.Compilation; using kOS.Safe.Persistence; using kOS.Safe.Encapsulation; +using kOS.Screen; namespace kOS { @@ -106,21 +107,21 @@ private string TraceLog() if (index > 0) { Opcode prevOpcode = Shared.Cpu.GetOpcodeAt(trace[index-1]); - if (prevOpcode.SourceName == thisOpcode.SourceName && + if (prevOpcode.SourcePath.Equals(thisOpcode.SourcePath) && prevOpcode.SourceLine == thisOpcode.SourceLine) { continue; } } - string textLine = (thisOpcode is OpcodeEOF) ? "<<--EOF" : GetSourceLine(thisOpcode.SourceName, thisOpcode.SourceLine); + string textLine = (thisOpcode is OpcodeEOF) ? "<<--EOF" : GetSourceLine(thisOpcode.SourcePath, thisOpcode.SourceLine); if (msg.Length == 0) msg += "At "; else msg += "Called from "; - msg += (thisOpcode is OpcodeEOF) ? "interpreter" : BuildLocationString(thisOpcode.SourceName, thisOpcode.SourceLine); + msg += (thisOpcode is OpcodeEOF) ? "interpreter" : BuildLocationString(thisOpcode.SourcePath, thisOpcode.SourceLine); msg += "\n" + textLine + "\n"; int useColumn = (thisOpcode is OpcodeEOF) ? 1 : thisOpcode.SourceColumn; @@ -142,7 +143,7 @@ private string TraceLog() } } - private string BuildLocationString(string source, int line) + private string BuildLocationString(GlobalPath path, int line) { if (line < 0) { @@ -151,21 +152,17 @@ private string BuildLocationString(string source, int line) // to recalculate LOCK THROTTLE and LOCK STEERING each time there's an Update). return "(kOS built-in Update)"; } - if (string.IsNullOrEmpty(source)) + if (path == GlobalPath.EMPTY) { return "<>"; } - string[] splitParts = source.Split('/'); - - if (splitParts.Length <= 1) - return string.Format("{0}, line {1}", source, line); - if (source == "interpreter history") + if (path == Interpreter.InterpreterHistory) return string.Format("interpreter line {0}", line); - return string.Format("{0} on {1}, line {2}", splitParts[1], splitParts[0], line); + return string.Format("{0}, line {2}", path, line); } - private string GetSourceLine(string filePath, int line) + private string GetSourceLine(GlobalPath path, int line) { string returnVal = "(Can't show source line)"; if (line < 0) @@ -176,37 +173,18 @@ private string GetSourceLine(string filePath, int line) return "<>"; } - if (string.IsNullOrEmpty(filePath)) + if (path == GlobalPath.EMPTY) { return "<>"; } - string[] pathParts = filePath.Split('/'); - string fileName = pathParts.Last(); - Volume vol; - if (pathParts.Length > 1) - { - string volName = pathParts.First(); - if (Regex.IsMatch(volName, @"^\d+$")) - { - // If the volume is a number, then get the volume by integer id. - int volNum; - int.TryParse(volName, out volNum); - vol = Shared.VolumeMgr.GetVolume(volNum); - } - else - { - // If the volume is not a number, then get the volume by name string. - vol = Shared.VolumeMgr.GetVolume(volName); - } - } - else - vol = Shared.VolumeMgr.CurrentVolume; + + Volume vol = Shared.VolumeMgr.GetVolumeFromPath(path); - if (fileName == "interpreter history") + if (path == Interpreter.InterpreterHistory) return Shared.Interpreter.GetCommandHistoryAbsolute(line); - VolumeFile file = vol.Open(fileName); - if (file!=null) + VolumeFile file = vol.Open(path) as VolumeFile; + if (file != null) { if (file.ReadAll().Category == FileCategory.KSM) return "<>"; @@ -219,6 +197,5 @@ private string GetSourceLine(string filePath, int line) } return returnVal; } - } -} +} \ No newline at end of file diff --git a/src/kOS/Module/kOSProcessor.cs b/src/kOS/Module/kOSProcessor.cs index 6759cd0d4..b4fb60cce 100644 --- a/src/kOS/Module/kOSProcessor.cs +++ b/src/kOS/Module/kOSProcessor.cs @@ -113,6 +113,15 @@ public kOSProcessor() ProcessorMode = ProcessorModes.READY; } + public VolumePath BootFilePath { + get { + return VolumePath.FromString(bootFile); + } + set { + bootFile = value.ToString(); + } + } + [KSPEvent(guiActive = true, guiName = "Open Terminal", category = "skip_delay;")] public void Activate() { @@ -277,13 +286,13 @@ private void InitUI() var bootFiles = new List(); - var temp = new Archive(); - var files = temp.FileList; + var temp = new Archive(SafeHouse.ArchiveFolder); + var files = temp.Root.List(); var maxchoice = 0; bootFiles.Add("None"); - foreach (KeyValuePair pair in files) + foreach (KeyValuePair pair in files) { - if (!pair.Key.StartsWith("boot", StringComparison.InvariantCultureIgnoreCase)) continue; + if (!(pair.Value is VolumeFile) || !pair.Key.StartsWith("boot", StringComparison.InvariantCultureIgnoreCase)) continue; bootFiles.Add(pair.Key); maxchoice++; } @@ -352,13 +361,14 @@ public void InitObjects() // populate it with the boot file, but only if using a new disk and in PRELAUNCH situation: if (vessel.situation == Vessel.Situations.PRELAUNCH && bootFile != "None" && !SafeHouse.Config.StartOnArchive) { - var bootVolumeFile = archive.Open(bootFile); + var bootVolumeFile = archive.Open(bootFile) as VolumeFile; if (bootVolumeFile != null) { + VolumePath bootFilePath = VolumePath.FromString(bootFile); FileContent content = bootVolumeFile.ReadAll(); - if (HardDisk.IsRoomFor(bootFile, content)) + if (HardDisk.IsRoomFor(bootFilePath, content)) { - HardDisk.Save(bootFile, content); + HardDisk.Save(bootFilePath, content); } else { diff --git a/src/kOS/Persistence/PersistenceExtensions.cs b/src/kOS/Persistence/PersistenceExtensions.cs index dfc24f057..3c0adc5ae 100644 --- a/src/kOS/Persistence/PersistenceExtensions.cs +++ b/src/kOS/Persistence/PersistenceExtensions.cs @@ -7,9 +7,15 @@ namespace kOS.Persistence { + + /// + /// Persistence extensions needed to store Harddisks in KSP saves files. Perhaps one day we could use serialization instead + /// and simplify all of this (and make it unit testable too). + /// public static class PersistenceExtensions { - private const string FILENAME_VALUE_STRING = "filename"; + private const string FilenameValueString = "filename"; + private const string DirnameValueString = "dirname"; public static Harddisk ToHardDisk(this ConfigNode configNode) { @@ -22,32 +28,61 @@ public static Harddisk ToHardDisk(this ConfigNode configNode) if (configNode.HasValue("volumeName")) toReturn.Name = configNode.GetValue("volumeName"); + toReturn.RootHarddiskDirectory = configNode.ToHarddiskDirectory(toReturn, VolumePath.EMPTY); + + return toReturn; + } + + private static HarddiskDirectory ToHarddiskDirectory(this ConfigNode configNode, Harddisk harddisk, VolumePath parentPath) + { + string dirName = configNode.GetValue(DirnameValueString); + HarddiskDirectory directory = new HarddiskDirectory(harddisk, VolumePath.FromString(dirName, parentPath)); + foreach (ConfigNode fileNode in configNode.GetNodes("file")) { - toReturn.Save(fileNode.ToHarddiskFile(toReturn)); + directory.CreateFile(fileNode.GetValue(FilenameValueString), fileNode.ToHarddiskFile(harddisk, directory)); } - return toReturn; + + foreach (ConfigNode dirNode in configNode.GetNodes("directory")) + { + directory.CreateDirectory(dirName, dirNode.ToHarddiskDirectory(harddisk, VolumePath.FromString(dirName, parentPath))); + } + + return directory; } - public static HarddiskFile ToHarddiskFile(this ConfigNode configNode, Harddisk harddisk) + public static FileContent ToHarddiskFile(this ConfigNode configNode, Harddisk harddisk, HarddiskDirectory directory) { - var filename = configNode.GetValue(FILENAME_VALUE_STRING); - - FileContent fileContent = Decode(configNode.GetValue("line")); - harddisk.Save(filename, fileContent); - return new HarddiskFile(harddisk, filename); + return Decode(configNode.GetValue("line")); } public static ConfigNode ToConfigNode(this Harddisk harddisk, string nodeName) { - var node = new ConfigNode(nodeName); + var node = harddisk.RootHarddiskDirectory.ToConfigNode(nodeName); node.AddValue("capacity", harddisk.Capacity); node.AddValue("volumeName", harddisk.Name); - foreach (VolumeFile volumeFile in harddisk.FileList.Values) + return node; + } + + public static ConfigNode ToConfigNode(this HarddiskDirectory directory, string nodeName) + { + ConfigNode node = new ConfigNode(nodeName); + node.AddValue(DirnameValueString, directory.Name); + + foreach (VolumeItem item in directory) { - var file = (HarddiskFile) volumeFile; - node.AddNode(file.ToConfigNode("file")); + if (item is HarddiskDirectory) + { + HarddiskDirectory dir = item as HarddiskDirectory; + node.AddNode(dir.ToConfigNode("directory")); + } + + if (item is HarddiskFile) + { + HarddiskFile file = item as HarddiskFile; + node.AddNode(file.ToConfigNode("file")); + } } return node; @@ -56,7 +91,7 @@ public static ConfigNode ToConfigNode(this Harddisk harddisk, string nodeName) public static ConfigNode ToConfigNode(this HarddiskFile file, string nodeName) { var node = new ConfigNode(nodeName); - node.AddValue(FILENAME_VALUE_STRING, file.Name); + node.AddValue(FilenameValueString, file.Name); FileContent content = file.ReadAll(); diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Interpreter.cs index fe59b0ad0..c963df3ce 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Interpreter.cs @@ -6,11 +6,14 @@ using kOS.Safe.Execution; using kOS.Safe.Screen; using kOS.Safe.UserIO; +using kOS.Safe.Persistence; namespace kOS.Screen { public class Interpreter : TextEditor, IInterpreter { + public static GlobalPath InterpreterHistory = GlobalPath.FromString("interpreterhistory:"); + private readonly List commandHistory = new List(); private int commandHistoryIndex; private bool locked; @@ -139,7 +142,7 @@ protected void CompileCommand(string commandText) IsCalledFromRun = false }; - List commandParts = Shared.ScriptHandler.Compile("interpreter history", commandHistoryIndex, commandText, "interpreter", options); + List commandParts = Shared.ScriptHandler.Compile(InterpreterHistory, commandHistoryIndex, commandText, "interpreter", options); if (commandParts == null) return; var interpreterContext = ((CPU)Shared.Cpu).GetInterpreterContext(); diff --git a/src/kOS/Screen/KOSTextEditPopup.cs b/src/kOS/Screen/KOSTextEditPopup.cs index 214298cc9..0d388ea4b 100644 --- a/src/kOS/Screen/KOSTextEditPopup.cs +++ b/src/kOS/Screen/KOSTextEditPopup.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using UnityEngine; +using kOS.Safe.Exceptions; namespace kOS.Screen { @@ -25,8 +26,8 @@ public class KOSTextEditPopup : KOSManagedWindow private Rect reloadCoords; private Rect resizeButtonCoords; private TermWindow term; // The terminal that this popup is attached to. - private string fileName = ""; - private string loadingFileName = ""; + private GlobalPath filePath; + private GlobalPath loadingPath; private Volume volume; private Volume loadingVolume; private string contents = ""; @@ -61,14 +62,14 @@ public void Awake() urlGetter.LoadImageIntoTexture(resizeImage); } - public void AttachTo(TermWindow termWindow, Volume attachVolume, string attachFileName = "") + public void AttachTo(TermWindow termWindow, Volume attachVolume, GlobalPath path) { term = termWindow; WindowRect = new Rect(0, 0, 470, 280); // will be resized and moved in onGUI. frozen = false; loadingVolume = attachVolume; - loadingFileName = attachFileName; - LoadContents(attachVolume, attachFileName); + loadingPath = path; + LoadContents(attachVolume, path); } public bool Contains(Vector2 posAbs) @@ -144,7 +145,7 @@ protected void ExitEditor() public void SaveContents() { - if (volume.Save(fileName, new FileContent(contents)) == null) + if (volume.Save(filePath, new FileContent(contents)) == null) { // For some reason the normal trap that prints exceptions on // the terminal doesn't work here in this part of the code, @@ -153,7 +154,7 @@ public void SaveContents() throw new Exception("File Save Failed from Text Editor."); } isDirty = false; - term.Print("[Saved changes to " + fileName + "]"); + term.Print("[Saved changes to " + filePath + "]"); } protected void ReloadContents() @@ -164,19 +165,19 @@ protected void ReloadContents() DelegateLoadContents(this); } - public void LoadContents(Volume vol, string fName) + public void LoadContents(Volume vol, GlobalPath path) { if (isDirty) { Freeze(true); InvokeDirtySaveLoadDialog(); loadingVolume = vol; - loadingFileName = fName; + loadingPath = path; } else { loadingVolume = vol; - loadingFileName = fName; + loadingPath = path; DelegateLoadContents(this); } } @@ -192,7 +193,7 @@ protected void InvokeDirtySaveExitDialog() choices.Add("Cancel"); actions.Add(DelegateCancel); - dialog.Invoke(this, "\"" + fileName + "\" has been edited. Save it before exiting?", choices, actions); + dialog.Invoke(this, "\"" + filePath + "\" has been edited. Save it before exiting?", choices, actions); } protected void InvokeDirtySaveLoadDialog() @@ -206,7 +207,7 @@ protected void InvokeDirtySaveLoadDialog() choices.Add("Cancel"); actions.Add(DelegateCancel); - dialog.Invoke(this, "\"" + fileName + "\" has been edited. Save before loading \"" + loadingFileName + "\"?", choices, actions); + dialog.Invoke(this, "\"" + filePath + "\" has been edited. Save before loading \"" + loadingPath.Name + "\"?", choices, actions); } protected void InvokeReloadConfirmDialog() @@ -218,7 +219,7 @@ protected void InvokeReloadConfirmDialog() choices.Add("No"); actions.Add(DelegateCancel); - dialog.Invoke(this, "\"" + fileName + "\" has been edited. Throw away changes and reload?", choices, actions); + dialog.Invoke(this, "\"" + filePath + "\" has been edited. Throw away changes and reload?", choices, actions); } protected static void DelegateSaveExit(KOSTextEditPopup me) @@ -242,18 +243,22 @@ protected static void DelegateSaveThenLoad(KOSTextEditPopup me) protected static void DelegateLoadContents(KOSTextEditPopup me) { - me.volume = me.loadingVolume; - me.fileName = me.loadingFileName; - VolumeFile file = me.volume.Open(me.fileName); - if (file == null) + VolumeItem item = me.loadingVolume.Open(me.filePath); + if (item == null) { me.term.Print("[New File]"); me.contents = ""; - } - else + } else if (item is VolumeFile) { + VolumeFile file = item as VolumeFile; me.contents = file.ReadAll().String; + } else + { + throw new KOSPersistenceException("Path '" + me.filePath + "' points to a directory"); } + + me.volume = me.loadingVolume; + me.filePath = me.loadingPath; me.isDirty = false; } @@ -515,8 +520,8 @@ public Rect GetRect() protected string BuildTitle() { if (volume.Name.Length > 0) - return fileName + " on " + volume.Name; - return fileName + " on local volume"; // Don't know which number because no link to VolumeManager from this class. + return filePath + " on " + volume.Name; + return filePath + " on local volume"; // Don't know which number because no link to VolumeManager from this class. } } } \ No newline at end of file diff --git a/src/kOS/Screen/TermWindow.cs b/src/kOS/Screen/TermWindow.cs index 7823e465f..7b0628fa4 100644 --- a/src/kOS/Screen/TermWindow.cs +++ b/src/kOS/Screen/TermWindow.cs @@ -194,9 +194,9 @@ public void LoadTexture(String relativePath, ref Texture2D targetTexture) if (imageLoader.isDone && imageLoader.size == 0) allTexturesFound = false; } - public void OpenPopupEditor( Volume v, string fName ) + public void OpenPopupEditor(Volume v, GlobalPath path) { - popupEditor.AttachTo(this, v, fName ); + popupEditor.AttachTo(this, v, path); popupEditor.Open(); } From fad654a96838f14508578313e07a1f98e5cf6328 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 01:03:10 +0200 Subject: [PATCH 03/48] PathValue structure --- .../Communication/MessageQueueTest.cs | 4 +- .../Persistence/GlobalPathTest.cs | 30 ++++++++ .../Persistence/VolumePathTest.cs | 2 + .../Serialization/FormatterTest.cs | 4 +- .../Serialization/JSONFormatterTest.cs | 1 - .../Serialization/TerminalFormatterTest.cs | 2 +- src/kOS.Safe/Encapsulation/EnumerableValue.cs | 2 +- src/kOS.Safe/Encapsulation/Lexicon.cs | 2 +- src/kOS.Safe/Encapsulation/TerminalStruct.cs | 6 +- src/kOS.Safe/Execution/CPU.cs | 5 +- src/kOS.Safe/Persistence/FileContent.cs | 4 +- src/kOS.Safe/Persistence/GlobalPath.cs | 46 ++++++++++- src/kOS.Safe/Persistence/PathValue.cs | 76 +++++++++++++++++++ src/kOS.Safe/Persistence/VolumeItem.cs | 3 +- src/kOS.Safe/Persistence/VolumePath.cs | 19 ++++- ...{SharedObjects.cs => SafeSharedObjects.cs} | 4 +- .../Serialization/IHasSafeSharedObjects.cs | 18 +++++ .../Serialization/SafeSerializationMgr.cs | 17 ++++- src/kOS.Safe/kOS.Safe.csproj | 4 +- src/kOS/Communication/InterVesselManager.cs | 4 +- src/kOS/Communication/Message.cs | 2 +- src/kOS/Function/PrintList.cs | 4 +- src/kOS/Serialization/IHasSharedObjects.cs | 7 ++ src/kOS/Serialization/SerializationMgr.cs | 12 ++- src/kOS/SharedObjects.cs | 2 +- 25 files changed, 240 insertions(+), 40 deletions(-) create mode 100644 src/kOS.Safe/Persistence/PathValue.cs rename src/kOS.Safe/{SharedObjects.cs => SafeSharedObjects.cs} (89%) create mode 100644 src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs diff --git a/src/kOS.Safe.Test/Communication/MessageQueueTest.cs b/src/kOS.Safe.Test/Communication/MessageQueueTest.cs index 4840211fd..c1b346415 100644 --- a/src/kOS.Safe.Test/Communication/MessageQueueTest.cs +++ b/src/kOS.Safe.Test/Communication/MessageQueueTest.cs @@ -141,9 +141,9 @@ public void CanHandleSerializableStructures() { Lexicon lex = new Lexicon(); lex.Add(new StringValue("key1"), new StringValue("value1")); - queue.Push(new BaseMessage(new SafeSerializationMgr().Dump(lex), 0, 0)); + queue.Push(new BaseMessage(new SafeSerializationMgr(null).Dump(lex), 0, 0)); - Lexicon read = new SafeSerializationMgr().CreateFromDump(queue.Pop().Content as Dump) as Lexicon; + Lexicon read = new SafeSerializationMgr(null).CreateFromDump(queue.Pop().Content as Dump) as Lexicon; Assert.AreEqual(new StringValue("value1"), read[new StringValue("key1")]); } diff --git a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs index 1234c9591..0fce65564 100644 --- a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs +++ b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs @@ -67,5 +67,35 @@ public void CanHandleGlobalPathWithLessThanZeroDepth() { GlobalPath.FromString("othervolume:/test/../../"); } + + [Test] + public void CanChangeExtension() + { + GlobalPath path = GlobalPath.FromString("othervolume:123"); + GlobalPath newPath = path.ChangeExtension("txt"); + Assert.AreEqual("othervolume", newPath.VolumeId); + Assert.AreEqual(1, newPath.Length); + Assert.AreEqual("123.txt", newPath.Name); + + path = GlobalPath.FromString("othervolume:/dir/file.jpg"); + newPath = path.ChangeExtension("txt"); + Assert.AreEqual("othervolume", newPath.VolumeId); + Assert.AreEqual(2, newPath.Length); + Assert.AreEqual("file.txt", newPath.Name); + + path = GlobalPath.FromString("othervolume:/dir/complex.file.name."); + newPath = path.ChangeExtension("txt"); + Assert.AreEqual("othervolume", newPath.VolumeId); + Assert.AreEqual(2, newPath.Length); + Assert.AreEqual("complex.file.name.txt", newPath.Name); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanHandleChangingExtensionOfRootPaths() + { + GlobalPath path = GlobalPath.FromString("othervolume:"); + path.ChangeExtension("txt"); + } } } \ No newline at end of file diff --git a/src/kOS.Safe.Test/Persistence/VolumePathTest.cs b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs index ad774a9a5..27f1f6f6d 100644 --- a/src/kOS.Safe.Test/Persistence/VolumePathTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs @@ -20,6 +20,8 @@ public void CanHandleRootPath() VolumePath path = VolumePath.FromString("/"); Assert.AreEqual(0, path.Length); Assert.AreEqual(0, path.Depth); + Assert.IsNull(path.Name); + Assert.IsNull(path.Extension); } [Test] diff --git a/src/kOS.Safe.Test/Serialization/FormatterTest.cs b/src/kOS.Safe.Test/Serialization/FormatterTest.cs index 9e7c07480..5e33b8137 100644 --- a/src/kOS.Safe.Test/Serialization/FormatterTest.cs +++ b/src/kOS.Safe.Test/Serialization/FormatterTest.cs @@ -98,12 +98,12 @@ public void CanSerializeQueues() private string Serialize(IDumper o) { - return new SafeSerializationMgr().Serialize(o, FormatWriter); + return new SafeSerializationMgr(null).Serialize(o, FormatWriter); } private IDumper Deserialize(string s) { - return new SafeSerializationMgr().Deserialize(s, FormatReader); + return new SafeSerializationMgr(null).Deserialize(s, FormatReader); } } } diff --git a/src/kOS.Safe.Test/Serialization/JSONFormatterTest.cs b/src/kOS.Safe.Test/Serialization/JSONFormatterTest.cs index 6013ee566..c1b2c5b50 100644 --- a/src/kOS.Safe.Test/Serialization/JSONFormatterTest.cs +++ b/src/kOS.Safe.Test/Serialization/JSONFormatterTest.cs @@ -23,6 +23,5 @@ protected override IFormatWriter FormatWriter return JsonFormatter.WriterInstance; } } - } } diff --git a/src/kOS.Safe.Test/Serialization/TerminalFormatterTest.cs b/src/kOS.Safe.Test/Serialization/TerminalFormatterTest.cs index 1d141aa02..b4de8ca94 100644 --- a/src/kOS.Safe.Test/Serialization/TerminalFormatterTest.cs +++ b/src/kOS.Safe.Test/Serialization/TerminalFormatterTest.cs @@ -36,7 +36,7 @@ public void CanSerializeLexicons() private string Serialize(SerializableStructure o) { - return new SafeSerializationMgr().Serialize(o, TerminalFormatter.Instance, false); + return new SafeSerializationMgr(null).Serialize(o, TerminalFormatter.Instance, false); } } } diff --git a/src/kOS.Safe/Encapsulation/EnumerableValue.cs b/src/kOS.Safe/Encapsulation/EnumerableValue.cs index 6a39083ae..f4e52123c 100644 --- a/src/kOS.Safe/Encapsulation/EnumerableValue.cs +++ b/src/kOS.Safe/Encapsulation/EnumerableValue.cs @@ -44,7 +44,7 @@ public int Count() public override string ToString() { - return new SafeSerializationMgr().ToString(this); + return new SafeSerializationMgr(null).ToString(this); } public override Dump Dump() diff --git a/src/kOS.Safe/Encapsulation/Lexicon.cs b/src/kOS.Safe/Encapsulation/Lexicon.cs index 38d4f054a..0e0d41805 100644 --- a/src/kOS.Safe/Encapsulation/Lexicon.cs +++ b/src/kOS.Safe/Encapsulation/Lexicon.cs @@ -266,7 +266,7 @@ public void SetIndex(int index, Structure value) public override string ToString() { - return new SafeSerializationMgr().ToString(this); + return new SafeSerializationMgr(null).ToString(this); } public override Dump Dump() diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index b48a01413..a292efe14 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -5,7 +5,7 @@ namespace kOS.Safe.Encapsulation [kOS.Safe.Utilities.KOSNomenclature("Terminal")] public class TerminalStruct : Structure { - private readonly SharedObjects shared; + private readonly SafeSharedObjects shared; // Some sanity values to prevent the terminal display from getting garbled up: // They may have to change after experimentation. @@ -23,14 +23,14 @@ public class TerminalStruct : Structure // // protected bool IsOpen { get { return shared.Window.IsOpen(); } set {if (value) shared.Window.Open(); else shared.Window.Close(); } } - public TerminalStruct(SharedObjects shared) + public TerminalStruct(SafeSharedObjects shared) { this.shared = shared; InitializeSuffixes(); } - protected internal SharedObjects Shared + protected internal SafeSharedObjects Shared { get { return shared; } } diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index d4ce221b9..f14ae1675 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -25,7 +25,8 @@ private enum Status private readonly VariableScope globalVariables; private Status currentStatus; private double currentTime; - private readonly SharedObjects shared; + private double timeWaitUntil; + private readonly SafeSharedObjects shared; private readonly List contexts; private ProgramContext currentContext; private VariableScope savedPointers; @@ -64,7 +65,7 @@ public int InstructionPointer public List ProfileResult { get; private set; } - public CPU(SharedObjects shared) + public CPU(SafeSharedObjects shared) { this.shared = shared; this.shared.Cpu = this; diff --git a/src/kOS.Safe/Persistence/FileContent.cs b/src/kOS.Safe/Persistence/FileContent.cs index a35ae36ed..bd36e0f8f 100644 --- a/src/kOS.Safe/Persistence/FileContent.cs +++ b/src/kOS.Safe/Persistence/FileContent.cs @@ -57,9 +57,7 @@ private void InitializeSuffixes() public override Dump Dump() { - Dump dump = new Dump { { DumpContent, PersistenceUtilities.EncodeBase64(Bytes) } }; - - return dump; + return new Dump { { DumpContent, PersistenceUtilities.EncodeBase64(Bytes) } }; } public override void LoadDump(Dump dump) diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs index a869e863d..152e4bee9 100644 --- a/src/kOS.Safe/Persistence/GlobalPath.cs +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -4,6 +4,7 @@ using kOS.Safe.Persistence; using kOS.Safe.Exceptions; using System.Linq; +using kOS.Safe.Encapsulation.Suffixes; namespace kOS.Safe.Persistence { @@ -21,12 +22,16 @@ public class GlobalPath : VolumePath public object VolumeId { get; private set; } - private GlobalPath(object volumeId, VolumePath path) : this(volumeId, new List(path.Segments)) + private GlobalPath(object volumeId) { + VolumeId = ValidateVolumeId(volumeId); + } + private GlobalPath(object volumeId, VolumePath path) : this(volumeId, new List(path.Segments)) + { } - private GlobalPath(object volumeId, List segments) : base(new List(segments)) + private GlobalPath(object volumeId, IEnumerable segments) : base(segments) { VolumeId = ValidateVolumeId(volumeId); } @@ -67,11 +72,48 @@ public bool IsParent(GlobalPath path) return VolumeId.Equals(path.VolumeId) && base.IsParent(path); } + public GlobalPath RootPath() + { + return new GlobalPath(VolumeId); + } + public static GlobalPath FromVolumePath(VolumePath volumePath, Volume volume) { return new GlobalPath(volume.Name, new List(volumePath.Segments)); } + public GlobalPath ChangeExtension(string newExtension) + { + if (Segments.Count == 0) + { + throw new KOSInvalidPathException("This path points to the root directory, you can't change its extension", this.ToString()); + } + + string nameSegment = Segments.Last(); + List newSegments = new List(Segments); + newSegments.RemoveAt(newSegments.Count - 1); + var nameParts = new List(nameSegment.Split('.')); + + if (nameParts.Count() > 1) + { + nameParts.RemoveAt(nameParts.Count() - 1); + } + + nameParts = new List(nameParts); + nameParts.Add(newExtension); + + var newName = String.Join(".", nameParts.ToArray()); + + newSegments.Add(newName); + + return new GlobalPath(VolumeId, newSegments); + } + + public GlobalPath Combine(string[] segments) + { + return new GlobalPath(VolumeId, Segments.Concat(segments)); + } + /// /// Create a GlobalPath from a base path and a relative path. /// diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs new file mode 100644 index 000000000..545bddcc4 --- /dev/null +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -0,0 +1,76 @@ +using System; +using kOS.Safe.Serialization; +using kOS.Safe.Persistence; +using kOS.Safe.Encapsulation.Suffixes; +using kOS.Safe.Encapsulation; +using System.Linq; + +namespace kOS.Safe +{ + /// + /// Contains suffixes related to GlobalPath. + /// + /// This exists as a separate class because some of the suffixes require an instance of VolumeManager to work. I think + /// it would be counter-productive to pass around an instance of VolumeManager whenever we're dealing with GlobalPath internally. + /// Instances of this class are on the other hand created only for the user. + /// + [kOS.Safe.Utilities.KOSNomenclature("Path")] + public class PathValue : SerializableStructure, IHasSafeSharedObjects + { + private const string DumpPath = "path"; + + public GlobalPath Path { get; private set; } + private SafeSharedObjects sharedObjects; + + public SafeSharedObjects Shared { + set { + sharedObjects = value; + } + } + + public PathValue(GlobalPath path, SafeSharedObjects sharedObjects) + { + Path = path; + this.sharedObjects = sharedObjects; + + InitializeSuffixes(); + } + + public PathValue FromPath(GlobalPath path) + { + return new PathValue(path, sharedObjects); + } + + public PathValue FromPath(VolumePath volumePath, Volume volume) + { + return new PathValue(GlobalPath.FromVolumePath(volumePath, volume), sharedObjects); + } + + private void InitializeSuffixes() + { + AddSuffix("VOLUME", new Suffix(() => sharedObjects.VolumeMgr.GetVolumeFromPath(Path))); + AddSuffix("PARENT", new Suffix(() => FromPath(Path.GetParent()))); + AddSuffix("ISPARENT", new OneArgsSuffix((p) => Path.IsParent(p.Path))); + AddSuffix("ROOT", new Suffix(() => FromPath(Path.RootPath()))); + AddSuffix("SEGMENTS", new Suffix(() => new ListValue(Path.Segments.Select((s) => (Structure)new StringValue(s))))); + AddSuffix("LENGTH", new Suffix(() => Path.Length)); + AddSuffix("DEPTH", new Suffix(() => Path.Depth)); + AddSuffix("NAME", new Suffix(() => Path.Name)); + AddSuffix("HASEXTENSION", new Suffix(() => string.IsNullOrEmpty(Path.Extension))); + AddSuffix("EXTENSION", new Suffix(() => Path.Extension)); + AddSuffix("CHANGEEXTENSION", new OneArgsSuffix((e) => FromPath(Path.ChangeExtension(e)))); + AddSuffix("COMBINE", new VarArgsSuffix((segments) => FromPath(Path.Combine(segments.Cast().ToArray())))); + } + + public override Dump Dump() + { + return new Dump { { DumpPath, Path.ToString() } }; + } + + public override void LoadDump(Dump dump) + { + Path = GlobalPath.FromString(dump[DumpPath] as string); + } + } +} + diff --git a/src/kOS.Safe/Persistence/VolumeItem.cs b/src/kOS.Safe/Persistence/VolumeItem.cs index ea3033fd2..921d6040e 100644 --- a/src/kOS.Safe/Persistence/VolumeItem.cs +++ b/src/kOS.Safe/Persistence/VolumeItem.cs @@ -22,8 +22,7 @@ public string Name public string Extension { get { - var fileParts = Name.Split('.'); - return fileParts.Count() > 1 ? fileParts.Last() : string.Empty; + return Path.Extension; } } diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs index 97f68cb8f..e4245704d 100644 --- a/src/kOS.Safe/Persistence/VolumePath.cs +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using kOS.Safe.Exceptions; +using kOS.Safe.Encapsulation; namespace kOS.Safe.Persistence { @@ -57,6 +58,18 @@ public string Name { } } + public string Extension { + get { + if (Name == null) + { + return null; + } + + var nameParts = Name.Split('.'); + return nameParts.Count() > 1 ? nameParts.Last() : string.Empty; + } + } + /// /// Gets this path's segments. /// @@ -114,14 +127,14 @@ protected static List GetSegmentsFromString(string pathString) return new List(segments); } - private VolumePath() + protected VolumePath() { this.Segments = new List(); } - protected VolumePath(List segments) + protected VolumePath(IEnumerable segments) { - this.Segments = segments; + this.Segments = new List(segments); Canonicalize(); } diff --git a/src/kOS.Safe/SharedObjects.cs b/src/kOS.Safe/SafeSharedObjects.cs similarity index 89% rename from src/kOS.Safe/SharedObjects.cs rename to src/kOS.Safe/SafeSharedObjects.cs index c6fdab116..1eeb413f2 100644 --- a/src/kOS.Safe/SharedObjects.cs +++ b/src/kOS.Safe/SafeSharedObjects.cs @@ -9,12 +9,12 @@ namespace kOS.Safe { - public class SharedObjects + public class SafeSharedObjects { public ICpu Cpu { get; set; } public IScreenBuffer Screen { get; set; } public IInterpreter Interpreter { get; set; } - public IBindingManager BindingMgr { get; set; } + public IBindingManager BindingMgr { get; set; } public Script ScriptHandler { get; set; } public ILogger Logger { get; set; } public IProcessor Processor { get; set; } diff --git a/src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs b/src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs new file mode 100644 index 000000000..962764196 --- /dev/null +++ b/src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs @@ -0,0 +1,18 @@ +using System; + +namespace kOS.Safe.Serialization +{ + /// + /// This exists so that we can keep some of the classes that depend on SharedObjects in kOS.Safe. + /// + /// kOS has 2 versions of SharedObjects, one from kOS and one from kOS.Safe. SerializationMgr will automatically supply an instance of + /// kOS.SharedObjects to any Structures that implements IHasSharedObjects during deserialization. However not all classes that are serializable + /// and require SharedObjects need the kOS version, some (for example GlobalPath) need only the lighter kOS.Safe version. SafeSerializationMgr + /// and SerializationMgr will both will both now supply an instance of kOS.Safe.SharedObjects to classes that implement this interface. + /// + public interface IHasSafeSharedObjects + { + SafeSharedObjects Shared { set; } + } +} + diff --git a/src/kOS.Safe/Serialization/SafeSerializationMgr.cs b/src/kOS.Safe/Serialization/SafeSerializationMgr.cs index 55efe33f4..33b15e907 100644 --- a/src/kOS.Safe/Serialization/SafeSerializationMgr.cs +++ b/src/kOS.Safe/Serialization/SafeSerializationMgr.cs @@ -12,6 +12,13 @@ public class SafeSerializationMgr public static string TYPE_KEY = "$type"; private static HashSet assemblies = new HashSet(); + private readonly SafeSharedObjects safeSharedObjects; + + public SafeSerializationMgr(SafeSharedObjects sharedObjects) + { + this.safeSharedObjects = sharedObjects; + } + public static void AddAssembly(string assembly) { assemblies.Add(assembly); @@ -131,7 +138,15 @@ public virtual IDumper CreateInstance(string typeFullName) } } - return Activator.CreateInstance(deserializedType) as IDumper; + IDumper instance = Activator.CreateInstance(deserializedType) as IDumper; + + if (instance is IHasSafeSharedObjects) + { + IHasSafeSharedObjects withSharedObjects = instance as IHasSafeSharedObjects; + withSharedObjects.Shared = safeSharedObjects; + } + + return instance; } public IDumper Deserialize(string input, IFormatReader formatter) diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index dff782c68..af77d417e 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -202,7 +202,6 @@ - @@ -249,6 +248,9 @@ + + + diff --git a/src/kOS/Communication/InterVesselManager.cs b/src/kOS/Communication/InterVesselManager.cs index 497e536aa..a5135bc62 100644 --- a/src/kOS/Communication/InterVesselManager.cs +++ b/src/kOS/Communication/InterVesselManager.cs @@ -43,7 +43,7 @@ public override void OnLoad(ConfigNode node) Dump queueDump = ConfigNodeFormatter.Instance.FromConfigNode(queueNode); - MessageQueue queue = new SafeSerializationMgr().CreateFromDump(queueDump) as MessageQueue; + MessageQueue queue = new SafeSerializationMgr(null).CreateFromDump(queueDump) as MessageQueue; if (queue.Count() > 0) { @@ -63,7 +63,7 @@ public override void OnSave(ConfigNode node) ConfigNode vesselEntry = new ConfigNode(VesselQueue); vesselEntry.AddValue(Id, id); - ConfigNode queueNode = ConfigNodeFormatter.Instance.ToConfigNode(new SafeSerializationMgr().Dump(vesselQueues[id])); + ConfigNode queueNode = ConfigNodeFormatter.Instance.ToConfigNode(new SafeSerializationMgr(null).Dump(vesselQueues[id])); queueNode.name = MessageQueue; vesselEntry.AddNode(queueNode); diff --git a/src/kOS/Communication/Message.cs b/src/kOS/Communication/Message.cs index c86474619..19b41a046 100644 --- a/src/kOS/Communication/Message.cs +++ b/src/kOS/Communication/Message.cs @@ -20,7 +20,7 @@ public static Message Create(object content, double sentAt, double receivedAt, V { if (content is SerializableStructure) { - return new Message(new SafeSerializationMgr().Dump(content as SerializableStructure), sentAt, receivedAt, sender); + return new Message(new SafeSerializationMgr(null).Dump(content as SerializableStructure), sentAt, receivedAt, sender); } else if (content is PrimitiveStructure) { return new Message(content as PrimitiveStructure, sentAt, receivedAt, sender); diff --git a/src/kOS/Function/PrintList.cs b/src/kOS/Function/PrintList.cs index 8fe7e3508..68411e781 100644 --- a/src/kOS/Function/PrintList.cs +++ b/src/kOS/Function/PrintList.cs @@ -81,7 +81,7 @@ public override void Execute(SharedObjects shared) } } - private kList GetFileList(Safe.SharedObjects shared) + private kList GetFileList(Safe.SafeSharedObjects shared) { var list = new kList(); list.AddColumn("Name", 30, ColumnAlignment.Left); @@ -107,7 +107,7 @@ private kList GetFileList(Safe.SharedObjects shared) return list; } - private kList GetVolumeList(Safe.SharedObjects shared) + private kList GetVolumeList(Safe.SafeSharedObjects shared) { var list = new kList { Title = "Volumes" }; list.AddColumn("ID", 6, ColumnAlignment.Left); diff --git a/src/kOS/Serialization/IHasSharedObjects.cs b/src/kOS/Serialization/IHasSharedObjects.cs index a378df8d4..b298a6320 100644 --- a/src/kOS/Serialization/IHasSharedObjects.cs +++ b/src/kOS/Serialization/IHasSharedObjects.cs @@ -1,7 +1,14 @@ using kOS.Safe.Encapsulation; +using kOS.Safe.Serialization; namespace kOS.Serialization { + /// + /// Indicates that a class need an instance of kOS.SharedObjects to function. + /// + /// SerializationMgr will provide an instance of SharedObjects during deserialization. + /// + /// public interface IHasSharedObjects { SharedObjects Shared { set; } diff --git a/src/kOS/Serialization/SerializationMgr.cs b/src/kOS/Serialization/SerializationMgr.cs index 61d5ee622..e0652a7e0 100644 --- a/src/kOS/Serialization/SerializationMgr.cs +++ b/src/kOS/Serialization/SerializationMgr.cs @@ -12,17 +12,11 @@ public class SerializationMgr : SafeSerializationMgr { private readonly SharedObjects sharedObjects; - static SerializationMgr() + public SerializationMgr(SharedObjects sharedObjects) : base(sharedObjects) { SafeSerializationMgr.AddAssembly(typeof(SerializationMgr).Assembly.FullName); } - public SerializationMgr(SharedObjects sharedObjects) - { - this.sharedObjects = sharedObjects; - } - - public override IDumper CreateAndLoad(string typeFullName, Dump data) { IDumper instance = base.CreateInstance(typeFullName); @@ -31,6 +25,10 @@ public override IDumper CreateAndLoad(string typeFullName, Dump data) { IHasSharedObjects withSharedObjects = instance as IHasSharedObjects; withSharedObjects.Shared = sharedObjects; + } else if (instance is IHasSafeSharedObjects) + { + IHasSafeSharedObjects withSharedObjects = instance as IHasSafeSharedObjects; + withSharedObjects.Shared = sharedObjects; } if (instance != null) diff --git a/src/kOS/SharedObjects.cs b/src/kOS/SharedObjects.cs index d24271a92..ba1954b82 100644 --- a/src/kOS/SharedObjects.cs +++ b/src/kOS/SharedObjects.cs @@ -6,7 +6,7 @@ namespace kOS { - public class SharedObjects : Safe.SharedObjects + public class SharedObjects : Safe.SafeSharedObjects { public Vessel Vessel { get; set; } public ProcessorManager ProcessorMgr { get; set; } From bf763e22a6e09e2be1eff3cbeac5e6f6c2a7687d Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 15:18:28 +0200 Subject: [PATCH 04/48] path() command --- .../Persistence/GlobalPathTest.cs | 4 +-- .../Serialization/IHasSafeSharedObjects.cs | 2 +- src/kOS/Function/Persistence.cs | 25 ++++++++++++++++++- src/kOS/Serialization/IHasSharedObjects.cs | 1 - 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs index 0fce65564..6f747672e 100644 --- a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs +++ b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs @@ -83,11 +83,11 @@ public void CanChangeExtension() Assert.AreEqual(2, newPath.Length); Assert.AreEqual("file.txt", newPath.Name); - path = GlobalPath.FromString("othervolume:/dir/complex.file.name."); + path = GlobalPath.FromString("othervolume:/dir/complex.file..name.."); newPath = path.ChangeExtension("txt"); Assert.AreEqual("othervolume", newPath.VolumeId); Assert.AreEqual(2, newPath.Length); - Assert.AreEqual("complex.file.name.txt", newPath.Name); + Assert.AreEqual("complex.file..name..txt", newPath.Name); } [Test] diff --git a/src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs b/src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs index 962764196..b3bc8933e 100644 --- a/src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs +++ b/src/kOS.Safe/Serialization/IHasSafeSharedObjects.cs @@ -8,7 +8,7 @@ namespace kOS.Safe.Serialization /// kOS has 2 versions of SharedObjects, one from kOS and one from kOS.Safe. SerializationMgr will automatically supply an instance of /// kOS.SharedObjects to any Structures that implements IHasSharedObjects during deserialization. However not all classes that are serializable /// and require SharedObjects need the kOS version, some (for example GlobalPath) need only the lighter kOS.Safe version. SafeSerializationMgr - /// and SerializationMgr will both will both now supply an instance of kOS.Safe.SharedObjects to classes that implement this interface. + /// and SerializationMgr will both now supply an instance of kOS.Safe.SharedObjects to classes that implement this interface. /// public interface IHasSafeSharedObjects { diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index be92eaa69..7706dc3e6 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -8,10 +8,33 @@ using System; using KSP.IO; using kOS.Safe; -using kOS.Safe.Exceptions; namespace kOS.Function { + [Function("path")] + public class FunctionPath : FunctionBase + { + public override void Execute(SharedObjects shared) + { + int remaining = CountRemainingArgs(shared); + + GlobalPath path; + + if (remaining == 0) + { + path = GlobalPath.FromVolumePath(shared.VolumeMgr.CurrentDirectory.Path, shared.VolumeMgr.CurrentVolume); + } else + { + string pathString = PopValueAssert(shared, true).ToString(); + path = shared.VolumeMgr.GlobalPathFromString(pathString); + } + + AssertArgBottomAndConsume(shared); + + ReturnValue = new PathValue(path, shared); + } + } + [Function("switch")] public class FunctionSwitch : FunctionBase { diff --git a/src/kOS/Serialization/IHasSharedObjects.cs b/src/kOS/Serialization/IHasSharedObjects.cs index b298a6320..2262740e4 100644 --- a/src/kOS/Serialization/IHasSharedObjects.cs +++ b/src/kOS/Serialization/IHasSharedObjects.cs @@ -14,4 +14,3 @@ public interface IHasSharedObjects SharedObjects Shared { set; } } } - From 6e42e0f3e118d2f01a435863db9f7256bd67b680 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 16:16:18 +0200 Subject: [PATCH 05/48] scriptpath() command --- src/kOS/Function/Persistence.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 7706dc3e6..393abc182 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -8,6 +8,7 @@ using System; using KSP.IO; using kOS.Safe; +using kOS.Safe.Compilation; namespace kOS.Function { @@ -35,6 +36,20 @@ public override void Execute(SharedObjects shared) } } + [Function("scriptpath")] + public class FunctionScriptPath : FunctionBase + { + public override void Execute(SharedObjects shared) + { + AssertArgBottomAndConsume(shared); + + int currentOpcode = shared.Cpu.GetCallTrace()[0]; + Opcode opcode = shared.Cpu.GetOpcodeAt(currentOpcode); + + ReturnValue = new PathValue(opcode.SourcePath, shared); + } + } + [Function("switch")] public class FunctionSwitch : FunctionBase { From baf23115e5dc118e5144e5afbe345ee9896ad38c Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 16:29:19 +0200 Subject: [PATCH 06/48] Initialize VolumeItem suffixes --- src/kOS.Safe/Persistence/VolumeItem.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/kOS.Safe/Persistence/VolumeItem.cs b/src/kOS.Safe/Persistence/VolumeItem.cs index 921d6040e..53baba6ff 100644 --- a/src/kOS.Safe/Persistence/VolumeItem.cs +++ b/src/kOS.Safe/Persistence/VolumeItem.cs @@ -30,12 +30,16 @@ public VolumeItem(Volume volume, VolumePath path) { Volume = volume; Path = path; + + InitializeSuffixes(); } public VolumeItem(Volume volume, VolumePath parentPath, String name) { Volume = volume; Path = VolumePath.FromString(name, parentPath); + + InitializeSuffixes(); } private void InitializeSuffixes() From 039c6e54fd2ae50ab3871fce50fd4cc6bfddd751 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 22:03:34 +0200 Subject: [PATCH 07/48] Make Volume:NAME settable --- src/kOS.Safe/Persistence/Volume.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index d7056e8b6..881a377c0 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -173,7 +173,7 @@ private void InitializeVolumeSuffixes() { AddSuffix("FREESPACE" , new Suffix(() => FreeSpace)); AddSuffix("CAPACITY" , new Suffix(() => Capacity)); - AddSuffix("NAME" , new Suffix(() => Name)); + AddSuffix("NAME" , new SetSuffix(() => Name, (newName) => Name = newName)); AddSuffix("RENAMEABLE" , new Suffix(() => Renameable)); AddSuffix("POWERREQUIREMENT" , new Suffix(() => RequiredPower())); From 3b02c7b6ce053ac103d170294fa1cc4dbf90b4aa Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 22:04:32 +0200 Subject: [PATCH 08/48] Change how create file/directory handles conflicts --- src/kOS.Safe.Test/Persistence/VolumeTest.cs | 13 ++++++++++++- src/kOS.Safe/Persistence/Archive.cs | 14 ++++++++++++-- src/kOS.Safe/Persistence/Harddisk.cs | 9 ++++++++- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 14 +------------- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs index 21ccbdf8b..ee98539ac 100644 --- a/src/kOS.Safe.Test/Persistence/VolumeTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -75,7 +75,8 @@ public void CanCreateSubdirectories() } [Test] - public void CanCreateDirectoryOverExistingDirectory() + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenCreatingDirectoryOverExistingDirectory() { string parent = "/parent1"; string dir = parent + "/sub1"; @@ -161,6 +162,16 @@ public void CanCreateFiles() Assert.IsInstanceOf(dir.List()["sub3"]); } + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenCreatingFileOverExistingFile() + { + string parent = "/parent1"; + string file = parent + "/file"; + + TestVolume.CreateFile(VolumePath.FromString(file)); + TestVolume.CreateFile(VolumePath.FromString(file)); + } [Test] [ExpectedException(typeof(KOSPersistenceException))] diff --git a/src/kOS.Safe/Persistence/Archive.cs b/src/kOS.Safe/Persistence/Archive.cs index 3cf385683..72b08236d 100644 --- a/src/kOS.Safe/Persistence/Archive.cs +++ b/src/kOS.Safe/Persistence/Archive.cs @@ -61,7 +61,7 @@ public override VolumeItem Open(VolumePath path, bool ksmDefault = false) var fileSystemInfo = Search(path, ksmDefault); if (fileSystemInfo == null) { - return null; + throw new KOSPersistenceException("Could not open path: " + path);; } else if (fileSystemInfo is FileInfo) { VolumePath filePath = VolumePath.FromString(fileSystemInfo.FullName.Substring(ArchiveFolder.Length)); @@ -81,12 +81,17 @@ public override VolumeDirectory CreateDirectory(VolumePath path) { string archivePath = GetArchivePath(path); + if (Directory.Exists(archivePath)) + { + throw new KOSPersistenceException("Already exists: " + path); + } + try { Directory.CreateDirectory(archivePath); } catch (IOException) { - throw new KOSPersistenceException("Already exists: " + path); + throw new KOSPersistenceException("Could not create directory: " + path); } return new ArchiveDirectory(this, path); @@ -96,6 +101,11 @@ public override VolumeFile CreateFile(VolumePath path) { string archivePath = GetArchivePath(path); + if (File.Exists(archivePath)) + { + throw new KOSPersistenceException("Already exists: " + path); + } + Directory.CreateDirectory(GetArchivePath(path.GetParent())); try { diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index 3f505eeaf..c95e78dcd 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -40,7 +40,14 @@ public override VolumeItem Open(VolumePath path, bool ksmDefault = false) { HarddiskDirectory directory = ParentDirectoryForPath(path); - return directory.Open(path.Name, ksmDefault); + VolumeItem result = directory.Open(path.Name, ksmDefault); + + if (result == null) + { + throw new KOSPersistenceException("Could not open path: " + path);; + } + + return result; } public override VolumeDirectory CreateDirectory(VolumePath path) diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index 0365ce752..e99e410a5 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -51,19 +51,7 @@ public HarddiskFile CreateFile(string name, FileContent fileContent) public HarddiskDirectory CreateDirectory(string name) { - try - { - return CreateDirectory(name, new HarddiskDirectory(Volume as Harddisk, VolumePath.FromString(name, Path))); - } catch (KOSPersistenceException e) - { - if (items[name] is HarddiskDirectory) - { - return items[name] as HarddiskDirectory; - } else - { - throw e; - } - } + return CreateDirectory(name, new HarddiskDirectory(Volume as Harddisk, VolumePath.FromString(name, Path))); } public HarddiskDirectory CreateDirectory(string name, HarddiskDirectory directory) From 84c86c4fef79348cc6d0a0f951d75dd18c959070 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 22:05:39 +0200 Subject: [PATCH 09/48] Add deprecation messages to old rename/delete/copy syntax --- src/kOS.Safe/Compilation/KS/Compiler.cs | 21 +++++-- src/kOS.Safe/Compilation/Opcode.cs | 2 +- .../KOSAtmosphereDeprecationException.cs | 2 +- .../Exceptions/KOSDeprecationException.cs | 2 +- .../KOSPatchesDeprecationException.cs | 2 +- src/kOS/Function/Persistence.cs | 56 +++++++++++++++++++ src/kOS/Suffixed/BodyAtmosphere.cs | 2 +- src/kOS/Suffixed/Part/DockingPortValue.cs | 6 +- src/kOS/Suffixed/VesselTarget.cs | 4 +- 9 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index be706a224..2bbb54cc0 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -2954,7 +2954,7 @@ private void VisitCompileStatement(ParseNode node) } AddOpcode(new OpcodeCall("load()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. - } + } private void VisitSwitchStatement(ParseNode node) { @@ -2974,7 +2974,7 @@ private void VisitCopyStatement(ParseNode node) AddOpcode(new OpcodePush(node.Nodes[2].Token.Type == TokenType.FROM ? "from" : "to")); VisitNode(node.Nodes[3]); - AddOpcode(new OpcodeCall("copy()")); + AddOpcode(new OpcodeCall("copy_deprecated()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -2984,21 +2984,32 @@ private void VisitRenameStatement(ParseNode node) int oldNameIndex = 2; int newNameIndex = 4; + bool renameFile = false; + AddOpcode(new OpcodePush(new KOSArgMarkerType())); if (node.Nodes.Count == 5) { oldNameIndex--; newNameIndex--; AddOpcode(new OpcodePush("file")); + renameFile = true; } else { - AddOpcode(new OpcodePush(node.Nodes[1].Token.Type == TokenType.FILE ? "file" : "volume")); + renameFile = node.Nodes[1].Token.Type == TokenType.FILE; + AddOpcode(new OpcodePush(renameFile ? "file" : "volume")); } VisitNode(node.Nodes[oldNameIndex]); VisitNode(node.Nodes[newNameIndex]); - AddOpcode(new OpcodeCall("rename()")); + + if (renameFile) + { + AddOpcode(new OpcodeCall("rename_file_deprecated()")); + } else + { + AddOpcode(new OpcodeCall("rename_volume_deprecated()")); + } AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } @@ -3013,7 +3024,7 @@ private void VisitDeleteStatement(ParseNode node) else AddOpcode(new OpcodePush(null)); - AddOpcode(new OpcodeCall("delete()")); + AddOpcode(new OpcodeCall("delete_deprecated()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 9aa8aa5c6..9f4e6053c 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -652,7 +652,7 @@ public override void Execute(ICpu cpu) } else { - throw new KOSDeprecationException("0.17","UNSET ALL", "", ""); + throw new KOSDeprecationException("0.17","UNSET ALL", ""); } } } diff --git a/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs b/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs index 247834d9a..c53eeb687 100644 --- a/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs +++ b/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs @@ -2,7 +2,7 @@ namespace kOS.Safe.Exceptions { public class KOSAtmosphereDeprecationException : KOSDeprecationException { - public KOSAtmosphereDeprecationException(string version, string oldUsage, string newUsage, string url) : base(version, oldUsage, newUsage, url) + public KOSAtmosphereDeprecationException(string version, string oldUsage, string newUsage) : base(version, oldUsage, newUsage) { } diff --git a/src/kOS.Safe/Exceptions/KOSDeprecationException.cs b/src/kOS.Safe/Exceptions/KOSDeprecationException.cs index 5a55ebba2..9ca6c0fa1 100644 --- a/src/kOS.Safe/Exceptions/KOSDeprecationException.cs +++ b/src/kOS.Safe/Exceptions/KOSDeprecationException.cs @@ -6,7 +6,7 @@ public class KOSDeprecationException : KOSException { protected static string TerseMessageFmt = "As of kOS {0}, {1} is obsolete and has been replaced with {2}"; - public KOSDeprecationException(string version, string oldUsage,string newUsage, string url) : + public KOSDeprecationException(string version, string oldUsage,string newUsage) : base( String.Format(TerseMessageFmt, version, oldUsage, newUsage) ) { } diff --git a/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs b/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs index f5aa5b5e1..ab9b3ac9c 100644 --- a/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs +++ b/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs @@ -8,7 +8,7 @@ public class KOSPatchesDeprecationException : KOSDeprecationException protected static string Url { get{return "TODO for v0.15 - Go back and fill in after docs are updated";}} public KOSPatchesDeprecationException() : - base(Version, OldUsage, NewUsage, Url) + base(Version, OldUsage, NewUsage) { } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 393abc182..490699a9e 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -12,6 +12,62 @@ namespace kOS.Function { + /* + * A couple of syntaxes from kRISC.tpg were deprecated when subdirectories where introduced. It will be possible to + * remove these function below as well any metions of delete/rename file/rename volume/copy from kRISC.tpg in the future. + */ + [Function("copy_deprecated")] + public class FunctionCopyDeprecated : FunctionWithCopy + { + public override void Execute(SharedObjects shared) + { + string destinationPathString = PopValueAssert(shared, true).ToString(); + string sourcePathString = PopValueAssert(shared, true).ToString(); + AssertArgBottomAndConsume(shared); + + throw new KOSDeprecationException("1.0.0", "`COPY FILENAME FROM VOLUMEID.` syntax", "`COPY(FROMPATH, TOPATH)`"); + } + } + + [Function("rename_file_deprecated")] + public class FunctionRenameFileDeprecated : FunctionWithCopy + { + public override void Execute(SharedObjects shared) + { + string destinationPathString = PopValueAssert(shared, true).ToString(); + string sourcePathString = PopValueAssert(shared, true).ToString(); + AssertArgBottomAndConsume(shared); + + throw new KOSDeprecationException("1.0.0", "`RENAME FILE OLDNAME TO NEWNAME.` syntax", "`MOVE(FROMPATH, TOPATH)`"); + } + } + + [Function("rename_volume_deprecated")] + public class FunctionRenameVolumeDeprecated : FunctionWithCopy + { + public override void Execute(SharedObjects shared) + { + string destinationPathString = PopValueAssert(shared, true).ToString(); + string sourcePathString = PopValueAssert(shared, true).ToString(); + AssertArgBottomAndConsume(shared); + + throw new KOSDeprecationException("1.0.0", "`RENAME VOLUME OLDNAME TO NEWNAME.` syntax", "`SET VOLUME:NAME TO NEWNAME.`"); + } + } + + [Function("delete_deprecated")] + public class FunctionDeleteDeprecated : FunctionWithCopy + { + public override void Execute(SharedObjects shared) + { + string destinationPathString = PopValueAssert(shared, true).ToString(); + string sourcePathString = PopValueAssert(shared, true).ToString(); + AssertArgBottomAndConsume(shared); + + throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETE(PATH)`"); + } + } + [Function("path")] public class FunctionPath : FunctionBase { diff --git a/src/kOS/Suffixed/BodyAtmosphere.cs b/src/kOS/Suffixed/BodyAtmosphere.cs index 64e4752f4..d19d49604 100644 --- a/src/kOS/Suffixed/BodyAtmosphere.cs +++ b/src/kOS/Suffixed/BodyAtmosphere.cs @@ -19,7 +19,7 @@ public BodyAtmosphere(CelestialBody celestialBody) AddSuffix("SEALEVELPRESSURE", new Suffix(()=> celestialBody.atmosphere ? celestialBody.atmospherePressureSeaLevel : 0)); AddSuffix("HEIGHT", new Suffix(()=> celestialBody.atmosphere ? celestialBody.atmosphereDepth : 0)); - AddSuffix("SCALE", new Suffix(() => { throw new KOSAtmosphereDeprecationException("0.17.2","SCALE","",string.Empty); })); + AddSuffix("SCALE", new Suffix(() => { throw new KOSAtmosphereDeprecationException("0.17.2", "SCALE", ""); })); } public override string ToString() diff --git a/src/kOS/Suffixed/Part/DockingPortValue.cs b/src/kOS/Suffixed/Part/DockingPortValue.cs index bf6834c11..c3941fa14 100644 --- a/src/kOS/Suffixed/Part/DockingPortValue.cs +++ b/src/kOS/Suffixed/Part/DockingPortValue.cs @@ -18,9 +18,9 @@ public DockingPortValue(ModuleDockingNode module, SharedObjects sharedObj) private void DockingInitializeSuffixes() { - AddSuffix("AQUIRERANGE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRERANGE", "ACQUIRERANGE", string.Empty); })); - AddSuffix("AQUIREFORCE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIREFORCE", "ACQUIREFORCE", string.Empty); })); - AddSuffix("AQUIRETORQUE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRETORQUE", "ACQUIRETORQUE", string.Empty); })); + AddSuffix("AQUIRERANGE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRERANGE", "ACQUIRERANGE"); })); + AddSuffix("AQUIREFORCE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIREFORCE", "ACQUIREFORCE"); })); + AddSuffix("AQUIRETORQUE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRETORQUE", "ACQUIRETORQUE"); })); AddSuffix("ACQUIRERANGE", new Suffix(() => module.acquireRange)); AddSuffix("ACQUIREFORCE", new Suffix(() => module.acquireForce)); AddSuffix("ACQUIRETORQUE", new Suffix(() => module.acquireTorque)); diff --git a/src/kOS/Suffixed/VesselTarget.cs b/src/kOS/Suffixed/VesselTarget.cs index a0e478556..4b6874e0d 100644 --- a/src/kOS/Suffixed/VesselTarget.cs +++ b/src/kOS/Suffixed/VesselTarget.cs @@ -435,12 +435,12 @@ private void InitializeSuffixes() AddSuffix("MASS", new Suffix(() => Vessel.GetTotalMass())); AddSuffix("VERTICALSPEED", new Suffix(() => Vessel.verticalSpeed)); AddSuffix("GROUNDSPEED", new Suffix(GetHorizontalSrfSpeed)); - AddSuffix("SURFACESPEED", new Suffix(() => { throw new KOSDeprecationException("0.18.0","SURFACESPEED","GROUNDSPEED",""); })); + AddSuffix("SURFACESPEED", new Suffix(() => { throw new KOSDeprecationException("0.18.0","SURFACESPEED","GROUNDSPEED"); })); AddSuffix("AIRSPEED", new Suffix(() => (Vessel.orbit.GetVel() - FlightGlobals.currentMainBody.getRFrmVel(Vessel.findWorldCenterOfMass())).magnitude, "the velocity of the vessel relative to the air")); AddSuffix(new[] { "SHIPNAME", "NAME" }, new SetSuffix(() => Vessel.vesselName, RenameVessel, "The KSP name for a craft, cannot be empty")); AddSuffix("TYPE", new SetSuffix(() => Vessel.vesselType.ToString(), RetypeVessel, "The Ship's KSP type (e.g. rover, base, probe)")); AddSuffix("SENSORS", new Suffix(() => new VesselSensors(Vessel))); - AddSuffix("TERMVELOCITY", new Suffix(() => { throw new KOSAtmosphereDeprecationException("17.2", "TERMVELOCITY", "", string.Empty); })); + AddSuffix("TERMVELOCITY", new Suffix(() => { throw new KOSAtmosphereDeprecationException("17.2", "TERMVELOCITY", ""); })); AddSuffix(new [] { "DYNAMICPRESSURE" , "Q"} , new Suffix(() => Vessel.dynamicPressurekPa * ConstantValue.KpaToAtm, "Dynamic Pressure in Atmospheres")); AddSuffix("LOADED", new Suffix(() => Vessel.loaded)); AddSuffix("UNPACKED", new Suffix(() => !Vessel.packed)); From 307a9096c9c8a23d925c477fd79a82dce50ed6f6 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 22:05:59 +0200 Subject: [PATCH 10/48] Add VolumeItem:ISFILE suffix --- src/kOS.Safe/Persistence/VolumeItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kOS.Safe/Persistence/VolumeItem.cs b/src/kOS.Safe/Persistence/VolumeItem.cs index 53baba6ff..4d8bce5f1 100644 --- a/src/kOS.Safe/Persistence/VolumeItem.cs +++ b/src/kOS.Safe/Persistence/VolumeItem.cs @@ -47,6 +47,7 @@ private void InitializeSuffixes() AddSuffix("NAME", new Suffix(() => Name)); AddSuffix("SIZE", new Suffix(() => new ScalarIntValue(Size))); AddSuffix("EXTENSION", new Suffix(() => Extension)); + AddSuffix("ISFILE", new Suffix(() => this is VolumeFile)); } public override string ToString() From 8cb2dd6d651daa3282ea6560de182065f33f2296 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 5 Apr 2016 22:06:11 +0200 Subject: [PATCH 11/48] Add volume() function --- src/kOS/Function/Persistence.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 490699a9e..c4ddbcdf9 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -92,6 +92,30 @@ public override void Execute(SharedObjects shared) } } + [Function("volume")] + public class FunctionVolume : FunctionBase + { + public override void Execute(SharedObjects shared) + { + int remaining = CountRemainingArgs(shared); + + Volume volume; + + if (remaining == 0) + { + volume = shared.VolumeMgr.CurrentVolume; + } else + { + object volumeId = PopValueAssert(shared, true); + volume = shared.VolumeMgr.GetVolume(volumeId); + } + + AssertArgBottomAndConsume(shared); + + ReturnValue = volume; + } + } + [Function("scriptpath")] public class FunctionScriptPath : FunctionBase { From 22c454e0099fe3c0a73f1e5ef0df7d1b98525022 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 00:17:26 +0200 Subject: [PATCH 12/48] Copy implemented --- src/kOS.Safe/Persistence/GlobalPath.cs | 2 +- src/kOS.Safe/Persistence/VolumePath.cs | 5 +++ src/kOS/Function/Persistence.cs | 53 ++++++++++++++++++++++---- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs index 152e4bee9..9dc6d53d3 100644 --- a/src/kOS.Safe/Persistence/GlobalPath.cs +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -109,7 +109,7 @@ public GlobalPath ChangeExtension(string newExtension) return new GlobalPath(VolumeId, newSegments); } - public GlobalPath Combine(string[] segments) + public GlobalPath Combine(params string[] segments) { return new GlobalPath(VolumeId, Segments.Concat(segments)); } diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs index e4245704d..ec42d4439 100644 --- a/src/kOS.Safe/Persistence/VolumePath.cs +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -88,6 +88,11 @@ public static Boolean IsAbsolute(String pathString) return pathString.StartsWith(PathSeparator.ToString()); } + public VolumePath Combine(params string[] segments) + { + return new VolumePath(Segments.Concat(segments)); + } + public static VolumePath FromString(string pathString, VolumePath basePath) { if (IsAbsolute(pathString)) diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index c4ddbcdf9..94c1436ed 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -9,6 +9,7 @@ using KSP.IO; using kOS.Safe; using kOS.Safe.Compilation; +using System.Collections.Generic; namespace kOS.Function { @@ -198,7 +199,7 @@ protected void Copy(IVolumeManager volumeManager, GlobalPath sourcePath, GlobalP Volume destinationVolume = volumeManager.GetVolumeFromPath(destinationPath); VolumeItem source = sourceVolume.Open(sourcePath); - VolumeItem destination = destinationVolume.Open(sourcePath); + VolumeItem destination = destinationVolume.Open(destinationPath); if (source == null) { @@ -212,12 +213,13 @@ protected void Copy(IVolumeManager volumeManager, GlobalPath sourcePath, GlobalP throw new KOSPersistenceException("Can't copy directory into a file"); } - CopyDirectory(source as VolumeDirectory, destination as VolumeDirectory); + CopyDirectory(volumeManager, sourcePath, destinationPath); } else { if (destination is VolumeFile || destination == null) { - CopyFile(source as VolumeFile, destinationPath); + Volume targetVolume = volumeManager.GetVolumeFromPath(destinationPath); + CopyFile(source as VolumeFile, destinationPath, targetVolume); } else { CopyFileToDirectory(source as VolumeFile, destination as VolumeDirectory); @@ -225,19 +227,54 @@ protected void Copy(IVolumeManager volumeManager, GlobalPath sourcePath, GlobalP } } - protected void CopyDirectory(VolumeDirectory volumeDirectory, VolumeDirectory volumeDirectory2) + protected void CopyDirectory(IVolumeManager volumeManager, GlobalPath sourcePath, GlobalPath destinationPath) { - throw new NotImplementedException(); + Volume sourceVolume = volumeManager.GetVolumeFromPath(sourcePath); + Volume destinationVolume = volumeManager.GetVolumeFromPath(destinationPath); + + VolumeDirectory source = sourceVolume.Open(sourcePath) as VolumeDirectory; + + VolumeDirectory destination; + + if (destinationVolume.Exists(destinationPath)) + { + destination = destinationVolume.CreateDirectory(destinationPath.Combine(sourcePath.Name)); + + if (destination == null) + { + throw new KOSException("Path was expected to point to a directory: " + destinationPath); + } + } else + { + destination = destinationVolume.CreateDirectory(destinationPath); + } + + foreach (KeyValuePair pair in source.List()) + { + if (pair.Value is VolumeDirectory) + { + CopyDirectory(volumeManager, sourcePath.Combine(pair.Key), destinationPath.Combine(pair.Key)); + } else + { + CopyFileToDirectory(pair.Value as VolumeFile, destination); + } + } } - protected void CopyFile(VolumeFile volumeFile, GlobalPath destinationPath) + protected void CopyFile(VolumeFile volumeFile, GlobalPath destinationPath, Volume targetVolume) { - throw new NotImplementedException(); + if (targetVolume.Save(destinationPath, volumeFile.ReadAll()) == null) + { + throw new KOSPersistenceException("Couldn not copy file"); + } } protected void CopyFileToDirectory(VolumeFile volumeFile, VolumeDirectory volumeDirectory) { - throw new NotImplementedException(); + if (volumeDirectory.Volume.Save(volumeDirectory.Path.Combine(volumeFile.Name), volumeFile.ReadAll()) == null) + { + throw new KOSPersistenceException("Couldn not copy file"); + } } } From 3361a6511adb5dfe668461777eb164e0cb9fa633 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 00:43:42 +0200 Subject: [PATCH 13/48] Harddisk persistence fix --- src/kOS/Persistence/PersistenceExtensions.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/kOS/Persistence/PersistenceExtensions.cs b/src/kOS/Persistence/PersistenceExtensions.cs index 3c0adc5ae..b493e5f98 100644 --- a/src/kOS/Persistence/PersistenceExtensions.cs +++ b/src/kOS/Persistence/PersistenceExtensions.cs @@ -33,10 +33,9 @@ public static Harddisk ToHardDisk(this ConfigNode configNode) return toReturn; } - private static HarddiskDirectory ToHarddiskDirectory(this ConfigNode configNode, Harddisk harddisk, VolumePath parentPath) + private static HarddiskDirectory ToHarddiskDirectory(this ConfigNode configNode, Harddisk harddisk, VolumePath path) { - string dirName = configNode.GetValue(DirnameValueString); - HarddiskDirectory directory = new HarddiskDirectory(harddisk, VolumePath.FromString(dirName, parentPath)); + HarddiskDirectory directory = new HarddiskDirectory(harddisk, path); foreach (ConfigNode fileNode in configNode.GetNodes("file")) { @@ -45,7 +44,9 @@ private static HarddiskDirectory ToHarddiskDirectory(this ConfigNode configNode, foreach (ConfigNode dirNode in configNode.GetNodes("directory")) { - directory.CreateDirectory(dirName, dirNode.ToHarddiskDirectory(harddisk, VolumePath.FromString(dirName, parentPath))); + string dirName = dirNode.GetValue(DirnameValueString); + + directory.CreateDirectory(dirName, dirNode.ToHarddiskDirectory(harddisk, VolumePath.FromString(dirName, path))); } return directory; From 1096094e0b505012fb945d252e57cb9de754eaf8 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 02:44:01 +0200 Subject: [PATCH 14/48] Bugfix in VolumeManager --- src/kOS.Safe/Persistence/VolumeManager.cs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index 2f88bd954..3bc5436b0 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -8,16 +8,8 @@ namespace kOS.Safe.Persistence public class VolumeManager : IVolumeManager { private readonly Dictionary volumes; - public virtual Volume CurrentVolume { get; private set; } - public VolumeDirectory CurrentDirectory { get - { - return CurrentDirectory; - } - set { - CurrentDirectory = value; - CurrentVolume = value.Volume; - } - } + public virtual Volume CurrentVolume { get { return CurrentDirectory != null ? CurrentDirectory.Volume : null; } } + public VolumeDirectory CurrentDirectory { get; set; } private int lastId; public Dictionary Volumes { get { return volumes; } } @@ -26,7 +18,6 @@ public VolumeDirectory CurrentDirectory { get public VolumeManager() { volumes = new Dictionary(); - CurrentVolume = null; CurrentDirectory = null; } @@ -108,9 +99,9 @@ public void Add(Volume volume) { volumes.Add(lastId++, volume); - if (CurrentVolume == null) + if (CurrentDirectory == null) { - CurrentVolume = volumes[0]; + CurrentDirectory = volumes[0].Root; UpdateRequiredPower(); } } @@ -133,12 +124,12 @@ public void Remove(int id) { if (volumes.Count > 0) { - CurrentVolume = volumes[0]; + CurrentDirectory = volumes[0].Root; UpdateRequiredPower(); } else { - CurrentVolume = null; + CurrentDirectory = null; } } } @@ -146,7 +137,6 @@ public void Remove(int id) public void SwitchTo(Volume volume) { - CurrentVolume = volume; CurrentDirectory = volume.Root; UpdateRequiredPower(); } From 8cceb42c888f207b91dac685a519cd9dd6e89d02 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 02:46:13 +0200 Subject: [PATCH 15/48] Added missing type names. --- src/kOS.Safe/Persistence/ArchiveDirectory.cs | 1 + src/kOS.Safe/Persistence/HarddiskDirectory.cs | 1 + src/kOS.Safe/Persistence/VolumeDirectory.cs | 1 + src/kOS.Safe/Persistence/VolumeItem.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/kOS.Safe/Persistence/ArchiveDirectory.cs b/src/kOS.Safe/Persistence/ArchiveDirectory.cs index 05bb00a1a..5cdd66ad0 100644 --- a/src/kOS.Safe/Persistence/ArchiveDirectory.cs +++ b/src/kOS.Safe/Persistence/ArchiveDirectory.cs @@ -7,6 +7,7 @@ namespace kOS.Safe.Persistence { + [kOS.Safe.Utilities.KOSNomenclature("VolumeDirectory", KOSToCSharp = false)] public class ArchiveDirectory : VolumeDirectory { private Archive archive; diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index e99e410a5..b5384375d 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -8,6 +8,7 @@ namespace kOS.Safe.Persistence { + [kOS.Safe.Utilities.KOSNomenclature("VolumeFile", KOSToCSharp = false)] public class HarddiskDirectory : VolumeDirectory, IEnumerable { private Dictionary items; diff --git a/src/kOS.Safe/Persistence/VolumeDirectory.cs b/src/kOS.Safe/Persistence/VolumeDirectory.cs index add0d3def..9f4ce4cbc 100644 --- a/src/kOS.Safe/Persistence/VolumeDirectory.cs +++ b/src/kOS.Safe/Persistence/VolumeDirectory.cs @@ -6,6 +6,7 @@ namespace kOS.Safe { + [kOS.Safe.Utilities.KOSNomenclature("VolumeDirectory")] public abstract class VolumeDirectory : VolumeItem { public VolumeDirectory(Volume volume, VolumePath path) : base(volume, path) diff --git a/src/kOS.Safe/Persistence/VolumeItem.cs b/src/kOS.Safe/Persistence/VolumeItem.cs index 4d8bce5f1..d6337e879 100644 --- a/src/kOS.Safe/Persistence/VolumeItem.cs +++ b/src/kOS.Safe/Persistence/VolumeItem.cs @@ -6,6 +6,7 @@ namespace kOS.Safe.Persistence { + [kOS.Safe.Utilities.KOSNomenclature("VolumeItem")] public abstract class VolumeItem : Structure { public Volume Volume { get; set; } From 864e45c0cd9f25a3518a7b707dc778f1ff1a7e81 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 03:13:17 +0200 Subject: [PATCH 16/48] Bug fixes --- src/kOS.Safe/Persistence/Harddisk.cs | 4 +++ src/kOS.Safe/Persistence/PathValue.cs | 5 +++ src/kOS.Safe/Persistence/VolumePath.cs | 2 +- src/kOS/Function/Persistence.cs | 44 ++++++++++++++++---------- src/kOS/KSPLogger.cs | 4 +-- src/kOS/Module/kOSProcessor.cs | 2 +- 6 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index c95e78dcd..1272c1fe6 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -38,6 +38,10 @@ private HarddiskDirectory ParentDirectoryForPath(VolumePath path, bool create = public override VolumeItem Open(VolumePath path, bool ksmDefault = false) { + if (path.Depth == 0) { + return Root; + } + HarddiskDirectory directory = ParentDirectoryForPath(path); VolumeItem result = directory.Open(path.Name, ksmDefault); diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index 545bddcc4..661196108 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -71,6 +71,11 @@ public override void LoadDump(Dump dump) { Path = GlobalPath.FromString(dump[DumpPath] as string); } + + public override string ToString() + { + return Path.ToString(); + } } } diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs index ec42d4439..4f2244b63 100644 --- a/src/kOS.Safe/Persistence/VolumePath.cs +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -54,7 +54,7 @@ public bool PointsOutside { /// public string Name { get { - return Segments.Count > 0 ? Segments.Last() : null; + return Segments.Count > 0 ? Segments.Last() : string.Empty; } } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 94c1436ed..18dac97e6 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -22,8 +22,8 @@ public class FunctionCopyDeprecated : FunctionWithCopy { public override void Execute(SharedObjects shared) { - string destinationPathString = PopValueAssert(shared, true).ToString(); - string sourcePathString = PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); throw new KOSDeprecationException("1.0.0", "`COPY FILENAME FROM VOLUMEID.` syntax", "`COPY(FROMPATH, TOPATH)`"); @@ -35,8 +35,8 @@ public class FunctionRenameFileDeprecated : FunctionWithCopy { public override void Execute(SharedObjects shared) { - string destinationPathString = PopValueAssert(shared, true).ToString(); - string sourcePathString = PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); throw new KOSDeprecationException("1.0.0", "`RENAME FILE OLDNAME TO NEWNAME.` syntax", "`MOVE(FROMPATH, TOPATH)`"); @@ -48,8 +48,8 @@ public class FunctionRenameVolumeDeprecated : FunctionWithCopy { public override void Execute(SharedObjects shared) { - string destinationPathString = PopValueAssert(shared, true).ToString(); - string sourcePathString = PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); throw new KOSDeprecationException("1.0.0", "`RENAME VOLUME OLDNAME TO NEWNAME.` syntax", "`SET VOLUME:NAME TO NEWNAME.`"); @@ -61,8 +61,8 @@ public class FunctionDeleteDeprecated : FunctionWithCopy { public override void Execute(SharedObjects shared) { - string destinationPathString = PopValueAssert(shared, true).ToString(); - string sourcePathString = PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETE(PATH)`"); @@ -174,19 +174,31 @@ public class FunctionCd : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); - AssertArgBottomAndConsume(shared); - - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); - Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + int remaining = CountRemainingArgs(shared); - VolumeDirectory directory = volume.Open(path) as VolumeDirectory; + VolumeDirectory directory; - if (directory == null) + if (remaining == 0) + { + directory = shared.VolumeMgr.CurrentVolume.Root; + } else { - throw new KOSException("Invalid directory: " + pathString); + string pathString = PopValueAssert(shared, true).ToString(); + + GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); + + directory = volume.Open(path) as VolumeDirectory; + + if (directory == null) + { + throw new KOSException("Invalid directory: " + pathString); + } + } + AssertArgBottomAndConsume(shared); + shared.VolumeMgr.CurrentDirectory = directory; } } diff --git a/src/kOS/KSPLogger.cs b/src/kOS/KSPLogger.cs index 20b4ff174..a9d7fa1ec 100644 --- a/src/kOS/KSPLogger.cs +++ b/src/kOS/KSPLogger.cs @@ -178,11 +178,11 @@ private string GetSourceLine(GlobalPath path, int line) return "<>"; } - Volume vol = Shared.VolumeMgr.GetVolumeFromPath(path); - if (path == Interpreter.InterpreterHistory) return Shared.Interpreter.GetCommandHistoryAbsolute(line); + Volume vol = Shared.VolumeMgr.GetVolumeFromPath(path); + VolumeFile file = vol.Open(path) as VolumeFile; if (file != null) { diff --git a/src/kOS/Module/kOSProcessor.cs b/src/kOS/Module/kOSProcessor.cs index b4fb60cce..851eb7bd6 100644 --- a/src/kOS/Module/kOSProcessor.cs +++ b/src/kOS/Module/kOSProcessor.cs @@ -59,7 +59,7 @@ public string Tag private const int PROCESSOR_HARD_CAP = 655360; [KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Boot File"), UI_ChooseOption(scene = UI_Scene.Editor)] - public string bootFile = "boot.ks"; + public string bootFile = "/boot.ks"; [KSPField(isPersistant = true, guiName = "kOS Disk Space", guiActive = true)] public int diskSpace = 1024; From ee8201f6e10b229c8d3547dd44cacb1b72441c2c Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 13:43:26 +0200 Subject: [PATCH 17/48] Moar bugfixes. --- .../Persistence/GlobalPathTest.cs | 15 ++++++ .../Persistence/VolumePathTest.cs | 11 +++-- src/kOS.Safe.Test/Persistence/VolumeTest.cs | 8 +++- src/kOS.Safe/Compilation/KS/Compiler.cs | 4 +- src/kOS.Safe/Persistence/Archive.cs | 4 +- src/kOS.Safe/Persistence/GlobalPath.cs | 5 -- src/kOS.Safe/Persistence/Harddisk.cs | 5 -- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 2 +- src/kOS.Safe/Persistence/PathValue.cs | 7 ++- src/kOS.Safe/Persistence/Volume.cs | 4 +- src/kOS.Safe/Persistence/VolumePath.cs | 5 -- src/kOS/Function/Persistence.cs | 47 +++++++++++-------- src/kOS/KSPLogger.cs | 2 +- src/kOS/Screen/Interpreter.cs | 2 +- 14 files changed, 70 insertions(+), 51 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs index 6f747672e..71e6d020e 100644 --- a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs +++ b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs @@ -97,5 +97,20 @@ public void CanHandleChangingExtensionOfRootPaths() GlobalPath path = GlobalPath.FromString("othervolume:"); path.ChangeExtension("txt"); } + + [Test] + public void CanCombine() + { + GlobalPath path = GlobalPath.FromString("othervolume:123"); + GlobalPath newPath = path.Combine("456", "789"); + Assert.AreEqual("othervolume", newPath.VolumeId); + Assert.AreEqual(3, newPath.Length); + Assert.AreEqual("789", newPath.Name); + + newPath = path.Combine("..", "abc"); + Assert.AreEqual("othervolume", newPath.VolumeId); + Assert.AreEqual(1, newPath.Length); + Assert.AreEqual("abc", newPath.Name); + } } } \ No newline at end of file diff --git a/src/kOS.Safe.Test/Persistence/VolumePathTest.cs b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs index 27f1f6f6d..05ab2eb43 100644 --- a/src/kOS.Safe.Test/Persistence/VolumePathTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs @@ -8,10 +8,13 @@ namespace kOS.Safe.Test.Persistence public class VolumePathTest { [Test] - [ExpectedException(typeof(KOSInvalidPathException))] public void CanHandleEmptyPath() { - VolumePath.FromString(""); + VolumePath path = VolumePath.FromString(""); + Assert.AreEqual(0, path.Length); + Assert.AreEqual(0, path.Depth); + Assert.AreEqual(string.Empty, path.Name); + Assert.AreEqual(string.Empty, path.Extension); } [Test] @@ -20,8 +23,8 @@ public void CanHandleRootPath() VolumePath path = VolumePath.FromString("/"); Assert.AreEqual(0, path.Length); Assert.AreEqual(0, path.Depth); - Assert.IsNull(path.Name); - Assert.IsNull(path.Extension); + Assert.AreEqual(string.Empty, path.Name); + Assert.AreEqual(string.Empty, path.Extension); } [Test] diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs index ee98539ac..beb9ddde0 100644 --- a/src/kOS.Safe.Test/Persistence/VolumeTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -222,6 +222,12 @@ public void CanReadAndWriteFiles() Assert.AreEqual(content, volumeFile.ReadAll().String); } + [Test] + public void CanHandleOpeningNonExistingFiles() + { + Assert.IsNull(TestVolume.Open(VolumePath.FromString("/idonotexist"))); + } + [Test] public void CanDeleteFiles() { @@ -231,7 +237,7 @@ public void CanDeleteFiles() TestVolume.CreateFile(VolumePath.FromString(file1)); TestVolume.CreateFile(VolumePath.FromString(file2)); - TestVolume.Delete(VolumePath.FromString(file1)); + Assert.IsTrue(TestVolume.Delete(VolumePath.FromString(file1))); VolumeDirectory dir = TestVolume.Open(VolumePath.FromString(parent1)) as VolumeDirectory; Assert.AreEqual(1, dir.List().Count); diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index 2bbb54cc0..2347d65e0 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -3021,10 +3021,8 @@ private void VisitDeleteStatement(ParseNode node) if (node.Nodes.Count == 5) VisitNode(node.Nodes[3]); - else - AddOpcode(new OpcodePush(null)); - AddOpcode(new OpcodeCall("delete_deprecated()")); + AddOpcode(new OpcodeCall("delete()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } diff --git a/src/kOS.Safe/Persistence/Archive.cs b/src/kOS.Safe/Persistence/Archive.cs index 72b08236d..9fcc118f6 100644 --- a/src/kOS.Safe/Persistence/Archive.cs +++ b/src/kOS.Safe/Persistence/Archive.cs @@ -24,7 +24,7 @@ public override VolumeDirectory Root { public Archive(string archiveFolder) { - ArchiveFolder = archiveFolder; + ArchiveFolder = Path.GetFullPath(archiveFolder).TrimEnd(VolumePath.PathSeparator); CreateArchiveDirectory(); Renameable = false; InitializeName(ArchiveName); @@ -61,7 +61,7 @@ public override VolumeItem Open(VolumePath path, bool ksmDefault = false) var fileSystemInfo = Search(path, ksmDefault); if (fileSystemInfo == null) { - throw new KOSPersistenceException("Could not open path: " + path);; + return null; } else if (fileSystemInfo is FileInfo) { VolumePath filePath = VolumePath.FromString(fileSystemInfo.FullName.Substring(ArchiveFolder.Length)); diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs index 9dc6d53d3..5bd6a58be 100644 --- a/src/kOS.Safe/Persistence/GlobalPath.cs +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -156,11 +156,6 @@ public static GlobalPath FromStringAndBase(string pathString, GlobalPath basePat throw new KOSInvalidPathException("GlobalPath should contain a volumeId", pathString); } - if (!pathString.StartsWith(PathSeparator.ToString())) - { - pathString = PathSeparator + pathString; - } - VolumePath path = VolumePath.FromString(pathString); return new GlobalPath(volumeName, path); } diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index 1272c1fe6..44762fea0 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -46,11 +46,6 @@ public override VolumeItem Open(VolumePath path, bool ksmDefault = false) VolumeItem result = directory.Open(path.Name, ksmDefault); - if (result == null) - { - throw new KOSPersistenceException("Could not open path: " + path);; - } - return result; } diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index b5384375d..381e1f29b 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -152,7 +152,7 @@ public override int Size { private VolumeItem Search(string name, bool ksmDefault = false) { object item = items.ContainsKey(name) ? items[name] : null; - if (item is byte[]) + if (item is FileContent) { return new HarddiskFile(this, name); } else if (item is HarddiskDirectory) diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index 661196108..b0ef08ec5 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -59,7 +59,12 @@ private void InitializeSuffixes() AddSuffix("HASEXTENSION", new Suffix(() => string.IsNullOrEmpty(Path.Extension))); AddSuffix("EXTENSION", new Suffix(() => Path.Extension)); AddSuffix("CHANGEEXTENSION", new OneArgsSuffix((e) => FromPath(Path.ChangeExtension(e)))); - AddSuffix("COMBINE", new VarArgsSuffix((segments) => FromPath(Path.Combine(segments.Cast().ToArray())))); + AddSuffix("COMBINE", new VarArgsSuffix(Combine)); + } + + public PathValue Combine(params StringValue[] segments) + { + return FromPath(Path.Combine(segments.Cast().ToArray())); } public override Dump Dump() diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index 881a377c0..0914ab69a 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -67,7 +67,7 @@ public VolumeItem Open(string pathString, bool ksmDefault = false) /// /// filename to get. if it has no filename extension, one will be guessed at, ".ks" usually. /// in the scenario where there is no filename extension, do we prefer the .ksm over the .ks? The default is to prefer .ks - /// the file + /// VolumeFile or VolumeDirectory. Null if not found. public abstract VolumeItem Open(VolumePath path, bool ksmDefault = false); public VolumeDirectory CreateDirectory(string pathString) @@ -166,7 +166,7 @@ public Lexicon ListAsLexicon() public override string ToString() { - return "Volume( " + Name + ", " + Capacity + ")"; + return "Volume(" + Name + ", " + Capacity + ")"; } private void InitializeVolumeSuffixes() diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs index 4f2244b63..5ef73cb9a 100644 --- a/src/kOS.Safe/Persistence/VolumePath.cs +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -109,11 +109,6 @@ public static VolumePath FromString(string pathString, VolumePath basePath) public static VolumePath FromString(string pathString) { - if (!pathString.StartsWith(PathSeparator.ToString())) - { - throw new KOSInvalidPathException("Absolute path expected", pathString); - } - return new VolumePath(GetSegmentsFromString(pathString)); } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 18dac97e6..6c1f81d21 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -22,8 +22,9 @@ public class FunctionCopyDeprecated : FunctionWithCopy { public override void Execute(SharedObjects shared) { - PopValueAssert(shared, true).ToString(); - PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true); + PopValueAssert(shared, true); + PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); throw new KOSDeprecationException("1.0.0", "`COPY FILENAME FROM VOLUMEID.` syntax", "`COPY(FROMPATH, TOPATH)`"); @@ -35,8 +36,10 @@ public class FunctionRenameFileDeprecated : FunctionWithCopy { public override void Execute(SharedObjects shared) { - PopValueAssert(shared, true).ToString(); - PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true); + PopValueAssert(shared, true); + PopValueAssert(shared, true); + AssertArgBottomAndConsume(shared); throw new KOSDeprecationException("1.0.0", "`RENAME FILE OLDNAME TO NEWNAME.` syntax", "`MOVE(FROMPATH, TOPATH)`"); @@ -48,27 +51,15 @@ public class FunctionRenameVolumeDeprecated : FunctionWithCopy { public override void Execute(SharedObjects shared) { - PopValueAssert(shared, true).ToString(); - PopValueAssert(shared, true).ToString(); + PopValueAssert(shared, true); + PopValueAssert(shared, true); + PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); throw new KOSDeprecationException("1.0.0", "`RENAME VOLUME OLDNAME TO NEWNAME.` syntax", "`SET VOLUME:NAME TO NEWNAME.`"); } } - [Function("delete_deprecated")] - public class FunctionDeleteDeprecated : FunctionWithCopy - { - public override void Execute(SharedObjects shared) - { - PopValueAssert(shared, true).ToString(); - PopValueAssert(shared, true).ToString(); - AssertArgBottomAndConsume(shared); - - throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETE(PATH)`"); - } - } - [Function("path")] public class FunctionPath : FunctionBase { @@ -334,6 +325,22 @@ public class FunctionDelete : FunctionBase { public override void Execute(SharedObjects shared) { + /* + * Parser will treat 'DELETE(filename)' as the old 'DELETE filename [FROM volume]' syntax. So we're unable + * to differentiate between new and old syntax. That's why currently both 'DELETE(filename)' + * and 'DELETE filename' will work. We only throw the depracation warning when 'FROM' is present, in this + * case we're sure that the user wanted to use the old syntax. + */ + int remaining = CountRemainingArgs(shared); + + if (remaining == 2) { + PopValueAssert(shared, true); + PopValueAssert(shared, true); + AssertArgBottomAndConsume(shared); + + throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETE(PATH)`"); + } + string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); @@ -384,7 +391,7 @@ public override void Execute(SharedObjects shared) GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); - VolumeFile volumeFile = volume.Open(pathString) as VolumeFile; + VolumeFile volumeFile = volume.Open(path) as VolumeFile; if (volumeFile == null) { diff --git a/src/kOS/KSPLogger.cs b/src/kOS/KSPLogger.cs index a9d7fa1ec..e0bba5772 100644 --- a/src/kOS/KSPLogger.cs +++ b/src/kOS/KSPLogger.cs @@ -159,7 +159,7 @@ private string BuildLocationString(GlobalPath path, int line) if (path == Interpreter.InterpreterHistory) return string.Format("interpreter line {0}", line); - return string.Format("{0}, line {2}", path, line); + return string.Format("{0}, line {1}", path, line); } private string GetSourceLine(GlobalPath path, int line) diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Interpreter.cs index c963df3ce..3ddbfc748 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Interpreter.cs @@ -12,7 +12,7 @@ namespace kOS.Screen { public class Interpreter : TextEditor, IInterpreter { - public static GlobalPath InterpreterHistory = GlobalPath.FromString("interpreterhistory:"); + public static GlobalPath InterpreterHistory = GlobalPath.FromString("terminal:"); private readonly List commandHistory = new List(); private int commandHistoryIndex; From f777034ddfc0758663b1993751f8fbd6d8645306 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 14:29:37 +0200 Subject: [PATCH 18/48] Fix edit() --- src/kOS.Safe.Test/Persistence/VolumeTest.cs | 14 +++++++++++ src/kOS.Safe/Persistence/Archive.cs | 9 +++++++- src/kOS.Safe/Persistence/Harddisk.cs | 12 ++++++---- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 23 +++++++++++++++---- src/kOS/Screen/KOSTextEditPopup.cs | 6 ++--- 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs index beb9ddde0..03d345f3b 100644 --- a/src/kOS.Safe.Test/Persistence/VolumeTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -220,6 +220,20 @@ public void CanReadAndWriteFiles() Assert.AreEqual(contentLength, volumeFile.Size); Assert.AreEqual(content, volumeFile.ReadAll().String); + + // we should be able to save the same file again + Assert.IsTrue(TestVolume.Save(volumeFile) != null); + } + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenSavingFileOverDirectory() + { + string parent1 = "/parent1"; + string file1 = parent1 + "/sub1"; + + TestVolume.CreateDirectory(VolumePath.FromString(file1)); + TestVolume.Save(VolumePath.FromString(file1), new FileContent()); } [Test] diff --git a/src/kOS.Safe/Persistence/Archive.cs b/src/kOS.Safe/Persistence/Archive.cs index 9fcc118f6..da6e33e31 100644 --- a/src/kOS.Safe/Persistence/Archive.cs +++ b/src/kOS.Safe/Persistence/Archive.cs @@ -145,9 +145,16 @@ public override VolumeFile Save(VolumePath path, FileContent content) { Directory.CreateDirectory(ArchiveFolder); + string archivePath = GetArchivePath(path); + FileAttributes attr = File.GetAttributes(archivePath); + if ((attr & FileAttributes.Directory) == FileAttributes.Directory) + { + throw new KOSPersistenceException("Can't save file over a directory: " + path); + } + byte[] fileBody = ConvertToWindowsNewlines(content.Bytes); - using (var outfile = new BinaryWriter(File.Open(GetArchivePath(path), FileMode.Create))) + using (var outfile = new BinaryWriter(File.Open(archivePath, FileMode.Create))) { outfile.Write(fileBody); } diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index 44762fea0..d2478ce6a 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -79,15 +79,19 @@ public override bool Delete(VolumePath path, bool ksmDefault = false) public override VolumeFile Save(VolumePath path, FileContent content) { - if (!IsRoomFor(path, content)) + try { + if (!IsRoomFor(path, content)) + { + return null; + } + } catch (KOSPersistenceException) { - return null; + throw new KOSPersistenceException("Can't save file over a directory: " + path); } HarddiskDirectory directory = ParentDirectoryForPath(path); - directory.CreateFile(path.Name, content); - return Open(path) as VolumeFile; + return directory.Save(path.Name, content) as VolumeFile; } } } diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index 381e1f29b..6b3ccbed0 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -40,13 +40,12 @@ public HarddiskFile CreateFile(string name) public HarddiskFile CreateFile(string name, FileContent fileContent) { - try { - items.Add(name, new FileContent(fileContent.Bytes.Clone() as byte[])); - } catch (ArgumentException) - { + if (items.ContainsKey(name)){ throw new KOSPersistenceException("Already exists: " + name); } + items[name] = new FileContent(fileContent.Bytes.Clone() as byte[]); + return new HarddiskFile(this, name); } @@ -68,6 +67,22 @@ public HarddiskDirectory CreateDirectory(string name, HarddiskDirectory director return directory; } + public VolumeFile Save(string name, FileContent fileContent) + { + if (!items.ContainsKey(name)) + { + return CreateFile(name, fileContent); + } else if (items[name] is VolumeDirectory) + { + throw new KOSPersistenceException("Can't save file over a directory: " + name); + } else + { + items[name] = new FileContent(fileContent.Bytes.Clone() as byte[]); + + return new HarddiskFile(this, name); + } + } + public bool Exists(string name, bool ksmDefault) { return Search(name) != null; diff --git a/src/kOS/Screen/KOSTextEditPopup.cs b/src/kOS/Screen/KOSTextEditPopup.cs index 0d388ea4b..40f4f961d 100644 --- a/src/kOS/Screen/KOSTextEditPopup.cs +++ b/src/kOS/Screen/KOSTextEditPopup.cs @@ -243,7 +243,7 @@ protected static void DelegateSaveThenLoad(KOSTextEditPopup me) protected static void DelegateLoadContents(KOSTextEditPopup me) { - VolumeItem item = me.loadingVolume.Open(me.filePath); + VolumeItem item = me.loadingVolume.Open(me.loadingPath); if (item == null) { me.term.Print("[New File]"); @@ -519,9 +519,7 @@ public Rect GetRect() protected string BuildTitle() { - if (volume.Name.Length > 0) - return filePath + " on " + volume.Name; - return filePath + " on local volume"; // Don't know which number because no link to VolumeManager from this class. + return filePath.ToString(); } } } \ No newline at end of file From 39ed5f6393d623a4d9b29e3e2c5790ad6ca34c4b Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 22:14:52 +0200 Subject: [PATCH 19/48] Copying unit tests --- .../ArchiveAndHarddiskCopyAndMoveTest.cs | 36 ++++ .../ArchiveToHarddiskCopyAndMoveTest.cs | 24 +++ .../Persistence/CopyAndMoveTest.cs | 191 ++++++++++++++++++ .../HarddiskToArchiveCopyAndMoveTest.cs | 20 ++ .../HarddiskToHarddiskCopyAndMoveTest.cs | 30 +++ src/kOS.Safe.Test/Persistence/VolumeTest.cs | 40 +++- src/kOS.Safe.Test/kOS.Safe.Test.csproj | 5 + src/kOS.Safe/Persistence/Archive.cs | 32 ++- src/kOS.Safe/Persistence/FileContent.cs | 10 + src/kOS.Safe/Persistence/GlobalPath.cs | 2 +- src/kOS.Safe/Persistence/Harddisk.cs | 32 ++- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 9 +- src/kOS.Safe/Persistence/IVolumeManager.cs | 2 + src/kOS.Safe/Persistence/Volume.cs | 8 +- src/kOS.Safe/Persistence/VolumeManager.cs | 96 +++++++++ src/kOS/Function/Misc.cs | 2 +- src/kOS/Function/Persistence.cs | 107 +--------- src/kOS/Module/kOSProcessor.cs | 2 +- src/kOS/Screen/Interpreter.cs | 15 +- src/kOS/Screen/KOSTextEditPopup.cs | 2 +- 20 files changed, 540 insertions(+), 125 deletions(-) create mode 100644 src/kOS.Safe.Test/Persistence/ArchiveAndHarddiskCopyAndMoveTest.cs create mode 100644 src/kOS.Safe.Test/Persistence/ArchiveToHarddiskCopyAndMoveTest.cs create mode 100644 src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs create mode 100644 src/kOS.Safe.Test/Persistence/HarddiskToArchiveCopyAndMoveTest.cs create mode 100644 src/kOS.Safe.Test/Persistence/HarddiskToHarddiskCopyAndMoveTest.cs diff --git a/src/kOS.Safe.Test/Persistence/ArchiveAndHarddiskCopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/ArchiveAndHarddiskCopyAndMoveTest.cs new file mode 100644 index 000000000..1cdcc328f --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/ArchiveAndHarddiskCopyAndMoveTest.cs @@ -0,0 +1,36 @@ +using System; +using NUnit.Framework; +using System.IO; +using kOS.Safe.Persistence; + +namespace kOS.Safe.Test.Persistence +{ + public abstract class ArchiveAndHarddiskCopyAndMoveTest : CopyAndMoveTest + { + public string KosTestDirectory = "kos_archive_tests"; + protected string archivePath; + protected Archive archive; + protected Harddisk harddisk; + + public ArchiveAndHarddiskCopyAndMoveTest() + { + archivePath = Path.Combine(Path.GetTempPath(), KosTestDirectory); + + archive = PrepareArchive(archivePath); + harddisk = new Harddisk(1000); + } + + private Archive PrepareArchive(string archivePath) + { + if (Directory.Exists(archivePath)) + { + Directory.Delete(archivePath, true); + } + + Directory.CreateDirectory(archivePath); + + return new Archive(archivePath); + } + } +} + diff --git a/src/kOS.Safe.Test/Persistence/ArchiveToHarddiskCopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/ArchiveToHarddiskCopyAndMoveTest.cs new file mode 100644 index 000000000..7ea2d5f76 --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/ArchiveToHarddiskCopyAndMoveTest.cs @@ -0,0 +1,24 @@ +using System; +using NUnit.Framework; +using System.IO; +using kOS.Safe.Persistence; + +namespace kOS.Safe.Test.Persistence +{ + [TestFixture] + public class ArchiveToHarddiskCopyAndMoveTest : ArchiveAndHarddiskCopyAndMoveTest + { + public override kOS.Safe.Persistence.Volume SourceVolume { + get { + return archive; + } + } + + public override kOS.Safe.Persistence.Volume TargetVolume { + get { + return harddisk; + } + } + } +} + diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs new file mode 100644 index 000000000..ac8815a08 --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -0,0 +1,191 @@ +using System; +using NUnit.Framework; +using System.IO; +using kOS.Safe.Persistence; +using kOS.Safe.Utilities; +using kOS.Safe.Exceptions; +using System.Text; +using System.Collections.Generic; + +namespace kOS.Safe.Test +{ + public abstract class CopyAndMoveTest + { + public abstract Volume SourceVolume { get; } + public abstract Volume TargetVolume { get; } + + protected VolumeManager volumeManager; + + protected string dir1 = "dir1", subdir1 = "subdir1", subdir2 = "subdir2", subsubdir1 = "subsubdir1"; + protected string file1 = "file1", file2 = "file2", file3 = "file3"; + + protected GlobalPath dir1Path, subdir1Path, subdir2Path, subsubdir1Path; + + protected GlobalPath file1Path, dir1File1Path, dir1File2Path, subdir1File1Path, subsubdir1File1Path; + + [SetUp] + public void SetupLogger() + { + SafeHouse.Logger = new TestLogger(); + } + + [SetUp] + public void SetupVolumeManager() + { + volumeManager = new VolumeManager(); + volumeManager.Add(SourceVolume); + volumeManager.Add(TargetVolume); + } + + [SetUp] + public void SetupVolumes() + { + dir1Path = GlobalPath.FromString("0:" + dir1); + subdir1Path = dir1Path.Combine(subdir1); + subdir2Path = dir1Path.Combine(subdir2); + subsubdir1Path = subdir1Path.Combine(subsubdir1); + + file1Path = GlobalPath.FromString("0:" + file1); + dir1File1Path = dir1Path.Combine(file1); + dir1File2Path = dir1Path.Combine(file2); + subdir1File1Path = subdir1Path.Combine(file1); + subsubdir1File1Path = subsubdir1Path.Combine(file1); + + SourceVolume.Clear(); + TargetVolume.Clear(); + + SourceVolume.CreateDirectory(subdir2Path); + SourceVolume.CreateDirectory(subsubdir1Path); + + SourceVolume.CreateFile(file1Path).WriteLn(file1); + SourceVolume.CreateFile(subsubdir1File1Path).WriteLn("subsubdir1File1"); + + var dir1List = SourceVolume.Root.List(); + + GlobalPath targetPath = GlobalPath.FromString("1:/dir1/file1"); + + } + + [Test] + public void CanCopyFileToExistingFile() + { + GlobalPath targetPath = GlobalPath.FromString("1:/dir1/file1"); + TargetVolume.CreateFile(targetPath); + volumeManager.Copy(subsubdir1File1Path, targetPath); + + Assert.AreEqual(1, TargetVolume.Root.List().Count); + VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); + Assert.AreEqual(1, parent.List().Count); + Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); + } + + [Test] + public void CanCopyFileToNewFile() + { + volumeManager.Copy(subsubdir1File1Path, GlobalPath.FromString("1:/dir1/file1")); + + Assert.AreEqual(1, TargetVolume.Root.List().Count); + VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); + Assert.AreEqual(1, parent.List().Count); + Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); + } + + + [Test] + public void CanCopyFileToDirectory() + { + GlobalPath targetPath = GlobalPath.FromString("1:/dir1"); + TargetVolume.CreateDirectory(targetPath); + volumeManager.Copy(subsubdir1File1Path, targetPath); + + Assert.AreEqual(1, TargetVolume.Root.List().Count); + VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); + Assert.AreEqual(1, parent.List().Count); + Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); + } + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenTryingToCopyDirectoryToAFile() + { + VolumePath filePath = TargetVolume.CreateFile("newfile").Path; + volumeManager.Copy(dir1Path, GlobalPath.FromString("1:/newfile")); + } + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenTryingToCopyDirectoryIntoItself() + { + volumeManager.Copy(dir1Path, subdir1Path); + } + + [Test] + public void CanCopyDirectoryToExistingDirectory() + { + TargetVolume.CreateDirectory(VolumePath.FromString("/newdirectory")); + volumeManager.Copy(dir1Path, GlobalPath.FromString("1:/newdirectory")); + + CompareDirectories(dir1Path, GlobalPath.FromString("1:/newdirectory/" + dir1)); + } + + [Test] + public void CanCopyDirectoryToNewDirectory() + { + GlobalPath targetPath = GlobalPath.FromString("1:/newname"); + volumeManager.Copy(dir1Path, targetPath); + + CompareDirectories(dir1Path, targetPath); + } + + /* + [Test] + public void CanFailIfThereIsNoSpaceToCopy() + { + Assert.Fail(); + } + + [Test] + public void CanMoveEvenIfThereIsNoSpaceToCopy() + { + Assert.Fail(); + } + */ + + private void CompareDirectories(GlobalPath dir1Path, GlobalPath dir2Path) + { + Volume dir1Volume = volumeManager.GetVolumeFromPath(dir1Path); + Volume dir2Volume = volumeManager.GetVolumeFromPath(dir2Path); + + VolumeDirectory dir1 = dir1Volume.Open(dir1Path) as VolumeDirectory; + VolumeDirectory dir2 = dir2Volume.Open(dir2Path) as VolumeDirectory; + + int dir1Count = dir1.List().Count; + int dir2Count = dir2.List().Count; + + if (dir1Count != dir2Count) + { + Assert.Fail("Item count not equal: " + dir1Count + " != " + dir2Count); + } + + foreach (KeyValuePair pair in dir1.List()) + { + VolumeItem dir2Item = dir2Volume.Open(dir2Path.Combine(pair.Key)); + + if (pair.Value is VolumeDirectory && dir2Item is VolumeDirectory) + { + CompareDirectories(dir1Path.Combine(pair.Key), dir2Path.Combine(pair.Key)); + } else if (pair.Value is VolumeFile && dir2Item is VolumeFile) + { + VolumeFile file1 = pair.Value as VolumeFile; + VolumeFile file2 = dir2Item as VolumeFile; + + Assert.AreEqual(file1.ReadAll(), file2.ReadAll()); + } else + { + Assert.Fail("Items are not of the same type: " + dir1Path.Combine(pair.Key) + ", " + dir2Path.Combine(pair.Key)); + } + } + } + } +} + diff --git a/src/kOS.Safe.Test/Persistence/HarddiskToArchiveCopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/HarddiskToArchiveCopyAndMoveTest.cs new file mode 100644 index 000000000..4475cc93e --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/HarddiskToArchiveCopyAndMoveTest.cs @@ -0,0 +1,20 @@ +using System; + +namespace kOS.Safe.Test.Persistence +{ + public class HarddiskToArchiveCopyAndMoveTest : ArchiveAndHarddiskCopyAndMoveTest + { + public override kOS.Safe.Persistence.Volume SourceVolume { + get { + return harddisk; + } + } + + public override kOS.Safe.Persistence.Volume TargetVolume { + get { + return archive; + } + } + } +} + diff --git a/src/kOS.Safe.Test/Persistence/HarddiskToHarddiskCopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/HarddiskToHarddiskCopyAndMoveTest.cs new file mode 100644 index 000000000..aedb0e4bf --- /dev/null +++ b/src/kOS.Safe.Test/Persistence/HarddiskToHarddiskCopyAndMoveTest.cs @@ -0,0 +1,30 @@ +using System; +using kOS.Safe.Persistence; +using NUnit.Framework; + +namespace kOS.Safe.Test.Persistence +{ + [TestFixture] + public class HarddiskToHarddiskCopyAndMoveTest : CopyAndMoveTest + { + protected Harddisk harddisk1, harddisk2; + + public HarddiskToHarddiskCopyAndMoveTest() + { + harddisk1 = new Harddisk(1000); + harddisk2 = new Harddisk(1000); + } + + public override Volume SourceVolume { + get { + return harddisk1; + } + } + public override Volume TargetVolume { + get { + return harddisk2; + } + } + } +} + diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs index 03d345f3b..18369889b 100644 --- a/src/kOS.Safe.Test/Persistence/VolumeTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -85,6 +85,13 @@ public void CanFailWhenCreatingDirectoryOverExistingDirectory() TestVolume.CreateDirectory(VolumePath.FromString(dir)); } + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenCreatingDirectoryOverRootDirectory() + { + TestVolume.CreateDirectory(VolumePath.FromString("/")); + } + [Test] [ExpectedException(typeof(KOSPersistenceException))] public void CanFailWhenCreatingDirectoryOverFile() @@ -139,7 +146,7 @@ public void CanDeleteNonExistingDirectories() public void CanCreateFiles() { string parent1 = "/parent1", parent2 = "/parent2"; - string file1 = parent1 + "/sub1", file2 = parent1 + "/sub2", file3 = parent2 + "/sub3"; + string file1 = parent1 + "/sub1/file", file2 = parent1 + "/sub2", file3 = parent2 + "/sub3"; TestVolume.CreateFile(VolumePath.FromString(file1)); TestVolume.CreateFile(VolumePath.FromString(file2)); @@ -151,11 +158,16 @@ public void CanCreateFiles() VolumeDirectory dir = TestVolume.Open(VolumePath.FromString(parent1)) as VolumeDirectory; Assert.AreEqual(2, dir.List().Count); - Assert.AreEqual(file1, dir.List()["sub1"].Path.ToString()); - Assert.IsInstanceOf(dir.List()["sub1"]); + Assert.AreEqual("/parent1/sub1", dir.List()["sub1"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["sub1"]); Assert.AreEqual(file2, dir.List()["sub2"].Path.ToString()); Assert.IsInstanceOf(dir.List()["sub2"]); + dir = TestVolume.Open(VolumePath.FromString("/parent1/sub1")) as VolumeDirectory; + Assert.AreEqual(1, dir.List().Count); + Assert.AreEqual("/parent1/sub1/file", dir.List()["file"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["file"]); + dir = TestVolume.Open(VolumePath.FromString(parent2)) as VolumeDirectory; Assert.AreEqual(1, dir.List().Count); Assert.AreEqual(file3, dir.List()["sub3"].Path.ToString()); @@ -173,6 +185,17 @@ public void CanFailWhenCreatingFileOverExistingFile() TestVolume.CreateFile(VolumePath.FromString(file)); } + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenCreatingFileInASubdirectoryThatIsAFile() + { + string parent = "/parent1"; + string file = parent + "/file"; + + TestVolume.CreateFile(VolumePath.FromString(parent)); + TestVolume.CreateFile(VolumePath.FromString(file)); + } + [Test] [ExpectedException(typeof(KOSPersistenceException))] public void CanFailWhenCreatingFileOverDirectory() @@ -184,6 +207,13 @@ public void CanFailWhenCreatingFileOverDirectory() TestVolume.CreateFile(VolumePath.FromString(file1)); } + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenCreatingFileOverRootDirectory() + { + TestVolume.CreateFile(VolumePath.FromString("/")); + } + [Test] [ExpectedException(typeof(KOSInvalidPathException))] public void CanFailWhenCreatingFileWithNegativeDepth() @@ -222,7 +252,7 @@ public void CanReadAndWriteFiles() Assert.AreEqual(content, volumeFile.ReadAll().String); // we should be able to save the same file again - Assert.IsTrue(TestVolume.Save(volumeFile) != null); + Assert.IsTrue(TestVolume.SaveFile(volumeFile) != null); } [Test] @@ -233,7 +263,7 @@ public void CanFailWhenSavingFileOverDirectory() string file1 = parent1 + "/sub1"; TestVolume.CreateDirectory(VolumePath.FromString(file1)); - TestVolume.Save(VolumePath.FromString(file1), new FileContent()); + TestVolume.SaveFile(VolumePath.FromString(file1), new FileContent()); } [Test] diff --git a/src/kOS.Safe.Test/kOS.Safe.Test.csproj b/src/kOS.Safe.Test/kOS.Safe.Test.csproj index f7f978990..cdc97c0fc 100644 --- a/src/kOS.Safe.Test/kOS.Safe.Test.csproj +++ b/src/kOS.Safe.Test/kOS.Safe.Test.csproj @@ -78,6 +78,11 @@ + + + + + diff --git a/src/kOS.Safe/Persistence/Archive.cs b/src/kOS.Safe/Persistence/Archive.cs index da6e33e31..0d5be7c6a 100644 --- a/src/kOS.Safe/Persistence/Archive.cs +++ b/src/kOS.Safe/Persistence/Archive.cs @@ -54,6 +54,14 @@ public string GetArchivePath(VolumePath path) return mergedPath; } + public override void Clear() + { + if (Directory.Exists(ArchiveFolder)) + { + Directory.Delete(ArchiveFolder, true); + } + } + public override VolumeItem Open(VolumePath path, bool ksmDefault = false) { try @@ -99,6 +107,11 @@ public override VolumeDirectory CreateDirectory(VolumePath path) public override VolumeFile CreateFile(VolumePath path) { + if (path.Depth == 0) + { + throw new KOSPersistenceException("Can't create a file over root directory"); + } + string archivePath = GetArchivePath(path); if (File.Exists(archivePath)) @@ -106,7 +119,12 @@ public override VolumeFile CreateFile(VolumePath path) throw new KOSPersistenceException("Already exists: " + path); } - Directory.CreateDirectory(GetArchivePath(path.GetParent())); + try { + Directory.CreateDirectory(GetArchivePath(path.GetParent())); + } catch (IOException) + { + throw new KOSPersistenceException("Parent directory for path does not exist: " + path.ToString()); + } try { File.Create(archivePath).Dispose(); @@ -141,17 +159,23 @@ public override bool Delete(VolumePath path, bool ksmDefault = false) return true; } - public override VolumeFile Save(VolumePath path, FileContent content) + public override VolumeFile SaveFile(VolumePath path, FileContent content) { Directory.CreateDirectory(ArchiveFolder); string archivePath = GetArchivePath(path); - FileAttributes attr = File.GetAttributes(archivePath); - if ((attr & FileAttributes.Directory) == FileAttributes.Directory) + if (Directory.Exists(archivePath)) { throw new KOSPersistenceException("Can't save file over a directory: " + path); } + string parentPath = Directory.GetParent(archivePath).FullName; + + if (!Directory.Exists(parentPath)) + { + Directory.CreateDirectory(parentPath); + } + byte[] fileBody = ConvertToWindowsNewlines(content.Bytes); using (var outfile = new BinaryWriter(File.Open(archivePath, FileMode.Create))) diff --git a/src/kOS.Safe/Persistence/FileContent.cs b/src/kOS.Safe/Persistence/FileContent.cs index bd36e0f8f..0134e9024 100644 --- a/src/kOS.Safe/Persistence/FileContent.cs +++ b/src/kOS.Safe/Persistence/FileContent.cs @@ -126,6 +126,16 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } + public override bool Equals(object obj) + { + return obj is FileContent && Bytes.Equals((obj as FileContent).Bytes); + } + + public override int GetHashCode() + { + return Bytes.GetHashCode(); + } + public override string ToString() { return "File content"; diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs index 5bd6a58be..d5341a152 100644 --- a/src/kOS.Safe/Persistence/GlobalPath.cs +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -22,7 +22,7 @@ public class GlobalPath : VolumePath public object VolumeId { get; private set; } - private GlobalPath(object volumeId) + protected GlobalPath(object volumeId) { VolumeId = ValidateVolumeId(volumeId); } diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index d2478ce6a..d0ea6fa7b 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -25,15 +25,21 @@ public Harddisk(int size) RootHarddiskDirectory = new HarddiskDirectory(this, VolumePath.EMPTY); } + public override void Clear() + { + RootHarddiskDirectory.Clear(); + } + private HarddiskDirectory ParentDirectoryForPath(VolumePath path, bool create = false) { HarddiskDirectory directory = RootHarddiskDirectory; - if (path.Depth > 1) + if (path.Depth > 0) + { + return RootHarddiskDirectory.GetSubdirectory(path.GetParent(), create); + } else { - directory = RootHarddiskDirectory.GetSubdirectory(path.GetParent(), create); + throw new Exception("This directory does not have a parent"); } - - return directory; } public override VolumeItem Open(VolumePath path, bool ksmDefault = false) @@ -44,13 +50,16 @@ public override VolumeItem Open(VolumePath path, bool ksmDefault = false) HarddiskDirectory directory = ParentDirectoryForPath(path); - VolumeItem result = directory.Open(path.Name, ksmDefault); - - return result; + return directory == null ? null : directory.Open(path.Name, ksmDefault); } public override VolumeDirectory CreateDirectory(VolumePath path) { + if (path.Depth == 0) + { + throw new KOSPersistenceException("Can't create a directory over root directory"); + } + HarddiskDirectory directory = ParentDirectoryForPath(path, true); return directory.CreateDirectory(path.Name); @@ -58,6 +67,11 @@ public override VolumeDirectory CreateDirectory(VolumePath path) public override VolumeFile CreateFile(VolumePath path) { + if (path.Depth == 0) + { + throw new KOSPersistenceException("Can't create a file over root directory"); + } + HarddiskDirectory directory = ParentDirectoryForPath(path, true); return directory.CreateFile(path.Name); @@ -77,7 +91,7 @@ public override bool Delete(VolumePath path, bool ksmDefault = false) return directory.Delete(path.Name, ksmDefault); } - public override VolumeFile Save(VolumePath path, FileContent content) + public override VolumeFile SaveFile(VolumePath path, FileContent content) { try { if (!IsRoomFor(path, content)) @@ -89,7 +103,7 @@ public override VolumeFile Save(VolumePath path, FileContent content) throw new KOSPersistenceException("Can't save file over a directory: " + path); } - HarddiskDirectory directory = ParentDirectoryForPath(path); + HarddiskDirectory directory = ParentDirectoryForPath(path, true); return directory.Save(path.Name, content) as VolumeFile; } diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index 6b3ccbed0..e2229ba93 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -18,6 +18,11 @@ public HarddiskDirectory(Harddisk harddisk, VolumePath path) : base(harddisk, pa items = new Dictionary(StringComparer.InvariantCultureIgnoreCase); } + public void Clear() + { + items.Clear(); + } + public VolumeItem Open(string name, bool ksmDefault = false) { return Search(name, ksmDefault); @@ -132,10 +137,10 @@ public HarddiskDirectory GetSubdirectory(VolumePath path, bool create = false) if (directory == null) { - throw new KOSException("Subdirectory does not exist: " + path.ToString()); + throw new KOSPersistenceException("Directory does not exist: " + path.ToString()); } - return directory; + return directory.GetSubdirectory(path, create); } public override IDictionary List() diff --git a/src/kOS.Safe/Persistence/IVolumeManager.cs b/src/kOS.Safe/Persistence/IVolumeManager.cs index cb51dcba5..f5a73534b 100644 --- a/src/kOS.Safe/Persistence/IVolumeManager.cs +++ b/src/kOS.Safe/Persistence/IVolumeManager.cs @@ -20,6 +20,8 @@ public interface IVolumeManager void SwitchTo(Volume volume); void UpdateVolumes(List attachedVolumes); string GetVolumeBestIdentifier(Volume volume); + bool Copy(GlobalPath sourcePath, GlobalPath destinationPath); + void Move(GlobalPath sourcePath, GlobalPath destinationPath); /// /// This creates a proper, absolute GlobalPath from the given string (which is assumed to come from the user). diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index 0914ab69a..f8158f992 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -57,6 +57,8 @@ protected void InitializeName(string name) this.name = name; } + public abstract void Clear(); + public VolumeItem Open(string pathString, bool ksmDefault = false) { return Open(VolumePath.FromString(pathString), ksmDefault); @@ -123,12 +125,12 @@ public bool Delete(string pathString, bool ksmDefault = false) public abstract bool Delete(VolumePath path, bool ksmDefault = false); //public abstract void Move(VolumePath oldPath, VolumePath newPath); - public VolumeFile Save(VolumeFile volumeFile) + public VolumeFile SaveFile(VolumeFile volumeFile) { - return Save(volumeFile.Path, volumeFile.ReadAll()); + return SaveFile(volumeFile.Path, volumeFile.ReadAll()); } - public abstract VolumeFile Save(VolumePath path, FileContent content); + public abstract VolumeFile SaveFile(VolumePath path, FileContent content); public bool IsRoomFor(VolumePath path, FileContent fileContent) { diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index 3bc5436b0..262cd028e 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -221,5 +221,101 @@ private void UpdateRequiredPower() { CurrentRequiredPower = (float)Math.Round(CurrentVolume.RequiredPower(), 4); } + + public bool Copy(GlobalPath sourcePath, GlobalPath destinationPath) + { + Volume sourceVolume = GetVolumeFromPath(sourcePath); + Volume destinationVolume = GetVolumeFromPath(destinationPath); + + VolumeItem source = sourceVolume.Open(sourcePath); + VolumeItem destination = destinationVolume.Open(destinationPath); + + if (source == null) + { + throw new KOSPersistenceException("Path does not exist: " + sourcePath); + } + + if (source is VolumeDirectory) + { + if (destination is VolumeFile) + { + throw new KOSPersistenceException("Can't copy directory into a file"); + } + + return CopyDirectory(sourcePath, destinationPath); + } else + { + if (destination is VolumeFile || destination == null) + { + Volume targetVolume = GetVolumeFromPath(destinationPath); + return CopyFile(source as VolumeFile, destinationPath, targetVolume); + } else + { + return CopyFileToDirectory(source as VolumeFile, destination as VolumeDirectory); + } + } + } + + protected bool CopyDirectory(GlobalPath sourcePath, GlobalPath destinationPath) + { + if (sourcePath.IsParent(destinationPath)) + { + throw new KOSPersistenceException("Can't copy directory to a subdirectory of itself: " + destinationPath); + } + + Volume sourceVolume = GetVolumeFromPath(sourcePath); + Volume destinationVolume = GetVolumeFromPath(destinationPath); + + VolumeDirectory source = sourceVolume.Open(sourcePath) as VolumeDirectory; + + VolumeDirectory destination; + + if (destinationVolume.Exists(destinationPath)) + { + destinationPath = destinationPath.Combine(sourcePath.Name); + destination = destinationVolume.CreateDirectory(destinationPath); + + if (destination == null) + { + throw new KOSException("Path was expected to point to a directory: " + destinationPath); + } + } else + { + destination = destinationVolume.CreateDirectory(destinationPath); + } + + var l = source.List(); + + foreach (KeyValuePair pair in l) + { + if (pair.Value is VolumeDirectory) + { + CopyDirectory(sourcePath.Combine(pair.Key), destinationPath.Combine(pair.Key)); + } else + { + if (!CopyFileToDirectory(pair.Value as VolumeFile, destination)) + { + return false; + } + } + } + + return true; + } + + protected bool CopyFile(VolumeFile volumeFile, GlobalPath destinationPath, Volume targetVolume) + { + return targetVolume.SaveFile(destinationPath, volumeFile.ReadAll()) != null; + } + + protected bool CopyFileToDirectory(VolumeFile volumeFile, VolumeDirectory volumeDirectory) + { + return volumeDirectory.Volume.SaveFile(volumeDirectory.Path.Combine(volumeFile.Name), volumeFile.ReadAll()) != null; + } + + public void Move(GlobalPath sourcePath, GlobalPath destinationPath) + { + throw new NotImplementedException(); + } } } diff --git a/src/kOS/Function/Misc.cs b/src/kOS/Function/Misc.cs index 40028959c..1e35ae160 100644 --- a/src/kOS/Function/Misc.cs +++ b/src/kOS/Function/Misc.cs @@ -298,7 +298,7 @@ public override void Execute(SharedObjects shared) if (justCompiling) { List compileParts = shared.ScriptHandler.Compile(path, 1, fileContent.String, String.Empty, options); - VolumeFile written = volume.Save(outPath, new FileContent(compileParts)); + VolumeFile written = volume.SaveFile(outPath, new FileContent(compileParts)); if (written == null) { throw new KOSFileException("Can't save compiled file: not enough space or access forbidden"); diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 6c1f81d21..8917723ee 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -18,7 +18,7 @@ namespace kOS.Function * remove these function below as well any metions of delete/rename file/rename volume/copy from kRISC.tpg in the future. */ [Function("copy_deprecated")] - public class FunctionCopyDeprecated : FunctionWithCopy + public class FunctionCopyDeprecated : FunctionBase { public override void Execute(SharedObjects shared) { @@ -32,7 +32,7 @@ public override void Execute(SharedObjects shared) } [Function("rename_file_deprecated")] - public class FunctionRenameFileDeprecated : FunctionWithCopy + public class FunctionRenameFileDeprecated : FunctionBase { public override void Execute(SharedObjects shared) { @@ -47,7 +47,7 @@ public override void Execute(SharedObjects shared) } [Function("rename_volume_deprecated")] - public class FunctionRenameVolumeDeprecated : FunctionWithCopy + public class FunctionRenameVolumeDeprecated : FunctionBase { public override void Execute(SharedObjects shared) { @@ -194,95 +194,8 @@ public override void Execute(SharedObjects shared) } } - public abstract class FunctionWithCopy : FunctionBase - { - protected void Copy(IVolumeManager volumeManager, GlobalPath sourcePath, GlobalPath destinationPath) - { - Volume sourceVolume = volumeManager.GetVolumeFromPath(sourcePath); - Volume destinationVolume = volumeManager.GetVolumeFromPath(destinationPath); - - VolumeItem source = sourceVolume.Open(sourcePath); - VolumeItem destination = destinationVolume.Open(destinationPath); - - if (source == null) - { - throw new KOSPersistenceException("Path does not exist: " + sourcePath); - } - - if (source is VolumeDirectory) - { - if (destination is VolumeFile) - { - throw new KOSPersistenceException("Can't copy directory into a file"); - } - - CopyDirectory(volumeManager, sourcePath, destinationPath); - } else - { - if (destination is VolumeFile || destination == null) - { - Volume targetVolume = volumeManager.GetVolumeFromPath(destinationPath); - CopyFile(source as VolumeFile, destinationPath, targetVolume); - } else - { - CopyFileToDirectory(source as VolumeFile, destination as VolumeDirectory); - } - } - } - - protected void CopyDirectory(IVolumeManager volumeManager, GlobalPath sourcePath, GlobalPath destinationPath) - { - Volume sourceVolume = volumeManager.GetVolumeFromPath(sourcePath); - Volume destinationVolume = volumeManager.GetVolumeFromPath(destinationPath); - - VolumeDirectory source = sourceVolume.Open(sourcePath) as VolumeDirectory; - - VolumeDirectory destination; - - if (destinationVolume.Exists(destinationPath)) - { - destination = destinationVolume.CreateDirectory(destinationPath.Combine(sourcePath.Name)); - - if (destination == null) - { - throw new KOSException("Path was expected to point to a directory: " + destinationPath); - } - } else - { - destination = destinationVolume.CreateDirectory(destinationPath); - } - - foreach (KeyValuePair pair in source.List()) - { - if (pair.Value is VolumeDirectory) - { - CopyDirectory(volumeManager, sourcePath.Combine(pair.Key), destinationPath.Combine(pair.Key)); - } else - { - CopyFileToDirectory(pair.Value as VolumeFile, destination); - } - } - } - - protected void CopyFile(VolumeFile volumeFile, GlobalPath destinationPath, Volume targetVolume) - { - if (targetVolume.Save(destinationPath, volumeFile.ReadAll()) == null) - { - throw new KOSPersistenceException("Couldn not copy file"); - } - } - - protected void CopyFileToDirectory(VolumeFile volumeFile, VolumeDirectory volumeDirectory) - { - if (volumeDirectory.Volume.Save(volumeDirectory.Path.Combine(volumeFile.Name), volumeFile.ReadAll()) == null) - { - throw new KOSPersistenceException("Couldn not copy file"); - } - } - } - [Function("copy")] - public class FunctionCopy : FunctionWithCopy + public class FunctionCopy : FunctionBase { public override void Execute(SharedObjects shared) { @@ -295,12 +208,12 @@ public override void Execute(SharedObjects shared) GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); - Copy(shared.VolumeMgr, sourcePath, destinationPath); + shared.VolumeMgr.Copy(sourcePath, destinationPath); } } [Function("move")] - public class FunctionMove : FunctionWithCopy + public class FunctionMove : FunctionBase { public override void Execute(SharedObjects shared) { @@ -313,10 +226,10 @@ public override void Execute(SharedObjects shared) GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); - Copy(shared.VolumeMgr, sourcePath, destinationPath); + //Copy(shared.VolumeMgr, sourcePath, destinationPath); - Volume sourceVolume = shared.VolumeMgr.GetVolumeFromPath(sourcePath); - sourceVolume.Delete(sourcePath); + //Volume sourceVolume = shared.VolumeMgr.GetVolumeFromPath(sourcePath); + //sourceVolume.Delete(sourcePath); } } @@ -376,7 +289,7 @@ public override void Execute(SharedObjects shared) GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); - ReturnValue = volume.Save(path, fileContent); + ReturnValue = volume.SaveFile(path, fileContent); } } diff --git a/src/kOS/Module/kOSProcessor.cs b/src/kOS/Module/kOSProcessor.cs index 851eb7bd6..6d59b3667 100644 --- a/src/kOS/Module/kOSProcessor.cs +++ b/src/kOS/Module/kOSProcessor.cs @@ -368,7 +368,7 @@ public void InitObjects() FileContent content = bootVolumeFile.ReadAll(); if (HardDisk.IsRoomFor(bootFilePath, content)) { - HardDisk.Save(bootFilePath, content); + HardDisk.SaveFile(bootFilePath, content); } else { diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Interpreter.cs index 3ddbfc748..ddf915f33 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Interpreter.cs @@ -12,7 +12,7 @@ namespace kOS.Screen { public class Interpreter : TextEditor, IInterpreter { - public static GlobalPath InterpreterHistory = GlobalPath.FromString("terminal:"); + public static GlobalPath InterpreterHistory = new InterpreterPath(); private readonly List commandHistory = new List(); private int commandHistoryIndex; @@ -178,5 +178,18 @@ public override void PrintAt(string textToPrint, int row, int column) base.PrintAt(textToPrint, row, column); RestoreCursorPos(); } + + private class InterpreterPath : GlobalPath + { + public InterpreterPath() : base("Interpreter") + { + + } + + public override string ToString() + { + return "[Interpreter]"; + } + } } } diff --git a/src/kOS/Screen/KOSTextEditPopup.cs b/src/kOS/Screen/KOSTextEditPopup.cs index 40f4f961d..2d50e5de7 100644 --- a/src/kOS/Screen/KOSTextEditPopup.cs +++ b/src/kOS/Screen/KOSTextEditPopup.cs @@ -145,7 +145,7 @@ protected void ExitEditor() public void SaveContents() { - if (volume.Save(filePath, new FileContent(contents)) == null) + if (volume.SaveFile(filePath, new FileContent(contents)) == null) { // For some reason the normal trap that prints exceptions on // the terminal doesn't work here in this part of the code, From d6c34fa5bee83fd6f9455034e54ba779b866d2ea Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 7 Apr 2016 23:45:16 +0200 Subject: [PATCH 20/48] More copy() unit tests --- .../Persistence/CopyAndMoveTest.cs | 94 ++++++++++++++++--- 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs index ac8815a08..707340a58 100644 --- a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -59,11 +59,6 @@ public void SetupVolumes() SourceVolume.CreateFile(file1Path).WriteLn(file1); SourceVolume.CreateFile(subsubdir1File1Path).WriteLn("subsubdir1File1"); - - var dir1List = SourceVolume.Root.List(); - - GlobalPath targetPath = GlobalPath.FromString("1:/dir1/file1"); - } [Test] @@ -71,7 +66,7 @@ public void CanCopyFileToExistingFile() { GlobalPath targetPath = GlobalPath.FromString("1:/dir1/file1"); TargetVolume.CreateFile(targetPath); - volumeManager.Copy(subsubdir1File1Path, targetPath); + Assert.IsTrue(volumeManager.Copy(subsubdir1File1Path, targetPath)); Assert.AreEqual(1, TargetVolume.Root.List().Count); VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); @@ -82,7 +77,7 @@ public void CanCopyFileToExistingFile() [Test] public void CanCopyFileToNewFile() { - volumeManager.Copy(subsubdir1File1Path, GlobalPath.FromString("1:/dir1/file1")); + Assert.IsTrue(volumeManager.Copy(subsubdir1File1Path, GlobalPath.FromString("1:/dir1/file1"))); Assert.AreEqual(1, TargetVolume.Root.List().Count); VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); @@ -96,7 +91,7 @@ public void CanCopyFileToDirectory() { GlobalPath targetPath = GlobalPath.FromString("1:/dir1"); TargetVolume.CreateDirectory(targetPath); - volumeManager.Copy(subsubdir1File1Path, targetPath); + Assert.IsTrue(volumeManager.Copy(subsubdir1File1Path, targetPath)); Assert.AreEqual(1, TargetVolume.Root.List().Count); VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); @@ -123,7 +118,7 @@ public void CanFailWhenTryingToCopyDirectoryIntoItself() public void CanCopyDirectoryToExistingDirectory() { TargetVolume.CreateDirectory(VolumePath.FromString("/newdirectory")); - volumeManager.Copy(dir1Path, GlobalPath.FromString("1:/newdirectory")); + Assert.IsTrue(volumeManager.Copy(dir1Path, GlobalPath.FromString("1:/newdirectory"))); CompareDirectories(dir1Path, GlobalPath.FromString("1:/newdirectory/" + dir1)); } @@ -132,14 +127,85 @@ public void CanCopyDirectoryToExistingDirectory() public void CanCopyDirectoryToNewDirectory() { GlobalPath targetPath = GlobalPath.FromString("1:/newname"); - volumeManager.Copy(dir1Path, targetPath); + Assert.IsTrue(volumeManager.Copy(dir1Path, targetPath)); CompareDirectories(dir1Path, targetPath); } + + [Test] + public void CanFailToCopyFileIfThereIsNoSpaceToCopy() + { + if (TargetVolume.Capacity == Volume.INFINITE_CAPACITY) + { + Assert.Pass(); + return; + } + + (SourceVolume.Open(subsubdir1File1Path) as VolumeFile) + .WriteLn(new string('a', (int)TargetVolume.Capacity / 2 + 1)); + Assert.IsTrue(volumeManager.Copy(subsubdir1File1Path, GlobalPath.FromString("1:/copy1"))); + Assert.IsFalse(volumeManager.Copy(subsubdir1File1Path, GlobalPath.FromString("1:/copy2"))); + } + + [Test] + public void CanFailToCopyDirectoryIfThereIsNoSpaceToCopy() + { + if (TargetVolume.Capacity == Volume.INFINITE_CAPACITY) + { + Assert.Pass(); + return; + } + + (SourceVolume.Open(subsubdir1File1Path) as VolumeFile) + .WriteLn(new string('a', (int)TargetVolume.Capacity / 4 + 1)); + SourceVolume.CreateFile(subdir1Path.Combine("other")) + .WriteLn(new string('a', (int)TargetVolume.Capacity / 4 + 1)); + Assert.IsTrue(volumeManager.Copy(subdir1Path, GlobalPath.FromString("1:/copy1"))); + Assert.IsFalse(volumeManager.Copy(subdir1Path, GlobalPath.FromString("1:/copy2"))); + } + /* [Test] - public void CanFailIfThereIsNoSpaceToCopy() + public void CanMoveFileToExistingFile() + { + Assert.Fail(); + } + + [Test] + public void CanMoveFileToNewFile() + { + Assert.Fail(); } + + + [Test] + public void CanMoveFileToDirectory() + { + Assert.Fail(); + } + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenTryingToMoveDirectoryToAFile() + { + Assert.Fail(); + } + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenTryingToMoveDirectoryIntoItself() + { + Assert.Fail(); + } + + [Test] + public void CanMoveDirectoryToExistingDirectory() + { + Assert.Fail(); + } + + [Test] + public void CanMoveDirectoryToNewDirectory() { Assert.Fail(); } @@ -147,8 +213,14 @@ public void CanFailIfThereIsNoSpaceToCopy() [Test] public void CanMoveEvenIfThereIsNoSpaceToCopy() { + if (TargetVolume.Capacity == Volume.INFINITE_CAPACITY) + { + return; + } + Assert.Fail(); } + */ private void CompareDirectories(GlobalPath dir1Path, GlobalPath dir2Path) From 53abaed735baba316243191942a1699c168a662b Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 11 Apr 2016 00:18:13 +0200 Subject: [PATCH 21/48] move() unit tests --- .../Persistence/CopyAndMoveTest.cs | 70 +++++++++++++++---- src/kOS.Safe.Test/Persistence/VolumeTest.cs | 5 +- src/kOS.Safe/Persistence/Archive.cs | 2 +- src/kOS.Safe/Persistence/Harddisk.cs | 9 ++- src/kOS.Safe/Persistence/IVolumeManager.cs | 4 +- src/kOS.Safe/Persistence/Volume.cs | 2 +- src/kOS.Safe/Persistence/VolumeManager.cs | 58 ++++++++++----- src/kOS/Function/Persistence.cs | 7 +- 8 files changed, 115 insertions(+), 42 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs index 707340a58..42331d55f 100644 --- a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -165,63 +165,109 @@ public void CanFailToCopyDirectoryIfThereIsNoSpaceToCopy() Assert.IsFalse(volumeManager.Copy(subdir1Path, GlobalPath.FromString("1:/copy2"))); } - /* + [Test] public void CanMoveFileToExistingFile() { - Assert.Fail(); + GlobalPath targetPath = GlobalPath.FromString("1:/dir1/file1"); + TargetVolume.CreateFile(targetPath); + Assert.IsTrue(volumeManager.Move(subsubdir1File1Path, targetPath)); + + Assert.IsFalse(SourceVolume.Exists(subsubdir1File1Path)); + Assert.AreEqual(1, TargetVolume.Root.List().Count); + VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); + Assert.AreEqual(1, parent.List().Count); + Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); + } [Test] public void CanMoveFileToNewFile() { - Assert.Fail(); } + Assert.IsTrue(volumeManager.Move(subsubdir1File1Path, GlobalPath.FromString("1:/dir1/file1"))); + + Assert.IsFalse(SourceVolume.Exists(subsubdir1File1Path)); + Assert.AreEqual(1, TargetVolume.Root.List().Count); + VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); + Assert.AreEqual(1, parent.List().Count); + Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); + } [Test] public void CanMoveFileToDirectory() { - Assert.Fail(); + GlobalPath targetPath = GlobalPath.FromString("1:/dir1"); + TargetVolume.CreateDirectory(targetPath); + Assert.IsTrue(volumeManager.Move(subsubdir1File1Path, targetPath)); + + Assert.IsFalse(SourceVolume.Exists(subsubdir1File1Path)); + Assert.AreEqual(1, TargetVolume.Root.List().Count); + VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); + Assert.AreEqual(1, parent.List().Count); + Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); + } [Test] [ExpectedException(typeof(KOSPersistenceException))] public void CanFailWhenTryingToMoveDirectoryToAFile() { - Assert.Fail(); + VolumePath filePath = TargetVolume.CreateFile("newfile").Path; + volumeManager.Move(dir1Path, GlobalPath.FromString("1:/newfile")); } [Test] [ExpectedException(typeof(KOSPersistenceException))] public void CanFailWhenTryingToMoveDirectoryIntoItself() { - Assert.Fail(); + volumeManager.Move(dir1Path, subdir1Path); } [Test] public void CanMoveDirectoryToExistingDirectory() { - Assert.Fail(); + TargetVolume.CreateDirectory(VolumePath.FromString("/newdirectory")); + Assert.IsTrue(volumeManager.Move(dir1Path, GlobalPath.FromString("1:/newdirectory"))); + Assert.IsFalse(SourceVolume.Exists(dir1Path)); } [Test] public void CanMoveDirectoryToNewDirectory() { - Assert.Fail(); + GlobalPath targetPath = GlobalPath.FromString("1:/newname"); + Assert.IsTrue(volumeManager.Move(dir1Path, targetPath)); + Assert.IsFalse(SourceVolume.Exists(dir1Path)); } [Test] - public void CanMoveEvenIfThereIsNoSpaceToCopy() + public void CanFailToMoveWhenTheresNoSpaceOnTargetVolume() { - if (TargetVolume.Capacity == Volume.INFINITE_CAPACITY) + if (TargetVolume.Capacity == Volume.INFINITE_CAPACITY) { + Assert.Pass(); return; } - Assert.Fail(); + (SourceVolume.Open(subsubdir1File1Path) as VolumeFile) + .WriteLn(new string('a', (int)TargetVolume.Capacity / 2 + 1)); + Assert.IsTrue(volumeManager.Copy(subdir1Path, GlobalPath.FromString("1:/copy1"))); + Assert.IsFalse(volumeManager.Move(subdir1Path, GlobalPath.FromString("1:/copy2"))); } - */ + [Test] + public void CanMoveEvenIfThereIsNoSpaceOnSameVolume() + { + if (SourceVolume.Capacity == Volume.INFINITE_CAPACITY) + { + Assert.Pass(); + return; + } + + (SourceVolume.Open(subsubdir1File1Path) as VolumeFile) + .WriteLn(new string('a', (int)SourceVolume.Capacity / 2 + 1)); + Assert.IsTrue(volumeManager.Move(subdir1Path, GlobalPath.FromString("0:/newname"))); + } private void CompareDirectories(GlobalPath dir1Path, GlobalPath dir2Path) { diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs index 18369889b..f1004fa1e 100644 --- a/src/kOS.Safe.Test/Persistence/VolumeTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -296,10 +296,9 @@ public void CanDeleteNonExistingFiles() TestVolume.CreateFile(path); // Delete the file twice - TestVolume.Delete(path); - TestVolume.Delete(path); + Assert.IsTrue(TestVolume.Delete(path)); + Assert.IsFalse(TestVolume.Delete(path)); } } } - diff --git a/src/kOS.Safe/Persistence/Archive.cs b/src/kOS.Safe/Persistence/Archive.cs index 0d5be7c6a..c5639b2ad 100644 --- a/src/kOS.Safe/Persistence/Archive.cs +++ b/src/kOS.Safe/Persistence/Archive.cs @@ -159,7 +159,7 @@ public override bool Delete(VolumePath path, bool ksmDefault = false) return true; } - public override VolumeFile SaveFile(VolumePath path, FileContent content) + public override VolumeFile SaveFile(VolumePath path, FileContent content, bool verifyFreeSpace = true) { Directory.CreateDirectory(ArchiveFolder); diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index d0ea6fa7b..13b5d478e 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -81,6 +81,11 @@ public override bool Exists(VolumePath path, bool ksmDefault = false) { HarddiskDirectory directory = ParentDirectoryForPath(path); + if (directory == null) + { + return false; + } + return directory.Exists(path.Name, ksmDefault); } @@ -91,10 +96,10 @@ public override bool Delete(VolumePath path, bool ksmDefault = false) return directory.Delete(path.Name, ksmDefault); } - public override VolumeFile SaveFile(VolumePath path, FileContent content) + public override VolumeFile SaveFile(VolumePath path, FileContent content, bool verifyFreeSpace = true) { try { - if (!IsRoomFor(path, content)) + if (verifyFreeSpace && !IsRoomFor(path, content)) { return null; } diff --git a/src/kOS.Safe/Persistence/IVolumeManager.cs b/src/kOS.Safe/Persistence/IVolumeManager.cs index f5a73534b..517974b7f 100644 --- a/src/kOS.Safe/Persistence/IVolumeManager.cs +++ b/src/kOS.Safe/Persistence/IVolumeManager.cs @@ -20,8 +20,8 @@ public interface IVolumeManager void SwitchTo(Volume volume); void UpdateVolumes(List attachedVolumes); string GetVolumeBestIdentifier(Volume volume); - bool Copy(GlobalPath sourcePath, GlobalPath destinationPath); - void Move(GlobalPath sourcePath, GlobalPath destinationPath); + bool Copy(GlobalPath sourcePath, GlobalPath destinationPath, bool verifyFreeSpace = true); + bool Move(GlobalPath sourcePath, GlobalPath destinationPath); /// /// This creates a proper, absolute GlobalPath from the given string (which is assumed to come from the user). diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index f8158f992..e63a250e2 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -130,7 +130,7 @@ public VolumeFile SaveFile(VolumeFile volumeFile) return SaveFile(volumeFile.Path, volumeFile.ReadAll()); } - public abstract VolumeFile SaveFile(VolumePath path, FileContent content); + public abstract VolumeFile SaveFile(VolumePath path, FileContent content, bool verifyFreeSpace = true); public bool IsRoomFor(VolumePath path, FileContent fileContent) { diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index 262cd028e..3a808fc7b 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -11,7 +11,7 @@ public class VolumeManager : IVolumeManager public virtual Volume CurrentVolume { get { return CurrentDirectory != null ? CurrentDirectory.Volume : null; } } public VolumeDirectory CurrentDirectory { get; set; } private int lastId; - + public Dictionary Volumes { get { return volumes; } } public float CurrentRequiredPower { get; private set; } @@ -30,7 +30,7 @@ public bool VolumeIsCurrent(Volume volume) private int GetVolumeId(string name) { int volumeId = -1; - + foreach (KeyValuePair kvp in volumes) { if (string.Equals(kvp.Value.Name, name, StringComparison.CurrentCultureIgnoreCase)) @@ -174,7 +174,7 @@ public string GetVolumeBestIdentifier(Volume volume) if (!string.IsNullOrEmpty(volume.Name)) return string.Format("#{0}: \"{1}\"", id, volume.Name); return "#" + id; } - + /// /// Like GetVolumeBestIdentifier, but without the extra string formatting. /// @@ -222,7 +222,7 @@ private void UpdateRequiredPower() CurrentRequiredPower = (float)Math.Round(CurrentVolume.RequiredPower(), 4); } - public bool Copy(GlobalPath sourcePath, GlobalPath destinationPath) + public bool Copy(GlobalPath sourcePath, GlobalPath destinationPath, bool verifyFreeSpace = true) { Volume sourceVolume = GetVolumeFromPath(sourcePath); Volume destinationVolume = GetVolumeFromPath(destinationPath); @@ -242,21 +242,21 @@ public bool Copy(GlobalPath sourcePath, GlobalPath destinationPath) throw new KOSPersistenceException("Can't copy directory into a file"); } - return CopyDirectory(sourcePath, destinationPath); + return CopyDirectory(sourcePath, destinationPath, verifyFreeSpace); } else { if (destination is VolumeFile || destination == null) { Volume targetVolume = GetVolumeFromPath(destinationPath); - return CopyFile(source as VolumeFile, destinationPath, targetVolume); + return CopyFile(source as VolumeFile, destinationPath, targetVolume, verifyFreeSpace); } else { - return CopyFileToDirectory(source as VolumeFile, destination as VolumeDirectory); + return CopyFileToDirectory(source as VolumeFile, destination as VolumeDirectory, verifyFreeSpace); } } } - protected bool CopyDirectory(GlobalPath sourcePath, GlobalPath destinationPath) + protected bool CopyDirectory(GlobalPath sourcePath, GlobalPath destinationPath, bool verifyFreeSpace) { if (sourcePath.IsParent(destinationPath)) { @@ -290,10 +290,13 @@ protected bool CopyDirectory(GlobalPath sourcePath, GlobalPath destinationPath) { if (pair.Value is VolumeDirectory) { - CopyDirectory(sourcePath.Combine(pair.Key), destinationPath.Combine(pair.Key)); + if (!CopyDirectory(sourcePath.Combine(pair.Key), destinationPath.Combine(pair.Key), verifyFreeSpace)) + { + return false; + } } else { - if (!CopyFileToDirectory(pair.Value as VolumeFile, destination)) + if (!CopyFileToDirectory(pair.Value as VolumeFile, destination, verifyFreeSpace)) { return false; } @@ -303,19 +306,42 @@ protected bool CopyDirectory(GlobalPath sourcePath, GlobalPath destinationPath) return true; } - protected bool CopyFile(VolumeFile volumeFile, GlobalPath destinationPath, Volume targetVolume) + protected bool CopyFile(VolumeFile volumeFile, GlobalPath destinationPath, Volume targetVolume, + bool verifyFreeSpace) { - return targetVolume.SaveFile(destinationPath, volumeFile.ReadAll()) != null; + return targetVolume.SaveFile(destinationPath, volumeFile.ReadAll(), verifyFreeSpace) != null; } - protected bool CopyFileToDirectory(VolumeFile volumeFile, VolumeDirectory volumeDirectory) + protected bool CopyFileToDirectory(VolumeFile volumeFile, VolumeDirectory volumeDirectory, + bool verifyFreeSpace) { - return volumeDirectory.Volume.SaveFile(volumeDirectory.Path.Combine(volumeFile.Name), volumeFile.ReadAll()) != null; + return volumeDirectory.Volume.SaveFile(volumeDirectory.Path.Combine(volumeFile.Name), volumeFile.ReadAll(), + verifyFreeSpace) != null; } - public void Move(GlobalPath sourcePath, GlobalPath destinationPath) + public bool Move(GlobalPath sourcePath, GlobalPath destinationPath) { - throw new NotImplementedException(); + if (sourcePath.IsParent(destinationPath)) + { + throw new KOSPersistenceException("Can't move directory to a subdirectory of itself: " + destinationPath); + } + + Volume sourceVolume = GetVolumeFromPath(sourcePath); + Volume destinationVolume = GetVolumeFromPath(destinationPath); + + bool verifyFreeSpace = sourceVolume != destinationVolume; + + if (!Copy(sourcePath, destinationPath, verifyFreeSpace)) + { + return false; + } + + if (!sourceVolume.Delete(sourcePath)) + { + throw new KOSPersistenceException("Can't remove: " + sourcePath); + } + + return true; } } } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 8917723ee..a102ea845 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -221,15 +221,12 @@ public override void Execute(SharedObjects shared) string sourcePathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - SafeHouse.Logger.Log(string.Format("FunctionCopy: {0} {1}", sourcePathString, destinationPathString)); + SafeHouse.Logger.Log(string.Format("FunctionMove: {0} {1}", sourcePathString, destinationPathString)); GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); - //Copy(shared.VolumeMgr, sourcePath, destinationPath); - - //Volume sourceVolume = shared.VolumeMgr.GetVolumeFromPath(sourcePath); - //sourceVolume.Delete(sourcePath); + shared.VolumeMgr.Move(sourcePath, destinationPath); } } From e88c262b26b4946a3930a57f617f627ccb400863 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 11 Apr 2016 20:18:58 +0200 Subject: [PATCH 22/48] Bug fixes --- .../Persistence/CopyAndMoveTest.cs | 19 ++++++++++++++++++- src/kOS.Safe.Test/Persistence/VolumeTest.cs | 8 ++++++++ src/kOS.Safe/Persistence/Archive.cs | 7 +++++++ src/kOS.Safe/Persistence/Harddisk.cs | 10 ++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs index 42331d55f..cc9ea0de8 100644 --- a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -85,9 +85,19 @@ public void CanCopyFileToNewFile() Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); } + [Test] + public void CanCopyFileToRootDirectory() + { + GlobalPath targetPath = GlobalPath.FromString("1:"); + Assert.IsTrue(volumeManager.Copy(subsubdir1File1Path, targetPath)); + + Assert.AreEqual(1, TargetVolume.Root.List().Count); + VolumeFile file = (TargetVolume.Open(file1) as VolumeFile); + Assert.AreEqual("subsubdir1File1\n", file.ReadAll().String); + } [Test] - public void CanCopyFileToDirectory() + public void CanCopyFileToSubdirectory() { GlobalPath targetPath = GlobalPath.FromString("1:/dir1"); TargetVolume.CreateDirectory(targetPath); @@ -132,6 +142,13 @@ public void CanCopyDirectoryToNewDirectory() CompareDirectories(dir1Path, targetPath); } + [Test] + public void CanCopyDirectoryToRootDirectory() + { + Assert.IsTrue(volumeManager.Copy(dir1Path, GlobalPath.FromString("1:/"))); + + CompareDirectories(dir1Path, GlobalPath.FromString("1:/" + dir1)); + } [Test] public void CanFailToCopyFileIfThereIsNoSpaceToCopy() diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs index f1004fa1e..aaad3085d 100644 --- a/src/kOS.Safe.Test/Persistence/VolumeTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -142,6 +142,14 @@ public void CanDeleteNonExistingDirectories() TestVolume.Delete(path); } + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailWhenTryingToDeleteRootDirectory() + { + VolumePath path = VolumePath.FromString("/"); + TestVolume.Delete(path); + } + [Test] public void CanCreateFiles() { diff --git a/src/kOS.Safe/Persistence/Archive.cs b/src/kOS.Safe/Persistence/Archive.cs index c5639b2ad..9ef7454d6 100644 --- a/src/kOS.Safe/Persistence/Archive.cs +++ b/src/kOS.Safe/Persistence/Archive.cs @@ -60,6 +60,8 @@ public override void Clear() { Directory.Delete(ArchiveFolder, true); } + + Directory.CreateDirectory(ArchiveFolder); } public override VolumeItem Open(VolumePath path, bool ksmDefault = false) @@ -143,6 +145,11 @@ public override bool Exists(VolumePath path, bool ksmDefault = false) public override bool Delete(VolumePath path, bool ksmDefault = false) { + if (path.Depth == 0) + { + throw new KOSPersistenceException("Can't delete root directory"); + } + var fileSystemInfo = Search(path, ksmDefault); if (fileSystemInfo == null) diff --git a/src/kOS.Safe/Persistence/Harddisk.cs b/src/kOS.Safe/Persistence/Harddisk.cs index 13b5d478e..1a0fa2ad9 100644 --- a/src/kOS.Safe/Persistence/Harddisk.cs +++ b/src/kOS.Safe/Persistence/Harddisk.cs @@ -79,6 +79,11 @@ public override VolumeFile CreateFile(VolumePath path) public override bool Exists(VolumePath path, bool ksmDefault = false) { + if (path.Depth == 0) + { + return true; + } + HarddiskDirectory directory = ParentDirectoryForPath(path); if (directory == null) @@ -91,6 +96,11 @@ public override bool Exists(VolumePath path, bool ksmDefault = false) public override bool Delete(VolumePath path, bool ksmDefault = false) { + if (path.Depth == 0) + { + throw new KOSPersistenceException("Can't delete root directory"); + } + HarddiskDirectory directory = ParentDirectoryForPath(path); return directory.Delete(path.Name, ksmDefault); From dc71b4ff3fc7e5006a2a15172bb63574f28bb21c Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 11 Apr 2016 21:08:41 +0200 Subject: [PATCH 23/48] Path serialization fix --- src/kOS.Safe/Persistence/PathValue.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index b0ef08ec5..2e81639b9 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -28,12 +28,15 @@ public SafeSharedObjects Shared { } } - public PathValue(GlobalPath path, SafeSharedObjects sharedObjects) + public PathValue() + { + InitializeSuffixes(); + } + + public PathValue(GlobalPath path, SafeSharedObjects sharedObjects) : this() { Path = path; this.sharedObjects = sharedObjects; - - InitializeSuffixes(); } public PathValue FromPath(GlobalPath path) From a805f7d9f168368ce52d107187967316314c2634 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 11 Apr 2016 21:38:23 +0200 Subject: [PATCH 24/48] Handle volumes without names --- src/kOS.Safe/Persistence/GlobalPath.cs | 4 ++-- src/kOS.Safe/Persistence/PathValue.cs | 4 ++-- src/kOS.Safe/Persistence/VolumeManager.cs | 6 ++++-- src/kOS/Function/Persistence.cs | 3 ++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs index d5341a152..dae9fd579 100644 --- a/src/kOS.Safe/Persistence/GlobalPath.cs +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -77,9 +77,9 @@ public GlobalPath RootPath() return new GlobalPath(VolumeId); } - public static GlobalPath FromVolumePath(VolumePath volumePath, Volume volume) + public static GlobalPath FromVolumePath(VolumePath volumePath, string volumeId) { - return new GlobalPath(volume.Name, new List(volumePath.Segments)); + return new GlobalPath(volumeId, new List(volumePath.Segments)); } public GlobalPath ChangeExtension(string newExtension) diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index 2e81639b9..661a99d58 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -44,9 +44,9 @@ public PathValue FromPath(GlobalPath path) return new PathValue(path, sharedObjects); } - public PathValue FromPath(VolumePath volumePath, Volume volume) + public PathValue FromPath(VolumePath volumePath, string volumeId) { - return new PathValue(GlobalPath.FromVolumePath(volumePath, volume), sharedObjects); + return new PathValue(GlobalPath.FromVolumePath(volumePath, volumeId), sharedObjects); } private void InitializeSuffixes() diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index 3a808fc7b..c15231bd6 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -196,10 +196,12 @@ public GlobalPath GlobalPathFromString(string pathString) { if (GlobalPath.IsAbsolute(pathString)) { - return GlobalPath.FromVolumePath(VolumePath.FromString(pathString), CurrentVolume); + return GlobalPath.FromVolumePath(VolumePath.FromString(pathString), + GetVolumeRawIdentifier(CurrentVolume)); } else { - return GlobalPath.FromStringAndBase(pathString, GlobalPath.FromVolumePath(CurrentDirectory.Path, CurrentVolume)); + return GlobalPath.FromStringAndBase(pathString, GlobalPath.FromVolumePath(CurrentDirectory.Path, + GetVolumeRawIdentifier(CurrentVolume))); } } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index a102ea845..dfe6f5da8 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -71,7 +71,8 @@ public override void Execute(SharedObjects shared) if (remaining == 0) { - path = GlobalPath.FromVolumePath(shared.VolumeMgr.CurrentDirectory.Path, shared.VolumeMgr.CurrentVolume); + path = GlobalPath.FromVolumePath(shared.VolumeMgr.CurrentDirectory.Path, + shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume)); } else { string pathString = PopValueAssert(shared, true).ToString(); From 217862f468e29aa22381e86121c546da960d3283 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 11 Apr 2016 23:30:50 +0200 Subject: [PATCH 25/48] Fix handling cooked file names by delete() --- .../Persistence/CopyAndMoveTest.cs | 28 +++++++++++++++++-- src/kOS.Safe.Test/Persistence/VolumeTest.cs | 16 ++++++++++- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 9 +++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs index cc9ea0de8..efd821a07 100644 --- a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -17,11 +17,11 @@ public abstract class CopyAndMoveTest protected VolumeManager volumeManager; protected string dir1 = "dir1", subdir1 = "subdir1", subdir2 = "subdir2", subsubdir1 = "subsubdir1"; - protected string file1 = "file1", file2 = "file2", file3 = "file3"; + protected string file1 = "file1", file2 = "file2", file3 = "file3.ks"; protected GlobalPath dir1Path, subdir1Path, subdir2Path, subsubdir1Path; - protected GlobalPath file1Path, dir1File1Path, dir1File2Path, subdir1File1Path, subsubdir1File1Path; + protected GlobalPath file1Path, dir1File1Path, dir1File2Path, dir1File3Path, subdir1File1Path, subsubdir1File1Path; [SetUp] public void SetupLogger() @@ -48,6 +48,7 @@ public void SetupVolumes() file1Path = GlobalPath.FromString("0:" + file1); dir1File1Path = dir1Path.Combine(file1); dir1File2Path = dir1Path.Combine(file2); + dir1File3Path = dir1Path.Combine(file3); subdir1File1Path = subdir1Path.Combine(file1); subsubdir1File1Path = subsubdir1Path.Combine(file1); @@ -58,6 +59,7 @@ public void SetupVolumes() SourceVolume.CreateDirectory(subsubdir1Path); SourceVolume.CreateFile(file1Path).WriteLn(file1); + SourceVolume.CreateFile(dir1File3Path).WriteLn(file2); SourceVolume.CreateFile(subsubdir1File1Path).WriteLn("subsubdir1File1"); } @@ -96,6 +98,16 @@ public void CanCopyFileToRootDirectory() Assert.AreEqual("subsubdir1File1\n", file.ReadAll().String); } + [Test] + public void CanCopyFileByCookedName() + { + GlobalPath targetPath = GlobalPath.FromString("1:"); + Assert.IsTrue(volumeManager.Copy(dir1Path.Combine("file3"), targetPath)); + + Assert.AreEqual(1, TargetVolume.Root.List().Count); + Assert.IsTrue(TargetVolume.Root.List()[file3] is VolumeFile); + } + [Test] public void CanCopyFileToSubdirectory() { @@ -226,6 +238,18 @@ public void CanMoveFileToDirectory() } + [Test] + public void CanMoveFileByCookedName() + { + var sourcePath = dir1Path.Combine("file3"); + GlobalPath targetPath = GlobalPath.FromString("1:"); + Assert.IsTrue(volumeManager.Move(sourcePath, targetPath)); + + Assert.IsFalse(SourceVolume.Exists(sourcePath)); + Assert.AreEqual(1, TargetVolume.Root.List().Count); + Assert.IsTrue(TargetVolume.Root.List()[file3] is VolumeFile); + } + [Test] [ExpectedException(typeof(KOSPersistenceException))] public void CanFailWhenTryingToMoveDirectoryToAFile() diff --git a/src/kOS.Safe.Test/Persistence/VolumeTest.cs b/src/kOS.Safe.Test/Persistence/VolumeTest.cs index aaad3085d..ece5e651f 100644 --- a/src/kOS.Safe.Test/Persistence/VolumeTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumeTest.cs @@ -20,7 +20,6 @@ public void SetupLogger() SafeHouse.Logger = new TestLogger(); } - [Test] public void CanReturnCapacity() { @@ -295,6 +294,21 @@ public void CanDeleteFiles() Assert.AreEqual(1, dir.List().Count); Assert.AreEqual(file2, dir.List()["sub2"].Path.ToString()); Assert.IsInstanceOf(dir.List()["sub2"]); + + TestVolume.CreateFile(VolumePath.FromString(file2 + ".ks")); + Assert.IsTrue(TestVolume.Delete(VolumePath.FromString(file2 + ".ks"))); + Assert.AreEqual(1, dir.List().Count); + Assert.AreEqual(file2, dir.List()["sub2"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["sub2"]); + + TestVolume.CreateFile(VolumePath.FromString(file2 + ".ks")); + Assert.IsTrue(TestVolume.Delete(VolumePath.FromString(file2))); + Assert.AreEqual(1, dir.List().Count); + Assert.AreEqual(file2 + ".ks", dir.List()["sub2.ks"].Path.ToString()); + Assert.IsInstanceOf(dir.List()["sub2.ks"]); + + Assert.IsTrue(TestVolume.Delete(VolumePath.FromString(file2))); + Assert.AreEqual(0, dir.List().Count); } [Test] diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index e2229ba93..629f95060 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -95,7 +95,14 @@ public bool Exists(string name, bool ksmDefault) public bool Delete(string name, bool ksmDefault) { - return items.Remove(name); + var toDelete = Search(name); + + if (toDelete == null) + { + return false; + } + + return items.Remove(toDelete.Name); } public IEnumerator GetEnumerator() From 0986d10b970db5a415e531805302a29adecfd4ca Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 12 Apr 2016 13:31:26 +0200 Subject: [PATCH 26/48] Look for boot files in 'boot' directory --- src/kOS.Safe/Execution/CPU.cs | 41 +++++-- src/kOS.Safe/Execution/InternalPath.cs | 15 +++ src/kOS.Safe/Module/IProcessor.cs | 2 +- src/kOS.Safe/kOS.Safe.csproj | 1 + .../RemoteTech/RemoteTechVolumeManager.cs | 3 +- src/kOS/Function/Misc.cs | 13 +- src/kOS/KSPLogger.cs | 27 +++-- src/kOS/Module/kOSProcessor.cs | 112 +++++++++++------- src/kOS/Screen/Interpreter.cs | 21 ++-- 9 files changed, 156 insertions(+), 79 deletions(-) create mode 100644 src/kOS.Safe/Execution/InternalPath.cs diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index f14ae1675..ba126d90c 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -25,7 +25,6 @@ private enum Status private readonly VariableScope globalVariables; private Status currentStatus; private double currentTime; - private double timeWaitUntil; private readonly SafeSharedObjects shared; private readonly List contexts; private ProgramContext currentContext; @@ -122,14 +121,21 @@ public void Boot() if (!shared.Processor.CheckCanBoot()) return; - VolumePath path = shared.Processor.BootFilePath; + GlobalPath path = shared.Processor.BootFilePath; + Volume sourceVolume = shared.VolumeMgr.GetVolumeFromPath(path); // Check to make sure the boot file name is valid, and then that the boot file exists. - if (path == null) { SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); } - else if (shared.VolumeMgr.CurrentVolume.Open(path) == null) { SafeHouse.Logger.Log(string.Format("Boot file \"{0}\" is missing, skipping boot script", path)); } - else + if (path == null) + { + SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); + } + else if (sourceVolume.Open(path) == null) + { + SafeHouse.Logger.Log(string.Format("Boot file \"{0}\" is missing, skipping boot script", path)); + } else { var bootContext = "program"; - string bootCommand = string.Format("run {0}.", path); + + string bootCommand = string.Format("run {0}.", path.Name); var options = new CompilerOptions { @@ -139,7 +145,8 @@ public void Boot() }; shared.ScriptHandler.ClearContext(bootContext); - List parts = shared.ScriptHandler.Compile(GlobalPath.FromString("sysboot:"), 1, bootCommand, bootContext, options); + List parts = shared.ScriptHandler.Compile(new BootGlobalPath(bootCommand), + 1, bootCommand, bootContext, options); IProgramContext programContext = SwitchToProgramContext(); programContext.Silent = true; @@ -1388,5 +1395,25 @@ public void StopCompileStopwatch() { compileWatch.Stop(); } + + private class BootGlobalPath : InternalPath + { + private string command; + + public BootGlobalPath(string command) : base() + { + this.command = command; + } + + public override string Line(int line) + { + return command; + } + + public override string ToString() + { + return "[Boot sequence]"; + } + } } } \ No newline at end of file diff --git a/src/kOS.Safe/Execution/InternalPath.cs b/src/kOS.Safe/Execution/InternalPath.cs new file mode 100644 index 000000000..a9531375b --- /dev/null +++ b/src/kOS.Safe/Execution/InternalPath.cs @@ -0,0 +1,15 @@ +using System; +using kOS.Safe.Persistence; + +namespace kOS.Safe.Execution +{ + public abstract class InternalPath : GlobalPath + { + public InternalPath() : base("kOS") + { + + } + + public abstract string Line(int line); + } +} diff --git a/src/kOS.Safe/Module/IProcessor.cs b/src/kOS.Safe/Module/IProcessor.cs index 6db85a47f..50eb8ae56 100644 --- a/src/kOS.Safe/Module/IProcessor.cs +++ b/src/kOS.Safe/Module/IProcessor.cs @@ -9,7 +9,7 @@ public interface IProcessor /// /// Gets or sets the boot file path. Has to be a valid path or null. /// - VolumePath BootFilePath { get; set; } + GlobalPath BootFilePath { get; } bool CheckCanBoot(); string Tag { get; } diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index af77d417e..dea183655 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -251,6 +251,7 @@ + diff --git a/src/kOS/AddOns/RemoteTech/RemoteTechVolumeManager.cs b/src/kOS/AddOns/RemoteTech/RemoteTechVolumeManager.cs index 3816199a1..39ee2d9ef 100644 --- a/src/kOS/AddOns/RemoteTech/RemoteTechVolumeManager.cs +++ b/src/kOS/AddOns/RemoteTech/RemoteTechVolumeManager.cs @@ -1,6 +1,7 @@ using System; using kOS.Persistence; using kOS.Safe.Persistence; +using kOS.Safe.Exceptions; namespace kOS.AddOns.RemoteTech { @@ -26,7 +27,7 @@ private Volume GetVolumeWithRangeCheck(Volume volume) { return volume; } - throw new Exception("Volume is out of range"); + throw new KOSException("Volume is out of range"); } // check the range on the current volume without calling GetVolumeWithRangeCheck diff --git a/src/kOS/Function/Misc.cs b/src/kOS/Function/Misc.cs index 1e35ae160..5a2b2cd3a 100644 --- a/src/kOS/Function/Misc.cs +++ b/src/kOS/Function/Misc.cs @@ -246,7 +246,7 @@ public override void Execute(SharedObjects shared) { bool defaultOutput = false; bool justCompiling = false; // is this load() happening to compile, or to run? - string outPathString = null; + GlobalPath outPath = null; object topStack = PopValueAssert(shared, true); // null if there's no output file (output file means compile, not run). if (topStack != null) { @@ -255,7 +255,7 @@ public override void Execute(SharedObjects shared) if (outputArg.Equals("-default-compile-out-")) defaultOutput = true; else - outPathString = outputArg; + outPath = shared.VolumeMgr.GlobalPathFromString(outputArg); } string pathString = null; @@ -265,23 +265,20 @@ public override void Execute(SharedObjects shared) AssertArgBottomAndConsume(shared); - if (pathString == null) + if (string.IsNullOrEmpty(pathString)) throw new KOSFileException("No filename to load was given."); GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); - VolumeFile file = volume.Open(path) as VolumeFile; // if running, look for KSM first. If compiling look for KS first. + VolumeFile file = volume.Open(path, !justCompiling) as VolumeFile; // if running, look for KSM first. If compiling look for KS first. if (file == null) throw new KOSFileException(string.Format("Can't find file '{0}'.", path)); - string fileName = file.Name; // just in case Get picked an extension that changed it. FileContent fileContent = file.ReadAll(); // filename is now guaranteed to have an extension. To make default output name, replace the extension with KSM: if (defaultOutput) - outPathString = fileName.Substring(0, fileName.LastIndexOf('.')) + "." + Volume.KOS_MACHINELANGUAGE_EXTENSION; - - GlobalPath outPath = shared.VolumeMgr.GlobalPathFromString(outPathString); + outPath = path.ChangeExtension(Volume.KOS_MACHINELANGUAGE_EXTENSION); if (path.Equals(outPath)) throw new KOSFileException("Input and output paths must differ."); diff --git a/src/kOS/KSPLogger.cs b/src/kOS/KSPLogger.cs index e0bba5772..3ef02c98f 100644 --- a/src/kOS/KSPLogger.cs +++ b/src/kOS/KSPLogger.cs @@ -7,6 +7,8 @@ using kOS.Safe.Persistence; using kOS.Safe.Encapsulation; using kOS.Screen; +using kOS.Safe.Exceptions; +using kOS.Safe.Execution; namespace kOS { @@ -121,7 +123,8 @@ private string TraceLog() else msg += "Called from "; - msg += (thisOpcode is OpcodeEOF) ? "interpreter" : BuildLocationString(thisOpcode.SourcePath, thisOpcode.SourceLine); + msg += (thisOpcode is OpcodeEOF) ? Interpreter.InterpreterName + : BuildLocationString(thisOpcode.SourcePath, thisOpcode.SourceLine); msg += "\n" + textLine + "\n"; int useColumn = (thisOpcode is OpcodeEOF) ? 1 : thisOpcode.SourceColumn; @@ -152,19 +155,14 @@ private string BuildLocationString(GlobalPath path, int line) // to recalculate LOCK THROTTLE and LOCK STEERING each time there's an Update). return "(kOS built-in Update)"; } - if (path == GlobalPath.EMPTY) - { - return "<>"; - } - if (path == Interpreter.InterpreterHistory) - return string.Format("interpreter line {0}", line); return string.Format("{0}, line {1}", path, line); } private string GetSourceLine(GlobalPath path, int line) { string returnVal = "(Can't show source line)"; + if (line < 0) { // Special exception - if line number is negative then this isn't from any @@ -173,15 +171,19 @@ private string GetSourceLine(GlobalPath path, int line) return "<>"; } - if (path == GlobalPath.EMPTY) + if (path is InternalPath) { - return "<>"; + return (path as InternalPath).Line(line); } - if (path == Interpreter.InterpreterHistory) - return Shared.Interpreter.GetCommandHistoryAbsolute(line); + Volume vol; - Volume vol = Shared.VolumeMgr.GetVolumeFromPath(path); + try { + vol = Shared.VolumeMgr.GetVolumeFromPath(path); + } catch (KOSPersistenceException) + { + return returnVal; + } VolumeFile file = vol.Open(path) as VolumeFile; if (file != null) @@ -195,6 +197,7 @@ private string GetSourceLine(GlobalPath path, int line) returnVal = splitLines[line-1]; } } + return returnVal; } } diff --git a/src/kOS/Module/kOSProcessor.cs b/src/kOS/Module/kOSProcessor.cs index 6d59b3667..bb2783814 100644 --- a/src/kOS/Module/kOSProcessor.cs +++ b/src/kOS/Module/kOSProcessor.cs @@ -50,7 +50,7 @@ public string Tag private MovingAverage averagePower = new MovingAverage(); - // This is the "constant" byte count used when calculating the EC + // This is the "constant" byte count used when calculating the EC // required by the archive volume (which has infinite space). // TODO: This corresponds to the existing value and should be adjusted for balance. private const int ARCHIVE_EFFECTIVE_BYTES = 50000; @@ -58,8 +58,12 @@ public string Tag //640K ought to be enough for anybody -sic private const int PROCESSOR_HARD_CAP = 655360; + private const string BootDirectoryName = "boot"; + private GlobalPath bootDirectoryPath = GlobalPath.FromVolumePath(VolumePath.FromString(BootDirectoryName), + Archive.ArchiveName); + [KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Boot File"), UI_ChooseOption(scene = UI_Scene.Editor)] - public string bootFile = "/boot.ks"; + public string bootFile = "boot.ks"; [KSPField(isPersistant = true, guiName = "kOS Disk Space", guiActive = true)] public int diskSpace = 1024; @@ -113,12 +117,9 @@ public kOSProcessor() ProcessorMode = ProcessorModes.READY; } - public VolumePath BootFilePath { + public GlobalPath BootFilePath { get { - return VolumePath.FromString(bootFile); - } - set { - bootFile = value.ToString(); + return bootDirectoryPath.Combine(bootFile); } } @@ -216,7 +217,7 @@ public override string GetInfo() // For the sake of GetInfo, prorate the EC usage based on the smallest physics frame currently selected // Because this is called before the part is set, we need to manually calculate it instead of letting Update handle it. double power = diskSpace * ECPerBytePerSecond + defaultAvgInstructions * ECPerInstruction / Time.fixedDeltaTime; - string chargeText = (ECPerInstruction == 0) ? + string chargeText = (ECPerInstruction == 0) ? "None. It's powered by pure magic ... apparently." : // for cheaters who use MM or editing part.cfg, to get rid of it. string.Format("1 per {0} instructions executed", (int)(1 / ECPerInstruction)); return string.Format(format, diskSpace, chargeText, power, defaultAvgInstructions); @@ -257,25 +258,32 @@ public ModifierChangeWhen GetModuleMassChangeWhen() public override void OnStart(StartState state) { - //if in Editor, populate boot script selector, diskSpace selector and etc. - if (state == StartState.Editor) + try { - if (baseDiskSpace == 0) - baseDiskSpace = diskSpace; + //if in Editor, populate boot script selector, diskSpace selector and etc. + if (state == StartState.Editor) + { + if (baseDiskSpace == 0) + baseDiskSpace = diskSpace; - InitUI(); - } + InitUI(); + } + + + UpdateCostAndMass(); - UpdateCostAndMass(); + //Do not start from editor and at KSP first loading + if (state == StartState.Editor || state == StartState.None) + { + return; + } - //Do not start from editor and at KSP first loading - if (state == StartState.Editor || state == StartState.None) + SafeHouse.Logger.Log(string.Format("OnStart: {0} {1}", state, ProcessorMode)); + InitObjects(); + } catch (Exception e) { - return; + SafeHouse.Logger.LogException(e); } - - SafeHouse.Logger.Log(string.Format("OnStart: {0} {1}", state, ProcessorMode)); - InitObjects(); } private void InitUI() @@ -286,19 +294,12 @@ private void InitUI() var bootFiles = new List(); - var temp = new Archive(SafeHouse.ArchiveFolder); - var files = temp.Root.List(); - var maxchoice = 0; bootFiles.Add("None"); - foreach (KeyValuePair pair in files) - { - if (!(pair.Value is VolumeFile) || !pair.Key.StartsWith("boot", StringComparison.InvariantCultureIgnoreCase)) continue; - bootFiles.Add(pair.Key); - maxchoice++; - } - //no need to show the control if there are no files starting with boot - options.controlEnabled = maxchoice > 0; - field.guiActiveEditor = maxchoice > 0; + bootFiles.AddRange(BootDirectoryFiles()); + + //no need to show the control if there are no available boot files + options.controlEnabled = bootFiles.Count > 1; + field.guiActiveEditor = bootFiles.Count > 1; options.options = bootFiles.ToArray(); //populate diskSpaceUI selector @@ -312,10 +313,35 @@ private void InitUI() options.options = sizeOptions; } - public void InitObjects() + private IEnumerable BootDirectoryFiles() { - SafeHouse.Logger.LogWarning("InitObjects: " + (shared == null)); + var result = new List(); + + var archive = new Archive(SafeHouse.ArchiveFolder); + + var bootDirectory = archive.Open(bootDirectoryPath) as VolumeDirectory; + if (bootDirectory == null) + { + return result; + } + + var files = bootDirectory.List(); + + foreach (KeyValuePair pair in files) + { + if (pair.Value is VolumeFile && (pair.Value.Extension.Equals(Volume.KERBOSCRIPT_EXTENSION) + || pair.Value.Extension.Equals(Volume.KOS_MACHINELANGUAGE_EXTENSION))) + { + result.Add(pair.Key); + } + } + + return result; + } + + public void InitObjects() + { shared = new SharedObjects(); CreateFactory(); @@ -344,6 +370,7 @@ public void InitObjects() // initialize archive var archive = shared.Factory.CreateArchive(); + shared.VolumeMgr.Add(archive); Messages = new MessageQueue(); @@ -361,16 +388,14 @@ public void InitObjects() // populate it with the boot file, but only if using a new disk and in PRELAUNCH situation: if (vessel.situation == Vessel.Situations.PRELAUNCH && bootFile != "None" && !SafeHouse.Config.StartOnArchive) { - var bootVolumeFile = archive.Open(bootFile) as VolumeFile; + var bootVolumeFile = archive.Open(BootFilePath) as VolumeFile; if (bootVolumeFile != null) { - VolumePath bootFilePath = VolumePath.FromString(bootFile); - FileContent content = bootVolumeFile.ReadAll(); - if (HardDisk.IsRoomFor(bootFilePath, content)) - { - HardDisk.SaveFile(bootFilePath, content); - } - else + GlobalPath harddiskPath = GlobalPath.FromVolumePath( + VolumePath.FromString(BootFilePath.Name), + shared.VolumeMgr.GetVolumeRawIdentifier(HardDisk)); + + if (HardDisk.SaveFile(harddiskPath, bootVolumeFile.ReadAll()) == null) { // Throwing an exception during InitObjects will break the initialization and won't show // the error to the user. So we just log the error instead. At some point in the future @@ -380,6 +405,7 @@ public void InitObjects() } } } + shared.VolumeMgr.Add(HardDisk); // process setting diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Interpreter.cs index ddf915f33..9ff9f5b08 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Interpreter.cs @@ -12,8 +12,7 @@ namespace kOS.Screen { public class Interpreter : TextEditor, IInterpreter { - public static GlobalPath InterpreterHistory = new InterpreterPath(); - + public const string InterpreterName = "interpreter"; private readonly List commandHistory = new List(); private int commandHistoryIndex; private bool locked; @@ -142,7 +141,8 @@ protected void CompileCommand(string commandText) IsCalledFromRun = false }; - List commandParts = Shared.ScriptHandler.Compile(InterpreterHistory, commandHistoryIndex, commandText, "interpreter", options); + List commandParts = Shared.ScriptHandler.Compile(new InterpreterPath(this), + commandHistoryIndex, commandText, InterpreterName, options); if (commandParts == null) return; var interpreterContext = ((CPU)Shared.Cpu).GetInterpreterContext(); @@ -166,7 +166,7 @@ public void SetInputLock(bool isLocked) public override void Reset() { - Shared.ScriptHandler.ClearContext("interpreter"); + Shared.ScriptHandler.ClearContext(InterpreterName); commandHistory.Clear(); commandHistoryIndex = 0; base.Reset(); @@ -179,16 +179,23 @@ public override void PrintAt(string textToPrint, int row, int column) RestoreCursorPos(); } - private class InterpreterPath : GlobalPath + private class InterpreterPath : InternalPath { - public InterpreterPath() : base("Interpreter") + private Interpreter interpreter; + + public InterpreterPath(Interpreter interpreter) : base() { + this.interpreter = interpreter; + } + public override string Line(int line) + { + return interpreter.GetCommandHistoryAbsolute(line); } public override string ToString() { - return "[Interpreter]"; + return InterpreterName; } } } From 4fac4fa65e51d3c27951c2fb482bf98ad9c429d6 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Tue, 12 Apr 2016 17:43:16 +0200 Subject: [PATCH 27/48] Revert "Add deprecation messages to old rename/delete/copy syntax" This reverts commit 9ee3289f83873195fe0dbf40b45de4cd392d00fa. --- src/kOS.Safe/Compilation/Opcode.cs | 2 +- .../Exceptions/KOSAtmosphereDeprecationException.cs | 2 +- src/kOS.Safe/Exceptions/KOSDeprecationException.cs | 2 +- src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs | 2 +- src/kOS/Function/Persistence.cs | 8 ++++---- src/kOS/Suffixed/BodyAtmosphere.cs | 2 +- src/kOS/Suffixed/Part/DockingPortValue.cs | 6 +++--- src/kOS/Suffixed/VesselTarget.cs | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 9f4e6053c..9aa8aa5c6 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -652,7 +652,7 @@ public override void Execute(ICpu cpu) } else { - throw new KOSDeprecationException("0.17","UNSET ALL", ""); + throw new KOSDeprecationException("0.17","UNSET ALL", "", ""); } } } diff --git a/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs b/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs index c53eeb687..247834d9a 100644 --- a/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs +++ b/src/kOS.Safe/Exceptions/KOSAtmosphereDeprecationException.cs @@ -2,7 +2,7 @@ namespace kOS.Safe.Exceptions { public class KOSAtmosphereDeprecationException : KOSDeprecationException { - public KOSAtmosphereDeprecationException(string version, string oldUsage, string newUsage) : base(version, oldUsage, newUsage) + public KOSAtmosphereDeprecationException(string version, string oldUsage, string newUsage, string url) : base(version, oldUsage, newUsage, url) { } diff --git a/src/kOS.Safe/Exceptions/KOSDeprecationException.cs b/src/kOS.Safe/Exceptions/KOSDeprecationException.cs index 9ca6c0fa1..5a55ebba2 100644 --- a/src/kOS.Safe/Exceptions/KOSDeprecationException.cs +++ b/src/kOS.Safe/Exceptions/KOSDeprecationException.cs @@ -6,7 +6,7 @@ public class KOSDeprecationException : KOSException { protected static string TerseMessageFmt = "As of kOS {0}, {1} is obsolete and has been replaced with {2}"; - public KOSDeprecationException(string version, string oldUsage,string newUsage) : + public KOSDeprecationException(string version, string oldUsage,string newUsage, string url) : base( String.Format(TerseMessageFmt, version, oldUsage, newUsage) ) { } diff --git a/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs b/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs index ab9b3ac9c..f5aa5b5e1 100644 --- a/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs +++ b/src/kOS.Safe/Exceptions/KOSPatchesDeprecationException.cs @@ -8,7 +8,7 @@ public class KOSPatchesDeprecationException : KOSDeprecationException protected static string Url { get{return "TODO for v0.15 - Go back and fill in after docs are updated";}} public KOSPatchesDeprecationException() : - base(Version, OldUsage, NewUsage) + base(Version, OldUsage, NewUsage, Url) { } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index dfe6f5da8..62c4d1329 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -27,7 +27,7 @@ public override void Execute(SharedObjects shared) PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - throw new KOSDeprecationException("1.0.0", "`COPY FILENAME FROM VOLUMEID.` syntax", "`COPY(FROMPATH, TOPATH)`"); + throw new KOSDeprecationException("1.0.0", "`COPY FILENAME FROM VOLUMEID.` syntax", "`COPY(FROMPATH, TOPATH)`", string.Empty); } } @@ -42,7 +42,7 @@ public override void Execute(SharedObjects shared) AssertArgBottomAndConsume(shared); - throw new KOSDeprecationException("1.0.0", "`RENAME FILE OLDNAME TO NEWNAME.` syntax", "`MOVE(FROMPATH, TOPATH)`"); + throw new KOSDeprecationException("1.0.0", "`RENAME FILE OLDNAME TO NEWNAME.` syntax", "`MOVE(FROMPATH, TOPATH)`", string.Empty); } } @@ -56,7 +56,7 @@ public override void Execute(SharedObjects shared) PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - throw new KOSDeprecationException("1.0.0", "`RENAME VOLUME OLDNAME TO NEWNAME.` syntax", "`SET VOLUME:NAME TO NEWNAME.`"); + throw new KOSDeprecationException("1.0.0", "`RENAME VOLUME OLDNAME TO NEWNAME.` syntax", "`SET VOLUME:NAME TO NEWNAME.`", string.Empty); } } @@ -249,7 +249,7 @@ public override void Execute(SharedObjects shared) PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETE(PATH)`"); + throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETE(PATH)`", string.Empty); } string pathString = PopValueAssert(shared, true).ToString(); diff --git a/src/kOS/Suffixed/BodyAtmosphere.cs b/src/kOS/Suffixed/BodyAtmosphere.cs index d19d49604..64e4752f4 100644 --- a/src/kOS/Suffixed/BodyAtmosphere.cs +++ b/src/kOS/Suffixed/BodyAtmosphere.cs @@ -19,7 +19,7 @@ public BodyAtmosphere(CelestialBody celestialBody) AddSuffix("SEALEVELPRESSURE", new Suffix(()=> celestialBody.atmosphere ? celestialBody.atmospherePressureSeaLevel : 0)); AddSuffix("HEIGHT", new Suffix(()=> celestialBody.atmosphere ? celestialBody.atmosphereDepth : 0)); - AddSuffix("SCALE", new Suffix(() => { throw new KOSAtmosphereDeprecationException("0.17.2", "SCALE", ""); })); + AddSuffix("SCALE", new Suffix(() => { throw new KOSAtmosphereDeprecationException("0.17.2","SCALE","",string.Empty); })); } public override string ToString() diff --git a/src/kOS/Suffixed/Part/DockingPortValue.cs b/src/kOS/Suffixed/Part/DockingPortValue.cs index c3941fa14..bf6834c11 100644 --- a/src/kOS/Suffixed/Part/DockingPortValue.cs +++ b/src/kOS/Suffixed/Part/DockingPortValue.cs @@ -18,9 +18,9 @@ public DockingPortValue(ModuleDockingNode module, SharedObjects sharedObj) private void DockingInitializeSuffixes() { - AddSuffix("AQUIRERANGE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRERANGE", "ACQUIRERANGE"); })); - AddSuffix("AQUIREFORCE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIREFORCE", "ACQUIREFORCE"); })); - AddSuffix("AQUIRETORQUE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRETORQUE", "ACQUIRETORQUE"); })); + AddSuffix("AQUIRERANGE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRERANGE", "ACQUIRERANGE", string.Empty); })); + AddSuffix("AQUIREFORCE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIREFORCE", "ACQUIREFORCE", string.Empty); })); + AddSuffix("AQUIRETORQUE", new Suffix(() => { throw new Safe.Exceptions.KOSDeprecationException("0.18.0", "AQUIRETORQUE", "ACQUIRETORQUE", string.Empty); })); AddSuffix("ACQUIRERANGE", new Suffix(() => module.acquireRange)); AddSuffix("ACQUIREFORCE", new Suffix(() => module.acquireForce)); AddSuffix("ACQUIRETORQUE", new Suffix(() => module.acquireTorque)); diff --git a/src/kOS/Suffixed/VesselTarget.cs b/src/kOS/Suffixed/VesselTarget.cs index 4b6874e0d..a0e478556 100644 --- a/src/kOS/Suffixed/VesselTarget.cs +++ b/src/kOS/Suffixed/VesselTarget.cs @@ -435,12 +435,12 @@ private void InitializeSuffixes() AddSuffix("MASS", new Suffix(() => Vessel.GetTotalMass())); AddSuffix("VERTICALSPEED", new Suffix(() => Vessel.verticalSpeed)); AddSuffix("GROUNDSPEED", new Suffix(GetHorizontalSrfSpeed)); - AddSuffix("SURFACESPEED", new Suffix(() => { throw new KOSDeprecationException("0.18.0","SURFACESPEED","GROUNDSPEED"); })); + AddSuffix("SURFACESPEED", new Suffix(() => { throw new KOSDeprecationException("0.18.0","SURFACESPEED","GROUNDSPEED",""); })); AddSuffix("AIRSPEED", new Suffix(() => (Vessel.orbit.GetVel() - FlightGlobals.currentMainBody.getRFrmVel(Vessel.findWorldCenterOfMass())).magnitude, "the velocity of the vessel relative to the air")); AddSuffix(new[] { "SHIPNAME", "NAME" }, new SetSuffix(() => Vessel.vesselName, RenameVessel, "The KSP name for a craft, cannot be empty")); AddSuffix("TYPE", new SetSuffix(() => Vessel.vesselType.ToString(), RetypeVessel, "The Ship's KSP type (e.g. rover, base, probe)")); AddSuffix("SENSORS", new Suffix(() => new VesselSensors(Vessel))); - AddSuffix("TERMVELOCITY", new Suffix(() => { throw new KOSAtmosphereDeprecationException("17.2", "TERMVELOCITY", ""); })); + AddSuffix("TERMVELOCITY", new Suffix(() => { throw new KOSAtmosphereDeprecationException("17.2", "TERMVELOCITY", "", string.Empty); })); AddSuffix(new [] { "DYNAMICPRESSURE" , "Q"} , new Suffix(() => Vessel.dynamicPressurekPa * ConstantValue.KpaToAtm, "Dynamic Pressure in Atmospheres")); AddSuffix("LOADED", new Suffix(() => Vessel.loaded)); AddSuffix("UNPACKED", new Suffix(() => !Vessel.packed)); From 1c94210b0284182d590dd718eff5331f9cbb76f8 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Wed, 13 Apr 2016 15:23:28 +0200 Subject: [PATCH 28/48] Volume:open should return false when item does not exist --- src/kOS.Safe/Persistence/Volume.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index e63a250e2..197185cf2 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -64,6 +64,13 @@ public VolumeItem Open(string pathString, bool ksmDefault = false) return Open(VolumePath.FromString(pathString), ksmDefault); } + public Structure OpenSafe(string pathString, bool ksmDefault = false) + { + VolumeItem item = Open(VolumePath.FromString(pathString), ksmDefault); + + return item != null ? (Structure)item : BooleanValue.False; + } + /// /// Get a file given its name /// @@ -183,7 +190,7 @@ private void InitializeVolumeSuffixes() AddSuffix("FILES" , new Suffix(ListAsLexicon)); AddSuffix("CREATE" , new OneArgsSuffix(path => CreateFile(path))); AddSuffix("CREATEDIR" , new OneArgsSuffix(path => CreateDirectory(path))); - AddSuffix("OPEN" , new OneArgsSuffix(path => Open(path))); + AddSuffix("OPEN" , new OneArgsSuffix(path => OpenSafe(path))); AddSuffix("DELETE" , new OneArgsSuffix(path => Delete(path))); } } From 16acf6aaee0fa6e38728495b7a31852f9e733551 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Mon, 18 Apr 2016 15:49:52 +0200 Subject: [PATCH 29/48] Rename COPY(), MOVE() and DELETE() --- src/kOS.Safe/Compilation/KS/Compiler.cs | 2 +- src/kOS/Function/Persistence.cs | 47 +++++++++++-------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/kOS.Safe/Compilation/KS/Compiler.cs b/src/kOS.Safe/Compilation/KS/Compiler.cs index 2347d65e0..7b3a1b6a9 100644 --- a/src/kOS.Safe/Compilation/KS/Compiler.cs +++ b/src/kOS.Safe/Compilation/KS/Compiler.cs @@ -3022,7 +3022,7 @@ private void VisitDeleteStatement(ParseNode node) if (node.Nodes.Count == 5) VisitNode(node.Nodes[3]); - AddOpcode(new OpcodeCall("delete()")); + AddOpcode(new OpcodeCall("delete_deprecated()")); AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore. } diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 62c4d1329..8163d0060 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -27,7 +27,7 @@ public override void Execute(SharedObjects shared) PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - throw new KOSDeprecationException("1.0.0", "`COPY FILENAME FROM VOLUMEID.` syntax", "`COPY(FROMPATH, TOPATH)`", string.Empty); + throw new KOSDeprecationException("1.0.0", "`COPY FILENAME FROM VOLUMEID.` syntax", "`COPYPATH(FROMPATH, TOPATH)`", string.Empty); } } @@ -42,7 +42,7 @@ public override void Execute(SharedObjects shared) AssertArgBottomAndConsume(shared); - throw new KOSDeprecationException("1.0.0", "`RENAME FILE OLDNAME TO NEWNAME.` syntax", "`MOVE(FROMPATH, TOPATH)`", string.Empty); + throw new KOSDeprecationException("1.0.0", "`RENAME FILE OLDNAME TO NEWNAME.` syntax", "`MOVEPATH(FROMPATH, TOPATH)`", string.Empty); } } @@ -60,6 +60,19 @@ public override void Execute(SharedObjects shared) } } + [Function("delete_deprecated")] + public class FunctionDeleteDeprecated : FunctionBase + { + public override void Execute(SharedObjects shared) + { + PopValueAssert(shared, true); + PopValueAssert(shared, true); + AssertArgBottomAndConsume(shared); + + throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETEPATH(PATH)`", string.Empty); + } + } + [Function("path")] public class FunctionPath : FunctionBase { @@ -195,8 +208,8 @@ public override void Execute(SharedObjects shared) } } - [Function("copy")] - public class FunctionCopy : FunctionBase + [Function("copypath")] + public class FunctionCopyPath : FunctionBase { public override void Execute(SharedObjects shared) { @@ -204,8 +217,6 @@ public override void Execute(SharedObjects shared) string sourcePathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - SafeHouse.Logger.Log(string.Format("FunctionCopy: {0} {1}", sourcePathString, destinationPathString)); - GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); @@ -213,7 +224,7 @@ public override void Execute(SharedObjects shared) } } - [Function("move")] + [Function("movepath")] public class FunctionMove : FunctionBase { public override void Execute(SharedObjects shared) @@ -222,8 +233,6 @@ public override void Execute(SharedObjects shared) string sourcePathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); - SafeHouse.Logger.Log(string.Format("FunctionMove: {0} {1}", sourcePathString, destinationPathString)); - GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); @@ -231,27 +240,11 @@ public override void Execute(SharedObjects shared) } } - [Function("delete")] - public class FunctionDelete : FunctionBase + [Function("deletepath")] + public class FunctionDeletePath : FunctionBase { public override void Execute(SharedObjects shared) { - /* - * Parser will treat 'DELETE(filename)' as the old 'DELETE filename [FROM volume]' syntax. So we're unable - * to differentiate between new and old syntax. That's why currently both 'DELETE(filename)' - * and 'DELETE filename' will work. We only throw the depracation warning when 'FROM' is present, in this - * case we're sure that the user wanted to use the old syntax. - */ - int remaining = CountRemainingArgs(shared); - - if (remaining == 2) { - PopValueAssert(shared, true); - PopValueAssert(shared, true); - AssertArgBottomAndConsume(shared); - - throw new KOSDeprecationException("1.0.0", "`DELETE FILENAME FROM VOLUMEID.` syntax", "`DELETE(PATH)`", string.Empty); - } - string pathString = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); From 92e1c01754242cfbd34b055b4fd7d3c2a877ce31 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Wed, 27 Apr 2016 00:26:10 +0200 Subject: [PATCH 30/48] Updated Volume documentation --- .../structures/volumes_and_files/volume.rst | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/doc/source/structures/volumes_and_files/volume.rst b/doc/source/structures/volumes_and_files/volume.rst index 4e58f4c90..ec0c0a880 100644 --- a/doc/source/structures/volumes_and_files/volume.rst +++ b/doc/source/structures/volumes_and_files/volume.rst @@ -34,27 +34,31 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. * - :attr:`FILES` - :struct:`Lexicon` - - Lexicon of all files on the volume + - Lexicon of all files and directories on the volume * - :attr:`POWERREQUIREMENT` - :ref:`scalar ` - Amount of power consumed when this volume is set as the current volume - * - :meth:`EXISTS(filename)` + * - :meth:`EXISTS(path)` - :ref:`boolean ` - - Returns true if the given file exists + - Returns true if the given file or directory exists - * - :meth:`CREATE(filename)` + * - :meth:`CREATE(path)` - :struct:`VolumeFile` - Creates a file - * - :meth:`OPEN(filename)` - - :struct:`VolumeFile` - - Opens a file + * - :meth:`CREATEDIR(path)` + - :struct:`VolumeDirectory` + - Creates a directory + + * - :meth:`OPEN(path)` + - :struct:`VolumeItem` + - Opens a file or directory - * - :meth:`DELETE(filename)` + * - :meth:`DELETE(path)` - :ref:`boolean ` - - Deletes a file + - Deletes a file or directory .. attribute:: Volume:FREESPACE @@ -87,10 +91,10 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. .. attribute:: Volume:FILES - :type: :struct:`Lexicon` of :struct:`VolumeFile` + :type: :struct:`Lexicon` of :struct:`VolumeItem` :access: Get only - List of files on this volume. Keys are the names of all files on this volume and values are the associated :struct:`VolumeFile` structures. + List of files and directories on this volume. Keys are the names of all items on this volume and values are the associated :struct:`VolumeItem` structures. .. attribute:: Volume:POWERREQUIREMENT @@ -101,28 +105,34 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. Amount of power consumed when this volume is set as the current volume -.. method:: Volume:EXISTS(filename) +.. method:: Volume:EXISTS(path) - :return: :ref:`boolean ` + :return: :struct:`Boolean` - Returns true if the given file exists. This will also return true when the given file does not exist, but there is a file with the same name and `.ks` or `.ksm` extension added. - Use ``Volume:FILES:HASKEY(filename)`` to perform a strict check. + Returns true if the given file or directory exists. This will also return true when the given file does not exist, but there is a file with the same name and `.ks` or `.ksm` extension added. + Use ``Volume:FILES:HASKEY(name)`` to perform a strict check. -.. method:: Volume:OPEN(filename) +.. method:: Volume:OPEN(path) - :return: :struct:`VolumeFile` + :return: :struct:`VolumeItem` - Opens the file with the given name and returns :struct:`VolumeFile`. It will fail if the file doesn't exist. + Opens the file or directory pointed to by the given path and returns :struct:`VolumeItem`. It will fail if the file or directory doesn't exist. -.. method:: Volume:CREATE(filename) +.. method:: Volume:CREATE(path) :return: :struct:`VolumeFile` - Creates a file with the given name and returns :struct:`VolumeFile`. It will fail if the file already exists. + Creates a file under the given path and returns :struct:`VolumeFile`. It will fail if the file already exists. + +.. method:: Volume:CREATEDIR(path) + + :return: :struct:`VolumeDirectory` + + Creates a directory under the given path and returns :struct:`VolumeDirectory`. It will fail if the directory already exists. -.. method:: Volume:DELETE(filename) +.. method:: Volume:DELETE(path) :return: boolean - Deletes the given file. It will return true if file was successfully deleted and false otherwise. + Deletes the given file or directory (recursively). It will return true if the given item was successfully deleted and false otherwise. From a3b9bb06f0bdbcefe1a8159aae2b864583da5bd0 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Wed, 27 Apr 2016 00:26:30 +0200 Subject: [PATCH 31/48] Few more GlobalPath tests --- src/kOS.Safe.Test/Persistence/GlobalPathTest.cs | 17 +++++++++++++++++ src/kOS.Safe/Persistence/VolumePath.cs | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs index 71e6d020e..49d3f46d6 100644 --- a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs +++ b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs @@ -57,6 +57,7 @@ public void CanHandleJustVolumeName() { GlobalPath path = GlobalPath.FromString("othervolume:"); Assert.AreEqual("othervolume", path.VolumeId); + Assert.IsEmpty(path.Name); Assert.AreEqual(0, path.Depth); Assert.AreEqual(0, path.Length); } @@ -112,5 +113,21 @@ public void CanCombine() Assert.AreEqual(1, newPath.Length); Assert.AreEqual("abc", newPath.Name); } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanFailToCombineSegmentsWithSlashes() + { + GlobalPath path = GlobalPath.FromString("othervolume:123"); + path.Combine("456/abc", "789"); + } + + [Test] + [ExpectedException(typeof(KOSInvalidPathException))] + public void CanFailToCombineOutside() + { + GlobalPath path = GlobalPath.FromString("othervolume:123"); + path.Combine("..", ".."); + } } } \ No newline at end of file diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs index 5ef73cb9a..99a69d7ac 100644 --- a/src/kOS.Safe/Persistence/VolumePath.cs +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -145,6 +145,11 @@ private void Canonicalize() for (int i = 0; i < Segments.Count; i++) { + if (Segments[i].Contains(PathSeparator)) + { + throw new KOSInvalidPathException("Segment can't contain '" + PathSeparator + "'", Segments[i]); + } + if (Segments[i].Equals(UpSegment) && newSegments.Count != 0 && !newSegments.Last().Equals(UpSegment)) { newSegments.RemoveAt(newSegments.Count() - 1); From 2e1c8df025779937c3a723b9de1ae904d54195c8 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Wed, 27 Apr 2016 00:26:49 +0200 Subject: [PATCH 32/48] Path documentation --- .../structures/volumes_and_files/path.rst | 134 ++++++++++++++++++ src/kOS.Safe/Persistence/PathValue.cs | 8 +- 2 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 doc/source/structures/volumes_and_files/path.rst diff --git a/doc/source/structures/volumes_and_files/path.rst b/doc/source/structures/volumes_and_files/path.rst new file mode 100644 index 000000000..9c8b81a3a --- /dev/null +++ b/doc/source/structures/volumes_and_files/path.rst @@ -0,0 +1,134 @@ +.. _path: + +Path +==== + +Represents a path. Contains suffixes that can be helpful when using and manipulating paths. You can use +:ref:`path() ` to create new instances. + +Instances of this structure can be passed as arguments instead of ordinary, string paths, for example:: + + copypath("../file", path()). + +.. structure:: Path + + .. list-table:: + :header-rows: 1 + :widths: 2 1 4 + + * - Suffix + - Type + - Description + + * - :attr:`VOLUME` + - :struct:`Volume` + - Volume this path belongs to + * - :attr:`SEGMENTS` + - :struct:`List` of :struct:`String` + - List of this path's segments + * - :attr:`LENGTH` + - :struct:`Scalar` + - Number of segments in this path + * - :attr:`NAME` + - :struct:`String` + - Name of file or directory this path points to + * - :attr:`HASEXTENSION` + - :struct:`Boolean` + - True if path contains an extension + * - :attr:`EXTENSION` + - :struct:`String` + - This path's extension + * - :attr:`ROOT` + - :struct:`Path` + - Root path of this path's volume + * - :attr:`PARENT` + - :struct:`Path` + - Parent path + * - :meth:`CHANGEEXTENSION(extension)` + - :struct:`Path` + - Returns a new path with extension changed + * - :meth:`ISPARENT(path)` + - :struct:`Boolean` + - True if `path` is the parent of this path + * - :meth:`COMBINE(name1, [name2, ...])` + - :struct:`Path` + - Returns a new path created from this one + +.. attribute:: Path:VOLUME + + :type: :struct:`Volume` + :access: Get only + + Volume this path belongs to. + +.. attribute:: Path:SEGMENTS + + :type: :struct:`List` of :struct:`String` + :access: Get only + + List of segments this path contains. Segments are parts of the path separated by `/`. For example path `0:/directory/subdirectory/script.ks` contains the following segments: + `directory`, `subdirectory` and `script.ks`. + +.. attribute:: Path:LENGTH + + :type: :struct:`Scalar` + :access: Get only + + Number of this path's segments. + +.. attribute:: Path:NAME + + :type: :struct:`String` + :access: Get only + + Name of file or directory this path points to (same as the last segment). + + +.. attribute:: Path:HASEXTENSION + + :type: :struct:`Boolean` + :access: Get only + + True if the last segment of this path has an extension. + +.. attribute:: Path:EXTENSION + + :type: :struct:`String` + :access: Get only + + Extension of the last segment of this path. + +.. attribute:: Path:ROOT + + :type: :struct:`Path` + :access: Get only + + Returns a new path that points to the root directory of this path's volume. + +.. attribute:: Path:PARENT + + :type: :struct:`Path` + :access: Get only + + Returns a new path that points to this path's parent. This method will throw an exception if this path does not have a parent (its length is 0). + +.. method:: Path:CHANGEEXTENSION(extension) + + :parameter extension: :struct:`String` new path extension + :return: :struct:`Path` + + Will return a new path with the extension of the last segment of this path replaced (or added if there's none). + +.. method:: Path:ISPARENT(path) + + :parameter path: :struct:`Path` path to check + :return: :struct:`Boolean` + + Returns true if `path` is the parent of this path. + +.. method:: Path:COMBINE(name1, [name2, ...]) + + :parameter name: :struct:`String` segments to add + :return: :struct:`Path` + + Will return a new path created by adding segments to this path. diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index 661a99d58..52294ea90 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -52,15 +52,15 @@ public PathValue FromPath(VolumePath volumePath, string volumeId) private void InitializeSuffixes() { AddSuffix("VOLUME", new Suffix(() => sharedObjects.VolumeMgr.GetVolumeFromPath(Path))); - AddSuffix("PARENT", new Suffix(() => FromPath(Path.GetParent()))); - AddSuffix("ISPARENT", new OneArgsSuffix((p) => Path.IsParent(p.Path))); - AddSuffix("ROOT", new Suffix(() => FromPath(Path.RootPath()))); AddSuffix("SEGMENTS", new Suffix(() => new ListValue(Path.Segments.Select((s) => (Structure)new StringValue(s))))); AddSuffix("LENGTH", new Suffix(() => Path.Length)); - AddSuffix("DEPTH", new Suffix(() => Path.Depth)); AddSuffix("NAME", new Suffix(() => Path.Name)); AddSuffix("HASEXTENSION", new Suffix(() => string.IsNullOrEmpty(Path.Extension))); AddSuffix("EXTENSION", new Suffix(() => Path.Extension)); + AddSuffix("ROOT", new Suffix(() => FromPath(Path.RootPath()))); + AddSuffix("PARENT", new Suffix(() => FromPath(Path.GetParent()))); + + AddSuffix("ISPARENT", new OneArgsSuffix((p) => Path.IsParent(p.Path))); AddSuffix("CHANGEEXTENSION", new OneArgsSuffix((e) => FromPath(Path.ChangeExtension(e)))); AddSuffix("COMBINE", new VarArgsSuffix(Combine)); } From 9d3366e2b7904854e6e5006122c64e4af0f9147e Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Wed, 27 Apr 2016 00:29:07 +0200 Subject: [PATCH 33/48] Update VOLUME:OPEN documentation --- doc/source/structures/volumes_and_files/volume.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/structures/volumes_and_files/volume.rst b/doc/source/structures/volumes_and_files/volume.rst index ec0c0a880..7c405a205 100644 --- a/doc/source/structures/volumes_and_files/volume.rst +++ b/doc/source/structures/volumes_and_files/volume.rst @@ -116,7 +116,7 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. :return: :struct:`VolumeItem` - Opens the file or directory pointed to by the given path and returns :struct:`VolumeItem`. It will fail if the file or directory doesn't exist. + Opens the file or directory pointed to by the given path and returns :struct:`VolumeItem`. It will return a boolean false if the given file or directory does not exist. .. method:: Volume:CREATE(path) From e97817d90c1bf1182ddf12b089433fcfbb2b7072 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 28 Apr 2016 19:33:31 +0200 Subject: [PATCH 34/48] Bugfixes --- src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs | 11 +++++++++++ src/kOS.Safe/Persistence/GlobalPath.cs | 2 +- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 2 +- src/kOS.Safe/Persistence/VolumeItem.cs | 2 +- src/kOS.Safe/Persistence/VolumeManager.cs | 12 +++++++++--- src/kOS.Safe/Persistence/VolumePath.cs | 9 +++++++++ 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs index efd821a07..379579621 100644 --- a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -162,6 +162,14 @@ public void CanCopyDirectoryToRootDirectory() CompareDirectories(dir1Path, GlobalPath.FromString("1:/" + dir1)); } + [Test] + public void CanCopyRootDirectoryToRootDirectory() + { + Assert.IsTrue(volumeManager.Copy(GlobalPath.FromString("0:/"), GlobalPath.FromString("1:/"))); + + CompareDirectories(GlobalPath.FromString("0:/"), GlobalPath.FromString("1:/")); + } + [Test] public void CanFailToCopyFileIfThereIsNoSpaceToCopy() { @@ -318,6 +326,9 @@ private void CompareDirectories(GlobalPath dir1Path, GlobalPath dir2Path) VolumeDirectory dir1 = dir1Volume.Open(dir1Path) as VolumeDirectory; VolumeDirectory dir2 = dir2Volume.Open(dir2Path) as VolumeDirectory; + Assert.NotNull(dir1); + Assert.NotNull(dir2); + int dir1Count = dir1.List().Count; int dir2Count = dir2.List().Count; diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs index dae9fd579..865a70855 100644 --- a/src/kOS.Safe/Persistence/GlobalPath.cs +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -109,7 +109,7 @@ public GlobalPath ChangeExtension(string newExtension) return new GlobalPath(VolumeId, newSegments); } - public GlobalPath Combine(params string[] segments) + public new GlobalPath Combine(params string[] segments) { return new GlobalPath(VolumeId, Segments.Concat(segments)); } diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index 629f95060..64d46974a 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -124,7 +124,7 @@ public HarddiskDirectory GetSubdirectory(VolumePath path, bool create = false) if (!Path.IsParent(path)) { - throw new KOSException("This directory does not contain that path: " + path.ToString()); + throw new KOSException("This directory does not contain path: " + path.ToString()); } string subdirectory = path.Segments[Path.Segments.Count]; diff --git a/src/kOS.Safe/Persistence/VolumeItem.cs b/src/kOS.Safe/Persistence/VolumeItem.cs index d6337e879..f7f0e01a0 100644 --- a/src/kOS.Safe/Persistence/VolumeItem.cs +++ b/src/kOS.Safe/Persistence/VolumeItem.cs @@ -53,7 +53,7 @@ private void InitializeSuffixes() public override string ToString() { - return Path.ToString(); + return string.IsNullOrEmpty(Path.Name) ? "Root directory" : Path.Name; } public abstract int Size { get; } diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index c15231bd6..ad7744d31 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -274,12 +274,18 @@ protected bool CopyDirectory(GlobalPath sourcePath, GlobalPath destinationPath, if (destinationVolume.Exists(destinationPath)) { - destinationPath = destinationPath.Combine(sourcePath.Name); - destination = destinationVolume.CreateDirectory(destinationPath); + if (!destinationPath.IsRoot || !sourcePath.IsRoot) + { + destinationPath = destinationPath.Combine(sourcePath.Name); + destination = destinationVolume.CreateDirectory(destinationPath); + } else + { + destination = destinationVolume.Open(destinationPath) as VolumeDirectory; + } if (destination == null) { - throw new KOSException("Path was expected to point to a directory: " + destinationPath); + throw new KOSException ("Path was expected to point to a directory: " + destinationPath); } } else { diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs index 99a69d7ac..b2806de66 100644 --- a/src/kOS.Safe/Persistence/VolumePath.cs +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -30,6 +30,15 @@ public int Length { } } + /// + /// True if path is a root path. + /// + public bool IsRoot { + get { + return Segments.Count == 0; + } + } + /// /// Depth of the path. Same as Length if the path does not contain any '..'. /// From 62c84a7627b0e4e5f009cb852e80cb02543e6317 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Thu, 28 Apr 2016 23:23:00 +0200 Subject: [PATCH 35/48] copypath() bugfixes --- .../Persistence/CopyAndMoveTest.cs | 20 +++++++++++ .../Persistence/VolumePathTest.cs | 9 +++++ src/kOS.Safe/Persistence/VolumeManager.cs | 36 +++++++++++-------- src/kOS.Safe/Persistence/VolumePath.cs | 4 +++ 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs index 379579621..d128f3192 100644 --- a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -170,6 +170,26 @@ public void CanCopyRootDirectoryToRootDirectory() CompareDirectories(GlobalPath.FromString("0:/"), GlobalPath.FromString("1:/")); } + [Test] + public void CanCopyRootDirectoryToExistingDirectory() + { + TargetVolume.CreateDirectory(VolumePath.FromString("/newdirectory")); + Assert.IsTrue(volumeManager.Copy(GlobalPath.FromString("0:/"), GlobalPath.FromString("1:/newdirectory"))); + + CompareDirectories(GlobalPath.FromString("0:/"), GlobalPath.FromString("1:/newdirectory")); + } + + [Test] + public void CanCopyRootDirectoryToRootDirectoryTwice() + { + GlobalPath source = GlobalPath.FromString("0:/"); + GlobalPath destination = GlobalPath.FromString("1:/"); + Assert.IsTrue(volumeManager.Copy(source, destination)); + Assert.IsTrue(volumeManager.Copy(source, destination)); + + CompareDirectories(source, destination); + } + [Test] public void CanFailToCopyFileIfThereIsNoSpaceToCopy() { diff --git a/src/kOS.Safe.Test/Persistence/VolumePathTest.cs b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs index 05ab2eb43..e84351760 100644 --- a/src/kOS.Safe.Test/Persistence/VolumePathTest.cs +++ b/src/kOS.Safe.Test/Persistence/VolumePathTest.cs @@ -27,6 +27,15 @@ public void CanHandleRootPath() Assert.AreEqual(string.Empty, path.Extension); } + [Test] + public void CanHandleMultipleSlashes() + { + VolumePath path = VolumePath.FromString("//test//test2/"); + Assert.AreEqual(2, path.Length); + Assert.AreEqual(2, path.Depth); + Assert.AreEqual("test2", path.Name); + } + [Test] public void CanHandleSimplePath() { diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index ad7744d31..046ab59a2 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -244,6 +244,20 @@ public bool Copy(GlobalPath sourcePath, GlobalPath destinationPath, bool verifyF throw new KOSPersistenceException("Can't copy directory into a file"); } + if (destination == null) + { + destination = destinationVolume.CreateDirectory(destinationPath); + } else if (!sourcePath.IsRoot) + { + destinationPath = destinationPath.Combine(sourcePath.Name); + destination = destinationVolume.CreateDirectory(destinationPath); + } + + if (destination == null) + { + throw new KOSException("Path was expected to point to a directory: " + destinationPath); + } + return CopyDirectory(sourcePath, destinationPath, verifyFreeSpace); } else { @@ -270,24 +284,16 @@ protected bool CopyDirectory(GlobalPath sourcePath, GlobalPath destinationPath, VolumeDirectory source = sourceVolume.Open(sourcePath) as VolumeDirectory; - VolumeDirectory destination; + VolumeItem destinationItem = destinationVolume.Open(destinationPath); - if (destinationVolume.Exists(destinationPath)) + if (destinationItem is VolumeFile) { - if (!destinationPath.IsRoot || !sourcePath.IsRoot) - { - destinationPath = destinationPath.Combine(sourcePath.Name); - destination = destinationVolume.CreateDirectory(destinationPath); - } else - { - destination = destinationVolume.Open(destinationPath) as VolumeDirectory; - } + throw new KOSPersistenceException("Can't copy directory into a file"); + } - if (destination == null) - { - throw new KOSException ("Path was expected to point to a directory: " + destinationPath); - } - } else + VolumeDirectory destination = destinationItem as VolumeDirectory; + + if (destination == null) { destination = destinationVolume.CreateDirectory(destinationPath); } diff --git a/src/kOS.Safe/Persistence/VolumePath.cs b/src/kOS.Safe/Persistence/VolumePath.cs index b2806de66..89d647b85 100644 --- a/src/kOS.Safe/Persistence/VolumePath.cs +++ b/src/kOS.Safe/Persistence/VolumePath.cs @@ -154,6 +154,10 @@ private void Canonicalize() for (int i = 0; i < Segments.Count; i++) { + if (string.IsNullOrEmpty(Segments[i])) { + continue; + } + if (Segments[i].Contains(PathSeparator)) { throw new KOSInvalidPathException("Segment can't contain '" + PathSeparator + "'", Segments[i]); From ec7bbf5ae4feeb62318ce86c536cc5eca0480b27 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Fri, 29 Apr 2016 00:21:13 +0200 Subject: [PATCH 36/48] More copy() tests and bugfixes --- .../Persistence/CopyAndMoveTest.cs | 28 ++++++++++++++++++- src/kOS.Safe/Persistence/Volume.cs | 1 - src/kOS.Safe/Persistence/VolumeManager.cs | 7 ++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs index d128f3192..73bda5b45 100644 --- a/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs +++ b/src/kOS.Safe.Test/Persistence/CopyAndMoveTest.cs @@ -145,6 +145,16 @@ public void CanCopyDirectoryToExistingDirectory() CompareDirectories(dir1Path, GlobalPath.FromString("1:/newdirectory/" + dir1)); } + [Test] + public void CanCopyDirectoryToExistingDirectoryTwice() + { + TargetVolume.CreateDirectory(VolumePath.FromString("/newdirectory")); + Assert.IsTrue(volumeManager.Copy(dir1Path, GlobalPath.FromString("1:/newdirectory"))); + Assert.IsTrue(volumeManager.Copy(dir1Path, GlobalPath.FromString("1:/newdirectory"))); + + CompareDirectories(dir1Path, GlobalPath.FromString("1:/newdirectory/" + dir1)); + } + [Test] public void CanCopyDirectoryToNewDirectory() { @@ -263,7 +273,6 @@ public void CanMoveFileToDirectory() VolumeDirectory parent = (TargetVolume.Open(dir1Path) as VolumeDirectory); Assert.AreEqual(1, parent.List().Count); Assert.AreEqual("subsubdir1File1\n", (parent.List()[file1] as VolumeFile).ReadAll().String); - } [Test] @@ -309,6 +318,23 @@ public void CanMoveDirectoryToNewDirectory() Assert.IsFalse(SourceVolume.Exists(dir1Path)); } + [Test] + public void CanMoveDirectoryToRootDirectory() + { + GlobalPath targetPath = GlobalPath.FromString("1:"); + Assert.IsTrue(volumeManager.Move(dir1Path, targetPath)); + Assert.IsFalse(SourceVolume.Exists(dir1Path)); + } + + [Test] + [ExpectedException(typeof(KOSPersistenceException))] + public void CanFailToMoveRootDirectory() + { + GlobalPath sourcePath = GlobalPath.FromString("0:"); + GlobalPath targetPath = GlobalPath.FromString("1:/newname"); + volumeManager.Move(sourcePath, targetPath); + } + [Test] public void CanFailToMoveWhenTheresNoSpaceOnTargetVolume() { diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index 197185cf2..6eab60cc9 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -130,7 +130,6 @@ public bool Delete(string pathString, bool ksmDefault = false) } public abstract bool Delete(VolumePath path, bool ksmDefault = false); - //public abstract void Move(VolumePath oldPath, VolumePath newPath); public VolumeFile SaveFile(VolumeFile volumeFile) { diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index 046ab59a2..2857baff3 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -250,7 +250,7 @@ public bool Copy(GlobalPath sourcePath, GlobalPath destinationPath, bool verifyF } else if (!sourcePath.IsRoot) { destinationPath = destinationPath.Combine(sourcePath.Name); - destination = destinationVolume.CreateDirectory(destinationPath); + destination = destinationVolume.OpenOrCreateDirectory(destinationPath); } if (destination == null) @@ -335,6 +335,11 @@ protected bool CopyFileToDirectory(VolumeFile volumeFile, VolumeDirectory volume public bool Move(GlobalPath sourcePath, GlobalPath destinationPath) { + if (sourcePath.IsRoot) + { + throw new KOSPersistenceException("Can't move root directory: " + sourcePath); + } + if (sourcePath.IsParent(destinationPath)) { throw new KOSPersistenceException("Can't move directory to a subdirectory of itself: " + destinationPath); From c531db5956e9bc131de42cada95a134e83923512 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 14:13:27 +0200 Subject: [PATCH 37/48] Volumes and VolumeItems can now be used as paths --- src/kOS.Safe/Persistence/IVolumeManager.cs | 8 +-- src/kOS.Safe/Persistence/VolumeManager.cs | 22 ++++++- src/kOS/Function/Misc.cs | 17 +++-- src/kOS/Function/Persistence.cs | 73 +++++++++++----------- 4 files changed, 69 insertions(+), 51 deletions(-) diff --git a/src/kOS.Safe/Persistence/IVolumeManager.cs b/src/kOS.Safe/Persistence/IVolumeManager.cs index 517974b7f..252e51fae 100644 --- a/src/kOS.Safe/Persistence/IVolumeManager.cs +++ b/src/kOS.Safe/Persistence/IVolumeManager.cs @@ -24,16 +24,16 @@ public interface IVolumeManager bool Move(GlobalPath sourcePath, GlobalPath destinationPath); /// - /// This creates a proper, absolute GlobalPath from the given string (which is assumed to come from the user). - /// This handles absolute paths (for example 'volume:/some/path'), paths relative to current volume ('/some/path') - /// and paths relative to current directory ('../some/path', 'some/path'). + /// This creates a proper, absolute GlobalPath from the given object (which is assumed to come from the user). + /// This handles volumes, files, directories, absolute paths (for example 'volume:/some/path'), + /// paths relative to current volume ('/some/path') and paths relative to current directory ('../some/path', 'some/path'). /// /// Relative paths need current volume and current directory for resolution, that's why this method is part of this /// interface. /// /// GlobalPath instance /// Path string. - GlobalPath GlobalPathFromString(string pathString); + GlobalPath GlobalPathFromObject(object pathObject); /// /// Like GetVolumeBestIdentifier, but without the extra string formatting. diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index 2857baff3..4adf9dbe2 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using kOS.Safe.Encapsulation; using kOS.Safe.Exceptions; +using kOS.Safe.Utilities; namespace kOS.Safe.Persistence { @@ -186,8 +187,27 @@ public string GetVolumeRawIdentifier(Volume volume) return !string.IsNullOrEmpty(volume.Name) ? volume.Name : id.ToString(); } + // Volumes, VolumeItems and strings + public GlobalPath GlobalPathFromObject(object pathObject) + { + if (pathObject is Volume) + { + GlobalPath p = GlobalPath.FromVolumePath(VolumePath.EMPTY, GetVolumeRawIdentifier(pathObject as Volume)); + SafeHouse.Logger.Log("Path from volume: " + p); + return p; + } else if (pathObject is VolumeItem) + { + VolumeItem volumeItem = pathObject as VolumeItem; + return GlobalPath.FromVolumePath(volumeItem.Path, GetVolumeRawIdentifier(volumeItem.Volume)); + } else + { + return GlobalPathFromString(pathObject.ToString()); + } + + } + // Handles global, absolute and relative paths - public GlobalPath GlobalPathFromString(string pathString) + private GlobalPath GlobalPathFromString(string pathString) { if (GlobalPath.HasVolumeId(pathString)) { diff --git a/src/kOS/Function/Misc.cs b/src/kOS/Function/Misc.cs index 5a2b2cd3a..ac364eece 100644 --- a/src/kOS/Function/Misc.cs +++ b/src/kOS/Function/Misc.cs @@ -147,7 +147,7 @@ public override void Execute(SharedObjects shared) // run() is strange. It needs two levels of args - the args to itself, and the args it is meant to // pass on to the program it's invoking. First, these are the args to run itself: object volumeId = PopValueAssert(shared, true); - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); // Now the args it is going to be passing on to the program: @@ -159,7 +159,7 @@ public override void Execute(SharedObjects shared) if (shared.VolumeMgr == null) return; - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeFile volumeFile = volume.Open(path) as VolumeFile; @@ -255,20 +255,17 @@ public override void Execute(SharedObjects shared) if (outputArg.Equals("-default-compile-out-")) defaultOutput = true; else - outPath = shared.VolumeMgr.GlobalPathFromString(outputArg); + outPath = shared.VolumeMgr.GlobalPathFromObject(outputArg); } - string pathString = null; - topStack = PopValueAssert(shared, true); - if (topStack != null) - pathString = topStack.ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - if (string.IsNullOrEmpty(pathString)) + if (pathObject == null) throw new KOSFileException("No filename to load was given."); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeFile file = volume.Open(path, !justCompiling) as VolumeFile; // if running, look for KSM first. If compiling look for KS first. @@ -356,7 +353,7 @@ public override void Execute(SharedObjects shared) if (shared.VolumeMgr != null) { - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathString); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeItem volumeItem = volume.Open(path) as VolumeFile; diff --git a/src/kOS/Function/Persistence.cs b/src/kOS/Function/Persistence.cs index 8163d0060..a12b01a6c 100644 --- a/src/kOS/Function/Persistence.cs +++ b/src/kOS/Function/Persistence.cs @@ -88,8 +88,8 @@ public override void Execute(SharedObjects shared) shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume)); } else { - string pathString = PopValueAssert(shared, true).ToString(); - path = shared.VolumeMgr.GlobalPathFromString(pathString); + object pathObject = PopValueAssert(shared, true); + path = shared.VolumeMgr.GlobalPathFromObject(pathObject); } AssertArgBottomAndConsume(shared); @@ -114,6 +114,11 @@ public override void Execute(SharedObjects shared) { object volumeId = PopValueAssert(shared, true); volume = shared.VolumeMgr.GetVolume(volumeId); + + if (volume == null) + { + throw new KOSPersistenceException("Could not find volume: " + volumeId); + } } AssertArgBottomAndConsume(shared); @@ -164,10 +169,10 @@ public class FunctionEdit : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume vol = shared.VolumeMgr.GetVolumeFromPath(path); shared.Window.OpenPopupEditor(vol, path); @@ -188,16 +193,16 @@ public override void Execute(SharedObjects shared) directory = shared.VolumeMgr.CurrentVolume.Root; } else { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); directory = volume.Open(path) as VolumeDirectory; if (directory == null) { - throw new KOSException("Invalid directory: " + pathString); + throw new KOSException("Invalid directory: " + pathObject); } } @@ -213,14 +218,14 @@ public class FunctionCopyPath : FunctionBase { public override void Execute(SharedObjects shared) { - string destinationPathString = PopValueAssert(shared, true).ToString(); - string sourcePathString = PopValueAssert(shared, true).ToString(); + object destinationPathObject = PopValueAssert(shared, true); + object sourcePathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); - GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); + GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromObject(sourcePathObject); + GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromObject(destinationPathObject); - shared.VolumeMgr.Copy(sourcePath, destinationPath); + ReturnValue = shared.VolumeMgr.Copy(sourcePath, destinationPath); } } @@ -229,14 +234,14 @@ public class FunctionMove : FunctionBase { public override void Execute(SharedObjects shared) { - string destinationPathString = PopValueAssert(shared, true).ToString(); - string sourcePathString = PopValueAssert(shared, true).ToString(); + object destinationPathObject = PopValueAssert(shared, true); + object sourcePathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromString(sourcePathString); - GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromString(destinationPathString); + GlobalPath sourcePath = shared.VolumeMgr.GlobalPathFromObject(sourcePathObject); + GlobalPath destinationPath = shared.VolumeMgr.GlobalPathFromObject(destinationPathObject); - shared.VolumeMgr.Move(sourcePath, destinationPath); + ReturnValue = shared.VolumeMgr.Move(sourcePath, destinationPath); } } @@ -245,17 +250,13 @@ public class FunctionDeletePath : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); - volume.Delete(path); - if (!volume.Delete(path)) - { - throw new Exception(string.Format("Could not remove '{0}'", path)); - } + ReturnValue = volume.Delete(path); } } @@ -264,7 +265,7 @@ public class FunctionWriteJson : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); SerializableStructure serialized = PopValueAssert(shared, true) as SerializableStructure; AssertArgBottomAndConsume(shared); @@ -277,7 +278,7 @@ public override void Execute(SharedObjects shared) FileContent fileContent = new FileContent(serializedString); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); ReturnValue = volume.SaveFile(path, fileContent); @@ -289,10 +290,10 @@ public class FunctionReadJson : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeFile volumeFile = volume.Open(path) as VolumeFile; @@ -312,10 +313,10 @@ public class FunctionExists : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); ReturnValue = volume.Exists(path); @@ -327,10 +328,10 @@ public class FunctionOpen : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeItem volumeItem = volume.Open(path); @@ -349,10 +350,10 @@ public class FunctionCreate : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeFile volumeFile = volume.CreateFile(path); @@ -366,10 +367,10 @@ public class FunctionCreateDirectory : FunctionBase { public override void Execute(SharedObjects shared) { - string pathString = PopValueAssert(shared, true).ToString(); + object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); - GlobalPath path = shared.VolumeMgr.GlobalPathFromString(pathString); + GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeDirectory volumeDirectory = volume.CreateDirectory(path); From 08188bccea9386d3a8d7d08c044e50a853f77fea Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 17:59:15 +0200 Subject: [PATCH 38/48] Commands->File I/O documentation page --- doc/source/commands/files.rst | 592 ++++++++++++++++++++------------- doc/source/general/volumes.rst | 55 ++- 2 files changed, 406 insertions(+), 241 deletions(-) diff --git a/doc/source/commands/files.rst b/doc/source/commands/files.rst index 21703239f..d2700feb1 100644 --- a/doc/source/commands/files.rst +++ b/doc/source/commands/files.rst @@ -3,243 +3,393 @@ File I/O ======== -For information about where files are kept and how to deal with volumes see the :ref:`Volumes ` page in the general topics section of this documentation. +For information about where files are kept and how to deal with volumes see the +:ref:`Volumes ` page in the general topics section of this +documentation. .. contents:: :local: :depth: 2 -.. note:: - *Limitations on file names used for programs* +Understanding directories +----------------------------------- - All file names used as program names with the ``run`` command must be - valid identifiers. They can not contain spaces or special characters. For - example, you can't have a program named ``this is my-file.ks``. This rule - does not necessarily apply to other filenames such as log files. However - to use a filename that contains spaces, you will have to put quotes around - it. +kOS, just as real life filesystems, has the ability to group files into +directories. Directories can contain other directories, which can result in +a tree-like structure. - On case-sensitive filesystems typically found on Linux and Mac, you should - name program files used with the ``run`` command entirely with - lowercase-only filenames or the system may fail to find them when you - use the ``run`` command. +Directories, contrary to files, do not take up space on the volume. That means +you can have as many directories on your volume as you want. -.. warning:: +Paths +----- + +kOS uses strings of a specific format as a way of describing the location +of files and directories. We will call them path strings or simply - paths. +They will look familiar to users of most real operating systems. On Windows +for example you might have seen something like this:: + + C:\Program Files\Some Directory\SomeFile.exe + +Linux users are probably more familiar with paths that look like this:: + + /home/user/somefile + +kOS's paths are quite similar, this is how a full path string might look like:: + + 0:/lib/launch/base.ks + +There are two types of paths in kOS. Absolute paths explicitly state all data +needed to locate an item. Relative paths describe the location of an item +relative to the current directory or current volume. + +Absolute paths +~~~~~~~~~~~~~~ + +Absolute paths have the following format:: + + volumeIdOrName:[/directory/subdirectory/...]/filename + +The first slash immediately after the colon is optional. + +Examples of valid absolute paths:: + + 0:flight_data/data.json + secondcpu: // refers to the root directory of a volume + 1:/boot.ks + +You can use a special two-dot directory name - `..` - to denote the parent +of a given directory. In the following example the two paths refer to the same +file:: + + 0:/directory/subdirectory/../file + 0:/directory/file - .. versionchanged:: 0.15 +A path that points to the parent of the root directory of a volume is considered +invalid. Those paths are all invalid:: - **Archive location and file extension change** + 0:.. + 0:/../.. + 0:/directory/../.. - The Archive where KerboScript files are kept has been changed from ``Plugins/PluginData/Archive`` to ``Ships/Script``, but still under the top-level **KSP** installation directory. The file name extensions have also changes from ``.txt`` to ``.ks``. +Current directory +~~~~~~~~~~~~~~~~~ -Volume and Filename arguments ------------------------------ +To facilitate the way you interact with volumes, directories and files kOS +has a concept of current directory. That means you can make a certain directory +a `default` one and kOS will look for files you pass on to kOS commands in that +directory. Let's say for example that you're testing a script located on the +Archive volume in the `launch_scripts` directory. Normally every time you'd like +to do something with it (edit it, run it, copy it etc) you'd have to tell kOS +exactly where that file is. That could be troublesome, especially when it would +have to be done multiple times. -Any of the commands below which use filename arguments, \*\*with the -exception -of the RUN command\*\*, follow these rules: +Instead you can change your current directory using :code:`cd(path)` +(as in `change directory`) command and then refer to all the files and +directories you need by using their relative paths (read more below). -- (expression filenames) A filename may be an expression which - evaluates to a string. -- (bareword filenames) A filename may also be an undefined identifier +You can always print out the current directory's path like this:: + + PRINT PATH(). + +Remember that you can print the contents of the current directory using the +:code:`LIST` command (which is a shortcut for :code:`LIST FILES`). + +Relative paths +~~~~~~~~~~~~~~ + +Relative paths are the second way you can create paths. Those paths are +transformed by kOS into absolute paths by adding them to the current directory. + +Let's say that you've changed your current directory to :code:`0:/scripts`. +If you pass :code:`launch.ks` path to any command kOS will add it to current +directory and create an absolute path this way:: + + CD("0:/scripts"). + DELETEPATH("launch.ks"). // this will remove 0:/scripts/launch.ks + COPYPATH("../launch.ks", ""). // this will copy 0:/launch.ks to 0:/scripts/launch.ks + +As you can see above an empty relative path results in a path pointing to the +current directory. + +If a relative path starts with :code:`/` kOS will only take the current +directory's volume and add it to the relative path:: + + CD("0:/scripts"). + COPYPATH("/launch.ks", "launch_scripts"). // will copy 0:/launch.ks to 0:/scripts/launch_scripts + + +Paths and bareword arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. warning:: + + kOS has historically always allowed to skip quotes around file names in certain + cases. Although it is still possible (explanation below) we recommend against + it now. kOS 1.0 has introduced directory support and as a result the number of + cases in which omitting quotes would be fine is less than before. Paths like + :code:`../file` make things very confusing to the kOS parser because + kerboscript uses a dot to denote the end of an expression. If you're used + to skipping quotes you might find that now you will often have to add them to make + the path understandable to kOS. The only case in which you can reliably omit + quotes is when you want to use simple, relative paths: + :code:`RUN script.`, :code:`CD(dir.ext)`. + +Any of the commands below which use path arguments follow these rules: + +- A path may be an expression which evaluates to a string. +- A path may also be an undefined identifier which does not match a variable name, in which case the bare word - name of the identifier will be used as the filename. If the + name of the identifier will be used as the path. If the identifier does match a variable name, then it will be evaluated as an expression and the variable's contents will be used as the - filename. -- A bareword filename may contain file extensions with dots, provided + path. +- A bareword path may contain file extensions with dots, provided it does not end in a dot. -- Commands that try to read files will add '.ks' to filenames if - the original filename was not found, for example ``RUN abc.`` - will first look for a file named ``abc``. If such a file is not found - it will look for ``abc.ks``. +- Bareword filenames containing any characters other than A-Z, 0-9, underscore, + and the period extension separator ('.'), can only be referred to + using a string expression (with quotes), and cannot be used as a + bareword expression (without quotes). This makes it impossible to construct + valid kOS paths that contain slashes using bareword paths - you will + need to use quotes. +- If your filesystem is case-sensitive (Linux and sometimes Mac OSX, or + even Windows if using some kinds of remote network drives), then + bareword filenames will only work properly on filenames that are all + lowercase. If you try to use a file with capital letters in the name + on these systems, you will only be able to do so by quoting it. -Putting the above rules together, you can refer to filenames in any of +Putting the above rules together, you can create paths in any of the following ways: -- copy myfilename to 1. // This is an example of a bareword filename. -- copy "myfilename" to 1. // This is an example of an EXPRESSION +- COPYPATH(myfilename, "1:"). // This is an example of a bareword filename. +- COPYPATH("myfilename", "1:"). // This is an example of an EXPRESSION filename. -- copy myfilename.ks to 1. // This is an example of a bareword +- COPYPATH(myfilename.ks, "1:"). // This is an example of a bareword filename. -- copy myfilename.txt to 1. // This is an example of a bareword +- COPYPATH(myfilename.txt, "1:"). // This is an example of a bareword filename. -- copy "myfilename.ks" to 1. // This is an example of an EXPRESSION +- COPYPATH("myfilename.ks", "1:"). // This is an example of an EXPRESSION filename -- set str to "myfile" + "name" + ".ks". copy str to 1. // This is an +- SET str TO "myfile" + "name" + ".ks". COPYPATH(str, "1:"). // This is an example of an EXPRESSION filename -**Limits:** -The following rules apply as limitations to the bareword filenames: +Other data types as paths +~~~~~~~~~~~~~~~~~~~~~~~~~ -- The **RUN command only works with bareword filenames**, not - expression filenames. Every other command works with either type of - filename. -- Filenames containing any characters other than A-Z, 0-9, underscore, - and the period extension separator ('.'), can only be referred to - using a string expression (with quotes), and cannot be used as a - bareword expression (without quotes). -- If your filesystem is case-sensitive (Linux and sometimes Mac OSX, or - even Windows if using some kinds of remote network drives), then - bareword filenames will only work properly on filenames that are all - lowercase. If you try to use a file with capital letters in the name - on these systems, you will only be able to do so by quoting it. +Whenever kOS expects a path string as an argument you can actually pass +one of the following data types instead: -**Volumes too:** +- :struct:`Path` +- :struct:`Volume` - will use volume's root path +- :struct:`VolumeFile` - will use file's path +- :struct:`VolumeDirectory` - will use directory's path -The rules for filenames also apply to volumes. You may do this for -example: -- set volNum to 1. copy "myfile" to volNum. +.. _path_command: +path(pathString) +~~~~~~~~~~~~~~~~ -COMPILE program (TO compiledProgram). -------------------------------------- +Will create a :struct:`Path` structure representing the given path string. You +can omit the argument to create a :struct:`Path` for the current directory. -**(experimental)** -Arguments: +scriptpath() +~~~~~~~~~~~~ - argument 1 - Name of source file. - argument 2 - Name of destination file. If the optional argument 2 is missing, it will assume it's the same as argument 1, but with a file extension changed to ``*.ksm``. +Will return a :struct:`Path` structure representing the path to the currently +running script. -Pre-compiles a script into an :ref:`Kerboscript ML Exceutable -image ` that can be used -instead of executing the program script directly. +Volumes +------- -The RUN command (elsewhere on this page) can work with either \*.ks -script files or \*.ksm compiled files. +volume(volumeIdOrName) +~~~~~~~~~~~~~~~~~~~~~~ -The full details of this process are long and complex enough to be -placed on a separate page. +Will return a :struct:`Volume` structure representing the volume with a given +id or name. You can omit the argument to create a :struct:`Volume` +for the current volume. -Please see :ref:`the details of the Kerboscript ML -Executable `. +SWITCH TO Volume|volumeId|volumeName. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -COPY programFile FROM/TO Volume|volumeId|volumeName. ----------------------------------------------------- +Changes the current directory to the root directory of the specified volume. +Volumes can be referenced by instances of :struct:`Volume`, their ID numbers +or their names if they've been given one. Understanding how +:ref:`volumes work ` is important to understanding this command. -Arguments -^^^^^^^^^ +Example:: -- argument 1: Name of target file. -- argument 2: Target volume. + SWITCH TO 0. // Switch to volume 0. + SET VOLUME(1):NAME TO AwesomeDisk. // Name volume 1 as AwesomeDisk. + SWITCH TO AwesomeDisk. // Switch to volume 1. + PRINT VOLUME:NAME. // Prints "AwesomeDisk". -Copies a file to or from another volume. Volumes can be referenced by -instances of :struct:`Volume`, their ID numbers or their names if they’ve been given one. See LIST, -SWITCH and RENAME. -Understanding how :ref:`volumes -work ` is important to -understanding this command. +Files and directories +--------------------- -Example:: +.. warning:: - SWITCH TO 1. // Makes volume 1 the active volume - COPY file1 FROM 0. // Copies a file called file1.ks from volume 0 to volume 1 - COPY file2 TO 0. // Copies a file called file2.ks from volume 1 to volume 0 - COPY file1.ks FROM 0. // Copies a file called file1.ks from volume 0 to volume 1 - COPY file2.ksm TO 0. // Copies a file called file2.ksm from volume 1 to volume 0 - COPY "file1.ksm" FROM 0. // Copies a file called file1.ksm from volume 0 to volume 1 - COPY "file1" + "." + "ks" FROM 0. // Copies a file called file1.ks from volume 0 to volume 1 - COPY file2.ksm TO CORE:VOLUME. // Copies a file called file2.ksm to active processor's volume - COPY file2.ksm TO "other". // Copies a file called file2.ksm to volume named 'other' + .. versionchanged:: 1.0.0 + **COPY, RENAME and DELETE are now deprecated** -DELETE filename FROM Volume|volumeId|volumeName. ------------------------------------------------- + Previously you could use the aformentioned commands to manipulate files. + Currently using them will result in a deprecation message being shown. + After subdirectories were introduced in kOS 1.0 it was necessary to add + more flexible commands that could deal with both files and directories. + The old syntax was not designed with directories in mind. It would also + make it difficult for the kOS parser to properly handle paths. -Deletes a file. Volumes can be referenced by instances of :struct:`Volume`, their ID numbers or their names -if they’ve been given one. + Please update your scripts to use the new commands: + :ref:`movepath(frompath, topath)`, :ref:`copypath(frompath, topath)` and + :ref:`deletepath(path)`. -Arguments -^^^^^^^^^ +LIST +~~~~ -- argument 1: Name of target file. -- argument 2: (optional) Target volume. +Shortcut for :code:`LIST FILES`. Prints the contents (files and directories) +of the current directory. -Example:: +CD(PATH) +~~~~~~~~ - DELETE file1. // Deletes file1.ks from the active volume. - DELETE "file1". // Deletes file1.ks from the active volume. - DELETE file1.txt. // Deletes file1.txt from the active volume. - DELETE "file1.txt". // Deletes file1.txt from the active volume. - DELETE file1 FROM 1. // Deletes file1.ks from volume 1 - DELETE file1 FROM CORE:VOLUME. // Deletes file1.ks from active processor's volume - DELETE file1 FROM "other". // Deletes file1.ks from volume name 'other' +Changes the current directory to the one pointed to by the :code:`PATH` +argument. This command will fail if the path is invalid or does not point +to an existing directory. +COPYPATH(FROMPATH, TOPATH) +~~~~~~~~~~~~~~~~~~~~~~~~~~ -EDIT program. -------------- +Copies the file or directory pointed to by :code:`FROMPATH` to the location +pointed to :code:`TOPATH`. Depending on what kind of items both paths point +to the exact behaviour of this command will differ: -Edits a program on the currently selected volume. +1. :code:`FROMPATH` points to a file -Arguments -^^^^^^^^^ + - :code:`TOPATH` points to a directory -- argument 1: Name of file for editing. + The file from :code:`FROMPATH` will be copied to the directory. -.. note:: + - :code:`TOPATH` points to a file - The Edit feature was lost in version 0.11 but is back again after version 0.12.2 under a new guise. The new editor is unable to show a monospace font for a series of complex reasons involving how Unity works and how squad bundled the KSP game. The editor works, but will be in a proportional width font, which isn't ideal for editing code. The best way to edit code remains to use a text editor external to KSP, however for a fast peek at the code during play, this editor is useful. + Contents of the file pointed to by :code:`FROMPATH` will overwrite + the contents of the file pointed to by :code:`TOPATH`. -Example:: + - :code:`TOPATH` points to a non-existing path - EDIT filename. // edits filename.ks - EDIT filename.ks. // edits filename.ks - EDIT "filename.ks". // edits filename.ks - EDIT "filename". // edits filename.ks - EDIT "filename.txt". // edits filename.txt + New file will be created at :code:`TOPATH`, along with any parent + directories if necessary. Its contents will be set to the contents of + the file pointed to by :code:`FROMPATH`. +2. :code:`FROMPATH` points to a directory -LOG text TO filename. ---------------------- + If :code:`FROMPATH` points to a directory kOS will copy recursively all + contents of that directory to the target location. -Logs the selected text to a file on the local volume. Can print strings, or the result of an expression. + - :code:`TOPATH` points to a directory -Arguments -^^^^^^^^^ + The directory from :code:`FROMPATH` will be copied inside the + directory pointed to by :code:`TOPATH`. -- argument 1: Value you would like to log. -- argument 2: Name of file to log into. + - :code:`TOPATH` points to a file -Example:: + The command will fail. - LOG "Hello" to mylog.txt. // logs to "mylog.txt". - LOG 4+1 to "mylog" . // logs to "mylog.ks" because .ks is the default extension. - LOG "4 times 8 is: " + (4*8) to mylog. // logs to mylog.ks because .ks is the default extension. + - :code:`TOPATH` points to a non-existing path + New directory will be created at :code:`TOPATH`, along with any + parent directories if necessary. Its contents will be set to the + contents of the directory pointed to by :code:`FROMPATH`. -RENAME VOLUME Volume|volumeId|oldVolumeName TO name. ----------------------------------------------------- +3. :code:`FROMPATH` points to a non-existing path -RENAME FILE oldName TO newName. -------------------------------- + The command will fail. -Renames a file or volume. Volumes can be referenced by -instances of :struct:`Volume`, their ID numbers or their names if they've been given one. +MOVEPATH(FROMPATH, TOPATH) +~~~~~~~~~~~~~~~~~~~~~~~~~~ -Arguments -^^^^^^^^^ +Moves the file or directory pointed to by :code:`FROMPATH` to the location +pointed to :code:`TOPATH`. Depending on what kind of items both paths point +to the exact behaviour of this command will differ, see :code:`COPYPATH` above. + +DELETEPATH(PATH) +~~~~~~~~~~~~~~~~ + +Deleted the file or directory pointed to by :code:`FROMPATH`. Directories are +removed along with all the items they contain. + +EXISTS(PATH) +~~~~~~~~~~~~ + +A shortcut for ``CORE:CURRENTVOLUME:EXISTS(PATH)``. See :meth:`Volume:EXISTS`. + +CREATE(PATH) +~~~~~~~~~~~~ + +A shortcut for ``CORE:CURRENTVOLUME:CREATE(PATH)``. See :meth:`Volume:CREATE`. + +CREATEDIR(PATH) +~~~~~~~~~~~~~~~ -- argument 1: Volume/File Name you would like to change. -- argument 2: New name for $1. +A shortcut for ``CORE:CURRENTVOLUME:CREATEDIR(PATH)``. See :meth:`Volume:CREATE`. + +OPEN(PATH) +~~~~~~~~~~ + +A shortcut for ``CORE:CURRENTVOLUME:OPEN(PATH)``. See :meth:`Volume:OPEN`. + + +JSON +---- + +.. _writejson: + +WRITEJSON(OBJECT, PATH) +~~~~~~~~~~~~~~~~~~~~~~~ + +Serializes the given object to JSON format and saves it under the given path. + +Go to :ref:`Serialization page ` to read more about serialization. + +Usage example:: + + SET L TO LEXICON(). + SET NESTED TO QUEUE(). + + L:ADD("key1", "value1"). + L:ADD("key2", NESTED). + + NESTED:ADD("nestedvalue"). + + WRITEJSON(l, "output.json"). + +READJSON(PATH) +~~~~~~~~~~~~~~ + +Reads the contents of a file previously created using ``WRITEJSON`` and deserializes them. + +Go to :ref:`Serialization page ` to read more about serialization. Example:: - RENAME VOLUME 1 TO AwesomeDisk - RENAME FILE MyFile TO AutoLaunch. + + SET L TO READJSON("output.json"). + PRINT L["key1"]. + +Miscellaneous +------------- .. _run_once: -RUN [ONCE] . ---------------------- +RUN [ONCE] . +~~~~~~~~~~~~~~~~~~ -Runs the specified file as a program, optionally passing information to the program in the form of a comma-separated list of arguments in parentheses. +Runs the specified file as a program, optionally passing information to the +program in the form of a comma-separated list of arguments in parentheses. If the optional ``ONCE`` keyword is used after the word ``RUN``, it means the run will not actually occur if the program has already been run once @@ -250,10 +400,26 @@ in two different subprograms. ``RUN ONCE`` means "Run unless it's already been run, in which case skip it." +.. note:: + + *Limitations on file names used for programs* + + All file names used as program names with the ``run`` command must be + valid identifiers. They can not contain spaces or special characters. For + example, you can't have a program named ``this is my-file.ks``. This rule + does not necessarily apply to other filenames such as log files. However + to use a filename that contains spaces, you will have to put quotes around + it. + + On case-sensitive filesystems typically found on Linux and Mac, you should + name program files used with the ``run`` command entirely with + lowercase-only filenames or the system may fail to find them when you + use the ``run`` command. + Arguments ^^^^^^^^^ -- : File to run. +- : File to run. - comma-separated-args: a list of values to pass into the program. Example:: @@ -293,99 +459,73 @@ about them at compile time, and the filename has to be set in stone at that time. Changing this would require a large re-write of some of the architecture of the virtual machine. +LOG TEXT TO PATH +~~~~~~~~~~~~~~~~ -SWITCH TO Volume|volumeId|volumeName. -------------------------------------- - -Switches to the specified volume. Volumes can be referenced by -instances of :struct:`Volume`, their ID numbers or their names if they've been given one. See LIST and RENAME. Understanding how -:ref:`volumes work ` is important to understanding this command. +Logs the selected text to a file. Can print strings, or the result of an expression. -Example:: - - SWITCH TO 0. // Switch to volume 0. - RENAME VOLUME 1 TO AwesomeDisk. // Name volume 1 as AwesomeDisk. - SWITCH TO AwesomeDisk. // Switch to volume 1. - PRINT VOLUME:NAME. // Prints "AwesomeDisk". +Arguments +^^^^^^^^^ -EXISTS(FILENAME). ------------------ +- argument 1: Value you would like to log. +- argument 2: Path pointing to the file to log into. -A shortcut for ``CORE:CURRENTVOLUME:EXISTS(FILENAME)``. See :meth:`Volume:EXISTS`. +Example:: -CREATE(FILENAME). ------------------ + LOG "Hello" to mylog.txt. // logs to "mylog.txt". + LOG 4+1 to "mylog" . // logs to "mylog.ks" because .ks is the default extension. + LOG "4 times 8 is: " + (4*8) to mylog. // logs to mylog.ks because .ks is the default extension. -A shortcut for ``CORE:CURRENTVOLUME:CREATE(FILENAME)``. See :meth:`Volume:CREATE`. -OPEN(FILENAME). ---------------- +COMPILE PROGRAM (TO COMPILEDPROGRAM) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A shortcut for ``CORE:CURRENTVOLUME:OPEN(FILENAME)``. See :meth:`Volume:OPEN`. +**(experimental)** -.. _writejson: +Arguments: -WRITEJSON(OBJECT, FILENAME). ----------------------------- + argument 1 + Path to the source file. + argument 2 + Path to the destination file. If the optional argument 2 is missing, it will assume it's the same as argument 1, but with a file extension changed to ``*.ksm``. -Serializes the given object to JSON format and saves it under the given filename on the current volume. +Pre-compiles a script into an :ref:`Kerboscript ML Exceutable +image ` that can be used +instead of executing the program script directly. -Go to :ref:`Serialization page ` to read more about serialization. +The RUN command (elsewhere on this page) can work with either \*.ks +script files or \*.ksm compiled files. -Usage example:: +The full details of this process are long and complex enough to be +placed on a separate page. - SET L TO LEXICON(). - SET NESTED TO QUEUE(). +Please see :ref:`the details of the Kerboscript ML +Executable `. - L:ADD("key1", "value1"). - L:ADD("key2", NESTED). +EDIT PATH +--------- - NESTED:ADD("nestedvalue"). +Edits a program pointed to by :code:`PATH`. - WRITEJSON(l, "output.json"). +Arguments +^^^^^^^^^ -READJSON(FILENAME). -------------------- +- argument 1: Path of the file for editing. -Reads the contents of a file previously created using ``WRITEJSON`` and deserializes them. +.. note:: -Go to :ref:`Serialization page ` to read more about serialization. + The Edit feature was lost in version 0.11 but is back again after version + 0.12.2 under a new guise. The new editor is unable to show a monospace + font for a series of complex reasons involving how Unity works and how + Squad bundled the KSP game. The editor works, but will be in a proportional + width font, which isn't ideal for editing code. The best way to edit code + remains to use a text editor external to KSP, however for a fast peek at + the code during play, this editor is useful. Example:: - - SET L TO READJSON("output.json"). - PRINT L["key1"]. - - -.. _boot: - -Special handling of files starting with "boot" (example ``boot.ks``) --------------------------------------------------------------------- -**(experimental)** - -For users requiring even more automation, the feature of custom boot scripts was introduced. If you have at least 1 file in your Archive volume starting with "boot" (for example "boot.ks", "boot2.ks" or even "boot_custom_script.ks"), you will be presented with the option to choose one of those files as a boot script for your kOS CPU. - -.. image:: http://i.imgur.com/05kp7Sy.jpg - -As soon as you vessel leaves VAB/SPH and is being initialised on the launchpad (e.g. its status is PRELAUNCH) the assigned script will be copied to CPU's local hard disk with the same name. If kOS is configured to start on the archive, the file will not be copied locally automatically. This script will be run as soon as CPU boots, e.g. as soon as you bring your CPU in physics range or power on your CPU if it was turned off. You may get or set the name of the boot file using the :ref:`core:bootfilename` suffix. - -.. warning:: - - .. versionchanged:: 0.18 - - **boot file name changed** - - Previously boot files were copied to the local hard disk as "boot.ks". This behaviour was changed so that boot files could be handled consistently if kOS is configured to start on the Archive. Some scripts may have terminated with a generic "delete boot." line to clear the boot script. Going forward you should use the new core:bootfilename suffix when dealing the boot file. - -Important things to consider: - * kOS CPU hard disk space is limited, avoid using complex boot scripts or increase disk space using MM config. - * Boot script runs immediately on initialisation, it should avoid interaction with parts/modules until physics fully load. It is best to wait for couple seconds or until certain trigger. - - -Possible uses for boot scripts: - - * Automatically activate sleeper/background scripts which will run on CPU until triggered by certain condition. - * Create basic station-keeping scripts - you will only have to focus your probes once in a while and let the boot script do the orbit adjustment automatically. - * Create multi-CPU vessels with certain cores dedicated to specific tasks, triggered by user input or external events (Robotic-heavy Vessels) - * Anything else you can come up with + EDIT filename. // edits filename.ks + EDIT filename.ks. // edits filename.ks + EDIT "filename.ks". // edits filename.ks + EDIT "filename". // edits filename.ks + EDIT "filename.txt". // edits filename.txt diff --git a/doc/source/general/volumes.rst b/doc/source/general/volumes.rst index e55c7ab9e..ed7945492 100644 --- a/doc/source/general/volumes.rst +++ b/doc/source/general/volumes.rst @@ -4,20 +4,12 @@ Files and Volumes ================= -Using the COPY, SWITCH, DELETE, and RENAME commands, you can manipulate the archive and the volumes as described in the :ref:`File I/O page `. But before you do that, it's useful to know how kOS manages the archive and the volumes, and what they mean. +Using the COPYPATH, SWITCH, DELETEPATH, and RENAMEPATH commands, you can manipulate the archive and the volumes as described in the :ref:`File I/O page `. But before you do that, it's useful to know how kOS manages the archive and the volumes, and what they mean. .. contents:: :local: :depth: 2 -.. warning:: - - .. versionchanged:: 0.15 - - **Archive location and file extension change** - - The Archive where KerboScript files are kept has been changed from ``Plugins/PluginData/Archive`` to ``Ships/Script``, but still under the top-level **KSP** installation directory. The file name extensions have also changes from ``.txt`` to ``.ks``. - Script Files ------------ @@ -53,7 +45,7 @@ program on it. To simulate the sense that this game takes place at the dawn of the space race with 1960's and 1970's technology, the storage capacity of a volume is very limited. -For example, the CX-4181 Scriptable Control System part defaults to only +For example, the CX-4181 Scriptable Control System part defaults to only allowing 1000 bytes of storage. The byte count of a program is just the @@ -99,8 +91,7 @@ volume inside it. If you have multiple CX-4181 parts on the same craft, they are assumed to be networked together on the same system, and capable of reading each other's hard drives. Their disk drives each have a different Volume, and -by default they are simply numbered 1,2,3, … unless you rename them with -the RENAME command. +by default they are simply numbered 1,2,3, … unless you rename them. For example, if you have two CX-4181's on the same craft, called 1 and 2, with volumes on them called 1 and 2, respectively, it is possible for @@ -116,11 +107,13 @@ different CPUs. The same volume which was called '2' when one CPU was looking at it might instead be called '1' when a different CPU is looking at it. Each CPU thinks of its OWN volume as number '1'. -Therefore using the RENAME command on the volumes is useful when dealing +Therefore using the SET command on the volumes is useful when dealing with multiple CX-4181's on the same vessel, so they all will refer to -the volumes using the same names. +the volumes using the same names:: + + SET VOLUME("0"):NAME TO "newname". -If a kOS processor has a name tag set, then that processor's volume +If a kOS processor has a name tag set, then that processor's volume will have its name initially set to the value of the name tag. Archive @@ -164,3 +157,35 @@ volume but with the following exceptions: constitutes a form of cheating similar to any other edit of the persistence file. +.. _boot: + +Special handling of files in the "boot" directory +------------------------------------------------- + +For users requiring even more automation, the feature of custom boot scripts +was introduced. If you have at least 1 file in the :code:`boot` directory on +your Archive volume, you will be presented with the option to choose one of +those files as a boot script for your kOS CPU. + +.. image:: http://i.imgur.com/05kp7Sy.jpg + +As soon as you vessel leaves VAB/SPH and is being initialised on the launchpad +(e.g. its status is PRELAUNCH) the assigned script will be copied to CPU's +local hard disk with the same name. If kOS is configured to start on the +archive, the file will not be copied locally automatically. This script will +be run as soon as CPU boots, e.g. as soon as you bring your CPU in physics +range or power on your CPU if it was turned off. You may get or set the name +of the boot file using the :attr:`kOSProcessor:BOOTFILENAME` suffix. + +Important things to consider: + + * kOS CPU hard disk space is limited, avoid using complex boot scripts or increase disk space using MM config. + * Boot script runs immediately on initialisation, it should avoid interaction with parts/modules until physics fully load. It is best to wait for couple seconds or until certain trigger. + +Possible uses for boot scripts: + + * Automatically activate sleeper/background scripts which will run on CPU until triggered by certain condition. + * Create basic station-keeping scripts - you will only have to focus your probes once in a while and let the boot script do the orbit adjustment automatically. + * Create multi-CPU vessels with certain cores dedicated to specific tasks, triggered by user input or external events (Robotic-heavy Vessels) + * Anything else you can come up with + From bb451908752eaeba6e92e2abe4adedb6c07bdc7c Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 18:54:20 +0200 Subject: [PATCH 39/48] Add PathValue:CHANGENAME --- doc/source/structures/volumes_and_files/path.rst | 10 ++++++++++ src/kOS.Safe.Test/Persistence/GlobalPathTest.cs | 16 ++++++++++++++++ src/kOS.Safe/Persistence/GlobalPath.cs | 15 +++++++++++++++ src/kOS.Safe/Persistence/PathValue.cs | 1 + 4 files changed, 42 insertions(+) diff --git a/doc/source/structures/volumes_and_files/path.rst b/doc/source/structures/volumes_and_files/path.rst index 9c8b81a3a..6299adf30 100644 --- a/doc/source/structures/volumes_and_files/path.rst +++ b/doc/source/structures/volumes_and_files/path.rst @@ -44,6 +44,9 @@ Instances of this structure can be passed as arguments instead of ordinary, stri * - :attr:`PARENT` - :struct:`Path` - Parent path + * - :meth:`CHANGENAME(name)` + - :struct:`Path` + - Returns a new path with its name (last segment) changed * - :meth:`CHANGEEXTENSION(extension)` - :struct:`Path` - Returns a new path with extension changed @@ -112,6 +115,13 @@ Instances of this structure can be passed as arguments instead of ordinary, stri Returns a new path that points to this path's parent. This method will throw an exception if this path does not have a parent (its length is 0). +.. method:: Path:CHANGENAME(name) + + :parameter name: :struct:`String` new path name + :return: :struct:`Path` + + Will return a new path with the value of the last segment of this path replaced (or added if there's none). + .. method:: Path:CHANGEEXTENSION(extension) :parameter extension: :struct:`String` new path extension diff --git a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs index 49d3f46d6..c2a841093 100644 --- a/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs +++ b/src/kOS.Safe.Test/Persistence/GlobalPathTest.cs @@ -69,6 +69,22 @@ public void CanHandleGlobalPathWithLessThanZeroDepth() GlobalPath.FromString("othervolume:/test/../../"); } + [Test] + public void CanChangeName() + { + GlobalPath path = GlobalPath.FromString("othervolume:123"); + GlobalPath newPath = path.ChangeName("abc"); + Assert.AreEqual("othervolume", newPath.VolumeId); + Assert.AreEqual(1, newPath.Length); + Assert.AreEqual("abc", newPath.Name); + + path = GlobalPath.FromString("othervolume:/dir/file.jpg"); + newPath = path.ChangeName("new.txt"); + Assert.AreEqual("othervolume", newPath.VolumeId); + Assert.AreEqual(2, newPath.Length); + Assert.AreEqual("new.txt", newPath.Name); + } + [Test] public void CanChangeExtension() { diff --git a/src/kOS.Safe/Persistence/GlobalPath.cs b/src/kOS.Safe/Persistence/GlobalPath.cs index 865a70855..10663f458 100644 --- a/src/kOS.Safe/Persistence/GlobalPath.cs +++ b/src/kOS.Safe/Persistence/GlobalPath.cs @@ -82,6 +82,21 @@ public static GlobalPath FromVolumePath(VolumePath volumePath, string volumeId) return new GlobalPath(volumeId, new List(volumePath.Segments)); } + public GlobalPath ChangeName(string newName) + { + if (Segments.Count == 0) + { + throw new KOSInvalidPathException("This path points to the root directory, you can't change its name", + this.ToString()); + } + + List newSegments = new List(Segments); + newSegments.RemoveAt(newSegments.Count - 1); + newSegments.Add(newName); + + return new GlobalPath(VolumeId, newSegments); + } + public GlobalPath ChangeExtension(string newExtension) { if (Segments.Count == 0) diff --git a/src/kOS.Safe/Persistence/PathValue.cs b/src/kOS.Safe/Persistence/PathValue.cs index 52294ea90..8bcf87abb 100644 --- a/src/kOS.Safe/Persistence/PathValue.cs +++ b/src/kOS.Safe/Persistence/PathValue.cs @@ -61,6 +61,7 @@ private void InitializeSuffixes() AddSuffix("PARENT", new Suffix(() => FromPath(Path.GetParent()))); AddSuffix("ISPARENT", new OneArgsSuffix((p) => Path.IsParent(p.Path))); + AddSuffix("CHANGENAME", new OneArgsSuffix((n) => FromPath(Path.ChangeName(n)))); AddSuffix("CHANGEEXTENSION", new OneArgsSuffix((e) => FromPath(Path.ChangeExtension(e)))); AddSuffix("COMBINE", new VarArgsSuffix(Combine)); } From 3ed5a0f7e0496d15ebb51e8cee0afed90698fe4f Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 19:36:24 +0200 Subject: [PATCH 40/48] Make directories enumerable --- src/kOS.Safe/Persistence/VolumeDirectory.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/kOS.Safe/Persistence/VolumeDirectory.cs b/src/kOS.Safe/Persistence/VolumeDirectory.cs index 9f4ce4cbc..23ec57408 100644 --- a/src/kOS.Safe/Persistence/VolumeDirectory.cs +++ b/src/kOS.Safe/Persistence/VolumeDirectory.cs @@ -3,11 +3,12 @@ using System.Collections.Generic; using kOS.Safe.Encapsulation; using kOS.Safe.Encapsulation.Suffixes; +using System.Collections; namespace kOS.Safe { [kOS.Safe.Utilities.KOSNomenclature("VolumeDirectory")] - public abstract class VolumeDirectory : VolumeItem + public abstract class VolumeDirectory : VolumeItem, IEnumerable { public VolumeDirectory(Volume volume, VolumePath path) : base(volume, path) { @@ -28,6 +29,16 @@ public Lexicon ListAsLexicon() public abstract IDictionary List(); + public IEnumerator GetEnumerator() + { + return List().Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + private void InitializeSuffixes() { AddSuffix("LIST", new Suffix(ListAsLexicon)); From f509340538afebf32bce1e4681c4bf1f95aa5f7b Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 20:32:02 +0200 Subject: [PATCH 41/48] Removed VolumeManager's electric power code It appears to have not been used, but was causing exceptions in certain cases --- src/kOS.Safe/Persistence/IVolumeManager.cs | 1 - src/kOS.Safe/Persistence/VolumeManager.cs | 9 --------- 2 files changed, 10 deletions(-) diff --git a/src/kOS.Safe/Persistence/IVolumeManager.cs b/src/kOS.Safe/Persistence/IVolumeManager.cs index 252e51fae..0f8dc68e9 100644 --- a/src/kOS.Safe/Persistence/IVolumeManager.cs +++ b/src/kOS.Safe/Persistence/IVolumeManager.cs @@ -7,7 +7,6 @@ public interface IVolumeManager Dictionary Volumes { get; } Volume CurrentVolume { get; } VolumeDirectory CurrentDirectory { get; set; } - float CurrentRequiredPower { get; } bool VolumeIsCurrent(Volume volume); int GetVolumeId(Volume volume); Volume GetVolume(object volumeId); diff --git a/src/kOS.Safe/Persistence/VolumeManager.cs b/src/kOS.Safe/Persistence/VolumeManager.cs index 4adf9dbe2..55e181b3f 100644 --- a/src/kOS.Safe/Persistence/VolumeManager.cs +++ b/src/kOS.Safe/Persistence/VolumeManager.cs @@ -14,7 +14,6 @@ public class VolumeManager : IVolumeManager private int lastId; public Dictionary Volumes { get { return volumes; } } - public float CurrentRequiredPower { get; private set; } public VolumeManager() { @@ -103,7 +102,6 @@ public void Add(Volume volume) if (CurrentDirectory == null) { CurrentDirectory = volumes[0].Root; - UpdateRequiredPower(); } } } @@ -126,7 +124,6 @@ public void Remove(int id) if (volumes.Count > 0) { CurrentDirectory = volumes[0].Root; - UpdateRequiredPower(); } else { @@ -139,7 +136,6 @@ public void Remove(int id) public void SwitchTo(Volume volume) { CurrentDirectory = volume.Root; - UpdateRequiredPower(); } public void UpdateVolumes(List attachedVolumes) @@ -239,11 +235,6 @@ public Volume GetVolumeFromPath(GlobalPath path) return volume; } - private void UpdateRequiredPower() - { - CurrentRequiredPower = (float)Math.Round(CurrentVolume.RequiredPower(), 4); - } - public bool Copy(GlobalPath sourcePath, GlobalPath destinationPath, bool verifyFreeSpace = true) { Volume sourceVolume = GetVolumeFromPath(sourcePath); From 6e46db044d05230c8c615db853d042df19ed0027 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 20:34:47 +0200 Subject: [PATCH 42/48] Fix some errors on the Commands/FileIO page --- doc/source/commands/files.rst | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/source/commands/files.rst b/doc/source/commands/files.rst index d2700feb1..338793453 100644 --- a/doc/source/commands/files.rst +++ b/doc/source/commands/files.rst @@ -245,8 +245,9 @@ Files and directories make it difficult for the kOS parser to properly handle paths. Please update your scripts to use the new commands: - :ref:`movepath(frompath, topath)`, :ref:`copypath(frompath, topath)` and - :ref:`deletepath(path)`. + :ref:`movepath(frompath, topath) `, + :ref:`copypath(frompath, topath) ` and + :ref:`deletepath(path) `. LIST ~~~~ @@ -261,6 +262,8 @@ Changes the current directory to the one pointed to by the :code:`PATH` argument. This command will fail if the path is invalid or does not point to an existing directory. +.. _copypath: + COPYPATH(FROMPATH, TOPATH) ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -309,6 +312,8 @@ to the exact behaviour of this command will differ: The command will fail. +.. _movepath: + MOVEPATH(FROMPATH, TOPATH) ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -316,6 +321,8 @@ Moves the file or directory pointed to by :code:`FROMPATH` to the location pointed to :code:`TOPATH`. Depending on what kind of items both paths point to the exact behaviour of this command will differ, see :code:`COPYPATH` above. +.. _deletepath: + DELETEPATH(PATH) ~~~~~~~~~~~~~~~~ @@ -325,22 +332,29 @@ removed along with all the items they contain. EXISTS(PATH) ~~~~~~~~~~~~ -A shortcut for ``CORE:CURRENTVOLUME:EXISTS(PATH)``. See :meth:`Volume:EXISTS`. +Returns true if there exists a file or a directory under the given path, +otherwise returns false. Also see :meth:`Volume:EXISTS`. CREATE(PATH) ~~~~~~~~~~~~ -A shortcut for ``CORE:CURRENTVOLUME:CREATE(PATH)``. See :meth:`Volume:CREATE`. +Creates a file under the given path. Will create parent directories if needed. +It will fail if a file or a directory already exists under the given path. +Also see :meth:`Volume:CREATE`. CREATEDIR(PATH) ~~~~~~~~~~~~~~~ -A shortcut for ``CORE:CURRENTVOLUME:CREATEDIR(PATH)``. See :meth:`Volume:CREATE`. +Creates a directory under the given path. Will create parent directories +if needed. It will fail if a file or a directory already exists under the +given path. Also see :meth:`Volume:CREATEDIR`. OPEN(PATH) ~~~~~~~~~~ -A shortcut for ``CORE:CURRENTVOLUME:OPEN(PATH)``. See :meth:`Volume:OPEN`. +Will return a :struct:`VolumeFile` or :struct:`Directory` representing the item +pointed to by :code:`PATH`. It will return a :struct:`Boolean` false if there's +nothing present under the given path. Also see :meth:`Volume:OPEN`. JSON @@ -503,7 +517,7 @@ Please see :ref:`the details of the Kerboscript ML Executable `. EDIT PATH ---------- +~~~~~~~~~ Edits a program pointed to by :code:`PATH`. From 10844236388270f7b820739710cef0b725344d4a Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 20:35:38 +0200 Subject: [PATCH 43/48] Add Volume:ROOT suffix --- doc/source/structures/volumes_and_files/volume.rst | 10 ++++++++++ src/kOS.Safe/Persistence/Volume.cs | 1 + 2 files changed, 11 insertions(+) diff --git a/doc/source/structures/volumes_and_files/volume.rst b/doc/source/structures/volumes_and_files/volume.rst index 7c405a205..b4f359a2b 100644 --- a/doc/source/structures/volumes_and_files/volume.rst +++ b/doc/source/structures/volumes_and_files/volume.rst @@ -32,6 +32,10 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. - :ref:`scalar ` - True if the name can be changed + * - :attr:`ROOT` + - :struct:`VolumeDirectory` + - Volume's root directory + * - :attr:`FILES` - :struct:`Lexicon` - Lexicon of all files and directories on the volume @@ -96,6 +100,12 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. List of files and directories on this volume. Keys are the names of all items on this volume and values are the associated :struct:`VolumeItem` structures. +.. attribute:: Volume:ROOT + + :type: :struct:`VolumeDirectory` + :access: Get only + + Returns volume's root directory .. attribute:: Volume:POWERREQUIREMENT diff --git a/src/kOS.Safe/Persistence/Volume.cs b/src/kOS.Safe/Persistence/Volume.cs index 6eab60cc9..de81e72e7 100644 --- a/src/kOS.Safe/Persistence/Volume.cs +++ b/src/kOS.Safe/Persistence/Volume.cs @@ -185,6 +185,7 @@ private void InitializeVolumeSuffixes() AddSuffix("RENAMEABLE" , new Suffix(() => Renameable)); AddSuffix("POWERREQUIREMENT" , new Suffix(() => RequiredPower())); + AddSuffix("ROOT" , new Suffix(() => Root)); AddSuffix("EXISTS" , new OneArgsSuffix(path => Exists(path))); AddSuffix("FILES" , new Suffix(ListAsLexicon)); AddSuffix("CREATE" , new OneArgsSuffix(path => CreateFile(path))); From 2ba2514ad0719289cf333adb9a397dd28f4b6d29 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 20:37:06 +0200 Subject: [PATCH 44/48] Fix VolumeDirectory enumeration --- src/kOS.Safe/Persistence/HarddiskDirectory.cs | 2 +- src/kOS.Safe/Persistence/VolumeDirectory.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kOS.Safe/Persistence/HarddiskDirectory.cs b/src/kOS.Safe/Persistence/HarddiskDirectory.cs index 64d46974a..9cacb0ad5 100644 --- a/src/kOS.Safe/Persistence/HarddiskDirectory.cs +++ b/src/kOS.Safe/Persistence/HarddiskDirectory.cs @@ -105,7 +105,7 @@ public bool Delete(string name, bool ksmDefault) return items.Remove(toDelete.Name); } - public IEnumerator GetEnumerator() + public new IEnumerator GetEnumerator() { return List().Values.GetEnumerator(); } diff --git a/src/kOS.Safe/Persistence/VolumeDirectory.cs b/src/kOS.Safe/Persistence/VolumeDirectory.cs index 23ec57408..9cdfdd5d8 100644 --- a/src/kOS.Safe/Persistence/VolumeDirectory.cs +++ b/src/kOS.Safe/Persistence/VolumeDirectory.cs @@ -41,6 +41,7 @@ IEnumerator IEnumerable.GetEnumerator() private void InitializeSuffixes() { + AddSuffix("ITERATOR", new NoArgsSuffix(() => new Enumerator(GetEnumerator()))); AddSuffix("LIST", new Suffix(ListAsLexicon)); } } From b05a5d6fe38eb7a5d117acf6d5c8808413d304e5 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 20:37:48 +0200 Subject: [PATCH 45/48] Add VolumeDirectory and VolumeItem doc pages --- .../volumes_and_files/volumedirectory.rst | 34 +++++++++++ .../volumes_and_files/volumefile.rst | 36 ++--------- .../volumes_and_files/volumeitem.rst | 59 +++++++++++++++++++ 3 files changed, 97 insertions(+), 32 deletions(-) create mode 100644 doc/source/structures/volumes_and_files/volumedirectory.rst create mode 100644 doc/source/structures/volumes_and_files/volumeitem.rst diff --git a/doc/source/structures/volumes_and_files/volumedirectory.rst b/doc/source/structures/volumes_and_files/volumedirectory.rst new file mode 100644 index 000000000..ab61b793f --- /dev/null +++ b/doc/source/structures/volumes_and_files/volumedirectory.rst @@ -0,0 +1,34 @@ +.. _volumedirectory: + +VolumeDirecotry +=============== + +Represents a directory on a kOS file system. + +Instances of this class are enumerable, every step of iteration will provide a :struct:`VolumeFile` or a :struct:`VolumeDirectory` contained in this directory. + +.. structure:: VolumeDirectory + + .. list-table:: Members + :header-rows: 1 + :widths: 1 1 4 + + * - Suffix + - Type + - Description + + * - All suffixes of :struct:`VolumeItem` + - + - :struct:`VolumeDirectory` objects are a type of :struct:`VolumeItem` + + * - :meth:`LIST` + - :struct:`List` of :struct:`VolumeFile` or :struct:`VolumeDirectory` + - Lists all files and directories + + +.. method:: VolumeDirectory:LIST + + :return: :struct:`List` of :struct:`VolumeFile` or :struct:`VolumeDirectory` + + Returns a list of all files and directories in this directory. + diff --git a/doc/source/structures/volumes_and_files/volumefile.rst b/doc/source/structures/volumes_and_files/volumefile.rst index 65c49d083..dbdf7b307 100644 --- a/doc/source/structures/volumes_and_files/volumefile.rst +++ b/doc/source/structures/volumes_and_files/volumefile.rst @@ -1,7 +1,7 @@ .. _volumefile: VolumeFile -================ +========== File name and size information. You can obtain a list of values of type VolumeFile using the :ref:`LIST FILES ` command. @@ -15,16 +15,10 @@ File name and size information. You can obtain a list of values of type VolumeFi - Type - Description + * - All suffixes of :struct:`VolumeItem` + - + - :struct:`VolumeFile` objects are a type of :struct:`VolumeItem` - * - :attr:`NAME` - - :struct:`String` - - Name of the file including extension - * - :attr:`EXTENSION` - - :struct:`String` - - File extension - * - :attr:`SIZE` - - :ref:`scalar ` (bytes) - - Size of the file * - :meth:`READALL` - :struct:`FileContent` - Reads file contents @@ -39,28 +33,6 @@ File name and size information. You can obtain a list of values of type VolumeFi - Clears this file -.. attribute:: VolumeFile:NAME - - :access: Get only - :type: :struct:`String` - - name of the file, including its file extension. - -.. attribute:: VolumeFile:EXTENSION - - :access: Get only - :type: :struct:`String` - - File extension (part of the filename after the last dot). - -.. attribute:: VolumeFile:SIZE - - :access: Get only - :type: :ref:`scalar ` - - size of the file, in bytes. - - .. method:: VolumeFile:READALL :return: :struct:`FileContent` diff --git a/doc/source/structures/volumes_and_files/volumeitem.rst b/doc/source/structures/volumes_and_files/volumeitem.rst new file mode 100644 index 000000000..8e1e07b22 --- /dev/null +++ b/doc/source/structures/volumes_and_files/volumeitem.rst @@ -0,0 +1,59 @@ +.. _volumeitem: + +VolumeItem +========== + +Contains suffixes common to :struct:`files ` and :struct:`directories `. + +.. structure:: VolumeItem + + .. list-table:: Members + :header-rows: 1 + :widths: 1 1 4 + + * - Suffix + - Type + - Description + + + * - :attr:`NAME` + - :struct:`String` + - Name of the item including extension + * - :attr:`EXTENSION` + - :struct:`String` + - Item extension + * - :attr:`SIZE` + - :struct:`Scalar` + - Size of the file + * - :attr:`ISFILE` + - :struct:`Scalar` + - Size of the file + +.. attribute:: VolumeItem:NAME + + :access: Get only + :type: :struct:`String` + + Name of the item, including the extension. + +.. attribute:: VolumeItem:EXTENSION + + :access: Get only + :type: :struct:`String` + + Item extension (part of the name after the last dot). + +.. attribute:: VolumeItem:SIZE + + :access: Get only + :type: :struct:`Scalar` + + Size of the item, in bytes. + +.. attribute:: VolumeItem:ISFILE + + :access: Get only + :type: :struct:`Boolean` + + True if this item is a file + From 05527a5cb1713e9eaff8cec8806a6eec63ca06d6 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 7 May 2016 20:38:06 +0200 Subject: [PATCH 46/48] Update Volume documentation page --- .../structures/volumes_and_files/volume.rst | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/doc/source/structures/volumes_and_files/volume.rst b/doc/source/structures/volumes_and_files/volume.rst index b4f359a2b..5f39a785f 100644 --- a/doc/source/structures/volumes_and_files/volume.rst +++ b/doc/source/structures/volumes_and_files/volume.rst @@ -17,19 +17,19 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. - Description * - :attr:`FREESPACE` - - :ref:`scalar ` + - :struct:`Scalar` - Free space left on the volume * - :attr:`CAPACITY` - - :ref:`scalar ` + - :struct:`Scalar` - Total space on the volume * - :attr:`NAME` - - :ref:`String` + - :struct:`String` - Volume name * - :attr:`RENAMEABLE` - - :ref:`scalar ` + - :struct:`Scalar` - True if the name can be changed * - :attr:`ROOT` @@ -41,11 +41,11 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. - Lexicon of all files and directories on the volume * - :attr:`POWERREQUIREMENT` - - :ref:`scalar ` + - :struct:`Scalar` - Amount of power consumed when this volume is set as the current volume * - :meth:`EXISTS(path)` - - :ref:`boolean ` + - :struct:`Boolean` - Returns true if the given file or directory exists * - :meth:`CREATE(path)` @@ -57,42 +57,41 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. - Creates a directory * - :meth:`OPEN(path)` - - :struct:`VolumeItem` + - :struct:`VolumeItem` or :struct:`Boolean` - Opens a file or directory * - :meth:`DELETE(path)` - - :ref:`boolean ` + - :struct:`Boolean` - Deletes a file or directory .. attribute:: Volume:FREESPACE - :type: :ref:`scalar ` + :type: :struct:`Scalar` :access: Get only Free space left on the volume .. attribute:: Volume:CAPACITY - :type: :ref:`scalar ` + :type: :struct:`Scalar` :access: Get only Total space on the volume .. attribute:: Volume:NAME - :type: :ref:`String` + :type: :struct:`String` :access: Get only Volume name. This name can be used instead of the volumeId with some :ref:`file and volume-related commands` .. attribute:: Volume:RENAMEABLE - :type: :ref:`boolean ` + :type: :struct:`Boolean` :access: Get only True if the name of this volume can be changed. Currently only the name of the archive can't be changed. - .. attribute:: Volume:FILES :type: :struct:`Lexicon` of :struct:`VolumeItem` @@ -109,12 +108,11 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. .. attribute:: Volume:POWERREQUIREMENT - :type: :ref:`scalar ` + :type: :struct:`Scalar` :access: Get only Amount of power consumed when this volume is set as the current volume - .. method:: Volume:EXISTS(path) :return: :struct:`Boolean` @@ -122,27 +120,37 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. Returns true if the given file or directory exists. This will also return true when the given file does not exist, but there is a file with the same name and `.ks` or `.ksm` extension added. Use ``Volume:FILES:HASKEY(name)`` to perform a strict check. + Paths passed as the argument to this command should not contain a volume id or name and should not be relative. + .. method:: Volume:OPEN(path) - :return: :struct:`VolumeItem` + :return: :struct:`VolumeItem` or :struct:`Boolean` false Opens the file or directory pointed to by the given path and returns :struct:`VolumeItem`. It will return a boolean false if the given file or directory does not exist. + Paths passed as the argument to this command should not contain a volume id or name and should not be relative. + .. method:: Volume:CREATE(path) :return: :struct:`VolumeFile` Creates a file under the given path and returns :struct:`VolumeFile`. It will fail if the file already exists. + Paths passed as the argument to this command should not contain a volume id or name and should not be relative. + .. method:: Volume:CREATEDIR(path) :return: :struct:`VolumeDirectory` Creates a directory under the given path and returns :struct:`VolumeDirectory`. It will fail if the directory already exists. + Paths passed as the argument to this command should not contain a volume id or name and should not be relative. + .. method:: Volume:DELETE(path) :return: boolean Deletes the given file or directory (recursively). It will return true if the given item was successfully deleted and false otherwise. + Paths passed as the argument to this command should not contain a volume id or name and should not be relative. + From b3fda238b479bf1e4c38b8f585d022e3589a952e Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 14 May 2016 12:34:33 +0200 Subject: [PATCH 47/48] Fix typos --- doc/source/commands/files.rst | 2 +- doc/source/structures/volumes_and_files/volumedirectory.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/commands/files.rst b/doc/source/commands/files.rst index 338793453..52066cd51 100644 --- a/doc/source/commands/files.rst +++ b/doc/source/commands/files.rst @@ -352,7 +352,7 @@ given path. Also see :meth:`Volume:CREATEDIR`. OPEN(PATH) ~~~~~~~~~~ -Will return a :struct:`VolumeFile` or :struct:`Directory` representing the item +Will return a :struct:`VolumeFile` or :struct:`VolumeDirectory` representing the item pointed to by :code:`PATH`. It will return a :struct:`Boolean` false if there's nothing present under the given path. Also see :meth:`Volume:OPEN`. diff --git a/doc/source/structures/volumes_and_files/volumedirectory.rst b/doc/source/structures/volumes_and_files/volumedirectory.rst index ab61b793f..7cd550a14 100644 --- a/doc/source/structures/volumes_and_files/volumedirectory.rst +++ b/doc/source/structures/volumes_and_files/volumedirectory.rst @@ -1,6 +1,6 @@ .. _volumedirectory: -VolumeDirecotry +VolumeDirectory =============== Represents a directory on a kOS file system. From 7a4439ece95c8b4093e1dc5e21d08b9693c70076 Mon Sep 17 00:00:00 2001 From: Tomek Piotrowski Date: Sat, 14 May 2016 23:21:13 +0200 Subject: [PATCH 48/48] Volume:NAME is now settable --- doc/source/structures/volumes_and_files/volume.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/structures/volumes_and_files/volume.rst b/doc/source/structures/volumes_and_files/volume.rst index 5f39a785f..129364cca 100644 --- a/doc/source/structures/volumes_and_files/volume.rst +++ b/doc/source/structures/volumes_and_files/volume.rst @@ -26,7 +26,7 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. * - :attr:`NAME` - :struct:`String` - - Volume name + - Get or set volume name * - :attr:`RENAMEABLE` - :struct:`Scalar` @@ -81,9 +81,9 @@ Represents a :struct:`kOSProcessor` hard disk or the archive. .. attribute:: Volume:NAME :type: :struct:`String` - :access: Get only + :access: Get and Set - Volume name. This name can be used instead of the volumeId with some :ref:`file and volume-related commands` + Gets or sets volume name. This name can be used instead of the volumeId with some :ref:`file and volume-related commands` .. attribute:: Volume:RENAMEABLE