Created
March 18, 2019 15:23
-
-
Save yurafuca/eac4ef68f9d0cfdb3da76c109d31bcaf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { diff } from 'deep-object-diff'; | |
| import { Community, Program } from "./Manageable"; | |
| import { ProgramBuilder, CommunityBuilder } from "./CheckableBuilder"; | |
| class BucketClient { | |
| private rev: number; | |
| private readonly token: string; | |
| constructor(revision: number, token: string) { | |
| this.rev = revision; | |
| this.token = token; | |
| } | |
| revision(): number { | |
| return this.rev; | |
| } | |
| setRevision(revision: number, token: string) { | |
| if (token == this.token) { | |
| this.rev = revision; | |
| } | |
| } | |
| } | |
| export class Bucket { | |
| private communities: Community[]; | |
| private revision: number; | |
| private readonly token: string; | |
| static ANONYMOUS_PREFIX = "@@ANONYMOUS"; | |
| private anonymousCount: number; | |
| constructor() { | |
| this.communities = new Array<Community>(); | |
| this.revision = 0; | |
| this.token = "@@NICOSAPO"; | |
| this.anonymousCount = 0; | |
| } | |
| touch(communityBuilder: CommunityBuilder) { | |
| this.touchCommunity(communityBuilder); | |
| } | |
| assign(communityBuilder: CommunityBuilder, programBuilder: ProgramBuilder) { | |
| this.touchBoth(communityBuilder, programBuilder, this.revision); | |
| } | |
| appoint(communityBuilder: CommunityBuilder, programBuilder: ProgramBuilder) { | |
| this.touchBoth(communityBuilder, programBuilder, -1); | |
| } | |
| assignOrphan(programBuilder: ProgramBuilder, thumbnail: string | null = null) { | |
| if (this.findProgram(programBuilder, this.basicCommunities()) != null) { | |
| return; | |
| } | |
| const prefix = this.takeAnonymousPrefix(programBuilder); | |
| const anonymous = new CommunityBuilder().id(prefix); | |
| if (thumbnail) { | |
| anonymous.thumbnailUrl(thumbnail); | |
| } | |
| this.assign(anonymous, programBuilder); | |
| } | |
| appointOrphan(programBuilder: ProgramBuilder, thumbnail: string | null = null) { | |
| if (this.findProgram(programBuilder, this.basicCommunities()) != null) { | |
| return; | |
| } | |
| const prefix = this.takeAnonymousPrefix(programBuilder); | |
| const anonymous = new CommunityBuilder().id(prefix); | |
| if (thumbnail) { | |
| anonymous.thumbnailUrl(thumbnail); | |
| } | |
| this.appoint(anonymous, programBuilder); | |
| } | |
| mask(communityBuilders: CommunityBuilder[]) { | |
| const communities = communityBuilders.map(builder => this.touchCommunity(builder)); | |
| const survivors = communities.map(c => c.id); | |
| this.communities = this.communities.filter(c => { | |
| return survivors.includes(c.id) || | |
| (c.id.startsWith(Bucket.ANONYMOUS_PREFIX) && c.programs.length > 0) || | |
| c.shouldOpenAutomatically || | |
| c.programs.some(p => p.shouldOpenAutomatically) || | |
| c.programs.some(p => p.isVisiting) | |
| }); | |
| this.revision += 1; | |
| } | |
| createClient(): BucketClient { | |
| return new BucketClient(0, this.token); | |
| } | |
| private takeAnonymousPrefix(programBuilder: ProgramBuilder) { | |
| const program = this.findProgram(programBuilder, this.anonymousCommunities()); | |
| if (program != null) { | |
| return program.community.id; | |
| } | |
| return Bucket.ANONYMOUS_PREFIX + "@" + this.anonymousCount++; | |
| } | |
| private touchCommunity(communityBuilder: CommunityBuilder): Community { | |
| const community = this.createCommunity(communityBuilder); | |
| // Replace. | |
| this.communities = this.communities.filter(c => c.id != community.id); | |
| this.communities.push(community); | |
| return community; | |
| } | |
| private touchBoth(communityBuilder: CommunityBuilder, programBuilder: ProgramBuilder, revision: number) { | |
| const gracefulProgram = this.findProgram(programBuilder, this.anonymousCommunities()); | |
| if (gracefulProgram != null) { | |
| gracefulProgram.community.detachProgram(gracefulProgram); | |
| const builder = new ProgramBuilder() | |
| .id(gracefulProgram.id) | |
| .title(gracefulProgram.title) | |
| .isVisiting(gracefulProgram.isVisiting) | |
| .shouldMoveAutomatically(gracefulProgram.shouldMoveAutomatically) | |
| .shouldOpenAutomatically(gracefulProgram.shouldOpenAutomatically); | |
| this.touchBoth(communityBuilder, builder, this.revision); | |
| } | |
| const community = this.touchCommunity(communityBuilder); | |
| const program = this.createProgram(programBuilder, community, revision); | |
| // Attach. | |
| community.attachProgram(program); | |
| } | |
| takeProgramsShouldCancelOpen(client: BucketClient): Program[] { | |
| const result = this.communities | |
| .map(c => c.programs) | |
| .reduce((array, v) => array.concat(v), []) | |
| .filter(p => p.isVisiting) | |
| .filter(p => !p.isVisitedAutomatically) | |
| .filter(p => | |
| p.shouldOpenAutomatically || p.community.shouldOpenAutomatically | |
| ) | |
| .filter(p => p.revision() != -1 && p.revision() > client.revision()); | |
| client.setRevision(this.revision, this.token); | |
| return result; | |
| } | |
| takeProgramsShouldOpen(client: BucketClient): Program[] { | |
| const result = this.communities | |
| .map(c => c.programs) | |
| .reduce((array, v) => array.concat(v), []) | |
| .filter(p => !p.isVisiting) | |
| .filter(p => | |
| p.shouldOpenAutomatically || p.community.shouldOpenAutomatically | |
| ) | |
| .filter(p => p.revision() != -1 && p.revision() > client.revision()); | |
| client.setRevision(this.revision, this.token); | |
| return result; | |
| } | |
| takeProgramsShouldNotify(client: BucketClient): Program[] { | |
| const result = this.communities | |
| .map(c => c.programs) | |
| .reduce((array, v) => array.concat(v), []) | |
| .filter(p => !p.isVisiting) | |
| .filter(p => p.community.isFollowing) | |
| .filter(p => | |
| !p.shouldOpenAutomatically && !p.community.shouldOpenAutomatically | |
| ) | |
| .filter(p => p.revision() != -1 && p.revision() > client.revision()); | |
| client.setRevision(this.revision, this.token); | |
| return result; | |
| } | |
| communitiesShouldPoll(): Community[] { | |
| return this.communities.filter(c => c.shouldOpenAutomatically); | |
| } | |
| programsShouldPoll(): Program[] { | |
| return this.communities | |
| .map(c => c.programs) | |
| .reduce((array, v) => array.concat(v), []) | |
| .filter(p => p.shouldOpenAutomatically); | |
| } | |
| programs(): Program[] { | |
| return this.communities | |
| .map(c => c.programs) | |
| .reduce((array, v) => array.concat(v), []) | |
| } | |
| communityList(): Community[] { | |
| return this.communities; | |
| } | |
| private createCommunity(builder: CommunityBuilder): Community { | |
| const draft = builder.build(); | |
| const previous = this.findCommunity(draft, this.communities); | |
| const reference = previous || draft; | |
| // Update. | |
| builder.title(builder.getTitle() || reference.title); | |
| builder.thumbnailUrl(builder.getThumbnailUrl() || reference.thumbnailUrl); | |
| const isFollowing = builder.getIsFollowing(); | |
| if (isFollowing != null) { | |
| builder.isFollowing(isFollowing); | |
| } else { | |
| builder.isFollowing(reference.isFollowing); | |
| } | |
| const shouldOpen = builder.getShouldOpenAutomatically(); | |
| if (shouldOpen != null) { | |
| builder.shouldOpenAutomatically(shouldOpen); | |
| } else { | |
| builder.shouldOpenAutomatically(reference.shouldOpenAutomatically) | |
| } | |
| // Build. | |
| const community = builder.build(); | |
| // Attach previous programs. | |
| reference.programs.forEach(p => { | |
| community.attachProgram(p); | |
| p.community = community; | |
| }); | |
| // Print diff. | |
| Bucket.difference(previous, community); | |
| return community; | |
| } | |
| private createProgram(builder: ProgramBuilder, parent: Community, revision: number): Program { | |
| const draft = builder.build(revision); | |
| const previous = this.findProgram(builder, [parent]); | |
| const reference = previous || draft; | |
| // Update. | |
| builder.title(builder.getTitle() || reference.title); | |
| const isVisiting = builder.getIsVisiting(); | |
| if (isVisiting != null) { | |
| builder.isVisiting(isVisiting); | |
| } else { | |
| builder.isVisiting(reference.isVisiting); | |
| } | |
| const shouldOpen = builder.getShouldOpenAutomatically(); | |
| if (shouldOpen != null) { | |
| builder.shouldOpenAutomatically(shouldOpen); | |
| } else { | |
| builder.shouldOpenAutomatically(reference.shouldOpenAutomatically) | |
| } | |
| const shouldMove = builder.getShouldMoveAutomatically(); | |
| if (shouldMove != null) { | |
| builder.shouldMoveAutomatically(shouldMove); | |
| } else { | |
| builder.shouldMoveAutomatically(reference.shouldMoveAutomatically) | |
| } | |
| // Choose revision. | |
| let rev: number; | |
| if (reference.revision() == -1) { | |
| rev = revision; | |
| } else { | |
| rev = reference.revision(); | |
| } | |
| // Build. | |
| const program = builder.build(rev); | |
| program.community = parent; | |
| // Print diff. | |
| Bucket.difference(previous, program); | |
| return program; | |
| } | |
| private findCommunity(community: Community, communities: Community[]): Community | null { | |
| const ids = communities.map(c => c.id).filter(id => id == community.id); | |
| if (ids.length == 0) { | |
| return null; | |
| } | |
| const id = ids[0]; | |
| return communities.filter(c => c.id == id)[0]; | |
| } | |
| private findProgram(programBuilder: ProgramBuilder, parents: Community[]): Program | null { | |
| const program = programBuilder.build(-100); // => dummy revision. | |
| return parents | |
| .map(c => c.programs) | |
| .reduce((array, v) => array.concat(v), []) | |
| .filter(p => p.id == program.id)[0]; | |
| } | |
| private anonymousCommunities() { | |
| return this.communities.filter(c => c.id.startsWith(Bucket.ANONYMOUS_PREFIX)); | |
| } | |
| private basicCommunities() { | |
| return this.communities.filter(c => !c.id.startsWith(Bucket.ANONYMOUS_PREFIX)); | |
| } | |
| private static difference(prev: object | null, next: object) { | |
| if (prev != null) { | |
| const difference = diff(prev, next); | |
| if (!Bucket.isEmpty(difference)) { | |
| console.log(difference) | |
| } | |
| } else { | |
| console.info(next); | |
| } | |
| } | |
| private static isEmpty(obj: {}): boolean { | |
| return Object.keys(obj).length === 0 && obj.constructor === Object | |
| } | |
| } | |
| const bucket = new Bucket(); | |
| export default bucket; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment