Skip to content

Commit

Permalink
Merge #3804 Cache path setting fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Mar 7, 2023
2 parents 483bb28 + 8aef365 commit 2d387d9
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
- [Core] Skip duplicate repo URLs during update (#3786 by: HebaruSan; reviewed: techman83)
- [Core] Mark new deps of upgrades as auto-installed (#3702 by: HebaruSan; reviewed: techman83)
- [GUI] Fix index -1 exception in Manage Instances (#3800 by: HebaruSan; reviewed: techman83)
- [Multiple] Cache path setting fixes (#3804 by: HebaruSan; reviewed: techman83)

### Internal

Expand Down
11 changes: 6 additions & 5 deletions Core/Extensions/IOExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ private static bool StringArrayStartsWith(string[] child, string[] parent)
return true;
}

private static readonly char[] pathDelims = new char[] {Path.DirectorySeparatorChar};

/// <summary>
/// Check whether a given path is an ancestor of another
/// </summary>
Expand All @@ -36,10 +38,8 @@ private static bool StringArrayStartsWith(string[] child, string[] parent)
/// <returns>true if child is a descendant of parent, false otherwise</returns>
public static bool IsAncestorOf(this DirectoryInfo parent, DirectoryInfo child)
=> StringArrayStartsWith(
child.FullName.Split(new char[] {Path.DirectorySeparatorChar},
StringSplitOptions.RemoveEmptyEntries),
parent.FullName.Split(new char[] {Path.DirectorySeparatorChar},
StringSplitOptions.RemoveEmptyEntries));
child.FullName.Split(pathDelims, StringSplitOptions.RemoveEmptyEntries),
parent.FullName.Split(pathDelims, StringSplitOptions.RemoveEmptyEntries));

/// <summary>
/// Extension method to fill in the gap of getting from a
Expand All @@ -51,7 +51,8 @@ public static bool IsAncestorOf(this DirectoryInfo parent, DirectoryInfo child)
/// <returns>The DriveInfo associated with this directory</returns>
public static DriveInfo GetDrive(this DirectoryInfo dir)
=> DriveInfo.GetDrives()
.Where(dr => dr.RootDirectory.IsAncestorOf(dir))
.Where(dr => dr.RootDirectory == dir
|| dr.RootDirectory.IsAncestorOf(dir))
.OrderByDescending(dr => dr.RootDirectory.FullName.Length)
.FirstOrDefault();

Expand Down
22 changes: 13 additions & 9 deletions Core/GameInstanceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
using System.IO;
using System.Linq;
using System.Transactions;

using Autofac;
using ChinhDo.Transactions.FileManager;
using log4net;

using CKAN.Versioning;
using CKAN.Configuration;
using CKAN.Games;
using CKAN.Extensions;

namespace CKAN
{
Expand Down Expand Up @@ -543,8 +546,12 @@ private void LoadCacheSettings()
}
}

string failReason;
TrySetupCache(Configuration.DownloadCacheDir, out failReason);
if (!TrySetupCache(Configuration.DownloadCacheDir, out string failReason))
{
log.ErrorFormat("Cache not found at configured path {0}: {1}", Configuration.DownloadCacheDir, failReason);
// Fall back to default path to minimize chance of ending up in an invalid state at startup
TrySetupCache("", out failReason);
}
}

/// <summary>
Expand All @@ -567,6 +574,8 @@ public bool TrySetupCache(string path, out string failureReason)
}
else
{
// Make sure we can access it
var bytesFree = new DirectoryInfo(path).GetDrive().AvailableFreeSpace;
Cache = new NetModuleCache(this, path);
Configuration.DownloadCacheDir = path;
}
Expand All @@ -575,17 +584,12 @@ public bool TrySetupCache(string path, out string failureReason)
}
catch (DirectoryNotFoundKraken)
{
Configuration.DownloadCacheDir = origPath;
failureReason = string.Format(Properties.Resources.GameInstancePathNotFound, path);
return false;
}
catch (PathErrorKraken ex)
{
failureReason = ex.Message;
return false;
}
catch (IOException ex)
catch (Exception ex)
{
// MoveFrom failed, possibly full disk, so undo the change
Configuration.DownloadCacheDir = origPath;
failureReason = ex.Message;
return false;
Expand Down
22 changes: 13 additions & 9 deletions Core/Net/NetFileCache.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -68,8 +68,8 @@ public NetFileCache(string path)
Properties.Resources.NetFileCacheCannotFind, cachePath));
}

// Files go here while they're downloading
Directory.CreateDirectory(InProgressPath);
// Make sure we can access it
var bytesFree = new DirectoryInfo(cachePath)?.GetDrive()?.AvailableFreeSpace ?? 0;

