From f45f9c88ae28c79c737d3f72111cffcc7a0e2980 Mon Sep 17 00:00:00 2001
From: Anton Vakhrushev <anwinged@ya.ru>
Date: Fri, 3 Apr 2020 12:22:49 +0300
Subject: [PATCH] Refactoring

---
 src/Action/ActionController.ts  |   3 +-
 src/Dashboard.ts                |   8 +-
 src/Errors.ts                   |  24 ++---
 src/Scheduler.ts                | 152 +++++++++++++++-----------------
 src/Task/SendOnAdventureTask.ts |  16 +---
 src/Task/TaskController.ts      |  29 +++++-
 src/Task/UpgradeBuildingTask.ts |  17 +---
 7 files changed, 121 insertions(+), 128 deletions(-)

diff --git a/src/Action/ActionController.ts b/src/Action/ActionController.ts
index 4242ae6..0835a5e 100644
--- a/src/Action/ActionController.ts
+++ b/src/Action/ActionController.ts
@@ -1,12 +1,11 @@
 import { Args } from '../Common';
 import { Task } from '../Storage/TaskQueue';
 import { GameState } from '../Storage/GameState';
-import Scheduler from '../Scheduler';
+import { Scheduler } from '../Scheduler';
 
 const actionMap: { [name: string]: Function | undefined } = {};
 
 export function registerAction(constructor: Function) {
-    console.log('REGISTER ACTION', constructor.name);
     actionMap[constructor.name] = constructor;
 }
 
diff --git a/src/Dashboard.ts b/src/Dashboard.ts
index 348c624..374a1d0 100644
--- a/src/Dashboard.ts
+++ b/src/Dashboard.ts
@@ -1,7 +1,7 @@
 import * as URLParse from 'url-parse';
-import { markPage, trimPrefix, uniqId, waitForLoad } from './utils';
-import Scheduler from './Scheduler';
-import UpgradeBuildingTask from './Task/UpgradeBuildingTask';
+import { markPage, uniqId, waitForLoad } from './utils';
+import { Scheduler } from './Scheduler';
+import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
 import { Command } from './Common';
 import TaskQueueRenderer from './TaskQueueRenderer';
 
@@ -51,7 +51,7 @@ export default class Dashboard {
     }
 
     private onScheduleBuilding(id: string) {
-        const queueItem = new Command(UpgradeBuildingTask.NAME, {
+        const queueItem = new Command(UpgradeBuildingTask.name, {
             id,
         });
         this.scheduler.scheduleTask(queueItem);
diff --git a/src/Errors.ts b/src/Errors.ts
index 0373ad7..16974dc 100644
--- a/src/Errors.ts
+++ b/src/Errors.ts
@@ -1,29 +1,29 @@
 import { TaskId } from './Storage/TaskQueue';
 
 export class ActionError extends Error {
-    readonly id: TaskId;
-    constructor(id: TaskId, msg: string = '') {
+    readonly taskId: TaskId;
+    constructor(taskId: TaskId, msg: string = '') {
         super(msg);
-        this.id = id;
+        this.taskId = taskId;
         Object.setPrototypeOf(this, ActionError.prototype);
     }
 }
 
 export class AbortTaskError extends Error {
-    readonly id: TaskId;
-    constructor(id: TaskId, msg: string = '') {
+    readonly taskId: TaskId;
+    constructor(taskId: TaskId, msg: string = '') {
         super(msg);
-        this.id = id;
+        this.taskId = taskId;
         Object.setPrototypeOf(this, AbortTaskError.prototype);
     }
 }
 
 export class TryLaterError extends Error {
     readonly seconds: number;
-    readonly id: TaskId;
-    constructor(seconds: number, id: TaskId, msg: string = '') {
+    readonly taskId: TaskId;
+    constructor(seconds: number, taskId: TaskId, msg: string = '') {
         super(msg);
-        this.id = id;
+        this.taskId = taskId;
         this.seconds = seconds;
         Object.setPrototypeOf(this, TryLaterError.prototype);
     }
@@ -31,10 +31,10 @@ export class TryLaterError extends Error {
 
 export class BuildingQueueFullError extends Error {
     readonly seconds: number;
-    readonly id: TaskId;
-    constructor(seconds: number, id: TaskId, msg: string = '') {
+    readonly taskId: TaskId;
+    constructor(seconds: number, taskId: TaskId, msg: string = '') {
         super(msg);
-        this.id = id;
+        this.taskId = taskId;
         this.seconds = seconds;
         Object.setPrototypeOf(this, BuildingQueueFullError.prototype);
     }
diff --git a/src/Scheduler.ts b/src/Scheduler.ts
index edf6564..221fda9 100644
--- a/src/Scheduler.ts
+++ b/src/Scheduler.ts
@@ -1,5 +1,5 @@
 import { markPage, sleepShort, timestamp } from './utils';
-import UpgradeBuildingTask from './Task/UpgradeBuildingTask';
+import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
 import {
     AbortTaskError,
     BuildingQueueFullError,
@@ -7,14 +7,14 @@ import {
 } from './Errors';
 import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue';
 import ActionQueue from './Storage/ActionQueue';
-import { Args, Command } from './Common';
+import { Command } from './Common';
 import TaskQueueRenderer from './TaskQueueRenderer';
-import { ActionController, createAction } from './Action/ActionController';
-import TaskController from './Task/TaskController';
-import SendOnAdventureTask from './Task/SendOnAdventureTask';
+import { createAction } from './Action/ActionController';
+import { createTask } from './Task/TaskController';
+import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
 import { GameState } from './Storage/GameState';
 
-export default class Scheduler {
+export class Scheduler {
     private readonly version: string;
     private taskQueue: TaskQueue;
     private actionQueue: ActionQueue;
@@ -48,9 +48,9 @@ export default class Scheduler {
     }
 
     private scheduleHeroAdventure() {
-        if (!this.taskQueue.hasNamed(SendOnAdventureTask.NAME)) {
+        if (!this.taskQueue.hasNamed(SendOnAdventureTask.name)) {
             this.taskQueue.push(
-                new Command(SendOnAdventureTask.NAME, {}),
+                new Command(SendOnAdventureTask.name, {}),
                 timestamp()
             );
         }
@@ -62,7 +62,7 @@ export default class Scheduler {
         const taskCommand = this.taskQueue.get(currentTs);
 
         // текущего таска нет, очищаем очередь действий по таску
-        if (taskCommand === undefined) {
+        if (!taskCommand) {
             this.log('NO ACTIVE TASK');
             this.actionQueue.clear();
             return;
@@ -73,40 +73,70 @@ export default class Scheduler {
         this.log('CURRENT TASK', taskCommand);
         this.log('CURRENT ACTION', actionCommand);
 
-        if (actionCommand) {
-            return await this.processActionCommand(actionCommand, taskCommand);
-        }
+        try {
+            if (actionCommand) {
+                return await this.processActionCommand(
+                    actionCommand,
+                    taskCommand
+                );
+            }
 
-        if (taskCommand) {
-            return await this.processTaskCommand(taskCommand);
-        }
-    }
-
-    private async processTaskCommand(task: Task) {
-        const taskController = this.createTaskControllerByName(task.cmd.name);
-        this.log(
-            'PROCESS TASK',
-            taskController?.constructor.name,
-            task,
-            taskController
-        );
-        if (taskController) {
-            taskController.run(task);
+            if (taskCommand) {
+                return await this.processTaskCommand(taskCommand);
+            }
+        } catch (e) {
+            this.handleError(e);
         }
     }
 
     private async processActionCommand(cmd: Command, task: Task) {
-        const actionController = this.createActionControllerByName(cmd.name);
-        this.log(
-            'PROCESS ACTION',
-            actionController?.constructor.name,
-            actionController
-        );
+        const actionController = createAction(cmd.name, this.gameState, this);
+        this.log('PROCESS ACTION', cmd.name, actionController);
         if (actionController) {
-            await this.runAction(actionController, cmd.args, task);
+            await actionController.run(cmd.args, task);
+        } else {
+            this.logError('ACTION NOT FOUND', cmd.name);
         }
     }
 
+    private async processTaskCommand(task: Task) {
+        const taskController = createTask(task.cmd.name, this);
+        this.log('PROCESS TASK', task.cmd.name, task, taskController);
+        if (taskController) {
+            await taskController.run(task);
+        } else {
+            this.logError('TASK NOT FOUND', task.cmd.name);
+        }
+    }
+
+    private handleError(err: Error) {
+        this.logWarn('ACTION ABORTED', err.message);
+        this.actionQueue.clear();
+
+        if (err instanceof AbortTaskError) {
+            this.logWarn('ABORT TASK', err.taskId);
+            this.completeTask(err.taskId);
+            return;
+        }
+
+        if (err instanceof TryLaterError) {
+            this.logWarn('TRY', err.taskId, 'AFTER', err.seconds);
+            this.taskQueue.postpone(err.taskId, timestamp() + err.seconds);
+            return;
+        }
+
+        if (err instanceof BuildingQueueFullError) {
+            this.logWarn('BUILDING QUEUE FULL, TRY ALL AFTER', err.seconds);
+            this.taskQueue.modify(
+                t => t.cmd.name === UpgradeBuildingTask.name,
+                t => t.withTime(timestamp() + err.seconds)
+            );
+            return;
+        }
+
+        throw err;
+    }
+
     getTaskItems(): TaskList {
         return this.taskQueue.seeItems();
     }
@@ -124,59 +154,15 @@ export default class Scheduler {
         this.actionQueue.assign(actions);
     }
 
-    private createTaskControllerByName(
-        taskName: string
-    ): TaskController | undefined {
-        switch (taskName) {
-            case UpgradeBuildingTask.NAME:
-                return new UpgradeBuildingTask(this);
-            case SendOnAdventureTask.NAME:
-                return new SendOnAdventureTask(this);
-        }
-        this.logError('TASK NOT FOUND', taskName);
-        return undefined;
-    }
-
-    private createActionControllerByName(
-        actionName: string
-    ): ActionController | undefined {
-        const action = createAction(actionName, this.gameState, this);
-        if (!action) {
-            this.logError('ACTION NOT FOUND', actionName);
-            return undefined;
-        }
-        return action;
-    }
-
-    private async runAction(action: ActionController, args: Args, task: Task) {
-        try {
-            await action.run(args, task);
-        } catch (e) {
-            console.warn('ACTION ABORTED', e.message);
-            this.actionQueue.clear();
-            if (e instanceof AbortTaskError) {
-                console.warn('ABORT TASK', e.id);
-                this.completeTask(e.id);
-            }
-            if (e instanceof TryLaterError) {
-                console.warn('TRY', task.id, 'AFTER', e.seconds);
-                this.taskQueue.postpone(task.id, timestamp() + e.seconds);
-            }
-            if (e instanceof BuildingQueueFullError) {
-                console.warn('BUILDING QUEUE FULL, TRY ALL AFTER', e.seconds);
-                this.taskQueue.modify(
-                    t => t.cmd.name === UpgradeBuildingTask.NAME,
-                    t => t.withTime(timestamp() + e.seconds)
-                );
-            }
-        }
-    }
-
     private log(...args) {
         console.log('SCHEDULER:', ...args);
     }
 
+    private logWarn(...args) {
+        console.warn('SCHEDULER:', ...args);
+    }
+
     private logError(...args) {
-        console.error(...args);
+        console.error('SCHEDULER:', ...args);
     }
 }
diff --git a/src/Task/SendOnAdventureTask.ts b/src/Task/SendOnAdventureTask.ts
index e03a49e..87e132a 100644
--- a/src/Task/SendOnAdventureTask.ts
+++ b/src/Task/SendOnAdventureTask.ts
@@ -1,23 +1,15 @@
-import Scheduler from '../Scheduler';
 import { Args, Command } from '../Common';
 import { Task } from '../Storage/TaskQueue';
-import TaskController from './TaskController';
+import { TaskController, registerTask } from './TaskController';
 import { GoToPageAction } from '../Action/GoToPageAction';
 import { GrabHeroAttributesAction } from '../Action/GrabHeroAttributesAction';
 import { CompleteTaskAction } from '../Action/CompleteTaskAction';
 import { SendOnAdventureAction } from '../Action/SendOnAdventureAction';
 import { ClickButtonAction } from '../Action/ClickButtonAction';
 
-export default class SendOnAdventureTask extends TaskController {
-    static NAME = 'send_on_adventure';
-    private scheduler: Scheduler;
-
-    constructor(scheduler: Scheduler) {
-        super();
-        this.scheduler = scheduler;
-    }
-
-    run(task: Task) {
+@registerTask
+export class SendOnAdventureTask extends TaskController {
+    async run(task: Task) {
         const args: Args = { ...task.cmd.args, taskId: task.id };
         this.scheduler.scheduleActions([
             new Command(GoToPageAction.name, { ...args, path: 'hero.php' }),
diff --git a/src/Task/TaskController.ts b/src/Task/TaskController.ts
index 55a8dd3..17645fe 100644
--- a/src/Task/TaskController.ts
+++ b/src/Task/TaskController.ts
@@ -1,5 +1,30 @@
 import { Task } from '../Storage/TaskQueue';
+import { Scheduler } from '../Scheduler';
 
-export default abstract class TaskController {
-    abstract run(task: Task);
+const taskMap: { [name: string]: Function | undefined } = {};
+
+export function registerTask(constructor: Function) {
+    taskMap[constructor.name] = constructor;
+}
+
+export function createTask(
+    name: string,
+    scheduler: Scheduler
+): TaskController | undefined {
+    const storedFunction = taskMap[name];
+    if (storedFunction === undefined) {
+        return undefined;
+    }
+    const constructor = (storedFunction as unknown) as typeof TaskController;
+    return new constructor(scheduler);
+}
+
+export class TaskController {
+    protected scheduler: Scheduler;
+
+    constructor(scheduler: Scheduler) {
+        this.scheduler = scheduler;
+    }
+
+    async run(task: Task) {}
 }
diff --git a/src/Task/UpgradeBuildingTask.ts b/src/Task/UpgradeBuildingTask.ts
index 2824753..b38e1c0 100644
--- a/src/Task/UpgradeBuildingTask.ts
+++ b/src/Task/UpgradeBuildingTask.ts
@@ -1,23 +1,14 @@
-import Scheduler from '../Scheduler';
 import { UpgradeBuildingAction } from '../Action/UpgradeBuildingAction';
 import { Args, Command } from '../Common';
 import { Task } from '../Storage/TaskQueue';
-import TaskController from './TaskController';
+import { TaskController, registerTask } from './TaskController';
 import { GoToPageAction } from '../Action/GoToPageAction';
 import { CheckBuildingRemainingTimeAction } from '../Action/CheckBuildingRemainingTimeAction';
 import { CompleteTaskAction } from '../Action/CompleteTaskAction';
 
-export default class UpgradeBuildingTask extends TaskController {
-    static NAME = 'upgrade_building';
-    private scheduler: Scheduler;
-
-    constructor(scheduler: Scheduler) {
-        super();
-        this.scheduler = scheduler;
-    }
-
-    run(task: Task) {
-        console.log('RUN', UpgradeBuildingTask.NAME, 'with', task);
+@registerTask
+export class UpgradeBuildingTask extends TaskController {
+    async run(task: Task) {
         const args: Args = { ...task.cmd.args, taskId: task.id };
         this.scheduler.scheduleActions([
             new Command(GoToPageAction.name, { ...args, path: '/dorf1.php' }),