Skip to content

Commit

Permalink
1.2.0 update
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog committed May 6, 2017
1 parent dd54d64 commit b8c5446
Show file tree
Hide file tree
Showing 23 changed files with 962 additions and 546 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
# Change Log
All notable changes to this project will be documented in this file.

## 1.2.0 - 2017-05-06
### Fixed
* Completely rewritten `vdf` file parser (both mine and [shortcuts.vdf](https://github.com/tirish/steam-shortcut-editor)) solves #2 and should solve #6 (not confirmed yet)
* Fixed #7

### Added
* Generated entries can now be removed
* All added entries can be removed (which are added since this release)
* New image provider - `retrogaming.cloud`

### Changed
* `Glob-regex` now supports `leftovers`, thus allowing `${regex}.ext`. It will now remove `.ext` and pass remaining string to regex parser.
* `Glob` and `Glob-regex` now properly replace `${title}` and `${regex}` with star (`*`). Earlier `dir\*\${title}.ext` would become `dir\*\*`. Not it will be replaced like this - `dir\*\*.ext`. This will eliminate a lot of "failed matches" messages and should increase `node-glob` performance.
* All image providers now have 40 seconds timeout and 3 retries. This should address #3

## 1.1.4 - 2017-05-02
### Fixed
* `shortcuts.vdf` should now have recurring titles removed as intended. If you had titles disappear, it was because Steam changed `AppName` property to `appname`. That resulted in too many titles and Steam got confused. Simply re-adding all titles via SRM should fix it as it will delete duplicates.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ No images found or you just want to use your own images? Fear not! Steam ROM Man

- Change almost every color by accessing color picker with **Alt + C**. If you lost it or can't reach it anymore, use **Alt + R** to reset it's position.
- Multiple user configuration support.
- Fully support downloading images from **SteamGridDB** and partially from **ConsoleGrid**.
- Fully support image downloads from **SteamGridDB**, **retrogaming.cloud** and partially from **ConsoleGrid**.

# Executable arguments for emulators

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "steam-rom-manager",
"version": "1.1.4",
"version": "1.2.0",
"license": "GPL-3.0",
"description": "An app for managing ROMs in Steam",
"author": {
Expand Down Expand Up @@ -68,7 +68,7 @@
"ngx-color-picker": "^4.0.0",
"reflect-metadata": "^0.1.10",
"rxjs": "^5.3.0",
"steam-shortcut-editor": "git+https://github.com/FrogTheFrog/steam-shortcut-editor.git",
"steam-shortcut-editor": "^3.0.0",
"to-css": "^1.2.1",
"vdf": "0.0.2",
"zone.js": "^0.8.5"
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/components/parsers.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ export class ParsersComponent implements OnInit, OnDestroy {
this.loggerService.success(`[${i + 1}/${totalLength}]: ${decodeURI(data.files[i].localImages[j])}`);
}
}
if (data.failed.length > 0){
this.loggerService.info('');
this.loggerService.error('Failed to match:');
}

for (let i = 0; i < data.failed.length; i++) {
this.loggerService.error(`[${i + 1 + data.files.length}/${totalLength}]: ${data.failed[i]}`);
}
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/components/preview.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export class PreviewComponent implements OnDestroy {
this.previewService.saveData();
}

remove() {
this.previewService.remove(false);
}

removeAll() {
this.previewService.remove(true);
}

ngOnDestroy() {
this.subscriptions.unsubscribe();
}
Expand Down
9 changes: 5 additions & 4 deletions src/renderer/lib/fuzzy-matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,18 @@ export class FuzzyMatcher {
if (lastSameScoreIndex === 0)
return matches[0].string;
else {
let minLoop = function (arr: number[]) {
let len = arr.length, min = Infinity;
let minLoopIndex = function (arr: number[]) {
let len = arr.length, min = Infinity, index = 0;
while (len--) {
if (arr[len] < min) {
min = arr[len];
index = len;
}
}
return min;
return index;
};

return matches[minLoop(lengthDiff)].string;
return matches[minLoopIndex(lengthDiff)].string;
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/renderer/lib/image-providers/consolegrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Http, Headers, URLSearchParams } from '@angular/http';
import { Observable } from "rxjs";

export class ConsoleGridProvider implements GenericImageProvider {
constructor(private http: Http, private downloadInterrupt: Observable<any>, private timeout: number = 120000) { }
constructor(private http: Http, private downloadInterrupt: Observable<any>, private timeout: number = 40000, private retryCount = 3) { }

getProvider() {
return 'ConsoleGrid';
Expand Down Expand Up @@ -58,9 +58,10 @@ export class ConsoleGridProvider implements GenericImageProvider {
return new Promise<{ url: string, failed: string }>((resolve, reject) => {
let data: { url: string, failed: string } = { url: undefined, failed: undefined };
let downloadStop = this.downloadInterrupt.subscribe(() => downloadStop.unsubscribe());
let subscription = this.http.get('http://consolegrid.com/api/top_picture', { params: params }).timeout(this.timeout).subscribe(
let subscription = this.http.get('http://consolegrid.com/api/top_picture', { params: params }).timeout(this.timeout).retry(this.retryCount).subscribe(
(response) => {
data.url = response.text();
data.failed = undefined;

if (!downloadStop.closed)
downloadStop.unsubscribe();
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/lib/image-providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './steamgriddb';
export * from './consolegrid';
export * from './consolegrid';
export * from './retrogaming.cloud';
101 changes: 101 additions & 0 deletions src/renderer/lib/image-providers/retrogaming.cloud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { GenericImageProvider, ImageContent, ImageProviderData } from "../../models";
import { Http, Headers, URLSearchParams } from '@angular/http';
import { Observable } from "rxjs";

export class RetroGamingCloudProvider implements GenericImageProvider {
constructor(private http: Http, private downloadInterrupt: Observable<any>, private timeout: number = 40000, private retryCount: number = 3) { }

getProvider() {
return 'retrogaming.cloud';
}

retrieveUrls(title: string) {
let data: ImageProviderData = { images: [], failed: [] };

return this.retrieveImageList(title).then((listData) => {
if (listData.failed) {
data.failed.push(listData.failed);
listData.list = [];
}
return listData;
}).then((listData) => {
if (listData.list.length > 0) {
let promises: Promise<{ images: ImageContent[], failed: string[] }>[] = [];
for (let i = 0; i < listData.list.length; i++) {
if (listData.list[i].id !== undefined)
promises.push(this.retrieveMediaData(listData.list[i].id));
}
return Promise.all(promises).then((results) => {
for (let i = 0; i < results.length; i++) {
data.images = data.images.concat(results[i].images);
data.failed = data.failed.concat(results[i].failed);
}
});
}
}).then(() => {
return data;
});
}

private retrieveMediaData(gameId: number) {
return new Promise<{ images: ImageContent[], failed: string[] }>((resolve, reject) => {
let data: { images: ImageContent[], failed: string[] } = { images: [], failed: [] };
let downloadStop = this.downloadInterrupt.subscribe(() => downloadStop.unsubscribe());
let subscription = this.http.get(`http://retrogaming.cloud/api/v1/game/${gameId}/media`).timeout(this.timeout).retry(this.retryCount).subscribe(
(response) => {
let returndeData = response.json();
let results = returndeData.results || [];

for (let i = 0; i < results.length; i++) {
if (results[i].url)
data.images.push({imageProvider: this.getProvider(), imageUploader: results[i].created_by ? results[i].created_by.name : undefined, imageUrl: results[i].url, loadStatus: 'none'});
}

if (!downloadStop.closed)
downloadStop.unsubscribe();
},
(error) => {
data.failed.push(`${error} (http://retrogaming.cloud/api/v1/game/${gameId}/media)`);

if (!downloadStop.closed)
downloadStop.unsubscribe();
}
)
downloadStop.add(() => {
if (!subscription.closed)
subscription.unsubscribe();
resolve(data);
});
});
}

private retrieveImageList(title: string) {
let params = new URLSearchParams();
params.append('name', title);

return new Promise<{ list: any[], failed: string }>((resolve, reject) => {
let data: { list: any[], failed: string } = { list: undefined, failed: undefined };
let downloadStop = this.downloadInterrupt.subscribe(() => downloadStop.unsubscribe());
let subscription = this.http.get('http://retrogaming.cloud/api/v1/game', { params: params }).timeout(this.timeout).retry(this.retryCount).subscribe(
(response) => {
let returndeData = response.json();
data.list = returndeData.results || [];

if (!downloadStop.closed)
downloadStop.unsubscribe();
},
(error) => {
data.failed = `${error} (http://retrogaming.cloud/api/v1/game?${params.toString()})`;

if (!downloadStop.closed)
downloadStop.unsubscribe();
}
)
downloadStop.add(() => {
if (!subscription.closed)
subscription.unsubscribe();
resolve(data);
});
});
}
}
4 changes: 2 additions & 2 deletions src/renderer/lib/image-providers/steamgriddb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Http, Headers, URLSearchParams } from '@angular/http';
import { Observable } from "rxjs";

export class SteamGridDbProvider implements GenericImageProvider {
constructor(private http: Http, private downloadInterrupt: Observable<any>, private timeout: number = 120000) { }
constructor(private http: Http, private downloadInterrupt: Observable<any>, private timeout: number = 40000, private retryCount: number = 3) { }

getProvider() {
return 'SteamGridDB';
Expand Down Expand Up @@ -32,7 +32,7 @@ export class SteamGridDbProvider implements GenericImageProvider {
return new Promise<number>((resolve, reject) => {
let next: number = undefined;
let downloadStop = this.downloadInterrupt.subscribe(() => { next = undefined; downloadStop.unsubscribe(); });
let subscription = this.http.get('http://www.steamgriddb.com/search.php', { params: params }).timeout(this.timeout).subscribe(
let subscription = this.http.get('http://www.steamgriddb.com/search.php', { params: params }).timeout(this.timeout).retry(this.retryCount).subscribe(
(response) => {
try {
let parsedBody = response.json();
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './file-parser';
export * from './vdfList';
export * from './reference';
export * from './image-provider';
export * from './fuzzy-matcher';
export * from './fuzzy-matcher';
export * from './vdf-manager';
54 changes: 39 additions & 15 deletions src/renderer/lib/parsers/glob-regex.parser.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Parser, GenericParser, UserConfiguration, ParsedData } from '../../models';
import { escapeRegExp } from "lodash";
import * as glob from 'glob';
import * as path from 'path';

interface TitleTagData {
direction: 'left' | 'right',
level: number,
finalGlob: string,
leftovers: { left: string, right: string },
regex: RegExp
}

Expand Down Expand Up @@ -92,10 +94,10 @@ export class GlobRegexParser implements GenericParser {
else if (match.length > 3)
return 'File glob must contain only one ${regex}!';

testRegExpr = /.*[^/]\${.+}.*|.*\${.+}[^/].*/i;
testRegExpr = /.*\*\${.+}.*|.*\${.+}\*.*/i;
match = testRegExpr.exec(fileGlob);
if (match !== null)
return '${regex} can be alone or only near directory seperator "/"!';
return 'Star (*) can not be next to ${regex}!';

testRegExpr = /\\/i;
match = testRegExpr.exec(fileGlob.replace(/(\${.+})/i, ''));
Expand Down Expand Up @@ -133,11 +135,26 @@ export class GlobRegexParser implements GenericParser {
return depth;
}

private getTitleLeftovers(fileGlob: string) {
let leftovers: { left: string, right: string } = { left: '', right: '' };
let titleSegmentMatch = fileGlob.match(/.*\/(.*\${.+}.*?)\/|.*\/(.*\${.+}.*)|(.*\${.+}.*?)\/|(.*\${.+}.*)/i);
if (titleSegmentMatch !== null) {
let titleSegments = (titleSegmentMatch[1] || titleSegmentMatch[2] || titleSegmentMatch[3] || titleSegmentMatch[4]).replace(/\${.*}/, '${}').split('*');
for (var i = 0; i < titleSegments.length; i++) {
if (titleSegments[i].search(/\${}/i) !== -1) {
let leftoverSegments = titleSegments[i].split(/\${}/i);
leftovers.left = leftoverSegments[0] !== undefined ? leftoverSegments[0] : '';
leftovers.right = leftoverSegments[1] !== undefined ? leftoverSegments[1] : '';
break;
}
}
}
return leftovers;
}

private getFinalGlob(fileGlob: string, depth: { direction: 'left' | 'right', level: number }) {
if (depth.level !== null) {
let fileSections = fileGlob.replace(/(\${.+})/i, '').split('/');
fileSections[depth.direction === 'right' ? fileSections.length - (depth.level + 1) : depth.level] = '*';
return fileSections.join('/');
return fileGlob.replace(/(\${.+})/i, '*')
}
else
return '**';
Expand All @@ -153,10 +170,13 @@ export class GlobRegexParser implements GenericParser {
}

private extractTitleTag(fileGlob: string) {
let extractedData: TitleTagData = { direction: undefined, level: undefined, finalGlob: undefined, regex: undefined };
let extractedData: TitleTagData = { direction: undefined, level: undefined, finalGlob: undefined, regex: undefined, leftovers: { left: undefined, right: undefined } };
let depth = this.getTitleDepth(fileGlob);
let leftovers = this.getTitleLeftovers(fileGlob);
extractedData.direction = depth.direction;
extractedData.level = depth.level;
extractedData.leftovers.left = leftovers.left ? '.*' + escapeRegExp(leftovers.left) : '';
extractedData.leftovers.right = leftovers.right ? escapeRegExp(leftovers.right) + '.*' : '';
extractedData.finalGlob = this.getFinalGlob(fileGlob, depth);
extractedData.regex = this.makeRegex(fileGlob);
return extractedData;
Expand All @@ -167,17 +187,21 @@ export class GlobRegexParser implements GenericParser {
let fileSections = file.split('/');
file = fileSections[titleData.direction === 'right' ? fileSections.length - (titleData.level + 1) : titleData.level];
}
let titleMatch = file.match(titleData.regex);
let titleRegExp = new RegExp(titleData.leftovers.left + '(.+)' + titleData.leftovers.right);
let titleMatch = file.match(titleRegExp);
if (titleMatch !== null) {
let title: string = '';
for (let i = 1; i < titleMatch.length; i++) {
if (titleMatch[i])
title += titleMatch[i];
titleMatch = titleMatch[1].match(titleData.regex);
if (titleMatch !== null) {
let title: string = '';
for (let i = 1; i < titleMatch.length; i++) {
if (titleMatch[i])
title += titleMatch[i];
}
if (title.length === 0)
return titleMatch[0].replace(/\//g, path.sep).trim();
else
return title.replace(/\//g, path.sep).trim();
}
if (title.length === 0)
return titleMatch[0].replace(/\//g, path.sep).trim();
else
return title.replace(/\//g, path.sep).trim();
}
return undefined;
}
Expand Down
5 changes: 2 additions & 3 deletions src/renderer/lib/parsers/glob.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export class GlobParser implements GenericParser {
let leftoverSegments = titleSegments[i].split(/\${title}/i);
leftovers.left = leftoverSegments[0] !== undefined ? leftoverSegments[0] : '';
leftovers.right = leftoverSegments[1] !== undefined ? leftoverSegments[1] : '';
break;
}
}
}
Expand All @@ -140,9 +141,7 @@ export class GlobParser implements GenericParser {

private getFinalGlob(fileGlob: string, depth: { direction: 'left' | 'right', level: number }) {
if (depth.level !== null) {
let fileSections = fileGlob.replace(/(\${.+})/i, '').split('/');
fileSections[depth.direction === 'right' ? fileSections.length - (depth.level + 1) : depth.level] = '*';
return fileSections.join('/');
return fileGlob.replace(/(\${.+})/i, '*')
}
else
return '**';
Expand Down
Loading

0 comments on commit b8c5446

Please sign in to comment.