Skip to content

Commit

Permalink
Merge pull request #1628 from ebkr/troubleshooting-info
Browse files Browse the repository at this point in the history
Add option for copying troubleshooting information to clipboard
  • Loading branch information
anttimaki authored Feb 6, 2025
2 parents 46d7cdb + bc47bc5 commit 9be88b0
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/components/settings-components/SettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ import CdnProvider from '../../providers/generic/connection/CdnProvider';
'fa-clipboard',
() => this.emitInvoke('CopyLogToClipboard')
),
new SettingsRow(
'Debugging',
'Copy troubleshooting information to clipboard',
'Copy settings and other information to the clipboard, with Discord formatting.',
async () => 'Share this information when requesting support on Discord.',
'fa-clipboard',
() => this.emitInvoke('CopyTroubleshootingInfoToClipboard')
),
new SettingsRow(
'Debugging',
'Toggle download cache',
Expand Down
8 changes: 8 additions & 0 deletions src/pages/Manager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,11 @@ import ModalCard from '../components/ModalCard.vue';
}
}
async copyTroubleshootingInfoToClipboard() {
const content = await this.$store.dispatch('profile/generateTroubleshootingString');
InteractionProvider.instance.copyToClipboard('```' + content + '```');
}
async changeDataFolder() {
try {
const folder = await DataFolderProvider.instance.showSelectionDialog();
Expand Down Expand Up @@ -551,6 +556,9 @@ import ModalCard from '../components/ModalCard.vue';
case "CopyLogToClipboard":
this.copyLogToClipboard();
break;
case "CopyTroubleshootingInfoToClipboard":
this.copyTroubleshootingInfoToClipboard();
break;
case "ToggleDownloadCache":
this.toggleIgnoreCache();
break;
Expand Down
4 changes: 4 additions & 0 deletions src/r2mm/manager/PackageDexieStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export async function getPackageVersionNumbers(community: string, packageName: s
return pkg.versions.map((v) => v.version_number);
}

export async function getPackageCount(community: string) {
return await db.packages.where({community}).count();
}

/**
* @param game Game (community) which package listings should be used in the lookup.
* @param dependencies Lookup targets as Thunderstore dependency strings.
Expand Down
24 changes: 24 additions & 0 deletions src/store/modules/ProfileModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ActionTree, GetterTree } from 'vuex';

import { CachedMod } from './TsModsModule';
import { State as RootState } from '../index';
import ManagerInformation from '../../_managerinf/ManagerInformation';
import R2Error from '../../model/errors/R2Error';
import ManifestV2 from '../../model/ManifestV2';
import Profile, { ImmutableProfile } from "../../model/Profile";
Expand All @@ -11,11 +12,13 @@ import { SortNaming } from '../../model/real_enums/sort/SortNaming';
import ThunderstoreCombo from '../../model/ThunderstoreCombo';
import ThunderstoreMod from '../../model/ThunderstoreMod';
import ConflictManagementProvider from '../../providers/generic/installing/ConflictManagementProvider';
import GameDirectoryResolverProvider from '../../providers/ror2/game/GameDirectoryResolverProvider';
import ProfileInstallerProvider from '../../providers/ror2/installing/ProfileInstallerProvider';
import ManagerSettings from '../../r2mm/manager/ManagerSettings';
import * as PackageDb from '../../r2mm/manager/PackageDexieStore';
import ModListSort from '../../r2mm/mods/ModListSort';
import ProfileModList from '../../r2mm/mods/ProfileModList';
import FileUtils from '../../utils/FileUtils';
import SearchUtils from '../../utils/SearchUtils';

interface State {
Expand Down Expand Up @@ -297,6 +300,27 @@ export default {
return await PackageDb.getCombosByDependencyStrings(game, outdated, useLatestVersion);
},

async generateTroubleshootingString({dispatch, getters, rootGetters, rootState}): Promise<string> {
const steamDirectory = await GameDirectoryResolverProvider.instance.getSteamDirectory();
const steamPath = steamDirectory instanceof R2Error ? '-' : FileUtils.hideWindowsUsername(steamDirectory);
const gameDirectory = await GameDirectoryResolverProvider.instance.getDirectory(rootState.activeGame);
const gamePath = gameDirectory instanceof R2Error ? '-' : FileUtils.hideWindowsUsername(gameDirectory);
const packageCacheDate = await PackageDb.getLastPackageListUpdateTime(rootState.activeGame.internalFolderName);
const packageCacheSize = await PackageDb.getPackageCount(rootState.activeGame.internalFolderName);
const packageVuexState: string = await dispatch('tsMods/generateTroubleshootingString', null, {root: true});

const content = `
App: ${ManagerInformation.APP_NAME} v${ManagerInformation.VERSION}
Game: ${rootState.activeGame.displayName} (${rootState.activeGame.activePlatform.storePlatform})
Steam path: ${steamPath}
Game path: ${gamePath}
Profile path: ${FileUtils.hideWindowsUsername(getters.activeProfile.getProfilePath())}
Custom launch arguments: ${rootGetters.settings.getContext().gameSpecific.launchParameters || '-'}
Mod list in cache: ${packageCacheSize} mods, hash updated ${packageCacheDate || 'never'}
Mod list in memory: ${packageVuexState}
`;
return content.replace(/(\n)\s+/g, '$1'); // Remove indentation but keep newlines
},

async loadLastSelectedProfile({commit, rootGetters}): Promise<string> {
const profileName = rootGetters['settings'].getContext().gameSpecific.lastSelectedProfile;
Expand Down
4 changes: 4 additions & 0 deletions src/store/modules/TsModsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ export const TsModsModule = {
return updated !== undefined;
},

async generateTroubleshootingString({state}): Promise<string> {
return `${state.mods.length} mods, updated ${state.modsLastUpdated || 'never'}`;
},

async getActiveGameCacheStatus({commit, state, rootState}): Promise<string> {
if (state.isThunderstoreModListUpdateInProgress) {
return "Online mod list is currently updating, please wait for the operation to complete";
Expand Down
9 changes: 9 additions & 0 deletions src/utils/FileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export default class FileUtils {
return Promise.resolve();
}

// Obfuscates the Windows username if it's part of the path.
public static hideWindowsUsername(dir: string) {
const separator = dir.includes('/') ? '/' : '\\';
return dir.replace(
/([A-Za-z]:)[\\\/]Users[\\\/][^\\\/]+[\\\/]/,
`$1${separator}Users${separator}***${separator}`
);
}

public static humanReadableSize(bytes: number) {
// NumberFormat renders GBs as BBs ("billion bytes") when using "byte" unit type.
if (bytes > 999999999 && bytes < 1000000000000) {
Expand Down
69 changes: 69 additions & 0 deletions test/jest/__tests__/utils/utils.FileUtils.ts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import FileUtils from "../../../../src/utils/FileUtils";

describe("FileUtils.hideWindowsUsername", () => {
it("Doesn't change slashes", () => {
expect(
FileUtils.hideWindowsUsername('C:\\Users\\Alice\\appData')
).toStrictEqual('C:\\Users\\***\\appData');

expect(
FileUtils.hideWindowsUsername('C:/Users/Bob/appData')
).toStrictEqual('C:/Users/***/appData');
});

it("Doesn't change drive letters", () => {
expect(
FileUtils.hideWindowsUsername('C:\\Users\\Charlie\\appData')
).toStrictEqual('C:\\Users\\***\\appData');

expect(
FileUtils.hideWindowsUsername('x:\\Users\\David\\appData')
).toStrictEqual('x:\\Users\\***\\appData');
});

it("Doesn't change the rest of the path", () => {
expect(
FileUtils.hideWindowsUsername('C:\\Users\\Eve\\')
).toStrictEqual('C:\\Users\\***\\');

expect(
FileUtils.hideWindowsUsername('C:\\Users\\Frank\\Desktop')
).toStrictEqual('C:\\Users\\***\\Desktop');

expect(
FileUtils.hideWindowsUsername('C:\\Users\\Grace\\Desktop\\')
).toStrictEqual('C:\\Users\\***\\Desktop\\');

expect(
FileUtils.hideWindowsUsername('C:\\Users\\Heidi\\Desktop\\file.txt')
).toStrictEqual('C:\\Users\\***\\Desktop\\file.txt');
});

it("Doesn't affect other paths", () => {
expect(
FileUtils.hideWindowsUsername('C:\\LUsers\\Ivan\\')
).toStrictEqual('C:\\LUsers\\Ivan\\');

expect(
FileUtils.hideWindowsUsername('C:\\temp\\Users\\Judy\\')
).toStrictEqual('C:\\temp\\Users\\Judy\\');
});

it("Isn't tricked by odd usernames", () => {
expect(
FileUtils.hideWindowsUsername('C:\\Users\\123\\')
).toStrictEqual('C:\\Users\\***\\');

expect(
FileUtils.hideWindowsUsername('C:\\Users\\@admin\\')
).toStrictEqual('C:\\Users\\***\\');

expect(
FileUtils.hideWindowsUsername('C:\\Users\\_\\')
).toStrictEqual('C:\\Users\\***\\');

expect(
FileUtils.hideWindowsUsername('C:\\Users\\***\\')
).toStrictEqual('C:\\Users\\***\\');
});
});

0 comments on commit 9be88b0

Please sign in to comment.