Skip to content

Commit 24486d5

Browse files
committed
Added tracking for online vs local sourced mods
1 parent 4a876d0 commit 24486d5

5 files changed

Lines changed: 91 additions & 1 deletion

File tree

src/model/ManifestV2.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export default class ManifestV2 {
3131
private enabled: boolean = true;
3232
private icon: string = '';
3333

34+
private onlineSource: boolean = false;
35+
3436
// Intended to be used to import a mod with only minimal fields specified.
3537
// Should support manifest V1. Defaults to an "Unknown" author field if not found.
3638
public makeSafeFromPartial(data: any): R2Error | ManifestV2 {
@@ -87,6 +89,7 @@ export default class ManifestV2 {
8789
this.setGameVersion(jsManifestObject.gameVersion);
8890
this.icon = path.join(PathResolver.MOD_ROOT, 'cache', this.getName(), this.versionNumber.toString(), 'icon.png');
8991
this.setInstalledAtTime(jsManifestObject.installedAtTime || 0);
92+
this.setOnlineSource(jsManifestObject.onlineSource || false);
9093
if (!jsManifestObject.enabled) {
9194
this.disable();
9295
}
@@ -245,4 +248,12 @@ export default class ManifestV2 {
245248
public getDependencyString(): string {
246249
return `${this.getName()}-${this.getVersionNumber().toString()}`;
247250
}
251+
252+
public isOnlineSource(): boolean {
253+
return this.onlineSource;
254+
}
255+
256+
public setOnlineSource(isOnlineSource: boolean) {
257+
this.onlineSource = isOnlineSource;
258+
}
248259
}

src/r2mm/downloading/BetterThunderstoreDownloader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import StatusEnum from '../../model/enums/StatusEnum';
21
import axios, { AxiosResponse } from 'axios';
32
import ThunderstoreCombo from '../../model/ThunderstoreCombo';
43
import ZipExtract from '../installing/ZipExtract';
@@ -61,6 +60,7 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
6160
try {
6261
const response = await this._downloadCombo(comboInProgress, singleModProgressCallback);
6362
await this._saveDownloadResponse(response, comboInProgress, singleModProgressCallback);
63+
await DownloadUtils.markAsDownloadedFromOnline(comboInProgress);
6464
} catch(e) {
6565
throw R2Error.fromThrownValue(e, `Failed to download mod ${comboInProgress.getVersion().getFullName()}`);
6666
}

src/r2mm/mods/CacheUtil.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ManifestV2 from '../../model/ManifestV2';
77
import VersionNumber from '../../model/VersionNumber';
88
import FsProvider from '../../providers/generic/file/FsProvider';
99
import path from '../../providers/node/path/path';
10+
import * as DownloadUtils from '../../utils/DownloadUtils';
1011

1112
export default class CacheUtil {
1213

@@ -42,8 +43,15 @@ export default class CacheUtil {
4243
const cacheDirectory = path.join(PathResolver.MOD_ROOT, "cache");
4344
await FileUtils.ensureDirectory(cacheDirectory);
4445
for (const folder of (await fs.readdir(cacheDirectory))) {
46+
if (folder === '_state') {
47+
continue;
48+
}
4549
if ((await fs.stat(path.join(cacheDirectory, folder))).isDirectory()) {
4650
if (this.hasNoVersions(folder, activeModSet)) {
51+
// Remove all online state files for this mod before deleting the folder
52+
for (const versionFolder of (await fs.readdir(path.join(cacheDirectory, folder)))) {
53+
await DownloadUtils.removeOnlineStateFile(folder, versionFolder);
54+
}
4755
await FileUtils.emptyDirectory(path.join(cacheDirectory, folder))
4856
await fs.rmdir(path.join(cacheDirectory, folder));
4957
} else {
@@ -52,6 +60,8 @@ export default class CacheUtil {
5260
const matchingVersion = versions.find(value => value.toString() === versionFolder);
5361
if (matchingVersion === undefined) {
5462
try {
63+
// Remove online state file for this version
64+
await DownloadUtils.removeOnlineStateFile(folder, versionFolder);
5565
await FileUtils.emptyDirectory(path.join(cacheDirectory, folder, versionFolder));
5666
await fs.rmdir(path.join(cacheDirectory, folder, versionFolder));
5767
} catch (e) {

src/utils/DownloadUtils.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,66 @@ export function generateProgressPercentage(currentProgress: number, targetProgre
8585
export function statusIsDownloadOrExtract(status: DownloadStatusEnum): boolean {
8686
return status === DownloadStatusEnum.DOWNLOADING || status === DownloadStatusEnum.EXTRACTING || status === DownloadStatusEnum.EXTRACTED;
8787
}
88+
89+
/**
90+
* Get the path to the online state directory in the cache.
91+
* This directory contains empty files named after dependency strings
92+
* to track which mods were downloaded from online sources.
93+
*/
94+
export function getOnlineStateDir(): string {
95+
return path.join(PathResolver.MOD_ROOT, 'cache', '_state', 'online');
96+
}
97+
98+
/**
99+
* Get the path to the online state file for a given dependency string.
100+
* @param dependencyString The dependency string (e.g., "AuthorName-ModName-1.0.0")
101+
*/
102+
export function getOnlineStatePath(dependencyString: string): string {
103+
return path.join(getOnlineStateDir(), dependencyString);
104+
}
105+
106+
/**
107+
* Check if a mod was previously downloaded from online (state file exists).
108+
* @param combo The combo to check.
109+
* @returns True if the mod was downloaded from online, false otherwise.
110+
*/
111+
export async function wasDownloadedFromOnline(combo: ThunderstoreCombo): Promise<boolean> {
112+
const statePath = getOnlineStatePath(combo.getDependencyString());
113+
return FsProvider.instance.exists(statePath);
114+
}
115+
116+
/**
117+
* Mark a mod as downloaded from online by creating a state file.
118+
* Errors are silently ignored to avoid failing downloads due to state tracking issues.
119+
* @param combo The combo to mark.
120+
*/
121+
export async function markAsDownloadedFromOnline(combo: ThunderstoreCombo): Promise<void> {
122+
try {
123+
const fs = FsProvider.instance;
124+
const stateDir = getOnlineStateDir();
125+
if (!await fs.exists(stateDir)) {
126+
await fs.mkdirs(stateDir);
127+
}
128+
const statePath = getOnlineStatePath(combo.getDependencyString());
129+
await fs.writeFile(statePath, '');
130+
} catch (e) {
131+
console.warn(`Failed to mark mod as downloaded from online: ${combo.getDependencyString()}`, e);
132+
}
133+
}
134+
135+
/**
136+
* Remove the online state file for a given mod version.
137+
* @param modName The full mod name (e.g., "AuthorName-ModName")
138+
* @param version The version string (e.g., "1.0.0")
139+
*/
140+
export async function removeOnlineStateFile(modName: string, version: string): Promise<void> {
141+
const fs = FsProvider.instance;
142+
const stateFile = path.join(getOnlineStateDir(), `${modName}-${version}`);
143+
try {
144+
if (await fs.exists(stateFile)) {
145+
await fs.unlink(stateFile);
146+
}
147+
} catch (e) {
148+
// Ignore errors when removing state files
149+
}
150+
}

src/utils/ProfileUtils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ZipProvider from "../providers/generic/zip/ZipProvider";
1414
import ProfileInstallerProvider from "../providers/ror2/installing/ProfileInstallerProvider";
1515
import * as PackageDb from '../r2mm/manager/PackageDexieStore';
1616
import ProfileModList from "../r2mm/mods/ProfileModList";
17+
import { wasDownloadedFromOnline } from './DownloadUtils';
1718

1819
export async function exportModsToCombos(
1920
exportMods: ExportMod[],
@@ -85,6 +86,11 @@ export async function installModsToProfile(
8586

8687
const manifestMod = new ManifestV2().fromThunderstoreCombo(comboMod);
8788

89+
// Mark as downloaded from online if the state file exists in cache
90+
if (await wasDownloadedFromOnline(comboMod)) {
91+
manifestMod.setOnlineSource(true);
92+
}
93+
8894
if (installedVersions.includes(manifestMod.getDependencyString())) {
8995
continue;
9096
}

0 commit comments

Comments
 (0)