Skip to content
Merged
Show file tree
Hide file tree
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ Create, mount and manage in-memory volumes that appear as normal drive letters i
- Live status bar: available system memory and most recently accessed file
- Per-disk high-usage warning with a configurable threshold
- Temp directory redirection to a disk's `Temp` folder (the **Temp** preset): TEMP and TMP get their previous values back on unmount, exit or after a crash, and a startup warning appears if TEMP is left on a RAM disk
- **Temp & Caches** presets (Temp, Node.js, NuGet, pip, browser cache) that can be combined on one disk: they create their folders on every mount and point the matching per-user environment variables (`TEMP`/`TMP`, `npm_config_cache`, `NUGET_PACKAGES`, `PIP_CACHE_DIR`, ...) into the disk, restoring your previous values on unmount, exit or when you untick them. Tick them when creating a disk (which also fills in size and label), in the edit dialog, or from the **Temp & Caches** submenu of a disk's right-click menu, which stays open so you can toggle several at once; what is ticked follows what your environment variables really point at. A preset that redirects variables can be on one disk only: choosing it on another disk takes it from the first (the browser cache only creates a folder and can be on several)
- **Restore environment variables** (toolbar button and tray submenu): lists the groups that are redirected right now (Temp, Node.js, NuGet, pip, other variables) and puts one group, or all of them, back to your previous values without unmounting anything. The mounted disks that had those presets turn them off too (their folders and files stay), so they do not redirect the variables again on the next mount; only programs started afterwards see the change
- **Temp & Caches** presets (Temp, Node.js, NuGet, pip, Go, C/C++ compiler caches, browser cache) that can be combined on one disk: they create their folders on every mount and point the matching per-user environment variables (`TEMP`/`TMP`, `npm_config_cache`, `NUGET_PACKAGES`, `PIP_CACHE_DIR`, `GOCACHE`, `CCACHE_DIR`, ...) into the disk, restoring your previous values on unmount, exit or when you untick them. Tick them when creating a disk (which also fills in size and label), in the edit dialog, or from the **Temp & Caches** submenu of a disk's right-click menu, which stays open so you can toggle several at once; what is ticked follows what your environment variables really point at. A preset that redirects variables can be on one disk only: choosing it on another disk takes it from the first (the browser cache only creates a folder and can be on several)
- **Restore environment variables** (toolbar button and tray submenu): lists the groups that are redirected right now (Temp, Node.js, NuGet, pip, Go, C/C++, other variables) and puts one group, or all of them, back to your previous values without unmounting anything. The mounted disks that had those presets turn them off too (their folders and files stay), so they do not redirect the variables again on the next mount; only programs started afterwards see the change
- Exit confirmation with a saving overlay while pending saves finish
- Double-click to open a disk in Explorer; right-click for shortcuts, **View Disk Contents...** or **Space Usage...** (a treemap of what fills the disk, colored by file type, with top folder/file/type lists)

