mirror of
https://gitlab.com/2009scape/Saradomin-Launcher.git
synced 2026-08-01 14:19:14 -06:00
Refactor paths
This commit is contained in:
parent
6745b5f35a
commit
cf863a9871
14 changed files with 53 additions and 154 deletions
|
|
@ -37,9 +37,9 @@ namespace Saradomin.Infrastructure.Services
|
|||
Arguments =
|
||||
$"-Dsun.java2d.uiScale={_settingsService.Client.UiScale} "
|
||||
+ $"-DclientFps={_settingsService.Client.Fps} "
|
||||
+ $"-DclientHomeOverride=\"{_settingsService.Launcher.InstallationDirectory}/\" "
|
||||
+ $"-DclientHomeOverride=\"{CrossPlatform.Get2009scapeHome()}/\" "
|
||||
+ $"-jar \"{_clientUpdateService.PreferredTargetFilePath}\"",
|
||||
WorkingDirectory = $"{_settingsService.Launcher.InstallationDirectory}",
|
||||
WorkingDirectory = $"{CrossPlatform.Get2009scapeHome()}",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
public string ClientHashURL => "https://gitlab.com/2009scape/rt4-client/-/jobs/artifacts/master/raw/client/build/libs/rt4-client.jar.sha256?job=build";
|
||||
|
||||
public string PreferredTargetFilePath =>
|
||||
CrossPlatform.Locate2009scapeExecutable();
|
||||
CrossPlatform.Get2009scapeExecutable();
|
||||
|
||||
public event EventHandler<float> DownloadProgressChanged;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ namespace Saradomin.Infrastructure.Services
|
|||
string downloadUrl = CrossPlatform.GetJava11DownloadUrl();
|
||||
|
||||
string downloadPath = Path.Combine(
|
||||
CrossPlatform.LocateDefault2009scapeHome(),
|
||||
CrossPlatform.Get2009scapeHome(),
|
||||
"jre11" + Path.GetExtension(downloadUrl)
|
||||
);
|
||||
string extractedPath = Path.Combine(
|
||||
CrossPlatform.LocateDefault2009scapeHome(),
|
||||
CrossPlatform.Get2009scapeHome(),
|
||||
"jre11"
|
||||
);
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
if (Path.GetExtension(downloadUrl) == ".zip")
|
||||
{
|
||||
// Don't use /tmp because Directory.Move doesn't work cross-partition
|
||||
string tempDir = Path.Combine(CrossPlatform.LocateDefault2009scapeHome(), "jre11_temp");
|
||||
string tempDir = Path.Combine(CrossPlatform.Get2009scapeHome(), "jre11_temp");
|
||||
if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true);
|
||||
await Task.Run(() => ZipFile.ExtractToDirectory(downloadPath, tempDir));
|
||||
Directory.Move(Directory.GetDirectories(tempDir)[0], extractedPath);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Saradomin.Utilities;
|
||||
|
||||
namespace Saradomin.Infrastructure.Services
|
||||
{
|
||||
|
|
@ -13,7 +14,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
|
||||
public PluginManagementService(ISettingsService settings)
|
||||
{
|
||||
PluginRepositoryPath = Path.Combine(settings.Launcher.InstallationDirectory, "plugins");
|
||||
PluginRepositoryPath = Path.Combine(CrossPlatform.Get2009scapeHome(), "plugins");
|
||||
}
|
||||
|
||||
public Task<List<string>> EnumerateInstalledPlugins()
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ namespace Saradomin.Infrastructure.Services
|
|||
public ClientSettings Client { get; private set; } = new();
|
||||
|
||||
private string ClientSettingsPath
|
||||
=> Path.Combine(Launcher.InstallationDirectory, ClientSettings.FileName);
|
||||
=> Path.Combine(CrossPlatform.Get2009scapeHome(), ClientSettings.FileName);
|
||||
|
||||
private string LauncherSettingsPath
|
||||
=> Path.Combine(CrossPlatform.LocateSaradominHome(), LauncherSettings.FileName);
|
||||
=> Path.Combine(CrossPlatform.GetSaradominHome(), LauncherSettings.FileName);
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
|
|
@ -30,7 +30,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
|
||||
private void TryReadConfigurationData()
|
||||
{
|
||||
Directory.CreateDirectory(CrossPlatform.LocateSaradominHome());
|
||||
Directory.CreateDirectory(CrossPlatform.GetSaradominHome());
|
||||
|
||||
if (File.Exists(LauncherSettingsPath))
|
||||
{
|
||||
|
|
@ -44,7 +44,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
SaveLauncherSettings();
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Launcher!.InstallationDirectory);
|
||||
Directory.CreateDirectory(CrossPlatform.Get2009scapeHome());
|
||||
if (File.Exists(ClientSettingsPath))
|
||||
{
|
||||
using (var stream = File.OpenRead(ClientSettingsPath))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Saradomin.Infrastructure.Services
|
|||
"https://gitlab.com/2009scape/singleplayer/windows/-/archive/master/windows-master.zip";
|
||||
|
||||
string downloadPath = Path.Combine(
|
||||
CrossPlatform.LocateDefault2009scapeHome(),
|
||||
CrossPlatform.Get2009scapeHome(),
|
||||
"singleplayer" + Path.GetExtension(downloadUrl)
|
||||
);
|
||||
|
||||
|
|
@ -54,13 +54,13 @@ namespace Saradomin.Infrastructure.Services
|
|||
|
||||
SingleplayerDownloadProgressChanged?.Invoke(this, new Tuple<float, bool>(1f, false));
|
||||
|
||||
if (Directory.Exists(CrossPlatform.LocateSingleplayerHome())) Directory.Delete(CrossPlatform.LocateSingleplayerHome(), true);
|
||||
if (Directory.Exists(CrossPlatform.GetSingleplayerHome())) Directory.Delete(CrossPlatform.GetSingleplayerHome(), true);
|
||||
|
||||
// Don't use /tmp because Directory.Move doesn't work cross-partition
|
||||
string tempDir = Path.Combine(CrossPlatform.LocateDefault2009scapeHome(), "singleplayer_temp");
|
||||
string tempDir = Path.Combine(CrossPlatform.Get2009scapeHome(), "singleplayer_temp");
|
||||
if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true);
|
||||
await Task.Run(() => ZipFile.ExtractToDirectory(downloadPath, tempDir));
|
||||
Directory.Move(Directory.GetDirectories(tempDir)[0], CrossPlatform.LocateSingleplayerHome());
|
||||
Directory.Move(Directory.GetDirectories(tempDir)[0], CrossPlatform.GetSingleplayerHome());
|
||||
Directory.Delete(tempDir, true);
|
||||
|
||||
File.Delete(downloadPath);
|
||||
|
|
|
|||
|
|
@ -9,12 +9,9 @@ namespace Saradomin.Model.Settings.Launcher
|
|||
public bool PlaceCloseButtonOnLeft { get; set; } = false;
|
||||
public bool ExitAfterLaunchingClient { get; set; } = true;
|
||||
public bool AllowMultiboxing { get; set; } = false;
|
||||
|
||||
public bool CheckForClientUpdatesOnLaunch { get; set; } = true;
|
||||
public bool CheckForServerProfilesOnLaunch { get; set; } = true;
|
||||
|
||||
public string JavaExecutableLocation { get; set; }
|
||||
public string InstallationDirectory { get; set; } = CrossPlatform.LocateDefault2009scapeHome();
|
||||
|
||||
protected override void OnSettingsModified(string propertyName)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ namespace Saradomin.Utilities
|
|||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Console.WriteLine("Opening " + path + "...");
|
||||
Process.Start("xdg-open", path);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
|
|
@ -183,7 +182,7 @@ namespace Saradomin.Utilities
|
|||
);
|
||||
}
|
||||
|
||||
public static string LocateDefault2009scapeHome()
|
||||
public static string Get2009scapeHome()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
|| RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
||||
|
|
@ -203,7 +202,7 @@ namespace Saradomin.Utilities
|
|||
}
|
||||
}
|
||||
|
||||
public static string LocateSaradominHome()
|
||||
public static string GetSaradominHome()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
|| RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
||||
|
|
@ -215,51 +214,37 @@ namespace Saradomin.Utilities
|
|||
"saradomin"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"2009scape",
|
||||
"saradomin"
|
||||
);
|
||||
}
|
||||
|
||||
return Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"2009scape",
|
||||
"saradomin"
|
||||
);
|
||||
}
|
||||
|
||||
public static string LocateSingleplayerBackupsHome()
|
||||
public static string GetSingleplayerBackupsHome()
|
||||
{
|
||||
return Path.Combine(LocateDefault2009scapeHome(), "singleplayer_backups");
|
||||
return Path.Combine(Get2009scapeHome(), "singleplayer_backups");
|
||||
}
|
||||
|
||||
public static string LocateSingleplayerHome()
|
||||
public static string GetSingleplayerHome()
|
||||
{
|
||||
return Path.Combine(LocateDefault2009scapeHome(), "singleplayer");
|
||||
return Path.Combine(Get2009scapeHome(), "singleplayer");
|
||||
}
|
||||
|
||||
public static string LocateSingleplayerExecutable()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return Path.Combine(LocateSingleplayerHome(), "launch.bat");
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return Path.Combine(LocateSingleplayerHome(), "launch.sh");
|
||||
}
|
||||
|
||||
// TODO - MacOS
|
||||
throw new InvalidOperationException("Unsupported OS for singleplayer: " +
|
||||
RuntimeInformation.OSArchitecture);
|
||||
return Path.Combine(GetSingleplayerHome(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "launch.bat" : "launch.sh");
|
||||
}
|
||||
|
||||
public static string Locate2009scapeExecutable()
|
||||
public static string Get2009scapeExecutable()
|
||||
{
|
||||
return Path.Combine(LocateDefault2009scapeHome(), "2009scape.jar");
|
||||
return Path.Combine(Get2009scapeHome(), "2009scape.jar");
|
||||
}
|
||||
|
||||
public static string LocateServerProfilesPath(string baseDirectory)
|
||||
public static string GetServerProfilePath(string baseDirectory)
|
||||
{
|
||||
baseDirectory ??= LocateDefault2009scapeHome();
|
||||
baseDirectory ??= Get2009scapeHome();
|
||||
return Path.Combine(baseDirectory, "server_profiles.json");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,22 +21,22 @@ public static class SingleplayerManagement
|
|||
|
||||
public static void MakeBackup(Action<string> log)
|
||||
{
|
||||
if (!Directory.Exists(CrossPlatform.LocateSingleplayerBackupsHome()))
|
||||
Directory.CreateDirectory(CrossPlatform.LocateSingleplayerBackupsHome());
|
||||
if (!Directory.Exists(CrossPlatform.GetSingleplayerBackupsHome()))
|
||||
Directory.CreateDirectory(CrossPlatform.GetSingleplayerBackupsHome());
|
||||
|
||||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
string newBackupDir = Path.Combine(
|
||||
CrossPlatform.LocateSingleplayerBackupsHome(),
|
||||
CrossPlatform.GetSingleplayerBackupsHome(),
|
||||
timestamp
|
||||
);
|
||||
Directory.CreateDirectory(newBackupDir);
|
||||
|
||||
foreach (string dir in DirsToBackup)
|
||||
{
|
||||
string sourcePath = Path.Combine(CrossPlatform.LocateSingleplayerHome(), dir);
|
||||
string sourcePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), dir);
|
||||
if (!Directory.Exists(sourcePath))
|
||||
{
|
||||
log($" Skipping backup of {dir} because it doesn't exist (Full path attempted: {sourcePath}");
|
||||
log($" Skipping backup of {dir} because it doesn't exist (Full path attempted: {sourcePath})");
|
||||
continue;
|
||||
}
|
||||
string destPath = Path.Combine(newBackupDir, dir);
|
||||
|
|
@ -45,10 +45,10 @@ public static class SingleplayerManagement
|
|||
|
||||
foreach (string file in FilesToBackup)
|
||||
{
|
||||
string sourceFilePath = Path.Combine(CrossPlatform.LocateSingleplayerHome(), file);
|
||||
string sourceFilePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), file);
|
||||
if (!File.Exists(sourceFilePath))
|
||||
{
|
||||
log($" Skipping backup of {file} because it doesn't exist (Full path attempted: {sourceFilePath}");
|
||||
log($" Skipping backup of {file} because it doesn't exist (Full path attempted: {sourceFilePath})");
|
||||
continue;
|
||||
}
|
||||
string destFilePath = Path.Combine(newBackupDir, file);
|
||||
|
|
@ -87,8 +87,8 @@ public static class SingleplayerManagement
|
|||
|
||||
public static void ApplyLatestBackup(Action<string> log)
|
||||
{
|
||||
var backupHome = CrossPlatform.LocateSingleplayerBackupsHome();
|
||||
var singleplayerHome = CrossPlatform.LocateSingleplayerHome();
|
||||
var backupHome = CrossPlatform.GetSingleplayerBackupsHome();
|
||||
var singleplayerHome = CrossPlatform.GetSingleplayerHome();
|
||||
|
||||
// Get all backup directories
|
||||
var backupDirectories = Directory.GetDirectories(backupHome);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
Width="300"
|
||||
Margin="2,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
IsReadOnly="True"
|
||||
Classes="NormalTextBox"
|
||||
Text="{Binding Launcher.JavaExecutableLocation, Mode=TwoWay}"
|
||||
Watermark="Enter a path to java or java.exe..." />
|
||||
|
|
@ -59,40 +60,6 @@
|
|||
ToolTip.Tip="Browse..." />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,2,0,4"
|
||||
ColumnDefinitions="*,Auto"
|
||||
RowDefinitions="Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="3,0,0,2"
|
||||
Foreground="{StaticResource DarkForegroundBrush}"
|
||||
Text="2009scape installation directory" />
|
||||
|
||||
<!--
|
||||
Don't let the user edit it directly to avoid creating a ton of directories,
|
||||
since we get a notification every time the textbox is updated.
|
||||
-->
|
||||
<TextBox Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Width="300"
|
||||
Margin="2,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Classes="NormalTextBox"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding Launcher.InstallationDirectory, Mode=TwoWay}"
|
||||
Watermark="Enter a path to your preferred installation directory..." />
|
||||
|
||||
<Button Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="22"
|
||||
Height="24"
|
||||
Margin="2,0,0,0"
|
||||
Classes="OutsideNavigator"
|
||||
Command="{Binding BrowseForInstallationDirectory}"
|
||||
Content="..."
|
||||
ToolTip.Tip="Browse..." />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,4"
|
||||
RowDefinitions="Auto, Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
|
|
|
|||
|
|
@ -182,32 +182,6 @@
|
|||
FontSize="11"
|
||||
Content="Open Backups"
|
||||
ZIndex="9999" />
|
||||
|
||||
<Grid Margin="0,2,0,4"
|
||||
ColumnDefinitions="*,Auto"
|
||||
RowDefinitions="Auto,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="3,0,0,2"
|
||||
Foreground="{StaticResource DarkForegroundBrush}"
|
||||
Text="2009scape installation directory" />
|
||||
|
||||
<!--
|
||||
Don't let the user edit it directly to avoid creating a ton of directories,
|
||||
since we get a notification every time the textbox is updated.
|
||||
-->
|
||||
<TextBox Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Width="300"
|
||||
Margin="2,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Classes="NormalTextBox"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding Launcher.InstallationDirectory, Mode=TwoWay}"
|
||||
Watermark="Enter a path to your preferred installation directory..." />
|
||||
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</HeaderedContentControl>
|
||||
|
||||
|
|
|
|||
|
|
@ -120,32 +120,5 @@ namespace Saradomin.ViewModel.Controls
|
|||
Launcher.JavaExecutableLocation = paths[0];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BrowseForInstallationDirectory()
|
||||
{
|
||||
var ofd = new OpenFolderDialog
|
||||
{
|
||||
Title = "Browse for Installation Directory...",
|
||||
Directory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
|
||||
};
|
||||
|
||||
var path = await ofd.ShowAsync(Application.Current.GetMainWindow());
|
||||
|
||||
if (path != null)
|
||||
{
|
||||
if (CrossPlatform.IsDirectoryWritable(path))
|
||||
{
|
||||
Launcher.InstallationDirectory = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
NotificationBox.DisplayNotification(
|
||||
"Access denied",
|
||||
"The location you have selected is not writable. Select the one you have permissions for.",
|
||||
Application.Current.GetMainWindow()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ public class SingleplayerViewModel : ViewModelBase
|
|||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public string SingleplayerDownloadText { get; private set; } =
|
||||
Directory.Exists(CrossPlatform.LocateSingleplayerHome()) ? "Update Singleplayer" : "Download Singleplayer";
|
||||
Directory.Exists(CrossPlatform.GetSingleplayerHome()) ? "Update Singleplayer" : "Download Singleplayer";
|
||||
|
||||
public bool CanDownload { get; private set; } = true;
|
||||
public bool CanLaunch { get; private set; } = File.Exists(CrossPlatform.LocateSingleplayerExecutable());
|
||||
|
|
@ -56,9 +56,10 @@ public class SingleplayerViewModel : ViewModelBase
|
|||
if (finished)
|
||||
{
|
||||
SingleplayerDownloadText = "Update Singleplayer";
|
||||
PrintLog($"Singleplayer Download complete");
|
||||
SingleplayerManagement.ApplyLatestBackup(PrintLog);
|
||||
PrintLog($"");
|
||||
PrintLog($"Singleplayer Download complete");
|
||||
CanLaunch = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +85,7 @@ public class SingleplayerViewModel : ViewModelBase
|
|||
|
||||
public void DownloadSingleplayer()
|
||||
{
|
||||
CanLaunch = false;
|
||||
MakeBackup();
|
||||
PrintLog($"Starting singleplayer download...");
|
||||
_singleplayerUpdateService.DownloadSingleplayer();
|
||||
|
|
@ -99,9 +101,9 @@ public class SingleplayerViewModel : ViewModelBase
|
|||
|
||||
public void OpenBackupFolder()
|
||||
{
|
||||
if (!Directory.Exists(CrossPlatform.LocateSingleplayerBackupsHome()))
|
||||
Directory.CreateDirectory(CrossPlatform.LocateSingleplayerBackupsHome());
|
||||
CrossPlatform.OpenFolder(CrossPlatform.LocateSingleplayerBackupsHome());
|
||||
if (!Directory.Exists(CrossPlatform.GetSingleplayerBackupsHome()))
|
||||
Directory.CreateDirectory(CrossPlatform.GetSingleplayerBackupsHome());
|
||||
CrossPlatform.OpenFolder(CrossPlatform.GetSingleplayerBackupsHome());
|
||||
}
|
||||
|
||||
private void PrintLog(string message)
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
}
|
||||
|
||||
|
||||
if (!File.Exists(CrossPlatform.LocateServerProfilesPath(Launcher.InstallationDirectory)) ||
|
||||
if (!File.Exists(CrossPlatform.GetServerProfilePath(CrossPlatform.Get2009scapeHome())) ||
|
||||
_settingsService.Launcher.CheckForServerProfilesOnLaunch)
|
||||
await AttemptServerProfileUpdate();
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
|
||||
private async Task AttemptServerProfileUpdate()
|
||||
{
|
||||
var serverProfilePath = CrossPlatform.LocateServerProfilesPath(Launcher.InstallationDirectory);
|
||||
var serverProfilePath = CrossPlatform.GetServerProfilePath(CrossPlatform.Get2009scapeHome());
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -269,7 +269,7 @@ namespace Saradomin.ViewModel.Windows
|
|||
{
|
||||
|
||||
LaunchText = $"Updating... (Downloading client: 0%)";
|
||||
Directory.CreateDirectory(Launcher.InstallationDirectory);
|
||||
Directory.CreateDirectory(CrossPlatform.Get2009scapeHome());
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue