refactor: add ExportResult (fix #12)

main
spacemeowx2 2022-11-26 22:45:44 +08:00 committed by imspace
parent 8d376cfd38
commit aabc9cf733
4 changed files with 108 additions and 49 deletions

View File

@ -35,6 +35,29 @@ type Progress = {
total: number; total: number;
}; };
class StepProgress {
currentUrl?: string;
total: number;
exported: number;
done: number;
skipped: Record<string, number>;
constructor() {
this.total = 1;
this.exported = 0;
this.done = 0;
this.skipped = {};
}
}
function progress({ total, currentUrl, done }: StepProgress): Progress {
return {
total,
currentUrl,
current: done,
};
}
export class App { export class App {
profile: Profile; profile: Profile;
env: Env; env: Env;
@ -128,7 +151,7 @@ export class App {
const exporters = await this.getExporters(); const exporters = await this.getExporters();
const initStats = () => const initStats = () =>
Object.fromEntries( Object.fromEntries(
exporters.map((e) => [e.name, 0]), exporters.map((e) => [e.name, new StepProgress()]),
); );
let stats = initStats(); let stats = initStats();
const skipMode = this.getSkipMode(); const skipMode = this.getSkipMode();
@ -158,13 +181,10 @@ export class App {
fetcher, fetcher,
exporter: e, exporter: e,
gameList, gameList,
onStep: (progress) => { stepProgress: stats[e.name],
redraw(e.name, progress); onStep: () => {
stats[e.name] = progress.current; redraw(e.name, progress(stats[e.name]));
}, },
})
.then((count) => {
stats[e.name] = count;
}), }),
) )
.catch((err) => { .catch((err) => {
@ -215,13 +235,10 @@ export class App {
fetcher, fetcher,
exporter: e, exporter: e,
gameList: coopBattleList, gameList: coopBattleList,
onStep: (progress) => { stepProgress: stats[e.name],
stats[e.name] = progress.current; onStep: () => {
redraw(e.name, progress); redraw(e.name, progress(stats[e.name]));
}, },
})
.then((count) => {
stats[e.name] = count;
}), }),
) )
.catch((err) => { .catch((err) => {
@ -295,20 +312,17 @@ export class App {
fetcher, fetcher,
exporter, exporter,
gameList, gameList,
stepProgress,
onStep, onStep,
}: { }: {
type: Game["type"]; type: Game["type"];
exporter: GameExporter; exporter: GameExporter;
fetcher: GameFetcher; fetcher: GameFetcher;
gameList: string[]; gameList: string[];
onStep: (progress: Progress) => void; stepProgress: StepProgress;
}) { onStep: () => void;
let exported = 0; }): Promise<StepProgress> {
onStep?.();
onStep?.({
current: 0,
total: 1,
});
const workQueue = [ const workQueue = [
...await exporter.notExported({ ...await exporter.notExported({
@ -320,39 +334,56 @@ export class App {
const step = async (id: string) => { const step = async (id: string) => {
const detail = await fetcher.fetch(type, id); const detail = await fetcher.fetch(type, id);
const { url } = await exporter.exportGame(detail); const result = await exporter.exportGame(detail);
exported += 1;
onStep?.({ stepProgress.done += 1;
currentUrl: url, stepProgress.currentUrl = undefined;
current: exported,
total: workQueue.length, if (result.status === "success") {
}); stepProgress.exported += 1;
stepProgress.currentUrl = result.url;
} else if (result.status === "skip") {
const { skipped } = stepProgress;
skipped[result.reason] = (skipped[result.reason] ?? 0) + 1;
} else {
const _never: never = result;
}
onStep?.();
}; };
if (workQueue.length > 0) { if (workQueue.length > 0) {
onStep?.({ stepProgress.total = workQueue.length;
current: exported, onStep?.();
total: workQueue.length,
});
for (const battle of workQueue) { for (const battle of workQueue) {
await step(battle); await step(battle);
} }
} else { } else {
onStep?.({ stepProgress.done = 1;
current: 1, onStep?.();
total: 1,
});
} }
return exported; return stepProgress;
} }
printStats(stats: Record<string, number>) { printStats(stats: Record<string, StepProgress>) {
this.env.logger.log( this.env.logger.log(
`Exported ${ `Exported ${
Object.entries(stats) Object.entries(stats)
.map(([name, count]) => `${name}: ${count}`) .map(([name, { exported }]) => `${name}: ${exported}`)
.join(", ") .join(", ")
}`, }`,
); );
if (Object.values(stats).some((i) => Object.keys(i.skipped).length > 0)) {
this.env.logger.log(
`Skipped ${
Object.entries(stats)
.map(([name, { skipped }]) =>
Object.entries(skipped).map(([reason, count]) =>
`${name}: ${reason} (${count})`
).join(", ")
)
}`,
);
}
} }
} }

View File

@ -1,4 +1,10 @@
import { CoopInfo, Game, GameExporter, VsInfo } from "../types.ts"; import {
CoopInfo,
ExportResult,
Game,
GameExporter,
VsInfo,
} from "../types.ts";
import { path } from "../../deps.ts"; import { path } from "../../deps.ts";
import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts"; import { NSOAPP_VERSION, S3SI_VERSION } from "../constant.ts";
import { parseHistoryDetailId, urlSimplify } from "../utils.ts"; import { parseHistoryDetailId, urlSimplify } from "../utils.ts";
@ -83,7 +89,7 @@ export class FileExporter implements GameExporter {
}, },
})); }));
} }
async exportGame(info: Game) { async exportGame(info: Game): Promise<ExportResult> {
await Deno.mkdir(this.exportPath, { recursive: true }); await Deno.mkdir(this.exportPath, { recursive: true });
const filename = this.getFilenameById(info.detail.id); const filename = this.getFilenameById(info.detail.id);
@ -103,6 +109,7 @@ export class FileExporter implements GameExporter {
); );
return { return {
status: "success",
url: filepath, url: filepath,
}; };
} }

View File

@ -8,6 +8,7 @@ import {
CoopHistoryDetail, CoopHistoryDetail,
CoopHistoryPlayerResult, CoopHistoryPlayerResult,
CoopInfo, CoopInfo,
ExportResult,
Game, Game,
GameExporter, GameExporter,
Image, Image,
@ -231,10 +232,13 @@ export class StatInkExporter implements GameExporter {
isTriColor({ vsMode }: VsHistoryDetail): boolean { isTriColor({ vsMode }: VsHistoryDetail): boolean {
return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8; return vsMode.mode === "FEST" && b64Number(vsMode.id) === 8;
} }
async exportGame(game: VsInfo | CoopInfo) { async exportGame(game: Game): Promise<ExportResult> {
if (game.type === "VsInfo" && this.isTriColor(game.detail)) { if (game.type === "VsInfo" && this.isTriColor(game.detail)) {
// TODO: support tri-color fest // TODO: support tri-color fest
return {}; return {
status: "skip",
reason: "Tri-color fest is not supported",
};
} }
if (game.type === "VsInfo") { if (game.type === "VsInfo") {
@ -242,6 +246,7 @@ export class StatInkExporter implements GameExporter {
const { url } = await this.api.postBattle(body); const { url } = await this.api.postBattle(body);
return { return {
status: "success",
url, url,
}; };
} else { } else {
@ -249,6 +254,7 @@ export class StatInkExporter implements GameExporter {
const { url } = await this.api.postCoop(body); const { url } = await this.api.postCoop(body);
return { return {
status: "success",
url, url,
}; };
} }

View File

@ -296,12 +296,27 @@ export type CoopHistoryDetail = {
jobBonus: null | number; jobBonus: null | number;
}; };
export type ExportResult = {
status: "success";
url?: string;
} | {
status: "skip";
reason: string;
};
export type SummaryFetcher = {
fetchSummary<T extends (typeof Queries)[keyof typeof SummaryEnum]>(
type: T,
): Promise<RespMap[T]>;
};
export type GameExporter = { export type GameExporter = {
name: string; name: string;
notExported: ( notExported: (
{ type, list }: { type: Game["type"]; list: string[] }, { type, list }: { type: Game["type"]; list: string[] },
) => Promise<string[]>; ) => Promise<string[]>;
exportGame: (game: Game) => Promise<{ url?: string }>; exportGame: (game: Game) => Promise<ExportResult>;
exportSummary?: (fetcher: SummaryFetcher) => Promise<ExportResult>;
}; };
export type BankaraBattleHistories = { export type BankaraBattleHistories = {
@ -737,7 +752,7 @@ export type RankParam = {
}; };
export enum SummaryEnum { export enum SummaryEnum {
ConfigureAnalyticsQuery, ConfigureAnalyticsQuery = Queries.ConfigureAnalyticsQuery,
HistoryRecordQuery, HistoryRecordQuery = Queries.HistoryRecordQuery,
CoopHistoryQuery, CoopHistoryQuery = Queries.CoopHistoryQuery,
} }