| @@ -0,0 +1,33 | |||||
|
|
1 | import { ICancellation } from "./interfaces"; | |||
|
|
2 | ||||
|
|
3 | export class Cancellation implements ICancellation { | |||
|
|
4 | isSupported(): boolean { | |||
|
|
5 | return false; | |||
|
|
6 | } | |||
|
|
7 | throwIfRequested(): void { | |||
|
|
8 | } | |||
|
|
9 | ||||
|
|
10 | isRequested(): boolean { | |||
|
|
11 | return false; | |||
|
|
12 | } | |||
|
|
13 | ||||
|
|
14 | register(_cb: (e:any) => void): void { | |||
|
|
15 | ||||
|
|
16 | } | |||
|
|
17 | ||||
|
|
18 | static readonly none : Cancellation = { | |||
|
|
19 | isSupported(): boolean { | |||
|
|
20 | return false; | |||
|
|
21 | }, | |||
|
|
22 | ||||
|
|
23 | throwIfRequested(): void { | |||
|
|
24 | }, | |||
|
|
25 | ||||
|
|
26 | isRequested(): boolean { | |||
|
|
27 | return false; | |||
|
|
28 | }, | |||
|
|
29 | ||||
|
|
30 | register(_cb: (e:any) => void): void { | |||
|
|
31 | } | |||
|
|
32 | }; | |||
|
|
33 | } No newline at end of file | |||
| @@ -0,0 +1,85 | |||||
|
|
1 | import { IObservable, IObserver, IDestroyable, ICancellation } from '../interfaces'; | |||
|
|
2 | import { Cancellation } from '../Cancellation' | |||
|
|
3 | import * as TraceSource from '../log/TraceSource' | |||
|
|
4 | import { argumentNotNull } from '../safe'; | |||
|
|
5 | ||||
|
|
6 | const trace = TraceSource.get('@implab/core/components/Observable'); | |||
|
|
7 | ||||
|
|
8 | ||||
|
|
9 | class Observable<T> implements IObservable<T> { | |||
|
|
10 | private _once = new Array<IObserver<T>>(); | |||
|
|
11 | ||||
|
|
12 | private readonly _observers = new Array<IObserver<T>>(); | |||
|
|
13 | ||||
|
|
14 | constructor(func: (notify: IObserver<T>) => void) { | |||
|
|
15 | argumentNotNull(func, "func"); | |||
|
|
16 | ||||
|
|
17 | func(this._notify.bind(this)); | |||
|
|
18 | } | |||
|
|
19 | ||||
|
|
20 | on(observer: IObserver<T>): IDestroyable { | |||
|
|
21 | argumentNotNull(observer, "observer"); | |||
|
|
22 | ||||
|
|
23 | this._observers.push(observer); | |||
|
|
24 | ||||
|
|
25 | let me = this; | |||
|
|
26 | return { | |||
|
|
27 | destroy() { | |||
|
|
28 | me._removeObserver(observer); | |||
|
|
29 | } | |||
|
|
30 | } | |||
|
|
31 | } | |||
|
|
32 | ||||
|
|
33 | wait(ct: ICancellation = Cancellation.none): Promise<T> { | |||
|
|
34 | return new Promise<T>((resolve, reject) => { | |||
|
|
35 | this._once.push(resolve); | |||
|
|
36 | if (ct.isSupported()) { | |||
|
|
37 | ct.register((e) => { | |||
|
|
38 | this._removeOnce(resolve); | |||
|
|
39 | reject(e); | |||
|
|
40 | }); | |||
|
|
41 | } | |||
|
|
42 | }); | |||
|
|
43 | } | |||
|
|
44 | ||||
|
|
45 | onObserverException(e: any) { | |||
|
|
46 | trace.error("Unhandled exception in the observer: {0}", e); | |||
|
|
47 | } | |||
|
|
48 | ||||
|
|
49 | private _removeOnce(d: IObserver<T>) { | |||
|
|
50 | let i = this._once.indexOf(d); | |||
|
|
51 | if (i >= 0) | |||
|
|
52 | this._once.splice(i); | |||
|
|
53 | } | |||
|
|
54 | ||||
|
|
55 | private _removeObserver(d: IObserver<T>) { | |||
|
|
56 | let i = this._observers.indexOf(d); | |||
|
|
57 | if (i >= 0) | |||
|
|
58 | this._observers.splice(i); | |||
|
|
59 | } | |||
|
|
60 | ||||
|
|
61 | private _notify(evt: T) { | |||
|
|
62 | let guard = (observer: IObserver<T>) => { | |||
|
|
63 | try { | |||
|
|
64 | observer(evt); | |||
|
|
65 | } catch (e) { | |||
|
|
66 | this.onObserverException(e); | |||
|
|
67 | } | |||
|
|
68 | } | |||
|
|
69 | ||||
|
|
70 | if (this._once.length) { | |||
|
|
71 | for (let i = 0; i < this._once.length; i++) | |||
|
|
72 | guard(this._once[i]); | |||
|
|
73 | this._once = []; | |||
|
|
74 | } | |||
|
|
75 | ||||
|
|
76 | for (let i = 0; i < this._observers.length; i++) | |||
|
|
77 | guard(this._observers[i]); | |||
|
|
78 | } | |||
|
|
79 | } | |||
|
|
80 | ||||
|
|
81 | namespace Observable { | |||
|
|
82 | export const traceSource = trace; | |||
|
|
83 | } | |||
|
|
84 | ||||
|
|
85 | export = Observable; No newline at end of file | |||
| @@ -0,0 +1,83 | |||||
|
|
1 | import { watchFile } from "fs"; | |||
|
|
2 | ||||
|
|
3 | export interface IDestroyable { | |||
|
|
4 | destroy(); | |||
|
|
5 | } | |||
|
|
6 | ||||
|
|
7 | export interface ICancellation { | |||
|
|
8 | throwIfRequested(): void; | |||
|
|
9 | isRequested(): boolean; | |||
|
|
10 | isSupported(): boolean; | |||
|
|
11 | register(cb: (e: any) => void): void; | |||
|
|
12 | } | |||
|
|
13 | ||||
|
|
14 | /** | |||
|
|
15 | * Интерфейс поддерживающий асинхронную активацию | |||
|
|
16 | */ | |||
|
|
17 | export interface IActivatable { | |||
|
|
18 | /** | |||
|
|
19 | * @returns Boolean indicates the current state | |||
|
|
20 | */ | |||
|
|
21 | isActive(): boolean; | |||
|
|
22 | ||||
|
|
23 | /** | |||
|
|
24 | * Starts the component activation | |||
|
|
25 | * @param ct cancellation token for this operation | |||
|
|
26 | */ | |||
|
|
27 | activate(ct?: ICancellation) : Promise<void>; | |||
|
|
28 | ||||
|
|
29 | /** | |||
|
|
30 | * Starts the component deactivation | |||
|
|
31 | * @param ct cancellation token for this operation | |||
|
|
32 | */ | |||
|
|
33 | deactivate(ct?: ICancellation) : Promise<void>; | |||
|
|
34 | ||||
|
|
35 | /** | |||
|
|
36 | * Sets the activation controller for this component | |||
|
|
37 | * @param controller The activation controller | |||
|
|
38 | * | |||
|
|
39 | * Activation controller checks whether this component | |||
|
|
40 | * can be activated and manages the active state of the | |||
|
|
41 | * component | |||
|
|
42 | */ | |||
|
|
43 | setActivationController(controller: IActivationController); | |||
|
|
44 | ||||
|
|
45 | /** | |||
|
|
46 | * Gets the current activation controller for this component | |||
|
|
47 | */ | |||
|
|
48 | getActivationController(): IActivationController; | |||
|
|
49 | } | |||
|
|
50 | ||||
|
|
51 | export interface IActivationController { | |||
|
|
52 | activating(component: IActivatable, ct?: ICancellation): Promise<void>; | |||
|
|
53 | ||||
|
|
54 | activated(component: IActivatable, ct?: ICancellation): Promise<void>; | |||
|
|
55 | ||||
|
|
56 | deactivating(component: IActivatable, ct?: ICancellation): Promise<void>; | |||
|
|
57 | ||||
|
|
58 | deactivated(component: IActivatable, ct?: ICancellation): Promise<void>; | |||
|
|
59 | ||||
|
|
60 | deactivate(ct?: ICancellation): Promise<void>; | |||
|
|
61 | ||||
|
|
62 | activate(component: IActivatable, ct?: ICancellation): Promise<void>; | |||
|
|
63 | ||||
|
|
64 | getActive(): IActivatable; | |||
|
|
65 | } | |||
|
|
66 | ||||
|
|
67 | export interface IAsyncComponent { | |||
|
|
68 | getCompletion(): Promise<void>; | |||
|
|
69 | } | |||
|
|
70 | ||||
|
|
71 | export interface ICancellable { | |||
|
|
72 | cancel(reason?: any): void; | |||
|
|
73 | } | |||
|
|
74 | ||||
|
|
75 | export interface IObserver<T> { | |||
|
|
76 | (x:T): void; | |||
|
|
77 | } | |||
|
|
78 | ||||
|
|
79 | export interface IObservable<T> { | |||
|
|
80 | on(observer: IObserver<T>): IDestroyable; | |||
|
|
81 | ||||
|
|
82 | wait(ct?: ICancellation) : Promise<T>; | |||
|
|
83 | } No newline at end of file | |||
| @@ -1,8 +1,6 | |||||
| 1 |
import { IActivationController } from '. |
|
1 | import { IActivationController, IActivatable, ICancellation } from '../interfaces'; | |
| 2 | import { IActivatable } from './IActivatable'; |
|
|||
| 3 | import { AsyncComponent } from './AsyncComponent'; |
|
2 | import { AsyncComponent } from './AsyncComponent'; | |
| 4 |
import { |
|
3 | import { Cancellation } from '../Cancellation'; | |
| 5 | import { EmptyCancellation } from '../EmptyCancellation'; |
|
|||
| 6 | import * as TraceSource from '../log/TraceSource'; |
|
4 | import * as TraceSource from '../log/TraceSource'; | |
| 7 |
|
5 | |||
| 8 | type Constructor<T = {}> = new (...args: any[]) => T; |
|
6 | type Constructor<T = {}> = new (...args: any[]) => T; | |
| @@ -37,11 +35,14 function ActivatableMixin<TBase extends | |||||
| 37 | await this._controller.activated(this, ct); |
|
35 | await this._controller.activated(this, ct); | |
| 38 | } |
|
36 | } | |
| 39 |
|
37 | |||
| 40 |
|
|
38 | activate(ct: ICancellation = Cancellation.none) { | |
|
|
39 | return this.runOperation(this._activateAsync.bind(this), ct); | |||
|
|
40 | } | |||
|
|
41 | ||||
|
|
42 | async _activateAsync(ct: ICancellation) { | |||
| 41 | if (this.isActive()) |
|
43 | if (this.isActive()) | |
| 42 | return; |
|
44 | return; | |
| 43 | ct = this.startOperation(ct); |
|
45 | ||
| 44 | try { |
|
|||
| 45 |
|
|
46 | await this.onActivating(ct); | |
| 46 |
|
|
47 | this._active = true; | |
| 47 |
|
|
48 | try { | |
| @@ -49,11 +50,6 function ActivatableMixin<TBase extends | |||||
| 49 |
|
|
50 | } catch (e) { | |
| 50 |
|
|
51 | log.error("Suppressed onActivated error: {0}", e); | |
| 51 |
|
|
52 | } | |
| 52 | this.completeSuccess(); |
|
|||
| 53 | } catch (e) { |
|
|||
| 54 | this.completeFail(e); |
|
|||
| 55 | } |
|
|||
| 56 | return this.getCompletion(); |
|
|||
| 57 | } |
|
53 | } | |
| 58 |
|
54 | |||
| 59 | async onDeactivating(ct: ICancellation) { |
|
55 | async onDeactivating(ct: ICancellation) { | |
| @@ -66,11 +62,13 function ActivatableMixin<TBase extends | |||||
| 66 | await this._controller.deactivated(this, ct); |
|
62 | await this._controller.deactivated(this, ct); | |
| 67 | } |
|
63 | } | |
| 68 |
|
64 | |||
| 69 |
|
|
65 | deactivate(ct: ICancellation = Cancellation.none) { | |
|
|
66 | return this.runOperation(this._deactivateAsync.bind(this), ct); | |||
|
|
67 | } | |||
|
|
68 | ||||
|
|
69 | async _deactivateAsync(ct: ICancellation) { | |||
| 70 | if (!this.isActive()) |
|
70 | if (!this.isActive()) | |
| 71 | return; |
|
71 | return; | |
| 72 | ct = this.startOperation(ct); |
|
|||
| 73 | try { |
|
|||
| 74 |
|
|
72 | await this.onDeactivating(ct); | |
| 75 |
|
|
73 | this._active = false; | |
| 76 |
|
|
74 | try { | |
| @@ -78,13 +76,7 function ActivatableMixin<TBase extends | |||||
| 78 |
|
|
76 | } catch (e) { | |
| 79 |
|
|
77 | log.error("Suppressed onDeactivated error: {0}", e); | |
| 80 |
|
|
78 | } | |
| 81 | this.completeSuccess(); |
|
|||
| 82 | } catch (e) { |
|
|||
| 83 | this.completeFail(e); |
|
|||
| 84 |
|
|
79 | } | |
| 85 | return this.getCompletion(); |
|
|||
| 86 | } |
|
|||
| 87 |
|
||||
| 88 | } |
|
80 | } | |
| 89 | } |
|
81 | } | |
| 90 |
|
82 | |||
| @@ -1,47 +1,17 | |||||
| 1 |
import { |
|
1 | import { Cancellation } from "../Cancellation"; | |
| 2 |
import { |
|
2 | import { IAsyncComponent, ICancellation } from "../interfaces"; | |
| 3 |
|
3 | |||
| 4 | export class AsyncComponent { |
|
4 | export class AsyncComponent implements IAsyncComponent { | |
| 5 | _completion: Promise<void>; |
|
5 | _completion: Promise<void> = Promise.resolve(); | |
| 6 |
|
||||
| 7 | _deferred: { |
|
|||
| 8 | resolve(): void |
|
|||
| 9 | reject(reason: any): void |
|
|||
| 10 | }; |
|
|||
| 11 |
|
6 | |||
| 12 | getCompletion() { return this._completion }; |
|
7 | getCompletion() { return this._completion }; | |
| 13 |
|
8 | |||
| 14 |
|
|
9 | runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { | |
| 15 | if (this._deferred) |
|
10 | // TODO create cancellation source here | |
| 16 | throw new Error("The async operation is already pending"); |
|
11 | async function guard() { | |
| 17 |
|
12 | await op(ct); | ||
| 18 | this._completion = new Promise<void>((resolve, reject) => { |
|
|||
| 19 | this._deferred = { |
|
|||
| 20 | resolve: resolve, |
|
|||
| 21 | reject: reject |
|
|||
| 22 | } |
|
|||
| 23 | }); |
|
|||
| 24 | return ct; |
|
|||
| 25 | } |
|
13 | } | |
| 26 |
|
14 | |||
| 27 | completeSuccess() { |
|
15 | return this._completion = guard(); | |
| 28 | this._deferred.resolve(); |
|
|||
| 29 | this._deferred = null; |
|
|||
| 30 | } |
|
|||
| 31 |
|
||||
| 32 | completeFail(reason: any) { |
|
|||
| 33 | this._deferred.reject(reason); |
|
|||
| 34 | this._deferred = null; |
|
|||
| 35 | } |
|
|||
| 36 |
|
||||
| 37 | async runOperation(cb: (ct: ICancellation) => Promise<void>, ct: ICancellation = EmptyCancellation.default) { |
|
|||
| 38 | //safe.argumentNotNull(cb, "cb") |
|
|||
| 39 | ct = this.startOperation(ct); |
|
|||
| 40 | try { |
|
|||
| 41 | await cb(ct); |
|
|||
| 42 | this.completeSuccess(); |
|
|||
| 43 | } catch(e) { |
|
|||
| 44 | this.completeFail(e); |
|
|||
| 45 | } |
|
|||
| 46 | } |
|
16 | } | |
| 47 | } No newline at end of file |
|
17 | } | |
| @@ -78,15 +78,15 tape('controller activation', async func | |||||
| 78 | t.comment("Active the component through the controller"); |
|
78 | t.comment("Active the component through the controller"); | |
| 79 | await c.activate(a); |
|
79 | await c.activate(a); | |
| 80 | t.true(a.isActive(), "The component should successfully activate"); |
|
80 | t.true(a.isActive(), "The component should successfully activate"); | |
| 81 |
t. |
|
81 | t.equal(c.getActive(), a, "The controller should point to the activated component"); | |
| 82 |
t. |
|
82 | t.equal(a.getActivationController(), c, "The component should point to the controller"); | |
| 83 |
|
83 | |||
| 84 | t.comment("Deactive the component throug the controller"); |
|
84 | t.comment("Deactive the component throug the controller"); | |
| 85 | await c.deactivate(); |
|
85 | await c.deactivate(); | |
| 86 |
|
86 | |||
| 87 | t.false(a.isActive(), "The component should successfully deactivate"); |
|
87 | t.false(a.isActive(), "The component should successfully deactivate"); | |
| 88 |
t. |
|
88 | t.equal(c.getActive(), null, "The controller shouldn't point to any component"); | |
| 89 |
t. |
|
89 | t.equal(a.getActivationController(), c, "The componet should point to it's controller"); | |
| 90 |
|
90 | |||
| 91 | t.end(); |
|
91 | t.end(); | |
| 92 | }); |
|
92 | }); | |
| @@ -3,7 +3,7 import * as tape from 'tape'; | |||||
| 3 |
|
3 | |||
| 4 | const sourceId = 'test/TraceSourceTests'; |
|
4 | const sourceId = 'test/TraceSourceTests'; | |
| 5 |
|
5 | |||
| 6 | tape('', t => { |
|
6 | tape('trace message', t => { | |
| 7 | let trace = TraceSource.get(sourceId); |
|
7 | let trace = TraceSource.get(sourceId); | |
| 8 |
|
8 | |||
| 9 | trace.level = TraceSource.DebugLevel; |
|
9 | trace.level = TraceSource.DebugLevel; | |
| @@ -11,7 +11,7 tape('', t => { | |||||
| 11 | let h = trace.on((sender,level,msg) => { |
|
11 | let h = trace.on((sender,level,msg) => { | |
| 12 | t.equal(sender, trace, "sender should be the current trace source"); |
|
12 | t.equal(sender, trace, "sender should be the current trace source"); | |
| 13 | t.equal(TraceSource.DebugLevel, level, "level should be debug level"); |
|
13 | t.equal(TraceSource.DebugLevel, level, "level should be debug level"); | |
| 14 |
t.equal(msg, "Hello, World!", "The message should be formatted |
|
14 | t.equal(msg, "Hello, World!", "The message should be a formatted message"); | |
| 15 |
|
15 | |||
| 16 | t.end(); |
|
16 | t.end(); | |
| 17 | }); |
|
17 | }); | |
| @@ -19,4 +19,26 tape('', t => { | |||||
| 19 | trace.debug("Hello, {0}!", "World"); |
|
19 | trace.debug("Hello, {0}!", "World"); | |
| 20 |
|
20 | |||
| 21 | h.destroy(); |
|
21 | h.destroy(); | |
|
|
22 | }); | |||
|
|
23 | ||||
|
|
24 | tape('trace event', t => { | |||
|
|
25 | let trace = TraceSource.get(sourceId); | |||
|
|
26 | ||||
|
|
27 | trace.level = TraceSource.DebugLevel; | |||
|
|
28 | ||||
|
|
29 | let event = { | |||
|
|
30 | name: "custom event" | |||
|
|
31 | }; | |||
|
|
32 | ||||
|
|
33 | let h = trace.on((sender,level,msg) => { | |||
|
|
34 | t.equal(sender, trace, "sender should be the current trace source"); | |||
|
|
35 | t.equal(TraceSource.DebugLevel, level, "level should be debug level"); | |||
|
|
36 | t.equal(msg, event, "The message should be the specified object"); | |||
|
|
37 | ||||
|
|
38 | t.end(); | |||
|
|
39 | }); | |||
|
|
40 | ||||
|
|
41 | trace.traceEvent(TraceSource.DebugLevel, event); | |||
|
|
42 | ||||
|
|
43 | h.destroy(); | |||
| 22 | }); No newline at end of file |
|
44 | }); | |
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
