Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing a save bug that occurs when changing a request/folder name #14

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 202 additions & 21 deletions src/Client/Workbooks/Services/WorkbookManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,44 +50,32 @@ public static async Task<WorkbookModel> LoadAsync(
return workbookModel;
}

var subdirectories = Directory
.GetDirectories(foldersPath, "*", SearchOption.AllDirectories)
.OrderBy(p => p);
var entries = ScanWorkbookEntries(foldersPath);

foreach (var subdirectory in subdirectories)
foreach (var entry in entries)
{
var isRequest = File.Exists(Path.Combine(subdirectory, "request.json"));

var relativePath = Path.GetRelativePath(foldersPath, subdirectory);
var pathPieces = relativePath
.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar })
.Select(Uri.UnescapeDataString);
var subdirectory = Path.Combine(foldersPath, entry.RelativeFilesystemPath);

var pathModel = pathPieces
.Aggregate(
PathModel.Root,
(current, pathSegment) => current.AddSegment(pathSegment));

if (isRequest)
if (entry.IsRequest)
{
var requestPath = Path.Combine(subdirectory, "request.json");
var requestJson = await File.ReadAllTextAsync(requestPath, cancellationToken);
var requestModel = JsonConvert.DeserializeObject<RequestModel>(requestJson)?
.WithNameAndParentPath(pathModel.Segments[^1], pathModel.GetParent());
.WithNameAndParentPath(entry.Path.Segments[^1], entry.Path.GetParent());

if (requestModel == null)
{
throw new Exception("Unable to load request model from saved workbook.");
}

workbookModel = workbookModel
.NewRequest(pathModel)
.UpdateRequest(pathModel, requestModel);
.NewRequest(entry.Path)
.UpdateRequest(entry.Path, requestModel);

continue;
}

if (!workbookModel.FolderExists(pathModel))
if (!workbookModel.FolderExists(entry.Path))
{
var folderPath = Path.Combine(subdirectory, "folder.json");

Expand All @@ -100,7 +88,7 @@ public static async Task<WorkbookModel> LoadAsync(
}

workbookModel = workbookModel
.NewFolder(pathModel, folderModel.Description);
.NewFolder(entry.Path, folderModel.Description);
}
}

Expand Down Expand Up @@ -144,13 +132,31 @@ public static async Task SaveAsync(
}

var workbookPath = Path.Combine(workbookModel.Path, "workbook.json");

var foldersPath = Path.Combine(workbookModel.Path, "folders");

if (!Directory.Exists(foldersPath))
{
Directory.CreateDirectory(foldersPath);
}

// Create a backup
var entries = ScanWorkbookEntries(foldersPath);

var backupPath = Path.Combine(workbookModel.Path, ".sgbackup");

await PurgeAndCreateBackupAsync(
backupPath,
workbookPath,
foldersPath,
entries,
cancellationToken);

DeleteStaleWorkbook(
workbookPath,
foldersPath,
entries);

var allFolders = workbookModel.GetFlatFolders();
foreach (var folderModel in allFolders)
{
Expand Down Expand Up @@ -200,5 +206,180 @@ await File.WriteAllTextAsync(
JsonConvert.SerializeObject(workbookModel),
cancellationToken);
}

private static async Task PurgeAndCreateBackupAsync(
string backupPath,
string workbookPath,
string foldersPath,
IEnumerable<WorkbookEntry> entries,
CancellationToken cancellationToken)
{
// Setup the backup directory
if (Directory.Exists(backupPath))
{
Directory.Delete(backupPath, true);
}

Directory.CreateDirectory(backupPath);

// Grab everything inside the workbook and folders directory to make a backup
if (File.Exists(workbookPath))
{
var workbookBackupContents = await File.ReadAllTextAsync(workbookPath, cancellationToken);

await File.WriteAllTextAsync(
Path.Combine(backupPath, "workbook.json"),
workbookBackupContents,
cancellationToken);
}

foreach (var entry in entries)
{
var subdirectory = Path.Combine(foldersPath, entry.RelativeFilesystemPath);
var backupSubdirectory = Path.Combine(backupPath, "folders", entry.RelativeFilesystemPath);

Directory.CreateDirectory(backupSubdirectory);

if (entry.IsFolder)
{
var folderBackupContents = await File.ReadAllTextAsync(
Path.Combine(subdirectory, "folder.json"),
cancellationToken);

await File.WriteAllTextAsync(
Path.Combine(backupSubdirectory, "folder.json"),
folderBackupContents,
cancellationToken);

continue;
}

var requestBackupContents = await File.ReadAllTextAsync(
Path.Combine(subdirectory, "request.json"),
cancellationToken);

await File.WriteAllTextAsync(
Path.Combine(backupSubdirectory, "request.json"),
requestBackupContents,
cancellationToken);
}
}