// Establish a watch on our cache. This means we can cache the directory contents,
// and discard that cache if we spot changes.
Expand Down Expand Up @@ -107,14 +107,18 @@ public void Dispose()
watcher.Dispose();
}

// Files go here while they're downloading
private string InProgressPath => Path.Combine(cachePath, "downloading");

private string GetInProgressFileName(string hash, string description)
=> Directory.EnumerateFiles(InProgressPath)
.Where(path => new FileInfo(path).Name.StartsWith(hash))
.FirstOrDefault()
// If not found, return the name to create
?? Path.Combine(InProgressPath, $"{hash}-{description}");
{
Directory.CreateDirectory(InProgressPath);
return Directory.EnumerateFiles(InProgressPath)
.Where(path => new FileInfo(path).Name.StartsWith(hash))
.FirstOrDefault()
// If not found, return the name to create
?? Path.Combine(InProgressPath, $"{hash}-{description}");
}

public string GetInProgressFileName(Uri url, string description)
=> GetInProgressFileName(NetFileCache.CreateURLHash(url),
Expand Down Expand Up @@ -300,7 +304,7 @@ public void GetSizeInfo(out int numFiles, out long numBytes, out long bytesFree)
numFiles = 0;
numBytes = 0;
GetSizeInfo(cachePath, ref numFiles, ref numBytes);
bytesFree = new DirectoryInfo(cachePath).GetDrive()?.AvailableFreeSpace ?? 0;
bytesFree = new DirectoryInfo(cachePath)?.GetDrive()?.AvailableFreeSpace ?? 0;
foreach (var legacyDir in legacyDirs())
{
GetSizeInfo(legacyDir, ref numFiles, ref numBytes);
Expand Down
104 changes: 66 additions & 38 deletions GUI/Dialogs/SettingsDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ public partial class SettingsDialog : Form
private static readonly ILog log = LogManager.GetLogger(typeof(SettingsDialog));

private IUser m_user;
private long m_cacheSize;
private int m_cacheFileCount;
private long m_cacheFreeSpace;
private IConfiguration config;

private List<Repository> _sortedRepos = new List<Repository>();
Expand Down Expand Up @@ -64,6 +61,20 @@ public void UpdateDialog()
UpdateCacheInfo(config.DownloadCacheDir);
}

protected override void OnFormClosing(FormClosingEventArgs e)
{
if (CachePath.Text != config.DownloadCacheDir
&& !Main.Instance.Manager.TrySetupCache(CachePath.Text, out string failReason))
{
m_user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason);
e.Cancel = true;
}
else
{
base.OnFormClosing(e);
}
}

private void UpdateRefreshRate()
{
int rate = config.RefreshRate;
Expand Down Expand Up @@ -113,48 +124,48 @@ private void UpdateLanguageSelectionComboBox()
LanguageSelectionComboBox.SelectedIndex = LanguageSelectionComboBox.FindStringExact(config.Language);
}

private bool updatingCache = false;

private void UpdateCacheInfo(string newPath)
{
string failReason;
if (updatingCache)
CachePath.Text = newPath;
// Background thread in case GetSizeInfo takes a while
Task.Factory.StartNew(() =>
{
return;
}
if (newPath == config.DownloadCacheDir
|| Main.Instance.Manager.TrySetupCache(newPath, out failReason))
{
updatingCache = true;
Task.Factory.StartNew(() =>
try
{
// This might take a little while if the cache is big
Main.Instance.Manager.Cache.GetSizeInfo(out m_cacheFileCount, out m_cacheSize, out m_cacheFreeSpace);
// Make a temporary cache object to validate the path without changing the setting till close
var cache = new NetModuleCache(newPath);
cache.GetSizeInfo(out int cacheFileCount, out long cacheSize, out long cacheFreeSpace);
Util.Invoke(this, () =>
{
if (config.CacheSizeLimit.HasValue)
{
// Show setting in MB
// Show setting in MiB
CacheLimit.Text = (config.CacheSizeLimit.Value / 1024 / 1024).ToString();
}
CachePath.Text = config.DownloadCacheDir;
CacheSummary.Text = string.Format(Properties.Resources.SettingsDialogSummmary, m_cacheFileCount, CkanModule.FmtSize(m_cacheSize), CkanModule.FmtSize(m_cacheFreeSpace));
CacheSummary.Text = string.Format(
Properties.Resources.SettingsDialogSummmary,
cacheFileCount, CkanModule.FmtSize(cacheSize), CkanModule.FmtSize(cacheFreeSpace));
CacheSummary.ForeColor = SystemColors.ControlText;
OpenCacheButton.Enabled = true;
ClearCacheButton.Enabled = (m_cacheSize > 0);
ClearCacheButton.Enabled = (cacheSize > 0);
PurgeToLimitMenuItem.Enabled = (config.CacheSizeLimit.HasValue
&& m_cacheSize > config.CacheSizeLimit.Value);
updatingCache = false;
&& cacheSize > config.CacheSizeLimit.Value);
});
});
}
else
{
CacheSummary.Text = string.Format(Properties.Resources.SettingsDialogSummaryInvalid, failReason);
CacheSummary.ForeColor = Color.Red;
OpenCacheButton.Enabled = false;
ClearCacheButton.Enabled = false;
}
}
catch (Exception ex)
{
Util.Invoke(this, () =>
{
CacheSummary.Text = string.Format(Properties.Resources.SettingsDialogSummaryInvalid,
ex.Message);
CacheSummary.ForeColor = Color.Red;
OpenCacheButton.Enabled = false;
ClearCacheButton.Enabled = false;
});
}
});
}

