Skip to content

Commit

Permalink
Split modIdUtil
Browse files Browse the repository at this point in the history
Fixed isDisabled check
  • Loading branch information
Aragas committed Jan 12, 2024
1 parent 4a718a0 commit 7b6845e
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 138 deletions.
18 changes: 16 additions & 2 deletions src/utils/LoadOrderManager/LoadOrderManager.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import React from 'react';
import { types } from 'vortex-api';
import { types, selectors } from 'vortex-api';
import { IInvalidResult } from 'vortex-api/lib/extensions/file_based_loadorder/types/types';
import { BannerlordModuleManager } from "@butr/vortexextensionnative";
import { ValidationManager, VortexLauncherManager, vortexToLibrary, libraryToVortex, forceRefresh } from '..';
import { ValidationManager, VortexLauncherManager, vortexToLibrary, libraryToVortex } from '..';
import { GAME_ID } from '../../common';
import { LoadOrderInfoPanel } from '../../views';
import { VortexLoadOrderStorage } from '../../types';

const forceRefresh = (api: types.IExtensionApi) => {
const state = api.getState();
const profileId = selectors.lastActiveProfileForGame(state, GAME_ID);
const action = {
type: 'SET_FB_FORCE_UPDATE',
payload: {
profileId,
},
};
if (api.store) {
api.store.dispatch(action);
}
}

export class LoadOrderManager implements types.ILoadOrderGameInfo {
public gameId: string = GAME_ID;
public toggleableEntries: boolean = true;
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export * from './blse';
export * from './game';
export * from './loadOrderConverter';
export * from './loadOrderPersistence';
export * from './modIdUtil';
export * from './util';
export * from './version';
2 changes: 1 addition & 1 deletion src/utils/loadOrderConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const vortexToLibrary = (loadOrder: VortexLoadOrderStorage): vetypes.Load
id: current.id,
name: current.name,
isSelected: current.enabled,
isDisabled: !current.locked || current.locked === `false` || current.locked === `never`,
isDisabled: !(!current.locked || current.locked === `false` || current.locked === `never`),
index: loadOrder.indexOf(current),
};
return map;
Expand Down
17 changes: 15 additions & 2 deletions src/utils/loadOrderPersistence.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { fs, types } from 'vortex-api';
import { getLoadOrderFilePath } from './util';
import { fs, selectors, types } from 'vortex-api';
import * as path from 'path';
import { GAME_ID, LOAD_ORDER_SUFFIX } from '../common';
import { PersistenceLoadOrderStorage } from '../types';

const getLoadOrderFileName = (api: types.IExtensionApi): string => {
const state = api.getState();
const profileId = selectors.lastActiveProfileForGame(state, GAME_ID);
return `${profileId}${LOAD_ORDER_SUFFIX}`;
}

const getLoadOrderFilePath = (api: types.IExtensionApi): string => {
const state = api.getState();
const loadOrderFileName = getLoadOrderFileName(api);
return path.join(selectors.installPathForGame(state, GAME_ID), loadOrderFileName);
}

/**
* We need to keep it sync while the LauncherManager doesn't support async
* @param api
Expand Down
69 changes: 69 additions & 0 deletions src/utils/modIdUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Bluebird, { Promise, method as toBluebird } from 'bluebird';
import * as path from 'path';
import { parseStringPromise } from 'xml2js';
import turbowalk, { IEntry, IWalkOptions } from 'turbowalk';
import { fs, selectors, types, util } from 'vortex-api';
import { types as vetypes } from '@butr/vortexextensionnative';
import { GAME_ID } from '../common';

interface IWalkOptionsWithFilter extends IWalkOptions {
filter?: (entry: IEntry) => boolean;
}
export const walkPath = async (dirPath: string, walkOptions?: IWalkOptionsWithFilter): Promise<IEntry[]> => {
walkOptions = walkOptions || { skipLinks: true, skipHidden: true, skipInaccessible: true };
const walkResults: IEntry[] = [];
return new Promise<IEntry[]>(async (resolve, reject) => {
await turbowalk(dirPath, (entries: IEntry[]) => {
const filtered = (walkOptions?.filter === undefined)
? entries
: entries.filter(walkOptions.filter);
walkResults.push(...filtered);
return Promise.resolve() as any;
// If the directory is missing when we try to walk it; it's most probably down to a collection being
// in the process of being installed/removed. We can safely ignore this.
}, walkOptions).catch((err: any) => err.code === 'ENOENT' ? Promise.resolve() : Promise.reject(err));
return resolve(walkResults);
});
}

export const resolveModuleId = async (subModulePath: string): Promise<string | undefined> => {
try {
await fs.statAsync(subModulePath);
const contents = await fs.readFileAsync(subModulePath, 'utf8');
const data = await parseStringPromise(contents, { explicitArray: false, mergeAttrs: true });
return data.Module.Id.value;
} catch {
return undefined;
}
}

type ModuleIdMap = { [moduleId: string]: string };
const _moduleIdMap: ModuleIdMap = {};

export const resolveModId = async (api: types.IExtensionApi, module: vetypes.ModuleViewModel|string): Promise<string|undefined> => {
const state = api.getState();
const moduleId = typeof module === 'object' ? module.moduleInfoExtended?.id : module;
if (moduleId === undefined) {
return Promise.reject(new util.DataInvalid(`Module ID is undefined!`));
}
if (_moduleIdMap[moduleId] !== undefined) {
return _moduleIdMap[moduleId];
}
const installationPath = selectors.installPathForGame(state, GAME_ID);
const mods: { [modId: string]: types.IMod } = util.getSafe(state, ['persistent', 'mods', GAME_ID], {});
for (const [id, mod] of Object.entries(mods)) {
const modPath = path.join(installationPath, mod.installationPath);
const submodules = await walkPath(modPath,
{ filter: (entry: IEntry) => path.basename(entry.filePath).toLowerCase() === 'submodule.xml' });
for (const submodule of submodules) {
const subModuleId = await resolveModuleId(submodule.filePath);
if (subModuleId === moduleId) {
_moduleIdMap[moduleId] = id;
return id;
}
}
}

return undefined;
}

147 changes: 14 additions & 133 deletions src/utils/util.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import Bluebird, { Promise, method as toBluebird } from 'bluebird';
import * as path from 'path';
import { parseStringPromise } from 'xml2js';
import turbowalk, { IEntry, IWalkOptions } from 'turbowalk';
import { actions, fs, selectors, types, util } from 'vortex-api';
import { types as vetypes } from '@butr/vortexextensionnative';
import { VortexLauncherManager, recommendBLSE, getBannerlordExec } from '.';
import { EPICAPP_ID, GOG_IDS, STEAMAPP_ID, XBOX_ID, BANNERLORD_EXEC, GAME_ID, BLSE_EXE, LOAD_ORDER_SUFFIX } from '../common';
import { fs, types, util } from 'vortex-api';
import { VortexLauncherManager, recommendBLSE } from '.';
import { EPICAPP_ID, GOG_IDS, STEAMAPP_ID, XBOX_ID, BLSE_EXE } from '../common';

let STORE_ID: string;

const getPathExistsAsync = async (path: string) => {
try {
await fs.statAsync(path);
return true;
}
catch(err) {
return false;
}
}

export const findGame = toBluebird<string>(async (): Promise<string> => {
const game = await util.GameStoreHelper.findByAppId([EPICAPP_ID, STEAMAPP_ID.toString(), ...GOG_IDS, XBOX_ID]);
STORE_ID = game.gameStoreId;
Expand Down Expand Up @@ -37,130 +44,4 @@ export const prepareForModding = async (context: types.IExtensionContext, discov
//manager.initializeModuleViewModels();
//manager.orderBySavedLoadOrder();
});
};

const getPathExistsAsync = async (path: string) => {
try {
await fs.statAsync(path);
return true;
}
catch(err) {
return false;
}
}


export const forceRefresh = (api: types.IExtensionApi) => {
const state = api.getState();
const profileId = selectors.lastActiveProfileForGame(state, GAME_ID);
const action = {
type: 'SET_FB_FORCE_UPDATE',
payload: {
profileId,
},
};
if (api.store) {
api.store.dispatch(action);
}
}

export const setLoadOrder = (api: types.IExtensionApi, loadOrder: types.LoadOrder) => {
const state = api.getState();
const profileId = selectors.lastActiveProfileForGame(state, GAME_ID);
const PARAMS_TEMPLATE = ['/{{gameMode}}', '_MODULES_{{subModIds}}*_MODULES_'];
const parameters = [
PARAMS_TEMPLATE[0].replace('{{gameMode}}', 'singleplayer'),
PARAMS_TEMPLATE[1].replace('{{subModIds}}', loadOrder.map(value => `*${value.id}`).join('')),
];
const action = {
type: 'SET_FB_LOAD_ORDER',
payload: {
profileId,
loadOrder,
},
};
if (api.store) {
const discoveryPath = selectors.discoveryByGame(api.getState(), GAME_ID)?.path;
const batched = [
action,
actions.setGameParameters(GAME_ID, {
executable: getBannerlordExec(discoveryPath, api) || BANNERLORD_EXEC,
parameters,
}),
];
util.batchDispatch(api.store, batched);
}
}

interface IWalkOptionsWithFilter extends IWalkOptions {
filter?: (entry: IEntry) => boolean;
}
export const walkPath = async (dirPath: string, walkOptions?: IWalkOptionsWithFilter): Promise<IEntry[]> => {
walkOptions = walkOptions || { skipLinks: true, skipHidden: true, skipInaccessible: true };
const walkResults: IEntry[] = [];
return new Promise<IEntry[]>(async (resolve, reject) => {
await turbowalk(dirPath, (entries: IEntry[]) => {
const filtered = (walkOptions?.filter === undefined)
? entries
: entries.filter(walkOptions.filter);
walkResults.push(...filtered);
return Promise.resolve() as any;
// If the directory is missing when we try to walk it; it's most probably down to a collection being
// in the process of being installed/removed. We can safely ignore this.
}, walkOptions).catch((err: any) => err.code === 'ENOENT' ? Promise.resolve() : Promise.reject(err));
return resolve(walkResults);
});
}

export const getLoadOrderFileName = (api: types.IExtensionApi): string => {
const state = api.getState();
const profileId = selectors.lastActiveProfileForGame(state, GAME_ID);
return `${profileId}${LOAD_ORDER_SUFFIX}`;
}

export const getLoadOrderFilePath = (api: types.IExtensionApi): string => {
const state = api.getState();
const loadOrderFileName = getLoadOrderFileName(api);
return path.join(selectors.installPathForGame(state, GAME_ID), loadOrderFileName);
}

export const resolveModuleId = async (subModulePath: string): Promise<string | undefined> => {
try {
await fs.statAsync(subModulePath);
const contents = await fs.readFileAsync(subModulePath, 'utf8');
const data = await parseStringPromise(contents, { explicitArray: false, mergeAttrs: true });
return data.Module.Id.value;
} catch {
return undefined;
}
}

type ModuleIdMap = { [moduleId: string]: string };
const _moduleIdMap: ModuleIdMap = {};

export const resolveModId = async (api: types.IExtensionApi, module: vetypes.ModuleViewModel|string): Promise<string|undefined> => {
const state = api.getState();
const moduleId = typeof module === 'object' ? module.moduleInfoExtended?.id : module;
if (moduleId === undefined) {
return Promise.reject(new util.DataInvalid(`Module ID is undefined!`));
}
if (_moduleIdMap[moduleId] !== undefined) {
return _moduleIdMap[moduleId];
}
const installationPath = selectors.installPathForGame(state, GAME_ID);
const mods: { [modId: string]: types.IMod } = util.getSafe(state, ['persistent', 'mods', GAME_ID], {});
for (const [id, mod] of Object.entries(mods)) {
const modPath = path.join(installationPath, mod.installationPath);
const submodules = await walkPath(modPath,
{ filter: (entry: IEntry) => path.basename(entry.filePath).toLowerCase() === 'submodule.xml' });
for (const submodule of submodules) {
const subModuleId = await resolveModuleId(submodule.filePath);
if (subModuleId === moduleId) {
_moduleIdMap[moduleId] = id;
return id;
}
}
}

return undefined;
}
};

0 comments on commit 7b6845e

Please sign in to comment.