private static void DeleteStaleWorkbook(
string workbookPath,
string foldersPath,
IEnumerable<WorkbookEntry> entries)
{
if (File.Exists(workbookPath))
{
File.Delete(workbookPath);
}

// Go backwards through the entries so we can delete them if there is nothing left in them after we remove our JSON files.
entries = entries.Reverse();

foreach (var entry in entries)
{
var subdirectory = Path.Combine(foldersPath, entry.RelativeFilesystemPath);
var jsonPath = Path.Combine(
subdirectory,
entry.IsFolder ? "folder.json" : "request.json");

if (File.Exists(jsonPath))
{
File.Delete(jsonPath);
}

if (IsDirectoryEmpty(subdirectory))
{
Directory.Delete(subdirectory);
}
}
}

private static List<WorkbookEntry> ScanWorkbookEntries(string foldersPath)
{
var workbookEntries = new List<WorkbookEntry>();
var subdirectories = Directory
.GetDirectories(foldersPath, "*", SearchOption.AllDirectories)
.OrderBy(p => p);

foreach (var subdirectory in subdirectories)
{
var isRequest = File.Exists(Path.Combine(subdirectory, "request.json"));
var isFolder = File.Exists(Path.Combine(subdirectory, "folder.json"));

var relativePath = Path.GetRelativePath(foldersPath, subdirectory);
var pathPieces = relativePath
.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar })
.Select(Uri.UnescapeDataString);

var pathModel = pathPieces
.Aggregate(
PathModel.Root,
(current, pathSegment) => current.AddSegment(pathSegment));

if (!isFolder && !isRequest)
{
continue;
}

workbookEntries.Add(new WorkbookEntry(
relativePath,
pathModel,
isRequest,
isFolder));
}

return workbookEntries;
}

private static bool IsDirectoryEmpty(string path)
=> !Directory.EnumerateFileSystemEntries(path).Any();

/// <summary>
/// An entry for a workbook that exists on the filesystem.
/// </summary>
public sealed class WorkbookEntry
{
/// <summary>
/// Initializes a new instance of the <see cref="WorkbookEntry"/> class.
/// </summary>
/// <param name="relativeFilesystemPath">The entry's relative path to the folders directory on the filesystem.</param>
/// <param name="path">The path model for the entry.</param>
/// <param name="isRequest">A value indicating whether or not the entry represents a request.</param>
/// <param name="isFolder">A value indicating whether or not the entry represents a folder.</param>
public WorkbookEntry(
string relativeFilesystemPath,
PathModel path,
bool isRequest,
bool isFolder)
{
this.RelativeFilesystemPath = relativeFilesystemPath;
this.Path = path;
this.IsRequest = isRequest;
this.IsFolder = isFolder;
}

/// <summary>
/// Gets the entry's relative path to the folders directory on the filesystem.
/// </summary>
public string RelativeFilesystemPath { get; }

/// <summary>
/// Gets the workbook entries path.
/// </summary>
public PathModel Path { get; }

/// <summary>
/// Gets a value indicating whether or not the entry represents a request.
/// </summary>
public bool IsRequest { get; }

/// <summary>
/// Gets a value indicating whether or not the entry represents a folder.
/// </summary>
public bool IsFolder { get; }
}
}
}