Expand Down Expand Up @@ -271,8 +271,8 @@ This project bundles [WinFsp](https://winfsp.dev/) and [SharpCompress](https://g
- 状态栏实时显示可用系统内存和最近访问的文件
- 每磁盘可配置高用量警告阈值
- 临时目录重定向到某磁盘的 `Temp` 文件夹(**临时目录**预设):卸载、退出或崩溃后重启时,TEMP 和 TMP 会恢复为原来的值;TEMP 遗留在内存盘上时启动提示
- **临时与缓存**预设(临时目录、Node.js、NuGet、pip、浏览器缓存),可在同一块盘上叠加:每次挂载时创建对应文件夹,并把相应的用户级环境变量(`TEMP`/`TMP`、`npm_config_cache`、`NUGET_PACKAGES`、`PIP_CACHE_DIR` 等)指向本盘,卸载、退出或取消勾选时还原为你原来的值。可在创建磁盘时勾选(同时自动填入容量和卷标)、在编辑对话框中勾选,或在磁盘右键菜单的**临时与缓存**子菜单里直接勾选(菜单不会关闭,可连续勾选多项);勾选状态以环境变量的实际指向为准。会修改环境变量的预设只能用于一块磁盘:在另一块盘上选用它,会把它从原来的磁盘上取消(浏览器缓存只创建文件夹,可用于多块盘)
- **还原环境变量**(工具栏按钮和托盘子菜单):列出当前被重定向的分组(临时目录、Node.js、NuGet、pip、其他变量),可单独还原某一组或全部还原为你原来的值,无需卸载磁盘。拥有这些预设的已挂载磁盘会同时关闭对应预设(磁盘上的文件夹和文件保留),因此下次挂载不会再次重定向这些变量;只有之后新启动的程序才会看到变化
- **临时与缓存**预设(临时目录、Node.js、NuGet、pip、Go、C/C++ 编译缓存、浏览器缓存),可在同一块盘上叠加:每次挂载时创建对应文件夹,并把相应的用户级环境变量(`TEMP`/`TMP`、`npm_config_cache`、`NUGET_PACKAGES`、`PIP_CACHE_DIR`、`GOCACHE`、`CCACHE_DIR` 等)指向本盘,卸载、退出或取消勾选时还原为你原来的值。可在创建磁盘时勾选(同时自动填入容量和卷标)、在编辑对话框中勾选,或在磁盘右键菜单的**临时与缓存**子菜单里直接勾选(菜单不会关闭,可连续勾选多项);勾选状态以环境变量的实际指向为准。会修改环境变量的预设只能用于一块磁盘:在另一块盘上选用它,会把它从原来的磁盘上取消(浏览器缓存只创建文件夹,可用于多块盘)
- **还原环境变量**(工具栏按钮和托盘子菜单):列出当前被重定向的分组(临时目录、Node.js、NuGet、pip、Go、C/C++、其他变量),可单独还原某一组或全部还原为你原来的值,无需卸载磁盘。拥有这些预设的已挂载磁盘会同时关闭对应预设(磁盘上的文件夹和文件保留),因此下次挂载不会再次重定向这些变量;只有之后新启动的程序才会看到变化
- 退出确认并显示保存遮罩直至待处理保存完成
- 双击在资源管理器中打开磁盘;右键提供快捷方式、**磁盘内容...** 或**空间占用分析...**(以矩形树图显示磁盘空间被什么占用,按文件类型着色,并附带占用最大的文件夹/文件/类型列表)

Expand Down
4 changes: 4 additions & 0 deletions src/ManagedDrive.App/Localization/Strings.en-US.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
<sys:String x:Key="Preset.nuget.Desc">NUGET_PACKAGES points into the disk.</sys:String>
<sys:String x:Key="Preset.python.Name">Python pip cache</sys:String>
<sys:String x:Key="Preset.python.Desc">PIP_CACHE_DIR points into the disk.</sys:String>
<sys:String x:Key="Preset.go.Name">Go caches</sys:String>
<sys:String x:Key="Preset.go.Desc">GOCACHE (build cache) and GOMODCACHE (module cache) point into the disk.</sys:String>
<sys:String x:Key="Preset.cpp.Name">C/C++ compiler caches</sys:String>
<sys:String x:Key="Preset.cpp.Desc">CCACHE_DIR (ccache) and SCCACHE_DIR (sccache) point into the disk.</sys:String>
<sys:String x:Key="Preset.browser.Name">Browser cache</sys:String>
<sys:String x:Key="Preset.browser.Desc">Creates a BrowserCache folder. Browsers have no environment variable for this: start the browser with --disk-cache-dir pointing at it.</sys:String>
<sys:String x:Key="CreateDisk.SaveOnExit">Save image on exit</sys:String>
Expand Down
4 changes: 4 additions & 0 deletions src/ManagedDrive.App/Localization/Strings.zh-CN.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
<sys:String x:Key="Preset.nuget.Desc">NUGET_PACKAGES 指向本盘。</sys:String>
<sys:String x:Key="Preset.python.Name">Python pip 缓存</sys:String>
<sys:String x:Key="Preset.python.Desc">PIP_CACHE_DIR 指向本盘。</sys:String>
<sys:String x:Key="Preset.go.Name">Go 缓存</sys:String>
<sys:String x:Key="Preset.go.Desc">GOCACHE(构建缓存)和 GOMODCACHE(模块缓存)指向本盘。</sys:String>
<sys:String x:Key="Preset.cpp.Name">C/C++ 编译缓存</sys:String>
<sys:String x:Key="Preset.cpp.Desc">CCACHE_DIR(ccache)和 SCCACHE_DIR(sccache)指向本盘。</sys:String>
<sys:String x:Key="Preset.browser.Name">浏览器缓存</sys:String>
<sys:String x:Key="Preset.browser.Desc">创建 BrowserCache 文件夹。浏览器没有对应的环境变量,需要用 --disk-cache-dir 参数启动浏览器并指向该文件夹。</sys:String>
<sys:String x:Key="CreateDisk.SaveOnExit">退出时保存镜像</sys:String>
Expand Down
34 changes: 33 additions & 1 deletion src/ManagedDrive.Core/DiskCreation/BuiltInPresets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,38 @@ public static class BuiltInPresets
EnvRedirects = [new() { Variable = "PIP_CACHE_DIR", SubPath = "pip-cache" }],
};

