<commit_before>import { RoomDataService, IRoomInfo } from './services/RoomDataService';


export class RoomController {
    service = new RoomDataService();
    users: any = { }; //[string] => userinfo
    host: IUserInfo = null;
    chatHistory: IChatText[] = [];
    roomInfo: IRoomInfo;
    namespace: any;
    
    public constructor(namespace, roomInfo: IRoomInfo) {
        this.roomInfo = roomInfo;
        this.namespace = namespace;
    }

    private getUserFromSocket(socket): IUserInfo {
        let names = Object.keys(this.users);
        for(let i = 0; i < names.length; i++) {
            if (socket.id == this.users[names[i]].socket.id) { return this.users[names[i]] }
        }
        return undefined;
    }
 
    protected getRoom() {
        return this.namespace.in(this.roomInfo.id);
    }

    protected removeUser(user: IUserInfo) {
        //remove from info
        delete this.users[user.name];

        //announce new user list
        this.getRoom().emit('users', Object.keys(this.users));
    }

    protected decideHost() {
        if (this.host != null) { return; } //host still exists

        let userList = Object.keys(this.users);
        if (userList.length == 0) { return; } //no one to be host

        // random host
        this.host = this.users[userList[Math.floor(Math.random() * userList.length)]];

        // announce result
        this.host.socket.emit('host', {
            is_host: true,
            name: this.host.name
        })
        this.host.socket.in(this.roomInfo.id).emit('host', {
            is_host: false,
            name: this.host.name
        });
    }

    public verifyUser(joinInfo): Promise<boolean> {
        // check if name conflicts
        // NOTE: this is a promise due to possible async actions in the future
        return new Promise<boolean>((resolve, reject) => {
            if (Object.keys(this.users).indexOf(joinInfo.name) >= 0) { 
                // Name taken, cannot join
                reject(new Error('Name taken.'));
            } else {
                resolve(true);
            }
        });
    }

    public clientJoin(socket, joinInfo, ackFn) {
        let user = {
            socket: socket,
            name: joinInfo.name
        };

        // Add user
        this.users[user.name] = user;

        this.decideHost();

        // announce user list
        this.getRoom().emit('users', Object.keys(this.users));

        // Bind events
        socket.on('text', (text) => {
            this.chatHistory.push(<IChatText>{ 
                user: user.name,
                message: text,
                time: new Date(Date.now())
            });
            this.getRoom().in(this.roomInfo.id).emit('text', user.name + ": " + text);
        });

        // sync room
        ackFn({ 
            chat: this.chatHistory,
            users: Object.keys(this.users),
        });
    }

    public clientLeave(socket) {
        let user = this.getUserFromSocket(socket);
        this.removeUser(user);
        if (this.host.name == user.name) { this.host = null; }

        if (this.host == null) { this.decideHost(); }
    }
}

export interface IUserInfo {
    socket;
    name: string;
}

export interface IChatText {
    user: string; // null if server
    message: string;
    time: Date;
}

export interface IStreamItem {
    user: string;
    url: string;
}<commit_msg>Added streaming functionality<commit_after>import { RoomDataService, IRoomInfo } from './services/RoomDataService';


export class RoomController {
    service = new RoomDataService();
    users: any = { }; //[string] => userinfo
    host: IUserInfo = null;
    chatHistory: IChatText[] = [];
    currentStreamInfo: IStreamStatus = null;
    roomInfo: IRoomInfo;
    namespace: any;
    
    public constructor(namespace, roomInfo: IRoomInfo) {
        this.roomInfo = roomInfo;
        this.namespace = namespace;
    }

    private getUserFromSocket(socket): IUserInfo {
        let names = Object.keys(this.users);
        for(let i = 0; i < names.length; i++) {
            if (socket.id == this.users[names[i]].socket.id) { return this.users[names[i]] }
        }
        return undefined;
    }
 
    protected getRoom() {
        return this.namespace.in(this.roomInfo.id);
    }

    protected removeUser(user: IUserInfo) {
        //remove from info
        delete this.users[user.name];

        //announce new user list
        this.getRoom().emit('users', Object.keys(this.users));
    }

    protected decideHost() {
        if (this.host != null) { return; } //host still exists

        let userList = Object.keys(this.users);
        if (userList.length == 0) { return; } //no one to be host

        // random host
        this.host = this.users[userList[Math.floor(Math.random() * userList.length)]];

        // announce result
        this.host.socket.emit('host', {
            is_host: true,
            name: this.host.name
        })
        this.host.socket.in(this.roomInfo.id).emit('host', {
            is_host: false,
            name: this.host.name
        });
    }

    public verifyUser(joinInfo): Promise<boolean> {
        // check if name conflicts
        // NOTE: this is a promise due to possible async actions in the future
        return new Promise<boolean>((resolve, reject) => {
            if (Object.keys(this.users).indexOf(joinInfo.name) >= 0) { 
                // Name taken, cannot join
                reject(new Error('Name taken.'));
            } else {
                resolve(true);
            }
        });
    }

    public clientJoin(socket, joinInfo, ackFn) {
        let user = {
            socket: socket,
            name: joinInfo.name
        };

        // Add user
        this.users[user.name] = user;

        this.decideHost();

        // announce user list
        this.getRoom().emit('users', Object.keys(this.users));

        // Bind events
        socket.on('text', (text) => {
            this.chatHistory.push(<IChatText>{ 
                user: user.name,
                message: text,
                time: new Date(Date.now())
            });
            this.getRoom().in(this.roomInfo.id).emit('text', user.name + ": " + text);
        });

        socket.on('queue', (url) => {
            //TODO: attempt to get metadata

        });

        //host events
        socket.on('stream', (url) => {
            if (user.name != this.host.name) { 
                ackFn({ error: 'You are not host.' }); 
                return 
            }
            
            this.currentStreamInfo = <IStreamStatus>{
                currentStream: <IStreamItem>{
                    url: url
                },
                isPlaying: false,
                lastPlay: null,
                lastPlayTime: 0
            }

            this.getRoom().emit('stream', this.currentStreamInfo.currentStream);
        });
        socket.on('play', (offset, time) => {
            if (user.name != this.host.name) { 
                ackFn({ error: 'You are not host.' }); 
                return 
            }
            this.currentStreamInfo.isPlaying = true;
            this.currentStreamInfo.lastPlay = time;
            this.currentStreamInfo.lastPlayTime = offset;
            socket.in(this.roomInfo.id).emit('play', {
                offset: offset,
                time: time
            });
        });
        socket.on('pause', (offset, time) => {
            if (user.name != this.host.name) { 
                ackFn({ error: 'You are not host.' }); 
                return  
            }
            this.currentStreamInfo.isPlaying = false;
            this.currentStreamInfo.lastPlay = time;
            this.currentStreamInfo.lastPlayTime = offset;
            socket.in(this.roomInfo.id).emit('pause', {
                offset: offset,
                time: time
            });
        })

        // sync room
        ackFn({ 
            chat: this.chatHistory,
            users: Object.keys(this.users),
        });
    }

    public clientLeave(socket) {
        let user = this.getUserFromSocket(socket);
        this.removeUser(user);
        if (this.host.name == user.name) { this.host = null; }

        if (this.host == null) { this.decideHost(); }
    }
}

export interface IUserInfo {
    socket;
    name: string;
}

export interface IChatText {
    user: string; // null if server
    message: string;
    time: Date;
}

export interface IStreamItem {
    url: string;
}

export interface IStreamStatus {
    currentStream: IStreamItem;
    isPlaying: boolean;
    lastPlay: Date;
    lastPlayTime: number;
}
{'fraction_non_alphanumeric': 0.1285033143388766, 'fraction_numerical': 0.000814048145133155, 'mean_word_length': 1.7069562480327354, 'pattern_counts': {'":': 2, '<': 13, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4249411', 'n_tokens_mistral': 2494, 'n_tokens_neox': 2390, 'n_words': 549}