Skip to content

Commit

Permalink
Merge pull request ebkr#1526 from ebkr/packageinstaller-disabling
Browse files Browse the repository at this point in the history
Support disabling/enabling mods via PackageInstaller and add implementation for GDWeavePluginInstaller
  • Loading branch information
anttimaki authored Nov 6, 2024
2 parents f2772d3 + 3ce06ed commit ef7ebf3
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 21 deletions.
70 changes: 51 additions & 19 deletions src/installers/GDWeaveInstaller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { InstallArgs, PackageInstaller } from './PackageInstaller';
import {
disableModByRenamingFiles,
enableModByRenamingFiles,
InstallArgs,
PackageInstaller
} from './PackageInstaller';
import path from 'path';
import FsProvider from '../providers/generic/file/FsProvider';
import FileUtils from '../utils/FileUtils';
Expand Down Expand Up @@ -87,38 +92,65 @@ async function searchForManifest(
}

export class GDWeavePluginInstaller implements PackageInstaller {
async install(args: InstallArgs) {
const { mod, packagePath, profile } = args;
getModFolderInProfile(args: InstallArgs): string {
return args.profile.joinToProfilePath(
'GDWeave',
'mods',
args.mod.getName()
);
}

async install(args: InstallArgs) {
// Packaging is all over the place. Find a folder with a manifest.json
// not at the top level (because the top level is Thunderstore's packaging)
const modFolder = await searchForManifest(packagePath);
if (!modFolder) {
const modFolderInCache = await searchForManifest(args.packagePath);
if (!modFolderInCache) {
throw new Error('Could not find mod folder');
}

const root = profile.joinToProfilePath(
'GDWeave',
'mods',
mod.getName()
);
await FsProvider.instance.copyFolder(modFolder, root);
const modFolderInProfile = this.getModFolderInProfile(args);

try {
await FsProvider.instance.copyFolder(modFolderInCache, modFolderInProfile);
} catch (e) {
const name = 'Failed to copy mod to profile';
throw FileWriteError.fromThrownValue(e, name);
}
}

async uninstall(args: InstallArgs): Promise<void> {
const { mod, profile } = args;
const root = profile.joinToProfilePath(
'GDWeave',
'mods',
mod.getName()
);

try {
await FileUtils.recursiveRemoveDirectoryIfExists(root);
await FileUtils.recursiveRemoveDirectoryIfExists(
this.getModFolderInProfile(args)
);
} catch (e) {
const name = 'Failed to delete mod from profile root';
const solution = 'Is the game still running?';
throw FileWriteError.fromThrownValue(e, name, solution);
}
}

async enable(args: InstallArgs): Promise<void> {
try {
await enableModByRenamingFiles(
this.getModFolderInProfile(args)
);
} catch (e) {
const name = 'Failed to enable mod';
const solution = 'Is the game still running?';
throw FileWriteError.fromThrownValue(e, name, solution);
}
}

async disable(args: InstallArgs): Promise<void> {
try {
await disableModByRenamingFiles(
this.getModFolderInProfile(args)
);
} catch (e) {
const name = 'Failed to disable mod';
const solution = 'Is the game still running?';
throw FileWriteError.fromThrownValue(e, name, solution);
}
}
}
34 changes: 34 additions & 0 deletions src/installers/PackageInstaller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import R2Error from "../model/errors/R2Error";
import FileTree from "../model/file/FileTree";
import { ImmutableProfile } from "../model/Profile";
import ManifestV2 from "../model/ManifestV2";
import FsProvider from "../providers/generic/file/FsProvider";

export type InstallArgs = {
mod: ManifestV2;
Expand All @@ -10,4 +13,35 @@ export type InstallArgs = {
export interface PackageInstaller {
install(args: InstallArgs): Promise<void>;
uninstall?(args: InstallArgs): Promise<void>;
enable?(args: InstallArgs): Promise<void>;
disable?(args: InstallArgs): Promise<void>;
}

export async function disableModByRenamingFiles(folderName: string) {
const tree = await FileTree.buildFromLocation(folderName);
if (tree instanceof R2Error) {
throw tree;
}

for (const filePath of tree.getRecursiveFiles()) {
if (!filePath.toLowerCase().endsWith(".old")) {
await FsProvider.instance.rename(filePath, `${filePath}.old`);
}
}
}

export async function enableModByRenamingFiles(folderName: string) {
const tree = await FileTree.buildFromLocation(folderName);
if (tree instanceof R2Error) {
throw tree;
}

for (const filePath of tree.getRecursiveFiles()) {
if (filePath.toLowerCase().endsWith(".old")) {
await FsProvider.instance.rename(
filePath,
filePath.substring(0, filePath.length - ('.old').length)
);
}
}
}
74 changes: 72 additions & 2 deletions src/r2mm/installing/profile_installers/GenericProfileInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,32 @@ export default class GenericProfileInstaller extends ProfileInstallerProvider {
}

async disableMod(mod: ManifestV2, profile: ImmutableProfile): Promise<R2Error | void> {
// Support for installer specific disable methods are rolled out
// gradually and therefore might not be defined yet. Disabling
// mod loader packages are intentionally not supported.
try {
if (await this.disableModWithInstaller(mod, profile)) {
return;
}
} catch (e) {
return R2Error.fromThrownValue(e);
}

return this.applyModMode(mod, profile, ModMode.DISABLED);
}

async enableMod(mod: ManifestV2, profile: ImmutableProfile): Promise<R2Error | void> {
// Support for installer specific enable methods are rolled out
// gradually and therefore might not be defined yet. Enabling
// mod loader packages are intentionally not supported.
try {
if (await this.enableModWithInstaller(mod, profile)) {
return;
}
} catch (e) {
return R2Error.fromThrownValue(e);
}

return this.applyModMode(mod, profile, ModMode.ENABLED);
}

Expand Down Expand Up @@ -308,7 +330,7 @@ export default class GenericProfileInstaller extends ProfileInstallerProvider {
* implements a custom uninstallation method.
* @return true if mod loader was uninstalled
*/
async uninstallModLoaderWithInstaller(mod: ManifestV2, profile: ImmutableProfile): Promise<boolean> {
private async uninstallModLoaderWithInstaller(mod: ManifestV2, profile: ImmutableProfile): Promise<boolean> {
const modLoader = this.getModLoader(mod);
const installerId = modLoader ? GetInstallerIdForLoader(modLoader.loaderType) : null;
return this.uninstallWithInstaller(installerId, mod, profile);
Expand All @@ -319,7 +341,7 @@ export default class GenericProfileInstaller extends ProfileInstallerProvider {
* uninstallation method.
* @return true if mod was uninstalled
*/
async uninstallModWithInstaller(mod: ManifestV2, profile: ImmutableProfile): Promise<boolean> {
private async uninstallModWithInstaller(mod: ManifestV2, profile: ImmutableProfile): Promise<boolean> {
const installerId = GetInstallerIdForPlugin(GameManager.activeGame.packageLoader);
return this.uninstallWithInstaller(installerId, mod, profile);
}
Expand All @@ -339,4 +361,52 @@ export default class GenericProfileInstaller extends ProfileInstallerProvider {

return false;
}

/**
* Enable mod if its registered installer implements a custom
* enable method.
* @return true if mod was enable
*/
private async enableModWithInstaller(mod: ManifestV2, profile: ImmutableProfile): Promise<boolean> {
const modLoader = this.getModLoader(mod);

if (modLoader) {
return false; // Don't process mod loader with plugin installer.
}

const installerId = GetInstallerIdForPlugin(GameManager.activeGame.packageLoader);
const installer = installerId ? PackageInstallers[installerId] : undefined;

if (installer && installer.enable) {
const args = this.getInstallArgs(mod, profile);
await installer.enable(args);
return true;
}

return false;
}

/**
* Disable mod if its registered installer implements a custom
* disable method.
* @return true if mod was disabled
*/
private async disableModWithInstaller(mod: ManifestV2, profile: ImmutableProfile): Promise<boolean> {
const modLoader = this.getModLoader(mod);

if (modLoader) {
return false; // Don't process mod loader with plugin installer.
}

const installerId = GetInstallerIdForPlugin(GameManager.activeGame.packageLoader);
const installer = installerId ? PackageInstallers[installerId] : undefined;

if (installer && installer.disable) {
const args = this.getInstallArgs(mod, profile);
await installer.disable(args);
return true;
}

return false;
}
}

0 comments on commit ef7ebf3

Please sign in to comment.