diff --git a/src/Action/UpgradeBuildingAction.ts b/src/Action/UpgradeBuildingAction.ts index 6ce2ca4..3f884f0 100644 --- a/src/Action/UpgradeBuildingAction.ts +++ b/src/Action/UpgradeBuildingAction.ts @@ -1,9 +1,11 @@ import Action from './Action'; +import { Args } from '../Common'; +import { TryLaterError } from '../Errors'; export default class UpgradeBuildingAction extends Action { static NAME = 'upgrade_building'; - async run(args): Promise { + async run(args: Args): Promise { const btn = jQuery( '.upgradeButtonsContainer .section1 button.green.build' ); @@ -11,6 +13,7 @@ export default class UpgradeBuildingAction extends Action { btn.trigger('click'); } else { console.log('NO UPGRADE BUTTON'); + throw new TryLaterError(60); } return null; } diff --git a/src/Common.ts b/src/Common.ts new file mode 100644 index 0000000..95bb74f --- /dev/null +++ b/src/Common.ts @@ -0,0 +1,13 @@ +export interface Args { + [name: string]: any; +} + +export class Command { + readonly name: string; + readonly args: Args; + + constructor(name: string, args: Args) { + this.name = name; + this.args = args; + } +} diff --git a/src/Dashboard.ts b/src/Dashboard.ts index f4388b7..9e2e8df 100644 --- a/src/Dashboard.ts +++ b/src/Dashboard.ts @@ -1,9 +1,9 @@ import * as URLParse from 'url-parse'; import { markPage, sleepShort } from './utils'; import { v4 as uuid } from 'uuid'; -import { QueueItem } from './Queue'; import Scheduler from './Scheduler'; import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; +import { Command } from './Common'; export default class Dashboard { private scheduler: Scheduler; @@ -27,7 +27,7 @@ export default class Dashboard { `
В очередь
` ); jQuery(`#${id}`).on('click', () => { - const queueItem = new QueueItem(UpgradeBuildingTask.NAME, { + const queueItem = new Command(UpgradeBuildingTask.NAME, { id: p.query['id'], }); this.scheduler.pushTask(queueItem); diff --git a/src/Errors.ts b/src/Errors.ts new file mode 100644 index 0000000..bbb27c6 --- /dev/null +++ b/src/Errors.ts @@ -0,0 +1,7 @@ +export class TryLaterError extends Error { + readonly seconds: number; + constructor(s: number) { + super(); + this.seconds = s; + } +} diff --git a/src/Queue.ts b/src/Queue.ts deleted file mode 100644 index 8931db3..0000000 --- a/src/Queue.ts +++ /dev/null @@ -1,52 +0,0 @@ -export class QueueItem { - readonly name: string; - - readonly args; - - constructor(name: string, args: { [name: string]: any }) { - this.name = name; - this.args = args; - } -} - -export class Queue { - private readonly name; - - constructor(name: string) { - this.name = name; - } - - pop() { - const serialized = localStorage.getItem(this.name); - if (serialized === null) { - return null; - } - const items = JSON.parse(serialized) as Array; - if (items.length === 0) { - return null; - } - const first = items.shift(); - - this.flush(items); - - if (first === undefined) { - return null; - } - - return new QueueItem(first.name || '', first.args || {}); - } - - push(item: QueueItem): void { - const serialized = localStorage.getItem(this.name); - const items = serialized - ? (JSON.parse(serialized) as Array) - : []; - items.push(item); - this.flush(items); - } - - private flush(items) { - console.log('SET NEW QUEUE', this.name, items); - localStorage.setItem(this.name, JSON.stringify(items)); - } -} diff --git a/src/Scheduler.ts b/src/Scheduler.ts index c6023a8..acc71ab 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -1,19 +1,19 @@ import { markPage, sleepLong, sleepShort } from './utils'; -import { Queue, QueueItem } from './Queue'; import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; import GoToBuildingAction from './Action/GoToBuildingAction'; import UpgradeBuildingAction from './Action/UpgradeBuildingAction'; - -const ACTION_QUEUE = 'action_queue'; -const TASK_QUEUE = 'task_queue'; +import { TryLaterError } from './Errors'; +import TaskQueue from './Storage/TaskQueue'; +import ActionQueue from './Storage/ActionQueue'; +import { Args, Command } from './Common'; export default class Scheduler { - taskQueue: Queue; - actionQueue: Queue; + taskQueue: TaskQueue; + actionQueue: ActionQueue; constructor() { - this.taskQueue = new Queue(TASK_QUEUE); - this.actionQueue = new Queue(ACTION_QUEUE); + this.taskQueue = new TaskQueue(); + this.actionQueue = new ActionQueue(); } async run() { @@ -27,7 +27,7 @@ export default class Scheduler { const action = this.createAction(actionItem); this.log('POP ACTION', action); if (action) { - await action.run(actionItem.args); + await this.runAction(action, actionItem.args); } } else { const taskItem = this.popTask(); @@ -43,25 +43,25 @@ export default class Scheduler { } } - pushTask(task: QueueItem): void { + pushTask(task: Command): void { this.log('PUSH TASK', task); this.taskQueue.push(task); } - pushAction(action: QueueItem): void { + pushAction(action: Command): void { this.log('PUSH ACTION', action); this.actionQueue.push(action); } - private popTask() { - const taskItem = this.taskQueue.pop(); - if (taskItem === null) { - return null; - } - return taskItem; + scheduleActions(actions: Array): void { + this.actionQueue.assign(actions); } - private createTask(taskItem: QueueItem) { + private popTask(): Command | null { + return this.taskQueue.current() || this.taskQueue.next(); + } + + private createTask(taskItem: Command) { switch (taskItem.name) { case UpgradeBuildingTask.NAME: return new UpgradeBuildingTask(this); @@ -72,14 +72,14 @@ export default class Scheduler { private popAction() { const actionItem = this.actionQueue.pop(); - if (actionItem === null) { + if (actionItem === undefined) { return null; } this.log('UNKNOWN ACTION', actionItem.name); return actionItem; } - private createAction(actionItem: QueueItem) { + private createAction(actionItem: Command) { if (actionItem.name === GoToBuildingAction.NAME) { return new GoToBuildingAction(); } @@ -89,7 +89,18 @@ export default class Scheduler { return null; } + private async runAction(action, args: Args) { + try { + await action.run(args); + } catch (e) { + console.warn('ACTION ERROR', e); + if (e instanceof TryLaterError) { + console.warn('TRY AFTER', e.seconds); + } + } + } + private log(...args) { - console.log('SCHEDULER', ...args); + console.log('SCHEDULER:', ...args); } } diff --git a/src/Storage/ActionQueue.ts b/src/Storage/ActionQueue.ts new file mode 100644 index 0000000..d3208c4 --- /dev/null +++ b/src/Storage/ActionQueue.ts @@ -0,0 +1,57 @@ +import { Command } from '../Common'; + +const QUEUE_NAME = 'action_queue:v2'; + +class State { + items: Array; + constructor(items: Array) { + this.items = items; + } + + pop(): Command | undefined { + return this.items.shift(); + } + + push(cmd: Command) { + this.items.push(cmd); + } +} + +export default class ActionQueue { + pop(): Command | undefined { + const state = this.getState(); + const first = state.pop(); + this.flushState(state); + return first; + } + + push(cmd: Command): void { + const state = this.getState(); + state.push(cmd); + this.flushState(state); + } + + assign(items: Array): void { + this.flushState(new State(items)); + } + + private getState(): State { + const serialized = localStorage.getItem(QUEUE_NAME); + if (serialized === null) { + return new State([]); + } + + let parsed = JSON.parse(serialized) as State; + this.log('STATE', parsed); + + return new State(parsed.items); + } + + private flushState(state: State): void { + localStorage.setItem(QUEUE_NAME, JSON.stringify(state)); + } + + private log(...args) { + console.log('ACTION QUEUE:', ...args); + } +} diff --git a/src/Storage/TaskQueue.ts b/src/Storage/TaskQueue.ts new file mode 100644 index 0000000..154121a --- /dev/null +++ b/src/Storage/TaskQueue.ts @@ -0,0 +1,85 @@ +import { Command } from '../Common'; + +const QUEUE_NAME = 'task_queue:v2'; + +class CommandWithTime { + readonly cmd: Command; + readonly ts: number; + constructor(cmd: Command, ts: number) { + this.cmd = cmd; + this.ts = ts; + } +} + +class State { + current: CommandWithTime | null; + items: Array; + constructor( + current: CommandWithTime | null, + items: Array + ) { + items.sort((x: CommandWithTime, y: CommandWithTime) => x.ts - y.ts); + this.current = current; + this.items = items; + } + + push(cmd: Command, ts: number): State { + const items = this.items.slice(); + items.push(new CommandWithTime(cmd, ts)); + return new State(this.current, items); + } + + next(): State { + const items = this.items.slice(); + const first = items.shift(); + if (first === undefined) { + return new State(null, []); + } + return new State(first, items); + } +} + +export default class TaskQueue { + push(cmd: Command, ts: number | null = null) { + this.log('PUSH TASK', cmd, ts); + const state = this.getState(); + this.flushState(state.push(cmd, ts || this.defaultTs())); + } + + current(): Command | null { + let current = this.getState().current; + return current ? current.cmd : null; + } + + next(): Command | null { + let state = this.getState().next(); + let current = state.current ? state.current.cmd : null; + this.flushState(state); + return current; + } + + private defaultTs(): number { + return Math.floor(Date.now() / 1000); + } + + private getState(): State { + const serialized = localStorage.getItem(QUEUE_NAME); + if (serialized === null) { + return new State(null, []); + } + + const s = JSON.parse(serialized) as State; + + this.log('STATE', s); + + return new State(s.current, s.items); + } + + private flushState(state: State): void { + localStorage.setItem(QUEUE_NAME, JSON.stringify(state)); + } + + private log(...args) { + console.log('TASK QUEUE:', ...args); + } +} diff --git a/src/Task/UpgradeBuildingTask.ts b/src/Task/UpgradeBuildingTask.ts index 98da2e7..5e7f327 100644 --- a/src/Task/UpgradeBuildingTask.ts +++ b/src/Task/UpgradeBuildingTask.ts @@ -1,7 +1,7 @@ import Scheduler from '../Scheduler'; import GoToBuildingAction from '../Action/GoToBuildingAction'; import UpgradeBuildingAction from '../Action/UpgradeBuildingAction'; -import { QueueItem } from '../Queue'; +import { Args, Command } from '../Common'; export default class UpgradeBuildingTask { static NAME = 'upgrade_building'; @@ -11,11 +11,11 @@ export default class UpgradeBuildingTask { this.scheduler = scheduler; } - run(args) { + run(args: Args) { console.log('RUN', UpgradeBuildingTask.NAME, 'with', args); - this.scheduler.pushAction(new QueueItem(GoToBuildingAction.NAME, args)); - this.scheduler.pushAction( - new QueueItem(UpgradeBuildingAction.NAME, args) - ); + this.scheduler.scheduleActions([ + new Command(GoToBuildingAction.NAME, args), + new Command(UpgradeBuildingAction.NAME, args), + ]); } }