-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Add Disk Space Checker #35964
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
base: master
Are you sure you want to change the base?
Add Disk Space Checker #35964
Changes from 2 commits
6232a8b
8ee8c7f
c641f0a
fb73dec
863a169
d81fb0a
2b47165
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
| // See the LICENCE file in the repository root for full licence text. | ||
|
|
||
| using System.IO; | ||
| using System.Threading.Tasks; | ||
| using NUnit.Framework; | ||
| using osu.Game.IO; | ||
|
|
||
| namespace osu.Game.Tests.Database | ||
| { | ||
| [TestFixture] | ||
| public class DiskUsageTests | ||
| { | ||
| private string tempDir = null!; | ||
|
|
||
| [SetUp] | ||
| public void SetUp() | ||
| { | ||
| // Create a temporary directory to ensure we are testing against a valid location | ||
| tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
| Directory.CreateDirectory(tempDir); | ||
| } | ||
|
|
||
| [TearDown] | ||
| public void TearDown() | ||
| { | ||
| if (Directory.Exists(tempDir)) | ||
| Directory.Delete(tempDir, true); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestSufficientSpace() | ||
| { | ||
| // Asking for 0 bytes should always succeed (unless the drive is 100% full) | ||
| Assert.DoesNotThrow(() => DiskUsage.EnsureSufficientSpace(tempDir, 0)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestInsufficientSpace() | ||
| { | ||
| // Asking for the maximum possible long value should always exceed available space | ||
| Assert.Throws<IOException>(() => DiskUsage.EnsureSufficientSpace(tempDir, long.MaxValue)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestNonExistentDirectory() | ||
| { | ||
| string nonExistentPath = Path.Combine(tempDir, "does_not_exist"); | ||
| Assert.Throws<DirectoryNotFoundException>(() => DiskUsage.EnsureSufficientSpace(nonExistentPath)); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task TestAsyncWrapper() | ||
| { | ||
| await DiskUsage.EnsureSufficientSpaceAsync(tempDir, 0); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,50 @@ | ||||||||||||||||||
| // Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||||||||||||||||||
| // See the LICENCE file in the repository root for full licence text. | ||||||||||||||||||
|
|
||||||||||||||||||
| using System.IO; | ||||||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||||||
| using osu.Framework.Logging; | ||||||||||||||||||
|
|
||||||||||||||||||
| namespace osu.Game.IO | ||||||||||||||||||
| { | ||||||||||||||||||
| public static class DiskUsage | ||||||||||||||||||
| { | ||||||||||||||||||
| /// <summary> | ||||||||||||||||||
| /// 500 MiB | ||||||||||||||||||
| /// </summary> | ||||||||||||||||||
| private const long required_space_default = 512L * 1024L * 1024L; | ||||||||||||||||||
|
|
||||||||||||||||||
| /// <summary> | ||||||||||||||||||
| /// Checks if the available free space on the drive containing the path is sufficient for normal operation. | ||||||||||||||||||
| /// This method is blocking, and <see cref="EnsureSufficientSpaceAsync"/> should be preferred in IO-bound scenarios. | ||||||||||||||||||
| /// </summary> | ||||||||||||||||||
| /// <param name="checkPath">A path to a file or directory in which the drive's disk space should be checked.</param> | ||||||||||||||||||
| /// <param name="requiredSpace">The amount of space to ensure is available in bytes. Defaults to <see cref="required_space_default"/>.</param> | ||||||||||||||||||
| public static void EnsureSufficientSpace(string checkPath, long requiredSpace = required_space_default) | ||||||||||||||||||
| { | ||||||||||||||||||
| if (!Directory.Exists(checkPath)) | ||||||||||||||||||
| throw new DirectoryNotFoundException($"The directory '{checkPath}' does not exist or could not be found."); | ||||||||||||||||||
|
|
||||||||||||||||||
| string? validPathRoot = Path.GetPathRoot(checkPath); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (string.IsNullOrEmpty(validPathRoot)) | ||||||||||||||||||
| throw new IOException($"The directory '{checkPath}' is not a valid path."); | ||||||||||||||||||
|
|
||||||||||||||||||
| var activeDriveInfo = new DriveInfo(validPathRoot); | ||||||||||||||||||
|
|
||||||||||||||||||
| long availableFreeSpace = activeDriveInfo.AvailableFreeSpace; | ||||||||||||||||||
|
|
||||||||||||||||||
| #if DEBUG | ||||||||||||||||||
| Logger.Log($"Available disk space: {availableFreeSpace / 1048576L} MiB"); | ||||||||||||||||||
| #endif | ||||||||||||||||||
|
|
||||||||||||||||||
| if (availableFreeSpace < requiredSpace) | ||||||||||||||||||
| throw new IOException($"Insufficient disk space available! Required: {requiredSpace} | Available: {availableFreeSpace}"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| public static async Task EnsureSufficientSpaceAsync(string checkDirectory, long requiredSpace = required_space_default) | ||||||||||||||||||
| { | ||||||||||||||||||
| await Task.Run(() => EnsureSufficientSpace(checkDirectory, requiredSpace)).ConfigureAwait(false); | ||||||||||||||||||
| } | ||||||||||||||||||
|
||||||||||||||||||
| public static async Task EnsureSufficientSpaceAsync(string checkDirectory, long requiredSpace = required_space_default) | |
| { | |
| await Task.Run(() => EnsureSufficientSpace(checkDirectory, requiredSpace)).ConfigureAwait(false); | |
| } | |
| public static Task EnsureSufficientSpaceAsync(string checkDirectory, long requiredSpace = required_space_default) | |
| { | |
| return Task.Run(() => EnsureSufficientSpace(checkDirectory, requiredSpace)); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've applied your suggestion in c641f0a
I did originally consider adding debouncing logic here. My original idea was to add a DateTime as a property of DiskUsage, storing DateTime.Now in it whenever EnsureSufficientSpace is called, and silently return in said method if it hasn't yet been 5 minutes since the last call.
I decided to leave that out of this PR as the async wrapper is mostly just future-proofing, and this logic could be added in the future if the async wrapper does ever end up being used in real-time contexts. But I can see the confusion in supplying this method, but not going the full mile and specializing it for its intended purpose 😅
I can probably just remove the async wrapper altogether if we decide to stick with the "only on startup" approach. The actual overhead of checking disk space isn't really high, but it can get bad if done in a loop.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm reading the documentation correctly, this will basically always return
"/"on non-Windows, making this code invalid if osu! is on an external disk. Have you checked that this correctly reports the free size when querying directories on external disks on macOS?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not, thanks for catching that. Should be fixed in fb73dec
Using
GetPathRoot()does always return"/". Fortunately, my main use for that method was simply to check if the provided path is valid, so changing it toGetFullPath()properly provides the correct disk space while still checking path validity.Here's a test on my Linux PC, having switched to

GetFullPath(), testing for"/"and my HDD:Which is roughly consistent with what my OS reports for my hard drive:

Note: The reason for the large difference here is that the logger reports the available space in MiB, while
dufreports in GB. Convert the 385,103 MiB to GB and you get 403GB, which is exactly what my OS reports.The reason for that second discrepancy (376GB vs 403GB), is due to the fact that
dufreports btrfs's actual free space including metadata usage, whileDiskInfojust checks whatever my OS returns. The available space for my HDD is reported as 403GB in nautilus, so everything here is accurate and working as intended, just confusing due to linux shenanigans.