Skip to content

Commit f51258a

Browse files
committed
V4.0.5 Trash limit warning and prompt are now fully functional.
The trash limit warning preference now functions. - "Log" will log a warning in the console. - "Prompt" will create a JOptionDialog warning the trash limit has been exceeded and prompts to do a clean now. Fixed ComboBox type QuestionCards not auto-populating their data when given an index instead of a text value. This was done because I prefer to strore the index value of combo boxes for preferences. This still supports being given a text value. How it works is that if the given value is not in the value options, it will try cast to an Integer and set the selectedIndex to it. Fixed the quick-trash not actually using the preference values. Fixed mods not loading for only the **first** attempted on boot. Well, it was loading but it would not re-draw unless something was clicked on. Now it will always update using a property change listener. Logs now include seconds. Updated the README to reflect some new features. Updated the FAQ to have some more relevant info for modders.
1 parent 4ab0a59 commit f51258a

23 files changed

Lines changed: 105 additions & 50 deletions

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ The goal is to create a practical project for both real-world usage but also as
5151
- Verify files Hashes match to detect changes and avoid unnecessary file system operations.
5252
- In case of final write failures, data stored in temp is recoverable (temp/ cleaning only takes place when no errors occur)
5353
- Visual feedback (Console) Actions are carefully logged, allowing exact failure pinpointing.
54+
- Trash utility: Manage the max size and number of days old trash files can be with automatic warnings and prompts to clean or empty trash files.
5455

5556
### Mods:
5657
- Track, add, remove and update Mods deployed into a game.
@@ -63,6 +64,9 @@ The goal is to create a practical project for both real-world usage but also as
6364
- Version number.
6465
- Download information. Store a download URL and tag the download source (eg: Nexus, ModDB, Steam, custom...)
6566
- Add tags to mods for GUI sorting and filtering.
67+
- Intuative updating. Mods can be updated easily at anytime, handling re-deployment automatically.
68+
Updates can provide new files or the exsisting files will be re-compiled.
69+
For modders, this makes tweaking/updating mods on the go for testing fast and painless. See the [FAQ](docs/FAQ.md) for more.
6670

6771
### Games:
6872
- Stores a current GameState with the game, all data needed to track and remove deployed Mods is stored within the game directory.
@@ -86,11 +90,11 @@ The goal is to create a practical project for both real-world usage but also as
8690
### GUI
8791
- Tile view of games with icons.
8892
- Manage Mods with a Card design to display mod info clearly.
89-
- Download link for mods will be a clickable link.
9093
- Interactive way to reorder mods by dragging mod tiles.
9194
- Enable/disable mods by dragging them to and from the deployed/stored containers.
95+
- Download link for mod are a clickable hyper-link.
9296
- All file/directory input fields allow Drag-and-Drop.
93-
- Write changes to a temp GameState or pick from saved profiles. Then Apply the GameState when ready. (TODO)
97+
- Write changes to a temp GameState or pick from saved profiles. Then Apply the GameState when ready.
9498

9599
### CLI Usage:
96100
The CLI offers an interactive interface to perform all the core operations for managing Games and Mods.

docs/FAQ.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
- ### What do I need to run FCMM?
44
All you need is a Java runtime. Ideally version 25 or newer.<br>
55
Changes in version 22 to `Java.nio` for Path handling made this project way cleaner but also not compatible with older runtimes.<br>
6-
All dependencies are very small (currently 1) and are hence bundled with the .jar file.
6+
All dependencies are very small (currently 1) and are hence bundled with the .jar file so you won't need anything else.
77

88
- ### How do I install and run FCMM?
99

@@ -64,8 +64,15 @@
6464
- ### Should I use this instead of Vortex from Nexus?
6565
No -well, it depends.<br>
6666
Vortex uses a virtual file-system (VFS) to deploy games within. In short, this means that when a game want say, file *A* the VFS could point that path to anywhere else, leading it to file *B* without the game necessarily knowing.<br>
67-
Why does this matter? It is unquestionably safer, and way faster when running the mod launcher simply because no files are actually moved or replaced. Its simply a different approach with different limitations.
67+
Why does this matter? It is unquestionably safer, and way faster when running the mod launcher, simply because no files are actually moved or replaced. Its simply a different approach with different limitations.
6868
6969
- ### But when *should* I use FCMM?
7070
While using a virtual file-system is "better", its not always supported. Some games cannot run in a VFS or their anti-cheat flags it as a problem. Or Vortex might not have an extension for your game!<br>
7171
In those cases, I recommend using FCMM, that is what it is made for and what I personally use it for.
72+
73+
- ### I'm a modder, what do I need to know?
74+
For modder, FCMM makes it easy to update mods quickly and seamlessly.<br>
75+
If you have a custom mod, what you should do is add it to FCMM, so the launcher handles its deployment. Then when you want to make changes to the mod:
76+
- Edit the mod-data **inside** your Mod Storage, editting the compiled mod-data for FCMM.
77+
- Doing this, to update the mod simply click "Edit", *switch to "Update"* and "Save".
78+
- **Done!** The mod will be automatically re-compiled using the exsisting data and re-deployed to the game with the same load order as before.