/// <summary>
/// Gets a disk for the Go build and module caches.
/// </summary>
public static DiskPreset Go { get; } = new()
{
Id = "go",
CapacityBytes = 2 * Gib,
VolumeLabel = "Go Cache",
Folders = ["go-build", "go-mod"],
EnvRedirects =
[
new() { Variable = "GOCACHE", SubPath = "go-build" },
new() { Variable = "GOMODCACHE", SubPath = "go-mod" },
],
};

/// <summary>
/// Gets a disk for the ccache and sccache compiler caches.
/// </summary>
public static DiskPreset Cpp { get; } = new()
{
Id = "cpp",
CapacityBytes = 2 * Gib,
VolumeLabel = "Compiler Cache",
Folders = ["ccache", "sccache"],
EnvRedirects =
[
new() { Variable = "CCACHE_DIR", SubPath = "ccache" },
new() { Variable = "SCCACHE_DIR", SubPath = "sccache" },
],
};

/// <summary>
/// Gets a disk for a browser cache. Browsers have no environment variable for this: the
/// folder is created, and the browser is started with <c>--disk-cache-dir</c>.
Expand All @@ -82,5 +114,5 @@ public static class BuiltInPresets
/// <summary>
/// Gets every built-in preset, in display order.
/// </summary>
public static IReadOnlyList<DiskPreset> All { get; } = [Temp, Node, NuGet, Python, Browser];
public static IReadOnlyList<DiskPreset> All { get; } = [Temp, Node, NuGet, Python, Go, Cpp, Browser];
}
13 changes: 13 additions & 0 deletions tests/ManagedDrive.Tests/DiskCreation/EnvRestoreGroupsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ public void Build_PresetVariables_AreGroupedByPresetInDisplayOrder()
Assert.Equal(["npm_config_cache"], groups[1].Variables);
}

/// <summary>
/// The Go and C/C++ variables are grouped under their presets, in display order.
/// </summary>
[Fact]
public void Build_ToolchainCacheVariables_AreGroupedUnderTheirPresets()
{
var groups = EnvRestoreGroups.Build(["SCCACHE_DIR", "CCACHE_DIR", "GOCACHE", "GOMODCACHE"]);

Assert.Equal(["go", "cpp"], groups.Select(g => g.Id));
Assert.Equal(["GOCACHE", "GOMODCACHE"], groups[0].Variables);
Assert.Equal(["CCACHE_DIR", "SCCACHE_DIR"], groups[1].Variables);
}

