Skip to content

Commit

Permalink
Merge pull request #23 from ebkr/feature/17/install-mod-with-dependen…
Browse files Browse the repository at this point in the history
…cies

Download with dependencies button now works.
  • Loading branch information
ebkr authored Dec 31, 2019
2 parents 637dfd8 + eb278a4 commit 4b4856d
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 35 deletions.
Binary file removed src-electron/icons/icon.icns
Binary file not shown.
Binary file removed src-electron/icons/icon.ico
Binary file not shown.
Binary file removed src-electron/icons/linux-512x512.png
Binary file not shown.
89 changes: 67 additions & 22 deletions src/pages/Manager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</div>
<div class='card-footer'>
<button class="button is-info" @click="downloadMod()">Download</button>
<button class="button is-primary" @click="downloadMod()">Download with dependencies</button>
<button class="button is-primary" @click="downloadWithDependencies()">Download with dependencies</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -189,7 +189,7 @@ import ManagerSettings from '../r2mm/manager/ManagerSettings';
import GameRunner from '../r2mm/manager/GameRunner';
import * as fs from 'fs-extra';
import { isNull } from 'util';
import { isNull, isNullOrUndefined } from 'util';
import ModLinker from '../r2mm/manager/ModLinker';
const settings = new ManagerSettings();
Expand Down Expand Up @@ -291,24 +291,9 @@ export default class Manager extends Vue {
const downloader: ThunderstoreDownloader = new ThunderstoreDownloader(refSelectedThunderstoreMod, Profile.getActiveProfile());
downloader.download((progress: number, status: number, error: DownloadError)=>{
if (status === StatusEnum.SUCCESS) {
const modFromManifest: Mod | R2Error = ModFromManifest.get(refSelectedThunderstoreMod.getFullName(), version.getVersionNumber());
if (!(modFromManifest instanceof R2Error)) {
const installError: R2Error | null = ProfileInstaller.installMod(modFromManifest);
if (!(installError instanceof R2Error)) {
const newModList: Mod[] | R2Error = ProfileModList.addMod(modFromManifest);
if (!(newModList instanceof R2Error)) {
this.localModList = newModList;
this.filterModLists();
}
} else {
// Show that installation failed
// (mod failed to be placed in /{profile} directory)
this.showError(installError);
}
} else {
// Show that mod has failed to register for profile
// (mod failed to add to mods.yml)
this.showError(modFromManifest);
const installErr = this.installModAfterDownload(refSelectedThunderstoreMod, version);
if (installErr instanceof R2Error) {
this.showError(installErr);
}
if (this.selectedThunderstoreMod === refSelectedThunderstoreMod) {
// Close modal if no other modal has been opened.
Expand All @@ -321,6 +306,66 @@ export default class Manager extends Vue {
}
installModAfterDownload(mod: ThunderstoreMod, version: ThunderstoreVersion): R2Error | void {
const modFromManifest: Mod | R2Error = ModFromManifest.get(mod.getFullName(), version.getVersionNumber());
if (!(modFromManifest instanceof R2Error)) {
const installError: R2Error | null = ProfileInstaller.installMod(modFromManifest);
if (!(installError instanceof R2Error)) {
const newModList: Mod[] | R2Error = ProfileModList.addMod(modFromManifest);
if (!(newModList instanceof R2Error)) {
this.localModList = newModList;
this.filterModLists();
}
} else {
// (mod failed to be placed in /{profile} directory)
return installError;
}
} else {
// (mod failed to add to mods.yml)
return modFromManifest;
}
}
downloadWithDependencies() {
const refSelectedThunderstoreMod: ThunderstoreMod | null = this.selectedThunderstoreMod;
const refSelectedVersion: string | null = this.selectedVersion;
if (refSelectedThunderstoreMod === null || refSelectedVersion === null) {
// Shouldn't happen, but shouldn't throw an error.
return;
}
const version = refSelectedThunderstoreMod.getVersions()
.find((modVersion: ThunderstoreVersion) => modVersion.getVersionNumber().toString() === refSelectedVersion);
if (version === undefined) {
return;
}
const downloader: ThunderstoreDownloader = new ThunderstoreDownloader(refSelectedThunderstoreMod, Profile.getActiveProfile());
downloader.downloadWithDependencies((progress: number, status: number, error: R2Error | void)=>{
if (status === StatusEnum.FAILURE) {
if (error instanceof R2Error) {
this.showError(error);
}
} else if (status === StatusEnum.SUCCESS) {
// To get to this stage, it must have already succeeded once.
const dependencies: ThunderstoreMod[] | R2Error = downloader.buildDependencyList(version, this.thunderstoreModList);
if (dependencies instanceof R2Error) {
return;
}
dependencies.forEach((mod: ThunderstoreMod) => {
const installErr = this.installModAfterDownload(mod, mod.getVersions()[0]);
if (installErr instanceof R2Error) {
this.showError(installErr);
return;
}
})
this.installModAfterDownload(refSelectedThunderstoreMod, version);
if (this.selectedThunderstoreMod === refSelectedThunderstoreMod) {
// Close modal if no other modal has been opened.
this.closeModal();
}
}
}, refSelectedThunderstoreMod, version, this.thunderstoreModList);
}
// eslint-disable-next-line
uninstallMod(vueMod: any) {
let mod: InvalidManifestError | ManifestV2 | Mod | R2Error = new ManifestV2().make(vueMod);
Expand Down Expand Up @@ -441,7 +486,7 @@ export default class Manager extends Vue {
this.gameRunning = true;
GameRunner.playModded(settings.riskOfRain2Directory, ()=>{
this.gameRunning = false;
});
}, settings);
}
}
Expand All @@ -451,7 +496,7 @@ export default class Manager extends Vue {
this.gameRunning = true;
GameRunner.playVanilla(settings.riskOfRain2Directory, ()=>{
this.gameRunning = false;
});
}, settings);
}
}
Expand Down
70 changes: 67 additions & 3 deletions src/r2mm/downloading/ThunderstoreDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as fs from 'fs-extra';
import * as path from 'path';
import ZipExtract from '../installing/ZipExtract';
import R2Error from 'src/model/errors/R2Error';
import { isUndefined } from 'util';

const cacheDirectory: string = path.join(process.cwd(), 'mods', 'cache');

Expand Down Expand Up @@ -64,7 +65,72 @@ export default class ThunderstoreDownloader {
}
}