private void CachePath_TextChanged(object sender, EventArgs e)
Expand Down Expand Up @@ -205,6 +216,14 @@ private void PurgeToLimitMenuItem_Click(object sender, EventArgs e)
// Purge old downloads if we're over the limit
if (config.CacheSizeLimit.HasValue)
{
// Switch main cache since user seems committed to this path
if (CachePath.Text != config.DownloadCacheDir
&& !Main.Instance.Manager.TrySetupCache(CachePath.Text, out string failReason))
{
m_user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason);
return;
}

Main.Instance.Manager.Cache.EnforceSizeLimit(
config.CacheSizeLimit.Value,
RegistryManager.Instance(Main.Instance.CurrentInstance).registry
Expand All @@ -215,13 +234,22 @@ private void PurgeToLimitMenuItem_Click(object sender, EventArgs e)

private void PurgeAllMenuItem_Click(object sender, EventArgs e)
{
// Switch main cache since user seems committed to this path
if (CachePath.Text != config.DownloadCacheDir
&& !Main.Instance.Manager.TrySetupCache(CachePath.Text, out string failReason))
{
m_user.RaiseError(Properties.Resources.SettingsDialogSummaryInvalid, failReason);
return;
}

Main.Instance.Manager.Cache.GetSizeInfo(
out int cacheFileCount, out long cacheSize, out long cacheFreeSpace);

YesNoDialog deleteConfirmationDialog = new YesNoDialog();
string confirmationText = String.Format
(
string confirmationText = String.Format(
Properties.Resources.SettingsDialogDeleteConfirm,
m_cacheFileCount,
CkanModule.FmtSize(m_cacheSize)
);
cacheFileCount,
CkanModule.FmtSize(cacheSize));

if (deleteConfirmationDialog.ShowYesNoDialog(this, confirmationText) == DialogResult.Yes)
{
Expand All @@ -245,7 +273,7 @@ private void PurgeAllMenuItem_Click(object sender, EventArgs e)
private void ResetCacheButton_Click(object sender, EventArgs e)
{
// Reset to default cache path
UpdateCacheInfo("");
UpdateCacheInfo(JsonConfiguration.DefaultDownloadCacheDir);
}

private void OpenCacheButton_Click(object sender, EventArgs e)
Expand Down Expand Up @@ -300,7 +328,7 @@ private void NewRepoButton_Click(object sender, EventArgs e)
}
catch (Exception)
{
Main.Instance.currentUser.RaiseError("Invalid repo format - should be \"<name> | <url>\"");
m_user.RaiseError("Invalid repo format - should be \"<name> | <url>\"");
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions GUI/Dialogs/SettingsDialog.resx
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@
<data name="NewAuthTokenButton.Text" xml:space="preserve"><value>New</value></data>
<data name="DeleteAuthTokenButton.Text" xml:space="preserve"><value>Delete</value></data>
<data name="CacheGroupBox.Text" xml:space="preserve"><value>Download Cache</value></data>
<data name="CacheSummary.Text" xml:space="preserve"><value>N files, M MB</value></data>
<data name="CacheSummary.Text" xml:space="preserve"><value>N files, M MiB</value></data>
<data name="CacheLimitPreLabel.Text" xml:space="preserve"><value>Maximum cache size:</value></data>
<data name="CacheLimitPostLabel.Text" xml:space="preserve"><value>MB (empty for unlimited)</value></data>
<data name="CacheLimitPostLabel.Text" xml:space="preserve"><value>MiB (empty for unlimited)</value></data>
<data name="ChangeCacheButton.Text" xml:space="preserve"><value>Change...</value></data>
<data name="ClearCacheButton.Text" xml:space="preserve"><value>Purge</value></data>
<data name="PurgeToLimitMenuItem.Text" xml:space="preserve"><value>Purge to limit</value></data>
Expand Down

0 comments on commit 2d387d9

Please sign in to comment.