[Fact]
public void Build_ListsOnlyTheVariablesThatAreRedirected()
{
Expand Down
82 changes: 82 additions & 0 deletions tests/ManagedDrive.Tests/DiskCreation/PresetSelectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,88 @@ namespace ManagedDrive.Tests;

public sealed class PresetSelectionTests
{
/// <summary>
/// A disk holding a toolchain preset's folders and variables is detected as having that preset.
/// </summary>
[Theory]
[InlineData("go")]
[InlineData("cpp")]
public void Detect_ToolchainPresetFoldersAndVariables_FindsThePreset(string id)
{
var preset = BuiltInPresets.All.Single(p => p.Id == id);

var detected = PresetSelection.Detect(BuiltInPresets.All, preset.Folders, preset.EnvRedirects);

Assert.Equal([id], detected);
}

/// <summary>
/// Go is not detected when one of its two variables is missing.
/// </summary>
[Fact]
public void Detect_GoWithOneVariableMissing_IsNotFound()
{
var detected = PresetSelection.Detect(
BuiltInPresets.All, BuiltInPresets.Go.Folders, [BuiltInPresets.Go.EnvRedirects[0]]);

Assert.Empty(detected);
}

/// <summary>
/// C/C++ is active once both of its variables point into the disk.
/// </summary>
[Fact]
public void DetectActive_CppAllVariablesPointIntoDisk_IsActive()
{
var cppVariables = BuiltInPresets.Cpp.EnvRedirects.Select(r => r.Variable).ToList();

var active = PresetSelection.DetectActive(
BuiltInPresets.All, BuiltInPresets.Cpp.EnvRedirects, redirect => cppVariables.Contains(redirect.Variable));

Assert.Equal(["cpp"], active.PresetIds);
}

/// <summary>
/// C/C++ is not active while only one of its two variables points into the disk.
/// </summary>
[Fact]
public void DetectActive_CppOnlyOneVariablePointsIntoDisk_IsNotActive()
{
var active = PresetSelection.DetectActive(
BuiltInPresets.All, BuiltInPresets.Cpp.EnvRedirects, redirect => redirect.Variable == "CCACHE_DIR");

Assert.DoesNotContain("cpp", active.PresetIds);
}

/// <summary>
/// Go is active once all of its variables point into the disk.
/// </summary>
[Fact]
public void DetectActive_GoAllVariablesPointIntoDisk_IsActive()
{
var goVariables = BuiltInPresets.Go.EnvRedirects.Select(r => r.Variable).ToList();

var active = PresetSelection.DetectActive(
BuiltInPresets.All, BuiltInPresets.Go.EnvRedirects, redirect => goVariables.Contains(redirect.Variable));

Assert.Equal(["go"], active.PresetIds);
Assert.Empty(active.CustomVariables);
}

/// <summary>
/// Releasing Go drops its variables and its folder entries from the disk's configuration.
/// </summary>
[Fact]
public void Release_GoClaimedByAnotherDisk_DropsItsVariablesAndFolders()
{
var result = PresetSelection.Release(
BuiltInPresets.All, BuiltInPresets.Go.Folders, BuiltInPresets.Go.EnvRedirects, [BuiltInPresets.Go]);

Assert.Empty(result.EnvRedirects);
Assert.Equal(["GOCACHE", "GOMODCACHE"], result.ReleasedVariables);
Assert.Empty(result.Folders);
}

[Fact]
public void Detect_DiskWithAPresetsFoldersAndVariables_FindsThePreset()
{
Expand Down
28 changes: 28 additions & 0 deletions tests/ManagedDrive.Tests/DiskCreation/PresetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,21 @@ public void Merge_NoPresets_ReturnsEmptyComposition()
Assert.Empty(result.Conflicts);
}

/// <summary>
/// Ticking Go and C/C++ together sums their capacities and unites folders and variables without conflicts.
/// </summary>
[Fact]
public void Merge_GoAndCpp_SumsCapacityAndUnitesFoldersAndVariables()
{
var result = PresetComposer.Merge([BuiltInPresets.Go, BuiltInPresets.Cpp]);

Assert.Equal(BuiltInPresets.Go.CapacityBytes + BuiltInPresets.Cpp.CapacityBytes, result.CapacityBytes);
Assert.Equal("Go Cache", result.VolumeLabel);
Assert.Equal(["go-build", "go-mod", "ccache", "sccache"], result.Folders);
Assert.Equal(["GOCACHE", "GOMODCACHE", "CCACHE_DIR", "SCCACHE_DIR"], result.EnvRedirects.Select(r => r.Variable));
Assert.Empty(result.Conflicts);
}

[Fact]
public void Merge_SumsCapacityAndUnitesFoldersAndVariables()
{
Expand Down Expand Up @@ -198,6 +213,19 @@ public void All_EveryRedirectTargetIsAlsoACreatedFolder()
}
}

/// <summary>
/// Each toolchain preset redirects exactly its documented variables into its documented folders.
/// </summary>
[Theory]
[InlineData("go", "GOCACHE=go-build", "GOMODCACHE=go-mod")]
[InlineData("cpp", "CCACHE_DIR=ccache", "SCCACHE_DIR=sccache")]
public void EnvRedirects_ToolchainPreset_RedirectsTheExpectedVariables(string id, params string[] expected)
{
var preset = BuiltInPresets.All.Single(p => p.Id == id);

Assert.Equal(expected, preset.EnvRedirects.Select(r => $"{r.Variable}={r.SubPath}"));
}

[Fact]
public void All_CanAllBeCombinedWithoutConflict()
{
Expand Down
Loading