fcmm/src/core/config/AppConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public final class AppConfig {
2323
/// Core:
2424
// #region
2525

26-
private final String AppVersion = "4.0.4"; // Program's version
26+
private final String AppVersion = "4.0.5"; // Program's version
2727

2828
private static final Path configPath = defaultConfig.CONFIG_FILE_PATH;
2929

fcmm/src/core/config/AppPreferences.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public enum properties {
2626

2727
NORMALISE_BY_GROUP("NORMALISE_BY_GROUP", "Normalise mods by groups", true),
2828

29-
TRASH_SIZE_WARNING("TRASH_SIZE_WARNING", "Trash size limit warning", "off"),
29+
TRASH_SIZE_WARNING("TRASH_SIZE_WARNING", "Trash size limit warning", 0),
3030
TRASH_SIZE_LIMIT("TRASH_SIZE_LIMIT", "Trash size limit", 100),
3131
TRASH_DAYS_OLD("TRASH_DAYS_OLD", "Trash days old limit", 30);
3232

fcmm/src/core/utils/DateUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class DateUtil {
2525
// For display in GUI: Dec 13, 2024 7:26 PM
2626
private static final DateTimeFormatter DISPLAY_FORMATTER = DateTimeFormatter.ofPattern("MMM dd, yyyy h:mm a");
2727

28-
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("MMM dd, yyyy h:mm a");
28+
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("MMM dd, yyyy h:mm:ss");
2929

3030
// For JSON (ISO): 2024-12-13T19:26:00Z (keep this for serialization!)
3131
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_INSTANT;

fcmm/src/core/utils/TrashUtil.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,15 @@ public static void cleanTrash(long maxMegabytes, LocalDate cutoffDate) {
101101

102102
/// /// /// Utilities /// /// ///
103103

104-
private static float megabyte(Long bytes) {
104+
public static float megabyte(Long bytes) {
105105
return bytes / (1000000.0f);
106106
}
107107

108+
/**
109+
*
110+
* @param dir
111+
* @return Size in bytes
112+
*/
108113
public static long getDiskSize(Path dir) {
109114
Long size = 0L;
110115
try {

fcmm/src/gui/App.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,15 @@ private JMenuBar createMenuBar() {
120120
///
121121

122122
private void quickCleanTrash() {
123+
int daysOld = AppConfig.getInstance().preferences.getAsInt(properties.TRASH_DAYS_OLD);
124+
int trashLimit = AppConfig.getInstance().preferences.getAsInt(properties.TRASH_SIZE_LIMIT);
125+
123126
int result = JOptionPane.showConfirmDialog(
124127
mainFrame,
125128
"Permanently delete all files in trash older than "
126-
+ AppConfig.getInstance().preferences.get(properties.TRASH_DAYS_OLD).toString()
129+
+ daysOld
127130
+ " days and trim to under "
128-
+ AppConfig.getInstance().preferences.get(properties.TRASH_SIZE_LIMIT).toString()
131+
+ trashLimit
129132
+ "MB?",
130133
"Clean Trash",
131134
JOptionPane.YES_NO_OPTION,
@@ -140,7 +143,7 @@ private void quickCleanTrash() {
140143
SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
141144
@Override
142145
protected Void doInBackground() throws Exception { // long-running task
143-
TrashUtil.cleanTrash(2000, LocalDate.now().minusDays(30));
146+
TrashUtil.cleanTrash(trashLimit, LocalDate.now().minusDays(daysOld));
144147
return null;
145148
}
146149

fcmm/src/gui/components/QuestionCard.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,15 @@ public void setValue(String value) {
278278
return;
279279
}
280280
}
281-
// If not found, try to add it
281+
// If not found, check if its a number (index)
282+
try {
283+
combo.setSelectedIndex(Integer.parseInt(value));
284+
return;
285+
} catch (Exception e) {
286+
// Do nothing
287+
}
288+
289+
// if still not found, try to add it
282290
try {
283291
setComboBoxValue(combo, value);
284292
combo.setSelectedItem(value);

fcmm/src/gui/views/ModManagerView.java

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import javax.swing.*;
88
import java.awt.*;
9+
import java.time.LocalDate;
910
import java.util.*;
1011
import java.util.List;
1112
import java.util.stream.Collectors;
@@ -18,11 +19,13 @@
1819
import gui.components.ModCard;
1920
import gui.components.DividerCard;
2021
import core.config.AppConfig;
22+
import core.config.AppPreferences;
2123
import core.config.AppPreferences.properties;
2224
import core.managers.ModManager;
2325
import core.objects.GameState;
2426
import core.objects.Mod;
2527
import core.utils.Logger;
28+
import core.utils.TrashUtil;
2629

2730
/**
2831
* This is the primary view responsible for displaying and managing Mods.
@@ -35,6 +38,7 @@ public class ModManagerView extends BaseView {
3538
// Globals
3639
private final ModManager manager;
3740
private boolean modsLoading = false;
41+
private final Logger log = Logger.getInstance();
3842

3943
// UI Components
4044
private JPanel utilityPanel;
@@ -260,30 +264,35 @@ private void loadMods() {
260264
if (allMods == null) {
261265
modsLoading = true;
262266
// Async / background loading of all Mods when needed.
263-
SwingWorker<Void, List<Mod>> worker = new SwingWorker<>() {
267+
SwingWorker<List<Mod>, Void> worker = new SwingWorker<>() {
264268
@Override
265-
protected Void doInBackground() throws Exception {
266-
Logger.getInstance().info(0, null, "Fetching all mods...");
269+
protected List<Mod> doInBackground() throws Exception {
270+
return manager.getAllMods();
271+
}
272+
273+
@Override
274+
protected void done() {
267275
try {
268-
allMods = manager.getAllMods();
276+
allMods = get();
269277
} catch (Exception e) {
270-
allMods.clear();
271-
showError("Failed to load mods", e);
278+
allMods = new ArrayList<>();
272279
}
273-
publish(allMods);
274-
return null;
275-
}
276280

277-
@Override
278-
protected void process(List<List<Mod>> chunks) {
279-
Logger.getInstance().info(0, null, "Mods retrieved");
280-
SwingUtilities.invokeLater(() -> {
281-
modsLoading = false;
282-
loadMods(); // This will now be called on EDT
281+
// Listen for property changes (including state changes)
282+
addPropertyChangeListener(evt -> {
283+
if ("state".equals(evt.getPropertyName())
284+
&& evt.getNewValue() == SwingWorker.StateValue.DONE) {
285+
286+
SwingUtilities.invokeLater(() -> {
287+
modsLoading = false;
288+
loadMods();
289+
});
290+
}
283291
});
284292
}
285293
};
286294
worker.execute();
295+
modListPanel.removeAll();
287296
modListPanel.add(new DividerCard("Loading...", Color.GRAY)); // show while loading.
288297
return; // don't procceed further because of loading
289298
}
@@ -304,7 +313,7 @@ protected Void doInBackground() throws Exception {
304313
.collect(Collectors.toSet());
305314

306315
// Logging for future testing.
307-
Logger.getInstance().info(0, null,
316+
log.info(0, null,
308317
"Filteres aplied: \n\tStatus: " + statusFilter
309318
+ "\n\tName: " + nameFilter
310319
+ "\n\tTags: " + tagFilters.toString());
@@ -348,7 +357,7 @@ protected Void doInBackground() throws Exception {
348357

349358
@Override
350359
protected void process(List<List<Mod>> chunks) {
351-
displayModList();
360+
displayModList(); // This populates modListPanel and repaints once all the processing is done.
352361
}
353362

354363
};
@@ -512,7 +521,7 @@ private void handleDragEnd(Mod mod) {
512521
Mod targetMod = findModAtPosition(mousePos);
513522
if (targetMod == null)
514523
return;
515-
Logger.getInstance().info(null, "\tDropped on: " + targetMod.getName());
524+
log.info(null, "\tDropped on: " + targetMod.getName());
516525

517526
if (targetMod != null && !targetMod.getId().equals(draggedMod.getId())) {
518527
moveDraggedMod(draggedMod, targetMod); // Move draggedMod to position before targetMod in allMods
@@ -585,7 +594,7 @@ private void moveDraggedMod(Mod modToMove, Mod targetMod) {
585594
modToMove.setLoadOrder(index);
586595
}
587596

588-
Logger.getInstance().info(0, null, "Dragger Mod " + modToMove.getId() + " to [" + modToMove.isEnabled()
597+
log.info(0, null, "Dragger Mod " + modToMove.getId() + " to [" + modToMove.isEnabled()
589598
+ "] : " + modToMove.getLoadOrder());
590599
}
591600

@@ -599,7 +608,7 @@ private void normaliseLoadOrder() {
599608
.collect(Collectors.toList());
600609

601610
if (AppConfig.getInstance().preferences.is(properties.NORMALISE_BY_GROUP)) {
602-
Logger.getInstance().info(null, "Normalising by group");
611+
log.info(null, "Normalising by group");
603612
// this allows duplicate loadorder values. All it does is ensure each group of
604613
// duplicates is sequential. (1,2,3...) to avoid oddly-high numbers (1,2,8...)
605614

@@ -614,7 +623,7 @@ private void normaliseLoadOrder() {
614623
enabledModsList.get(i).setLoadOrder(groupCnt);
615624
}
616625
} else {
617-
Logger.getInstance().info(null, "Normalising sequentially");
626+
log.info(null, "Normalising sequentially");
618627
// simply orders in sequence
619628
for (int i = 0; i < enabledModsList.size(); i++) {
620629
enabledModsList.get(i).setLoadOrder(i + 1);
@@ -662,6 +671,39 @@ private void applyChanges() {
662671
@Override
663672
protected Void doInBackground() throws Exception { // long-running task
664673
manager.deployGameState(gameState);
674+
675+
// check for trash size limit warning
676+
int warning = AppConfig.getInstance().preferences
677+
.getAsInt(AppPreferences.properties.TRASH_SIZE_WARNING);
678+
if (warning != 0) {
679+
680+
long trashLimit = AppConfig.getInstance().preferences
681+
.getAsInt(AppPreferences.properties.TRASH_SIZE_LIMIT);
682+
float trashSize = TrashUtil
683+
.megabyte(TrashUtil.getDiskSize(AppConfig.getInstance().getTrashDir()));
684+
685+
if (trashLimit <= trashSize) {
686+
687+
if (warning == 1) { // 1. log warning
688+
log.info(0, "ALERT: Trash Size limit of " + trashLimit + "MB has been reached!");
689+
} else if (warning == 2) { // 2. prompt
690+
// popup with options to clean trash
691+
692+
int daysOld = AppConfig.getInstance().preferences.getAsInt(properties.TRASH_DAYS_OLD);
693+
String msg = String.format(
694+
"Your trash disk-size is %.2fMB out of your limit of %dMB\nWould you like to clean now?",
695+
trashSize, trashLimit);
696+
697+
int result = JOptionPane.showConfirmDialog(
698+
navigator.getMainFrame(), msg, "Trash Limit warning",
699+
JOptionPane.YES_NO_OPTION,
700+
JOptionPane.WARNING_MESSAGE);
701+
if (result == JOptionPane.YES_OPTION) {
702+
TrashUtil.cleanTrash(trashLimit, LocalDate.now().minusDays(daysOld));
703+
}
704+
}
705+
} // if limit reached
706+
} // if warning not off
665707
return null;
666708
}
667709

mod_manager/config.json

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1 @@
1-
{
2-
"preferences": {
3-
"TRASH_DAYS_OLD": 20,
4-
"TRASH_SIZE_LIMIT": 50,
5-
"NORMALISE_BY_GROUP": true,
6-
"TRASH_SIZE_WARNING": 0
7-
},
8-
"MANAGER_DIR": ".mod_manager",
9-
"GAME_DIR": "mod_manager/games",
10-
"TRASH_DIR": "mod_manager/.temp/trash",
11-
"DEFAULT_MOD_DIR": "/home/hdd700/Mods/#_FCMM",
12-
"TEMP_DIR": "mod_manager/.temp",
13-
"APP_VERSION": "4.0.4",
14-
"LOG_DIR": "mod_manager/logs"
15-
}
1+
{"preferences":{"TRASH_DAYS_OLD":20,"TRASH_SIZE_LIMIT":50,"NORMALISE_BY_GROUP":true,"TRASH_SIZE_WARNING":2},"MANAGER_DIR":".mod_manager","GAME_DIR":"mod_manager/games","TRASH_DIR":"mod_manager/.temp/trash","DEFAULT_MOD_DIR":"/home/hdd700/Mods/#_FCMM","TEMP_DIR":"mod_manager/.temp","APP_VERSION":"4.0.5","LOG_DIR":"mod_manager/logs"}

0 commit comments

Comments
 (0)