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

4 drag and drop support in character overview #35

Merged
merged 9 commits into from
Sep 16, 2023
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
15 changes: 14 additions & 1 deletion src/GIMI-ModManager.Core/Entities/GenshinCharacter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace GIMI_ModManager.Core.Entities;

public record GenshinCharacter : IEqualityComparer<GenshinCharacter>
public record GenshinCharacter : IGenshinCharacter, IEqualityComparer<GenshinCharacter>
{
public int Id { get; set; } = -1;
public string DisplayName { get; set; } = string.Empty;
Expand All @@ -30,4 +30,17 @@ public int GetHashCode(GenshinCharacter obj)
{
return obj.Id;
}
}

public interface IGenshinCharacter
{
int Id { get; }
public string DisplayName { get; set; }
public string[] Keys { get; set; }
public DateTime ReleaseDate { get; set; }
public string? ImageUri { get; set; }
public int Rarity { get; set; }
public string Element { get; set; }
public string Weapon { get; set; }
public string[] Region { get; set; }
}
2 changes: 1 addition & 1 deletion src/GIMI-ModManager.Core/Services/GenshinService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public Dictionary<GenshinCharacter, int> GetCharacters(string searchQuery,
var splitNames = loweredDisplayName.Split();
var sameStartChars = 0;
var bestResultOfNames = 0;

// This loop will give points for each name that starts with the same chars as the search query
foreach (var name in splitNames)
{
sameStartChars = 0;
Expand Down
1 change: 1 addition & 0 deletions src/GIMI-ModManager.WinUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public App()

services.AddSingleton<IWindowManagerService, WindowManagerService>();
services.AddSingleton<NotificationManager>();
services.AddTransient<ModDragAndDropService>();

services.AddSingleton<ElevatorService>();
services.AddSingleton<GenshinProcessManager>();
Expand Down
3 changes: 2 additions & 1 deletion src/GIMI-ModManager.WinUI/Assets/characters.json
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,8 @@
"Id": 40,
"DisplayName": "Nahida",
"Keys": [
"nahida"
"nahida",
"buer"
],
"ReleaseDate": "0001-01-01T00:00:00",
"ImageUri": "Character_Nahida_Thumb.png",
Expand Down
4 changes: 4 additions & 0 deletions src/GIMI-ModManager.WinUI/GIMI-ModManager.WinUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
<None Remove="Views\Controls\EditableTextBlock.xaml" />
<None Remove="Views\Controls\FolderSelector.xaml" />
<None Remove="Views\Controls\ModListOverview.xaml" />
<None Remove="Views\Controls\SelectCharacterFromGrid.xaml" />
<None Remove="Views\DebugPage.xaml" />
<None Remove="Views\NotificationsPage.xaml" />
</ItemGroup>
Expand Down Expand Up @@ -409,6 +410,9 @@
<None Update="Assets\Images\Character_Zhongli_Thumb.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Page Update="Views\Controls\SelectCharacterFromGrid.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Views\Controls\EditableTextBlock.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public class CharacterOverviewOptions

public int[] PinedCharacters { get; set; } = Array.Empty<int>();
public int[] HiddenCharacters { get; set; } = Array.Empty<int>();
public int[] IgnoreMultipleModsWarning { get; set; } = Array.Empty<int>();
public bool ShowOnlyCharactersWithMods { get; set; } = false;
}
27 changes: 27 additions & 0 deletions src/GIMI-ModManager.WinUI/Models/Options/ModAttentionSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Newtonsoft.Json;

namespace GIMI_ModManager.WinUI.Models.Options;

public class ModAttentionSettings
{
[JsonIgnore] public const string Key = "ModAttentionSettings";

public ModNotification[] ModNotifications { get; set; } = Array.Empty<ModNotification>();
}

public sealed class ModNotification
{
public string ModCustomName { get; set; }
public string ModFolderName { get; set; }
public bool ShowOnOverview { get; set; }
public AttentionType AttentionType { get; set; }
public string Message { get; set; }
}

public enum AttentionType
{
Added,
Modified,
UpdateAvailable,
Error
}
152 changes: 152 additions & 0 deletions src/GIMI-ModManager.WinUI/Services/ModDragAndDropService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using GIMI_ModManager.Core.Entities;
using GIMI_ModManager.Core.Services;
using Serilog;
using Windows.Storage;
using GIMI_ModManager.Core.Contracts.Entities;

namespace GIMI_ModManager.WinUI.Services;

public class ModDragAndDropService
{
private readonly ILogger _logger;

private readonly NotificationManager _notificationManager;

public ModDragAndDropService(ILogger logger, NotificationManager notificationManager)
{
_notificationManager = notificationManager;
_logger = logger.ForContext<ModDragAndDropService>();
}

// Drag and drop directly from 7zip is REALLY STRANGE, I don't know why 7zip 'usually' deletes the files before we can copy them
// Sometimes only a few folders are copied, sometimes only a single file is copied, but usually 7zip removes them and the app just crashes
// This code is a mess, but it works.
public async Task AddStorageItemFoldersAsync(ICharacterModList modList, IReadOnlyList<IStorageItem>? storageItems)
{
if (storageItems is null || !storageItems.Any())
{
_logger.Warning("Drag and drop files called with null/0 storage items.");
return;
}


if (storageItems.Count > 1)
{
_notificationManager.ShowNotification(
"Drag and drop called with more than one storage item, this is currently not supported", "",
TimeSpan.FromSeconds(5));
return;
}

// Warning mess below
foreach (var storageItem in storageItems)
{
var destDirectoryInfo = new DirectoryInfo(modList.AbsModsFolderPath);
destDirectoryInfo.Create();


if (storageItem is StorageFile)
{
using var scanner = new DragAndDropScanner();
var extractResult = scanner.Scan(storageItem.Path);
extractResult.ExtractedMod.MoveTo(destDirectoryInfo.FullName);
if (extractResult.IgnoredMods.Any())
App.MainWindow.DispatcherQueue.TryEnqueue(() =>
_notificationManager.ShowNotification(
"Multiple folders detected during extraction, first one was extracted",
$"Ignored Folders: {string.Join(" | ", extractResult.IgnoredMods)}",
TimeSpan.FromSeconds(7)));
continue;
}

if (storageItem is not StorageFolder sourceFolder)
{
_logger.Information("Unknown storage item type from drop: {StorageItemType}", storageItem.GetType());
continue;
}


_logger.Debug("Source destination folder for drag and drop: {Source}", sourceFolder.Path);
_logger.Debug("Copying folder {FolderName} to {DestinationFolder}", sourceFolder.Path,
destDirectoryInfo.FullName);


var sourceFolderPath = sourceFolder.Path;


if (sourceFolderPath is null)
{
_logger.Warning("Source folder path is null, skipping.");
continue;
}

var tmpFolder = Path.GetTempPath();

Action<StorageFolder, StorageFolder> recursiveCopy = null!;

if (sourceFolderPath.Contains(tmpFolder)) // Is 7zip
recursiveCopy = RecursiveCopy7z;
else // StorageFolder from explorer
{
destDirectoryInfo = new DirectoryInfo(Path.Combine(modList.AbsModsFolderPath, sourceFolder.Name));
destDirectoryInfo.Create();
recursiveCopy = RecursiveCopy;
}

//IsAddingModFolder = true; // This was used to disable the UI while adding a mod, but it's not in use anymore

recursiveCopy.Invoke(sourceFolder,
await StorageFolder.GetFolderFromPathAsync(destDirectoryInfo.FullName));
}
}

// ReSharper disable once InconsistentNaming
private static void RecursiveCopy7z(StorageFolder sourceFolder, StorageFolder destinationFolder)
{
var tmpFolder = Path.GetTempPath();
var parentDir = new DirectoryInfo(Path.GetDirectoryName(sourceFolder.Path)!);
parentDir.MoveTo(Path.Combine(tmpFolder, "JASM_TMP", Guid.NewGuid().ToString("N")));
var mod = new Mod(parentDir.GetDirectories().First()!);
mod.MoveTo(destinationFolder.Path);
}

private void RecursiveCopy(StorageFolder sourceFolder, StorageFolder destinationFolder)
{
if (sourceFolder == null || destinationFolder == null)
{
throw new ArgumentNullException("Source and destination folders cannot be null.");
}

var sourceDir = new DirectoryInfo(sourceFolder.Path);

// Copy files
foreach (var file in sourceDir.GetFiles())
{
_logger.Debug("Copying file {FileName} to {DestinationFolder}", file.FullName, destinationFolder.Path);
if (!File.Exists(file.FullName))
{
_logger.Warning("File {FileName} does not exist.", file.FullName);
continue;
}

file.CopyTo(Path.Combine(destinationFolder.Path, file.Name), true);
}
// Recursively copy subfolders

foreach (var subFolder in sourceDir.GetDirectories())
{
_logger.Debug("Copying subfolder {SubFolderName} to {DestinationFolder}", subFolder.FullName,
destinationFolder.Path);
if (!Directory.Exists(subFolder.FullName))
{
_logger.Warning("Subfolder {SubFolderName} does not exist.", subFolder.FullName);
continue;
}

var newSubFolder = new DirectoryInfo(Path.Combine(destinationFolder.Path, subFolder.Name));
newSubFolder.Create();
RecursiveCopy(StorageFolder.GetFolderFromPathAsync(subFolder.FullName).GetAwaiter().GetResult(),
StorageFolder.GetFolderFromPathAsync(newSubFolder.FullName).GetAwaiter().GetResult());
}
}
}
47 changes: 40 additions & 7 deletions src/GIMI-ModManager.WinUI/Services/ProcessManagerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public abstract partial class BaseProcessManager<TProcessOptions> : ObservableOb
{
private protected readonly ILogger _logger;
private protected readonly ILocalSettingsService _localSettingsService;
private readonly NotificationManager _notificationManager = new();

private Process? _process;
private protected string _prcoessPath = null!;
Expand All @@ -25,6 +26,17 @@ public abstract partial class BaseProcessManager<TProcessOptions> : ObservableOb

public string ProcessName { get; protected set; } = string.Empty;

public string ProcessPath
{
get => _prcoessPath;
private protected set
{
if (value == _prcoessPath) return;
_prcoessPath = value;
OnPropertyChanged();
}
}


[ObservableProperty] private string? _errorMessage = null;

Expand Down Expand Up @@ -52,7 +64,8 @@ public async Task<bool> TryInitialize()
return false;
}

_prcoessPath = processOptions.ProcessPath;
ProcessPath = processOptions.ProcessPath;
ProcessName = Path.GetFileNameWithoutExtension(ProcessPath);
ProcessStatus = ProcessStatus.NotRunning;
return true;
}
Expand All @@ -76,7 +89,7 @@ public async Task SetPath(string processName, string path, string? workingDirect
private async Task _SetStartupPath(string processName, string path, string? workingDirectory = null)
{
ProcessName = processName;
_prcoessPath = path;
ProcessPath = path;
_workingDirectory = workingDirectory ?? Path.GetDirectoryName(path) ?? "";

var processOptions = await ReadProcessOptions();
Expand Down Expand Up @@ -111,23 +124,42 @@ public void StartProcess()

try
{
_process = Process.Start(new ProcessStartInfo(_prcoessPath)
_process = Process.Start(new ProcessStartInfo(ProcessPath)
{
WorkingDirectory = _workingDirectory == string.Empty
? Path.GetDirectoryName(_prcoessPath) ?? ""
? Path.GetDirectoryName(ProcessPath) ?? ""
: _workingDirectory,
Arguments = _isGenshinClass ? "runas" : "",
UseShellExecute = _isGenshinClass
});
}
catch (Win32Exception e)
{
_logger.Error(e, $"Failed to start {ProcessName}, this is likely due to the user cancelling the UAC (admin) prompt");
ErrorMessage = $"Failed to start {ProcessName}";
if (e.NativeErrorCode == 1223)
{
_logger.Error(e,
$"Failed to start {ProcessName}, this can happen due to the user cancelling the UAC (admin) prompt");
ErrorMessage =
$"Failed to start {ProcessName}, this can happen due to the user cancelling the UAC (admin) prompt";
}
else if (e.NativeErrorCode == 740)
{
_logger.Error(e,
$"Failed to start {ProcessName}, this can happen if the exe has the 'Run as administrator' option enabled");
ErrorMessage =
$"Failed to start {ProcessName}, this can happen if the exe has the 'Run as administrator' option enabled in the exe properties";
}
else
{
_logger.Error(e, $"Failed to start {ProcessName}");
ErrorMessage = $"Failed to start {ProcessName} due to an unknown error, see logs for details";
}


ErrorMessage ??= $"Failed to start {ProcessName}";
return;
}


if (_process == null || _process.HasExited)
{
ProcessStatus = ProcessStatus.NotInitialized;
Expand All @@ -136,6 +168,7 @@ public void StartProcess()
return;
}

ErrorMessage = null;
ProcessStatus = ProcessStatus.Running;

_process.Exited += (sender, args) =>
Expand Down
Loading
Loading