Skip to content

Commit

Permalink
feat!: Redid Folder structure (Jorixon#109)
Browse files Browse the repository at this point in the history
feat: Character/ModObject folders are now created on demand

feat: Added Mod counter on overview and sort by mod count

chore: Added "Date Added" to grid in CharacterDetails page
  • Loading branch information
Jorixon authored Jan 6, 2024
1 parent 4ee26d6 commit 62622a6
Show file tree
Hide file tree
Showing 21 changed files with 716 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,12 @@ public interface ICharacterModList : IDisposable
/// Deletes a mod from the mod list. This deletes entire mod from the mod folder.
/// </summary>
public void DeleteModBySkinEntryId(Guid skinEntryId, bool moveToRecycleBin = true);

/// <summary>
/// If true, then the the mod list folder has been created. If false, then the mod list folder has not been created.
/// </summary>
/// <returns></returns>
public bool IsCharacterFolderCreated();

public void InstantiateCharacterFolder();
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface ISkinManagerService : IDisposable
public Task ScanForModsAsync();
public ICharacterModList GetCharacterModList(string internalName);
public ICharacterModList GetCharacterModList(IModdableObject character);
public ICharacterModList? GetCharacterModListOrDefault(string internalName);

public Task InitializeAsync(string activeModsFolderPath, string? unloadedModsFolderPath,
string? threeMigotoRootfolder = null);
Expand Down Expand Up @@ -76,6 +77,8 @@ public void ExportMods(ICollection<ICharacterModList> characterModLists, string
/// <param name="modList">The modList where the mod will be moved to</param>
/// <param name="move">If true, will move the mod instead of copying it</param>
public ISkinMod AddMod(ISkinMod mod, ICharacterModList modList, bool move = false);

public ICollection<DirectoryInfo> CleanCharacterFolders();
}

public enum SetModStatus
Expand Down
126 changes: 115 additions & 11 deletions src/GIMI-ModManager.Core/Entities/CharacterModList.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using GIMI_ModManager.Core.Contracts.Entities;
using GIMI_ModManager.Core.Entities.Mods.SkinMod;
using GIMI_ModManager.Core.GamesService.Interfaces;
Expand All @@ -26,29 +27,128 @@ public IReadOnlyCollection<CharacterSkinEntry> Mods
public const string DISABLED_PREFIX = ModFolderHelpers.DISABLED_PREFIX;
public const string ALT_DISABLED_PREFIX = ModFolderHelpers.ALT_DISABLED_PREFIX;
public string DisabledPrefix => DISABLED_PREFIX;
private readonly FileSystemWatcher _watcher;
private FileSystemWatcher? _watcher;

private readonly FileSystemWatcher _selfWatcher;

public IModdableObject Character { get; }

private readonly object _modsLock = new();

[MemberNotNullWhen(true, nameof(_watcher))]
public bool IsCharacterFolderCreated()
{
var modFolderExists = Directory.Exists(AbsModsFolderPath);

if (modFolderExists && _watcher is null)
throw new InvalidOperationException("mod Watcher is null when mod folder exists");

if (!modFolderExists && _watcher is not null)
throw new InvalidOperationException("mod Watcher is not null when mod folder does not exist");

return modFolderExists;
}

internal CharacterModList(IModdableObject character, string absPath, ILogger? logger = null)
{
ArgumentNullException.ThrowIfNull(character);
ArgumentNullException.ThrowIfNull(absPath);


_logger = logger?.ForContext<CharacterModList>();
Character = character;
AbsModsFolderPath = absPath;
_watcher = new FileSystemWatcher(AbsModsFolderPath);
_watcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.CreationTime;
_watcher.Renamed += OnModRenamed;
_watcher.Created += OnModCreated;
_watcher.Deleted += OnModDeleted;
_watcher.Error += OnWatcherError;

_watcher.IncludeSubdirectories = false;
_watcher.EnableRaisingEvents = true;

_selfWatcher = new FileSystemWatcher(Directory.GetParent(absPath)!.FullName);
_selfWatcher.NotifyFilter = NotifyFilters.DirectoryName;
_selfWatcher.Renamed += OnSelfFolderCreated;
_selfWatcher.Created += OnSelfFolderCreated;
_selfWatcher.Deleted += OnSelfFolderDeleted;
_selfWatcher.Error += OnWatcherError;

_selfWatcher.EnableRaisingEvents = true;

if (!Directory.Exists(AbsModsFolderPath))
return;


_watcher = CreateModWatcher();
}

public event EventHandler<ModFolderChangedArgs>? ModsChanged;

private void OnSelfFolderCreated(object sender, FileSystemEventArgs e)
{
if (!Character.InternalNameEquals(e.Name))
return;

if (_watcher is not null)
throw new InvalidOperationException("Watcher is not null when mod folder is created");

_watcher = CreateModWatcher();
}

private void OnSelfFolderRenamed(object sender, FileSystemEventArgs e)
{
if (!Character.InternalNameEquals(e.Name))
return;

if (_watcher is not null)
throw new InvalidOperationException("Watcher is not null when mod folder is created");

_watcher = CreateModWatcher();
}


private void OnSelfFolderDeleted(object sender, FileSystemEventArgs e)
{
if (!Character.InternalNameEquals(e.Name))
return;

if (_watcher is null)
throw new InvalidOperationException("Self watcher is null when mod folder is deleted");

var oldModWatcher = _watcher;
_watcher = null;
oldModWatcher?.Dispose();

lock (_modsLock)
{
_mods.Clear();
}
}


private FileSystemWatcher CreateModWatcher()
{
var watcher = new FileSystemWatcher(AbsModsFolderPath);
watcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.CreationTime;
watcher.Renamed += OnModRenamed;
watcher.Created += OnModCreated;
watcher.Deleted += OnModDeleted;
watcher.Error += OnWatcherError;

watcher.IncludeSubdirectories = false;
watcher.EnableRaisingEvents = true;
return watcher;
}


public void InstantiateCharacterFolder()
{
lock (_modsLock)
{
if (IsCharacterFolderCreated())
return;

using var disableSelfWatcher = new DisableWatcher(_selfWatcher);
Directory.CreateDirectory(AbsModsFolderPath);
_watcher = CreateModWatcher();
}

_logger?.Debug("Created {CharacterName} mod folder", Character.InternalName);
}

private void OnWatcherError(object sender, ErrorEventArgs e)
{
_logger?.Error(e.GetException(), "Error in FileSystemWatcher");
Expand Down Expand Up @@ -270,7 +370,9 @@ private bool ModAlreadyAdded(ISkinMod mod)

public void Dispose()
{
_watcher.Dispose();
_watcher?.Dispose();
_selfWatcher.Dispose();
_watcher = null;
}

public override string ToString()
Expand All @@ -280,6 +382,7 @@ public override string ToString()

public DisableWatcher DisableWatcher()
{
InstantiateCharacterFolder();
return new DisableWatcher(_watcher);
}

Expand Down Expand Up @@ -363,6 +466,7 @@ public class DisableWatcher : IDisposable

public DisableWatcher(FileSystemWatcher watcher)
{
ArgumentNullException.ThrowIfNull(watcher);
_watcher = watcher;
_watcher.EnableRaisingEvents = false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/GIMI-ModManager.Core/GamesService/GameService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ private static int GetBaseSearchResult(string searchQuery, INameable character)

// If the search query contains the display name, we give it a lot of points
var sameChars = loweredDisplayName.Split().Count(searchQuery.Contains);
result += sameChars * 50;
result += sameChars * 60;

var splitNames = loweredDisplayName.Split();
var sameStartChars = 0;
Expand All @@ -364,7 +364,7 @@ private static int GetBaseSearchResult(string searchQuery, INameable character)
}
}

result += sameStartChars * 5; // Give more points for same start chars
result += sameStartChars * 10; // Give more points for same start chars

result += loweredDisplayName.Split()
.Max(name => Fuzz.PartialRatio(name, searchQuery)); // Do a partial ratio for each name
Expand Down
11 changes: 11 additions & 0 deletions src/GIMI-ModManager.Core/Helpers/ModFolderHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,15 @@ public static bool FolderHasDisabledPrefix(string folderName, bool absolutePath
return folderName.StartsWith(DISABLED_PREFIX, StringComparison.CurrentCultureIgnoreCase) ||
folderName.StartsWith(ALT_DISABLED_PREFIX, StringComparison.CurrentCultureIgnoreCase);
}


public static bool IsJASMFileEntry(string entry)
{
return entry.StartsWith(".JASM_", StringComparison.OrdinalIgnoreCase);
}

public static bool IsJASMFileEntry(FileSystemInfo entry)
{
return entry.Name.StartsWith(".JASM_", StringComparison.OrdinalIgnoreCase);
}
}
2 changes: 1 addition & 1 deletion src/GIMI-ModManager.Core/Services/ModCrawlerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private bool IsOfSkinType(FileInfo file, ICharacterSkin skin)
private bool IsOfModType(FileInfo file, IModdableObject moddableObject)
=> StartsWithModFilesName(file, moddableObject.ModFilesName);

private static readonly string[] ModExtensions = { ".buf", ".dds", ".ib" };
public static readonly string[] ModExtensions = { ".buf", ".dds", ".ib" };

private bool StartsWithModFilesName(FileInfo file, string modFilesName)
{
Expand Down
Loading

0 comments on commit 62622a6

Please sign in to comment.