public saveToFile(response: Buffer, versionNumber: VersionNumber, callback: (success: boolean) => void): R2Error | null {
public downloadWithDependencies(callback: (progress: number, status: number, error: R2Error | void) => void, mod: ThunderstoreMod, version: ThunderstoreVersion, modList: ThunderstoreMod[]) {
const dependencyList = this.buildDependencyList(version, modList);
if (dependencyList instanceof R2Error) {
callback(
0,
StatusEnum.FAILURE,
dependencyList
);
return;
}
let listStep = 0;
const downloader = new ThunderstoreDownloader(dependencyList[listStep], Profile.getActiveProfile());
const onFinalDownload = (progress: number, status: number, error: R2Error | void) => {
if (status === StatusEnum.SUCCESS) {
callback(100, StatusEnum.SUCCESS);
} else if (status === StatusEnum.FAILURE && error instanceof R2Error) {
callback(0, StatusEnum.FAILURE, error);
}
}
const onStepDownload = (progress: number, status: number, error: R2Error | void) => {
if (status === StatusEnum.SUCCESS) {
listStep += 1;
if (listStep < dependencyList.length) {
const downloader = new ThunderstoreDownloader(dependencyList[listStep], Profile.getActiveProfile());
downloader.download(onStepDownload, dependencyList[listStep].getVersions()[0].getVersionNumber());
} else {
const downloader = new ThunderstoreDownloader(mod, Profile.getActiveProfile());
downloader.download(onFinalDownload, version.getVersionNumber());
}
} else if (status === StatusEnum.FAILURE && error instanceof R2Error) {
callback(
0,
StatusEnum.FAILURE,
error)
}
}
downloader.download(onStepDownload, dependencyList[listStep].getVersions()[0].getVersionNumber());
}

public buildDependencyList(version: ThunderstoreVersion, modList: ThunderstoreMod[]): ThunderstoreMod[] | R2Error {
const list: ThunderstoreMod[] = [];
try {
version.getDependencies().forEach((dependency: string) => {
const foundMod = modList.find((listMod: ThunderstoreMod) => dependency.startsWith(listMod.getFullName()));
if (isUndefined(foundMod)) {
throw new Error(`Unable to find Thunderstore dependency with author-name of ${dependency}`)
} else {
list.push(foundMod);
const findDependencyError = this.buildDependencyList(foundMod, modList);
if (findDependencyError instanceof R2Error) {
throw findDependencyError;
}
list.push(...findDependencyError);
}
})
} catch(e) {
const err: Error = e;
return new R2Error(
`Failed to find all dependencies of mod ${version.getFullName()}`,
err.message
)
}
return list;
}

private saveToFile(response: Buffer, versionNumber: VersionNumber, callback: (success: boolean) => void): R2Error | null {
try {
fs.mkdirsSync(path.join(cacheDirectory, this.mod.getFullName()));
fs.writeFileSync(
Expand All @@ -83,9 +149,7 @@ export default class ThunderstoreDownloader {
);
return extractError;
} catch(e) {
console.log('Couldn\'t write file');
const err: Error = e;
console.log(err);
return new FileWriteError(
'File write error',
`Failed to write downloaded zip of ${this.mod.getFullName()} to profile directory of ${this.profile.getPathOfProfile()}. \nReason: ${err.message}`
Expand Down
19 changes: 11 additions & 8 deletions src/r2mm/installing/ProfileInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Profile from 'src/model/Profile';
import FileWriteError from 'src/model/errors/FileWriteError';
import ModMode from 'src/model/enums/ModMode';
import { isNull } from 'util';
import { lstatSync } from 'fs-extra';

const cacheDirectory: string = path.join(process.cwd(), 'mods', 'cache');

Expand Down Expand Up @@ -44,14 +45,16 @@ export default class ProfileInstaller {
try {
fs.readdirSync(bepInExLocation)
.forEach((file: string) => {
fs.readdirSync(path.join(bepInExLocation, file))
.forEach((folder: string) => {
const folderPath: string = path.join(bepInExLocation, file, folder);
if (folder === mod.getName() && fs.lstatSync(folderPath).isDirectory()) {
fs.emptyDirSync(folderPath);
fs.removeSync(folderPath);
}
})
if (lstatSync(path.join(bepInExLocation, file)).isDirectory()) {
fs.readdirSync(path.join(bepInExLocation, file))
.forEach((folder: string) => {
const folderPath: string = path.join(bepInExLocation, file, folder);
if (folder === mod.getName() && fs.lstatSync(folderPath).isDirectory()) {
fs.emptyDirSync(folderPath);
fs.removeSync(folderPath);
}
})
}
});
} catch(e) {
const err: Error = e;
Expand Down
4 changes: 2 additions & 2 deletions src/r2mm/manager/GameRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import * as path from 'path';

export default class GameRunner {

public static playModded(ror2Directory: string, onComplete: ()=>void) {
public static playModded(ror2Directory: string, onComplete: ()=>void, settings: ManagerSettings) {
child.spawn(path.join(ror2Directory, 'Risk of Rain 2.exe'), ['--doorstop-enable', 'true', '--doorstop-target', 'r2modman\\BepInEx\\core\\BepInEx.Preloader.dll']).on('exit', onComplete);
}

public static playVanilla(ror2Directory: string, onComplete: ()=>void) {
public static playVanilla(ror2Directory: string, onComplete: ()=>void, settings: ManagerSettings) {
child.spawn(path.join(ror2Directory, 'Risk of Rain 2.exe'), ['--doorstop-enable', 'false']).on('exit', onComplete);
}

Expand Down
Binary file modified src/statics/icons/icon-256x256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4b4856d

Please sign in to comment.