Skip to content

Commit

Permalink
settings option to specify dns server + fixed import/export art
Browse files Browse the repository at this point in the history
  • Loading branch information
cbartondock committed May 31, 2024
1 parent 48e41ba commit cf94af1
Show file tree
Hide file tree
Showing 14 changed files with 118 additions and 37 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.

## 2.5.9
### Added
* Settings option to manually set DNS Server (fixes many timeout issues on the Steam Deck)
### Changed
* Manual Import / Export of Image Choices now matches on image pool.

## 2.5.8
### Fixed
* Provider workers not returning images under certain conditions
Expand Down
1 change: 1 addition & 0 deletions src/lang/en-US/langStrings.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@
"fuzzy_verbose": "Log matching results",
"fuzzy_filter": "Filter images",
"enabledProviders": "Enabled image providers",
"dnsServers": "DNS manual override",
"selectLanguage": "Select language",
"selectTheme": "Select theme",
"resetFuzzy_desc": "fuzzy list",
Expand Down
4 changes: 3 additions & 1 deletion src/lang/en-US/markdown/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ Clears the cache of titles that fuzzy matching has already seen (try this if cha
### Preload retrieved images `[Recommend disabled]`
When enabled, SRM will pull all available artwork for every game, rather than pulling one piece of artwork at a time as the user flips through the images. Don't enable this unless you have a good reason and a very small library of games, otherwise it could result in very large (slow) network requests.
### Enabled providers
Global setting to disable certain providers. Currently the only image provider is `SteamGridDB` since ConsoleGrid and RetroGaming.cloud are defunct.
Global setting to enable/disable particular image providers. Current options are `SteamGridDB` and `Steam Official`.
### DNS manual override
Set this if you want SRM to do DNS resolution internally, as opposed to relying on your system's default DNS server. This solves many timeout issues on the Steam Deck.

## Community Variables and Presets
### Force download custom variables.
Expand Down
67 changes: 52 additions & 15 deletions src/lib/helpers/url/image-downloader.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
import * as fs from 'fs-extra'
import fetch, { AbortError } from 'node-fetch';
// import { Resolver } from 'dns';
import { Resolver } from 'dns';
import { decodeFile } from './encode-file'

export class ImageDownloader {
// private dnsResolver = new Resolver();
private dnsResolver;
private timeout: number = 10000;
// private dnsCache: {[host: string]: string} = {};
private dnsCache: {[host: string]: string};

constructor() {
// this.dnsResolver.setServers(['1.1.1.1', '8.8.8.8']);
constructor(dnsServers: string[]) {
if(dnsServers && dnsServers.length) {
this.dnsResolver = new Resolver();
this.dnsResolver.setServers(dnsServers);
this.dnsCache = {};
}
}

async downloadAndSaveImage(imageUrl: string, filePath: string, retryCount?: number, secondaryPath?: string): Promise<void> {
async downloadAndSaveImage(imageUrl: string, filePath: string, retryCount?: number, secondaryPath?: string, externalDNS?: string[]): Promise<void> {
if(imageUrl.startsWith('file://')) {
await fs.copyFile(decodeFile(imageUrl), filePath);
} else {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
// const {resolved, host} = await this.resolveDNS(imageUrl)
const res = await fetch(imageUrl, {
signal: controller.signal,
method: 'GET',
// headers: {
// Host: host
// }
});
let res;
if(this.dnsResolver) {
const {resolved, host} = await this.resolveDNS(imageUrl);
res = await fetch(resolved, {
signal: controller.signal,
method: 'GET',
headers: {
Host: host
}
});
} else {
res = await fetch(imageUrl, {
signal: controller.signal,
method: 'GET'
});
}

const arrayBuff = Buffer.from(await res.arrayBuffer());
await fs.outputFile(filePath, arrayBuff);
if(secondaryPath) {
Expand All @@ -37,14 +50,38 @@ export class ImageDownloader {
if(retryCount && retryCount > 0) {
return this.downloadAndSaveImage(imageUrl, filePath, retryCount - 1);
} else {
throw `Request timed out after ${this.timeout} milliseconds.`
throw `Request timed out after ${this.timeout} milliseconds. URL: ${imageUrl}`
}
} else {
throw error;
}
}
}
}

resolveDNS(imageUrl: string) {
return new Promise<{resolved: string, host: string}>((resolve,reject)=> {
const { host, pathname, protocol } = new URL(imageUrl);
if(this.dnsCache[host]) {
resolve({
resolved: `${protocol}//${this.dnsCache[host]}${pathname}`,
host: host
})
} else {
this.dnsResolver.resolve(host, (err, addresses) => {
if(err || !addresses.length) {
reject(err)
} else {
this.dnsCache[host] = addresses[0];
resolve({
resolved: `${protocol}//${addresses[0]}${pathname}`,
host: host
})
}
})
}
})
}
}


8 changes: 8 additions & 0 deletions src/lib/image-providers/available-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,12 @@ export const providersSelect = onlineProviders.map((provider)=>{
return {value: provider, displayValue: imageProviderNames[provider]}
})

export const dnsSelect = [
{value: '1.1.1.1', displayValue: 'Cloudflare'},
{value: '1.0.0.1', displayValue: 'Cloudflare (Alt)'},
{value: '8.8.8.8', displayValue: 'Google'},
{value: '8.8.4.4', displayValue: 'Google (Alt)'},

]

export const sgdbIdRegex: RegExp = /^\$\{gameid\:([0-9]*?)\}$/;
4 changes: 2 additions & 2 deletions src/lib/vdf-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export class VDF_Manager {
})
}

write(batch: boolean, batchSize?: number, options?: { shortcuts?: boolean, addedItems?: boolean, screenshots?: boolean}) {
write(batch: boolean, batchSize?: number, dnsServers?: string[], options?: { shortcuts?: boolean, addedItems?: boolean, screenshots?: boolean}) {
return new Promise<{nonFatal: VDF_Error, outcomes: VDF_AllScreenshotsOutcomes}>((resolve,reject)=>{
let shortcutPromises: Promise<void>[] = [];
let addedItemsPromises: Promise<void>[] = [];
Expand All @@ -160,7 +160,7 @@ export class VDF_Manager {
addedItemsPromises.push(this.data[steamDirectory][userId].addedItems.write());
}
if (writeScreenshots) {
screenshotPromises.push(this.data[steamDirectory][userId].screenshots.write(batch, batchSize).then((outcome: VDF_ScreenshotsOutcome)=>{
screenshotPromises.push(this.data[steamDirectory][userId].screenshots.write(batch, batchSize, dnsServers).then((outcome: VDF_ScreenshotsOutcome)=>{
screenshotsOutcomes[steamDirectory][userId] = outcome;
return outcome.error
}))
Expand Down
4 changes: 2 additions & 2 deletions src/lib/vdf-screenshots-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ export class VDF_ScreenshotsFile {
return vdfErrors;
}

async write(batch: boolean, batchSizeInput?: number) {
async write(batch: boolean, batchSizeInput?: number, dnsServers?: string[]) {
let addErrors: (VDF_Error|void)[] = [];
let screenshotsData: VDF_ScreenshotsData = this.data;
const extraneous = [...this.extraneous, ...Object.keys(screenshotsData).filter(appId=> !screenshotsData[appId])];
const batchSize = batchSizeInput || 50;
const imageDownloader: ImageDownloader = new ImageDownloader();
const imageDownloader: ImageDownloader = new ImageDownloader(dnsServers);
const addableAppIds = Object.keys(screenshotsData).filter((appId)=>{
return screenshotsData[appId] !== undefined && (typeof screenshotsData[appId] !== 'string')
});
Expand Down
1 change: 1 addition & 0 deletions src/models/language.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ export interface languageStruct {
fuzzy_verbose: string,
fuzzy_filter: string,
enabledProviders: string,
dnsServers: string,
selectLanguage: string,
selectTheme: string,
resetFuzzy_desc: string,
Expand Down
1 change: 1 addition & 0 deletions src/models/settings.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface AppSettings {
offlineMode: boolean,
enabledProviders: OnlineProviderType[],
batchDownloadSize: number,
dnsServers: string[],
previewSettings: PreviewSettings,
navigationWidth: number,
clearLogOnTest: boolean
Expand Down
8 changes: 6 additions & 2 deletions src/renderer/components/settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { fstat } from 'fs';
import * as fs from 'fs';
import * as path from 'path';
import * as paths from '../../paths';
import { providersSelect } from '../../lib/image-providers/available-providers';
import { providersSelect, dnsSelect } from '../../lib/image-providers/available-providers';

@Component({
selector: 'settings',
Expand Down Expand Up @@ -62,7 +62,11 @@ export class SettingsComponent implements OnDestroy {
}

get availableProviders() {
return providersSelect
return providersSelect;
}

get availableDNS() {
return dnsSelect;
}

get lang() {
Expand Down
8 changes: 7 additions & 1 deletion src/renderer/modifiers/app-settings.modifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ let versionUp = (version: number) => { return version + 1 };

export const appSettings: ValidatorModifier<AppSettings> = {
controlProperty: 'version',
latestVersion: 7,
latestVersion: 8,
fields: {
undefined: {
'version': { method: () => 0 },
Expand Down Expand Up @@ -89,6 +89,12 @@ export const appSettings: ValidatorModifier<AppSettings> = {
'enabledProviders': {method: (oldValue) => {
return oldValue.length ? ['sgdb', 'steamCDN'] : []
}}
},
7: {
'version': {method: versionUp},
'dnsServers': {method: (oldValue) => {
return [];
}}
}
}
};
7 changes: 7 additions & 0 deletions src/renderer/schemas/app-settings.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export const appSettings = {
}
},
batchDownloadSize: { type: 'number', default: 50 },
dnsServers: {
type: 'array',
default: [] as string[],
items: {
type: "string"
}
},
language: { type: 'string', default: languageManager.getDefaultLanguage(), enum: languageManager.getAvailableLanguages() },
theme: {type:'string', default: 'Deck', enum: availableThemes},
emudeckInstall: {type: 'boolean', default: false},
Expand Down
27 changes: 13 additions & 14 deletions src/renderer/services/preview.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export class PreviewService {
}
})
}
return vdfManager.write(batchWrite, this.appSettings.batchDownloadSize);
return vdfManager.write(batchWrite, this.appSettings.batchDownloadSize, this.appSettings.dnsServers);
})
.then(({nonFatal, outcomes}: {nonFatal: VDF_Error, outcomes: VDF_AllScreenshotsOutcomes})=> {
if(nonFatal) {
Expand Down Expand Up @@ -817,7 +817,7 @@ export class PreviewService {
}

async exportSelection() {
const imageDownloader = new url.ImageDownloader();
const imageDownloader = new url.ImageDownloader(this.appSettings.dnsServers);
async function saveImage(imageUrl: string, temporaryDir: string, append: string) {
const extension = imageUrl.split(/[#?]/)[0].split('.').pop().trim();
const filename = `${append}.${extension}`;
Expand Down Expand Up @@ -869,13 +869,10 @@ export class PreviewService {
const currentImage = appImage.getCurrentImage(app.images[artworkType], this.onlineImages[artworkType]);
if(currentImage) {
const imageUrl = currentImage.imageUrl;
const nintendoSucks = imageUrl.slice(-1) == '?';
if(!nintendoSucks) {
selection.images[artworkType] = {
pool: app.images[artworkType].imagePool,
filename: await saveImage(imageUrl, packagePath,`${saveId}.${artworkType}`)
};
}
selection.images[artworkType] = {
pool: app.images[artworkType].imagePool,
filename: await saveImage(imageUrl, packagePath,`${saveId}.${artworkType}`)
};
}
}
apps.push(selection);
Expand Down Expand Up @@ -930,18 +927,20 @@ export class PreviewService {
imageUrl: url.encodeFile(path.join(packagePath, selection.images[artworkType].filename)),
loadStatus: 'done'
}, artworkType, 'imported')
if(!importedApps.includes(selection.images[artworkType].pool)) {
importedApps.push(selection.images[artworkType].pool)
}
}
}
importedApps.push(selection.title);
}

for (const directory in this.previewData) {
for (const userId in this.previewData[directory]) {
for (const appId in this.previewData[directory][userId].apps) {
const app: PreviewDataApp = this.previewData[directory][userId].apps[appId];
if (importedApps.includes(app.extractedTitle)) {
for(const artworkType of artworkTypes) {
this.setImageIndex(app, this.getTotalLengthOfImages(app, artworkType, true) -1, artworkType, true);
for(const artworkType of artworkTypes) {
const imageRanges = appImage.getImageRanges(app.images[artworkType], this.onlineImages[artworkType]);
if(imageRanges.imported.start < imageRanges.imported.end) {
this.setImageIndex(app, imageRanges.imported.end - 1, artworkType, true);
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/templates/settings.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@
<div class=text style="margin-left: 0.25em;">{{lang.text.enabledProviders}}</div>
</div>
</div>
<div class="inputContainer">
<div class="inlineGroup">
<ng-select style="font-size: 0.85em;" class="ngSelect"
[ngModel]="settings.dnsServers" (ngModelChange)="settings.dnsServers = $event; onSettingsChange();"
[multiple]="true" [allowEmpty]="true" [placeholder]="lang.placeholder.noProviders" [values]="availableDNS">
</ng-select>
<div class=text style="margin-left: 0.25em;">{{lang.text.dnsServers}}</div>
</div>
</div>
<div class="inputContainer">
<div class="inlineGroup">
<ng-select style="font-size: 0.85em;" class="ngSelect"
Expand Down

0 comments on commit cf94af1

Please sign in to comment.