Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/config-components/ConfigSelectionLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ import ProfileModList from '../../r2mm/mods/ProfileModList';

async created() {
const fs = FsProvider.instance;
const configLocation = this.$store.getters['profile/activeProfile'].getPathOfProfile();
const configLocation = this.$store.getters['profile/activeProfile'].getProfilePath();
const tree = await FileTree.buildFromLocation(configLocation);
if (tree instanceof R2Error) {
return;
Expand Down
12 changes: 6 additions & 6 deletions src/components/profiles-modals/ImportProfileModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ export default class ImportProfileModal extends mixins(ProfilesMixin) {
this.activeStep = 'PROFILE_IS_BEING_IMPORTED';
if (this.importUpdateSelection === 'UPDATE') {
profileName = "_profile_update";
if (await fs.exists(path.join(Profile.getDirectory(), profileName))) {
await FileUtils.emptyDirectory(path.join(Profile.getDirectory(), profileName));
await fs.rmdir(path.join(Profile.getDirectory(), profileName));
if (await fs.exists(path.join(Profile.getRootDir(), profileName))) {
await FileUtils.emptyDirectory(path.join(Profile.getRootDir(), profileName));
await fs.rmdir(path.join(Profile.getRootDir(), profileName));
}
await this.$store.dispatch('profiles/setSelectedProfile', { profileName: profileName, prewarmCache: true });
}
Expand All @@ -188,12 +188,12 @@ export default class ImportProfileModal extends mixins(ProfilesMixin) {
if (this.importUpdateSelection === 'UPDATE') {
this.activeProfileName = event.detail;
try {
await FileUtils.emptyDirectory(path.join(Profile.getDirectory(), event.detail));
await FileUtils.emptyDirectory(path.join(Profile.getRootDir(), event.detail));
} catch (e) {
console.log("Failed to empty directory:", e);
}
await fs.rmdir(path.join(Profile.getDirectory(), event.detail));
await fs.rename(path.join(Profile.getDirectory(), profileName), path.join(Profile.getDirectory(), event.detail));
await fs.rmdir(path.join(Profile.getRootDir(), event.detail));
await fs.rename(path.join(Profile.getRootDir(), profileName), path.join(Profile.getRootDir(), event.detail));
}
await this.$store.dispatch('profiles/setSelectedProfile', { profileName: event.detail, prewarmCache: true });
this.closeModal();
Expand Down
2 changes: 1 addition & 1 deletion src/components/settings-components/SettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ import CdnProvider from '../../providers/generic/connection/CdnProvider';
'Browse profile folder',
'Open the folder where mods are stored for the current profile.',
async () => {
return this.$store.getters['profile/activeProfile'].getPathOfProfile();
return this.$store.getters['profile/activeProfile'].getProfilePath();
},
'fa-door-open',
() => this.emitInvoke('BrowseProfileFolder')
Expand Down
4 changes: 2 additions & 2 deletions src/installers/BepInExInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export class BepInExInstaller implements PackageInstaller {
for (const item of (await FsProvider.instance.readdir(bepInExRoot))) {
if (!basePackageFiles.includes(item.toLowerCase())) {
if ((await FsProvider.instance.stat(path.join(bepInExRoot, item))).isFile()) {
await FsProvider.instance.copyFile(path.join(bepInExRoot, item), path.join(profile.getPathOfProfile(), item));
await FsProvider.instance.copyFile(path.join(bepInExRoot, item), profile.joinToProfilePath(item));
} else {
await FsProvider.instance.copyFolder(path.join(bepInExRoot, item), path.join(profile.getPathOfProfile(), item));
await FsProvider.instance.copyFolder(path.join(bepInExRoot, item), profile.joinToProfilePath(item));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/installers/GodotMLInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class GodotMLInstaller implements PackageInstaller {
const { packagePath, profile } = args;

const copyFrom = path.join(packagePath, "addons", "mod_loader");
const copyTo = path.join(profile.getPathOfProfile(), "addons", "mod_loader");
const copyTo = profile.joinToProfilePath("addons", "mod_loader");
const fs = FsProvider.instance;

if (await fs.exists(copyFrom)) {
Expand Down
20 changes: 10 additions & 10 deletions src/installers/InstallRuleInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type InstallRuleArgs = {

async function installUntracked(profile: Profile, rule: ManagedRule, installSources: string[], mod: ManifestV2) {
// Functionally identical to the install method of subdir, minus the subdirectory.
const ruleDir = path.join(profile.getPathOfProfile(), rule.route);
const ruleDir = profile.joinToProfilePath(rule.route);
await FileUtils.ensureDirectory(ruleDir);
for (const source of installSources) {
if ((await FsProvider.instance.lstat(source)).isFile()) {
Expand All @@ -48,7 +48,7 @@ async function installSubDir(
installSources: string[],
mod: ManifestV2,
) {
const subDir = path.join(profile.getPathOfProfile(), rule.route, mod.getName());
const subDir = profile.joinToProfilePath(rule.route, mod.getName());
await FileUtils.ensureDirectory(subDir);
for (const source of installSources) {
if ((await FsProvider.instance.lstat(source)).isFile()) {
Expand All @@ -75,7 +75,7 @@ async function installPackageZip(profile: Profile, rule: ManagedRule, installSou
destination route. Essentially the same as SUBDIR_NO_FLATTEN, but as a
zip instead of a directory. The zip name will be the mod ID.
*/
const destDir = path.join(profile.getPathOfProfile(), rule.route);
const destDir = profile.joinToProfilePath(rule.route);
await FileUtils.ensureDirectory(destDir);
const destination = path.join(destDir, `${mod.getName()}.ts.zip`);
const cacheDirectory = path.join(PathResolver.MOD_ROOT, 'cache');
Expand All @@ -87,7 +87,7 @@ async function installPackageZip(profile: Profile, rule: ManagedRule, installSou


async function installSubDirNoFlatten(profile: Profile, rule: ManagedRule, installSources: string[], mod: ManifestV2) {
const subDir = path.join(profile.getPathOfProfile(), rule.route, mod.getName());
const subDir = profile.joinToProfilePath(rule.route, mod.getName());
await FileUtils.ensureDirectory(subDir);
const cacheDirectory = path.join(PathResolver.MOD_ROOT, 'cache');
const cachedLocationOfMod: string = path.join(cacheDirectory, mod.getName(), mod.getVersionNumber().toString());
Expand Down Expand Up @@ -170,10 +170,10 @@ async function buildInstallForRuleSubtype(


export async function addToStateFile(mod: ManifestV2, files: Map<string, string>, profile: Profile) {
await FileUtils.ensureDirectory(path.join(profile.getPathOfProfile(), "_state"));
await FileUtils.ensureDirectory(profile.joinToProfilePath("_state"));
let existing: Map<string, string> = new Map();
if (await FsProvider.instance.exists(path.join(profile.getPathOfProfile(), "_state", `${mod.getName()}-state.yml`))) {
const read = await FsProvider.instance.readFile(path.join(profile.getPathOfProfile(), "_state", `${mod.getName()}-state.yml`));
if (await FsProvider.instance.exists(profile.joinToProfilePath("_state", `${mod.getName()}-state.yml`))) {
const read = await FsProvider.instance.readFile(profile.joinToProfilePath("_state", `${mod.getName()}-state.yml`));
const tracker = (yaml.parse(read.toString()) as ModFileTracker);
existing = new Map(tracker.files);
}
Expand All @@ -184,7 +184,7 @@ export async function addToStateFile(mod: ManifestV2, files: Map<string, string>
modName: mod.getName(),
files: Array.from(existing.entries())
}
await FsProvider.instance.writeFile(path.join(profile.getPathOfProfile(), "_state", `${mod.getName()}-state.yml`), yaml.stringify(mft));
await FsProvider.instance.writeFile(profile.joinToProfilePath("_state", `${mod.getName()}-state.yml`), yaml.stringify(mft));
await ConflictManagementProvider.instance.overrideInstalledState(mod, profile);
}

Expand All @@ -209,8 +209,8 @@ async function installState(args: InstallRuleArgs) {
}
}
for (let [source, relative] of fileRelocations.entries()) {
await FileUtils.ensureDirectory(path.join(profile.getPathOfProfile(), path.dirname(relative)));
await FsProvider.instance.copyFile(source, path.join(profile.getPathOfProfile(), relative));
await FileUtils.ensureDirectory(profile.joinToProfilePath(path.dirname(relative)));
await FsProvider.instance.copyFile(source, profile.joinToProfilePath(relative));
}
await addToStateFile(mod, fileRelocations, profile);
}
Expand Down
12 changes: 5 additions & 7 deletions src/installers/LovelyInstaller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InstallArgs, PackageInstaller } from "./PackageInstaller";
import { InstallRuleInstaller, addToStateFile } from "./InstallRuleInstaller";
import { addToStateFile } from "./InstallRuleInstaller";
import FsProvider from "../providers/generic/file/FsProvider";
import FileUtils from "../utils/FileUtils";
import FileTree from "../model/file/FileTree";
Expand All @@ -14,13 +14,12 @@ export class LovelyInstaller implements PackageInstaller {
profile,
} = args;

const profilePath = profile.getPathOfProfile();
const fs = FsProvider.instance;
const fileRelocations = new Map<string, string>();

// Manually copy over version.dll
const dwmSrc = path.join(packagePath, "version.dll");
const dwmDest = path.join(profilePath, "version.dll");
const dwmDest = profile.joinToProfilePath("version.dll");
await fs.copyFile(dwmSrc, dwmDest);
fileRelocations.set(dwmSrc, "version.dll");

Expand All @@ -33,7 +32,7 @@ export class LovelyInstaller implements PackageInstaller {
const targets = lovelyTree.getRecursiveFiles().map((x) => x.replace(packagePath, "")).map((x) => [x, path.join("mods", x)]);
for (const target of targets) {
const absSrc = path.join(packagePath, target[0]);
const absDest = path.join(profilePath, target[1]);
const absDest = profile.joinToProfilePath(target[1]);

await FileUtils.ensureDirectory(path.dirname(absDest));
await fs.copyFile(absSrc, absDest);
Expand All @@ -53,7 +52,6 @@ export class LovelyPluginInstaller implements PackageInstaller {
profile,
} = args;

const profilePath = profile.getPathOfProfile();
const installDir = path.join("mods", mod.getName());

const fs = FsProvider.instance;
Expand All @@ -63,11 +61,11 @@ export class LovelyPluginInstaller implements PackageInstaller {
if (srcTree instanceof R2Error) {
throw R2Error;
}

const srcFiles = srcTree.getRecursiveFiles();
for (const srcFile of srcFiles) {
const relFile = srcFile.replace(packagePath, "");
const destFile = path.join(profilePath, installDir, relFile);
const destFile = profile.joinToProfilePath(installDir, relFile);

await FileUtils.ensureDirectory(path.dirname(destFile));
await fs.copyFile(srcFile, destFile);
Expand Down
4 changes: 2 additions & 2 deletions src/installers/MelonLoaderInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export class MelonLoaderInstaller implements PackageInstaller {
for (const item of (await FsProvider.instance.readdir(packagePath))) {
if (!basePackageFiles.includes(item.toLowerCase())) {
if ((await FsProvider.instance.stat(path.join(packagePath, item))).isFile()) {
await FsProvider.instance.copyFile(path.join(packagePath, item), path.join(profile.getPathOfProfile(), item));
await FsProvider.instance.copyFile(path.join(packagePath, item), profile.joinToProfilePath(item));
} else {
await FsProvider.instance.copyFolder(path.join(packagePath, item), path.join(profile.getPathOfProfile(), item));
await FsProvider.instance.copyFolder(path.join(packagePath, item), profile.joinToProfilePath(item));
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/installers/NorthstarInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export class NorthstarInstaller implements PackageInstaller {
for (const item of (await FsProvider.instance.readdir(northstarRoot))) {
if (!basePackageFiles.includes(item.toLowerCase())) {
if ((await FsProvider.instance.stat(path.join(northstarRoot, item))).isFile()) {
await FsProvider.instance.copyFile(path.join(northstarRoot, item), path.join(profile.getPathOfProfile(), item));
await FsProvider.instance.copyFile(path.join(northstarRoot, item), profile.joinToProfilePath(item));
} else {
await FsProvider.instance.copyFolder(path.join(northstarRoot, item), path.join(profile.getPathOfProfile(), item));
await FsProvider.instance.copyFolder(path.join(northstarRoot, item), profile.joinToProfilePath(item));
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/installers/ReturnOfModdingInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class ReturnOfModdingInstaller implements PackageInstaller {
for (const fileOrFolder of toCopy) {
await FileUtils.copyFileOrFolder(
path.join(root, fileOrFolder),
path.join(profile.getPathOfProfile(), fileOrFolder)
profile.joinToProfilePath(fileOrFolder)
);
}
}
Expand All @@ -44,11 +44,11 @@ export class ReturnOfModdingInstaller implements PackageInstaller {

try {
// Delete all files except mods.yml from profile root. Ignore directories.
for (const file of (await fs.readdir(profile.getPathOfProfile()))) {
for (const file of (await fs.readdir(profile.getProfilePath()))) {
if (file.toLowerCase() === 'mods.yml') {
continue;
}
const filePath = path.join(profile.getPathOfProfile(), file);
const filePath = profile.joinToProfilePath(file);
if ((await fs.lstat(filePath)).isFile()) {
await fs.unlink(filePath);
}
Expand Down Expand Up @@ -108,8 +108,8 @@ export class ReturnOfModdingPluginInstaller implements PackageInstaller {
// Remove the data dir, but keep the cache subdir if it exists.
// Leave the config dir alone.
try {
const pluginPath = path.join(profile.getPathOfProfile(), this._ROOT, this._PLUGINS, mod.getName())
const dataPath = path.join(profile.getPathOfProfile(), this._ROOT, this._DATA, mod.getName());
const pluginPath = profile.joinToProfilePath(this._ROOT, this._PLUGINS, mod.getName())
const dataPath = profile.joinToProfilePath(this._ROOT, this._DATA, mod.getName());

await FileUtils.recursiveRemoveDirectoryIfExists(pluginPath);

Expand Down
4 changes: 2 additions & 2 deletions src/installers/ShimloaderInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class ShimloaderInstaller implements PackageInstaller {

for (const targetPath of targets) {
const absSrc = path.join(packagePath, targetPath[0]);
const absDest = path.join(profile.getPathOfProfile(), targetPath[1]);
const absDest = profile.joinToProfilePath(targetPath[1]);

await FileUtils.ensureDirectory(path.dirname(absDest));
await fs.copyFile(absSrc, absDest);
Expand All @@ -48,7 +48,7 @@ export class ShimloaderInstaller implements PackageInstaller {
}

// The config subdir needs to be created for shimloader (it will get cranky if it's not there).
const configDir = path.join(profile.getPathOfProfile(), "shimloader", "cfg");
const configDir = profile.joinToProfilePath("shimloader", "cfg");
if (!await fs.exists(configDir)) {
await fs.mkdirs(configDir);
}
Expand Down
88 changes: 70 additions & 18 deletions src/model/Profile.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,94 @@
import * as path from 'path'
import PathResolver from '../r2mm/manager/PathResolver';
import ProfileProvider from '../providers/ror2/model_implementation/ProfileProvider';
import * as path from 'path';

let activeProfile: Profile;
import ProfileProvider from '../providers/ror2/model_implementation/ProfileProvider';
import PathResolver from '../r2mm/manager/PathResolver';

export default class Profile {
interface ProfileCompatible {
getProfileName: () => string;
getProfilePath: () => string;
joinToProfilePath: (...paths: [string, ...string[]]) => string;
}

/**
* ImmutableProfile does not know about the current active profile or
* other parts of the app state. It's ideal to be used as a function
* argument in cases where the active profile changing during the
* execution would cause unwanted behaviour.
*/
export class ImmutableProfile implements ProfileCompatible {
private readonly profileName: string = '';
private readonly directory: string = '.';
private readonly rootDir: string = '.';

public constructor(name: string) {
this.profileName = name;
this.directory = this.getDirectory();
ProfileProvider.instance.ensureProfileDirectory(this.directory, this.profileName);
activeProfile = this;
this.rootDir = path.join(PathResolver.MOD_ROOT, 'profiles');
ProfileProvider.instance.ensureProfileDirectory(this.rootDir, this.profileName);
}

// Profile name
public getProfileName(): string {
return this.profileName;
}

// Directory of profile folder (/mods/profiles/)
public getDirectory(): string {
return Profile.getDirectory();
public getProfilePath(): string {
return path.join(this.rootDir, this.profileName);
}

public static getDirectory(): string {
return path.join(PathResolver.MOD_ROOT, 'profiles');
public joinToProfilePath(...paths: [string, ...string[]]): string {
return path.join(this.getProfilePath(), ...paths);
}
}

let activeProfile: Profile;

/**
* Profile updates and provides access to activeProfile singleton.
* It's ideal to be used when an operation targets the active profile
* but you don't have access to the it in the current scope.
*
* Note that while changes to the active game are reflected to already
* instantiated objects, changes to the active profile are not. To get
* guaranteed latest data, access instance methods via
* Profile.getActiveProfile().
*/
export default class Profile implements ProfileCompatible {
private readonly profileName: string = '';

public constructor(name: string) {
this.profileName = name;
activeProfile = this;
}

// Directory of profile (/mods/profiles/a_profile)
public getPathOfProfile(): string {
return path.join(this.directory, this.profileName);
// Return a snapshot of the currently active profile.
public static getActiveAsImmutableProfile(): ImmutableProfile {
return new ImmutableProfile(activeProfile.profileName);
}

public static getActiveProfile() {
return activeProfile;
}

/**
* Returns the root dir for profiles for the currently active game.
* PathResolver.MOD_ROOT is updated when the active game changes.
* E.g. DataFolder/[Game.internalFolderName]/profiles
*/
public static getRootDir(): string {
return path.join(PathResolver.MOD_ROOT, 'profiles');
}

public asImmutableProfile(): ImmutableProfile {
return Profile.getActiveAsImmutableProfile();
}

public getProfileName(): string {
return this.profileName;
}

public getProfilePath(): string {
return path.join(Profile.getRootDir(), this.profileName);
}

public joinToProfilePath(...paths: [string, ...string[]]): string {
return path.join(this.getProfilePath(), ...paths);
}
}
Loading
Loading