<commit_before>dfterm3_playing = function() {
    // imports
    var do_after = dfterm3_timings.do_after;
    var seconds = dfterm3_timings.seconds;
    var fade_out = dfterm3_timings.fade_out;
    var fade_in = dfterm3_timings.fade_in;

    var exports = { };

    // protocol message types.
    var SCREEN_DATA = 1;
    var JSON_DATA = 2;
    var CHAT_MESSAGE = 3;
    var LOGIN_REJECTED = 3;
    var LOGIN_ACCEPTED = 4;

    var logged_in = false;

    if ( WebSocket === undefined ) {
        exports.websockets_are_supported = false;
        return exports;
    }
    exports.websockets_are_supported = true;

    // Call this when you want to connect to Dfterm3.
    exports.play = function( host           // Where is Dfterm3? This is a
                                            // WebSocket address, e.g.
                                            // "ws://127.0.0.1:8000/"
                           , host_dom_element
                                              // ^ The DOM element inside which
                                              // all the game stuff will be
                                              // put in.
                           ) {
        var terminal_dom_element = document.createElement("div");
        var title = document.createElement("span"); // title above the terminal
                                                    // itself.
        var terminal = undefined;                  // The terminal. Inside this
                                                   // you can see the actual
                                                   // game.
        var br_element = document.createElement("br"); // separates title from
                                                       // the game
        var second_br_element = document.createElement("br"); // separates game
                                                              // from whatever
                                                              // comes after it
        var status_element = document.createElement("span");

        var go_back_button = document.createElement("div");
        var go_back_button_a = document.createElement("a");
        go_back_button_a.textContent = "Go back to game list";
        go_back_button_a.setAttribute("href", "#");
        go_back_button.appendChild(go_back_button_a);
        go_back_button.setAttribute("class", "go_back_button");

        go_back_button_a.onclick = function () {
            socket.send("\x02");
        }

        title.setAttribute("id", "game_title");

        var cover = document.createElement("div");
        cover.setAttribute("class", "cover");

        var showCover = function() {
            cover.style.display = "block";
        }
        var hideCover = function() {
            cover.style.display = "none";
        }

        terminal_dom_element.setAttribute( "class"
                                         , "dfterm3" );

        var who_is_playing_box = document.createElement("h3");
        var resetWhoIsPlaying = function() {
            who_is_playing_box.textContent = "No one is currently playing";
        }
        resetWhoIsPlaying();
        who_is_playing_box.setAttribute("id", "who_is_playing");

        var chat_box = document.createElement("div");
        var login_form = document.createElement("form");
        var login_text = document.createElement("input");
        var login_title = document.createElement("h3");
        var login_label = document.createElement("label");
        login_form.setAttribute("action", "#chat");
        login_text.setAttribute("type", "text");
        login_label.textContent = "User name:";
        login_form.appendChild( login_title );
        login_form.appendChild(login_label);
        login_form.appendChild( login_text );
        login_text.setAttribute("id", "login_text");
        login_text.setAttribute("maxlength", 20);
        login_title.textContent = "Login";

        chat_box.appendChild( login_form );
        chat_box.setAttribute( "class", "chat" );
        var chat_title = document.createElement("h3");
        chat_title.textContent = "Chat";
        chat_box.appendChild( chat_title );
        var chat_form = document.createElement("form");
        var chat_text = document.createElement("input");
        var chat_hr = document.createElement("hr")
        chat_form.setAttribute("action", "#chat");
        chat_text.setAttribute("type", "text");
        chat_text.style.display = "none";
        chat_text.setAttribute("maxlength", 800);
        chat_form.appendChild( chat_text );
        chat_box.appendChild( chat_form );
        chat_box.appendChild( chat_hr );

        var clearChat = function () {
            var start;
            while ( (start = chat_hr.nextSibling) ) {
                chat_box.removeChild(start);
            }
        }

        login_form.onsubmit = function (event) {
            var login_name = login_text.value;
            login_text.value = "";

            socket.send("\x04" + login_name);
            event.preventDefault();
        }

        chat_form.onsubmit = function (event) {
            var msg = chat_text.value;
            chat_text.value = "";

            socket.send("\x03" + msg);
            event.preventDefault();
        }

        host_dom_element.appendChild( cover );
        host_dom_element.appendChild( terminal_dom_element );

        status_element.textContent =
            "Trying to make a connection to Dfterm3 server..."
        status_element.setAttribute( "class"
                                   , "dfterm3_connection_status_element_good" );

        title.textContent = "Unnamed"

        var status = function( str, badness ) {
            fade_status_in_after( 0.0 );
            status_element.textContent = str;
            if ( badness ) {
                status_element.setAttribute(
                          "class"
                        , "dfterm3_connection_status_element_bad" );
            } else {
                status_element.setAttribute(
                          "class"
                        , "dfterm3_connection_status_element_good" );
            }
            fade_status_out_after( 2.0 );
        }

        var fade_status_out_after = function ( time ) {
            fade_out( status_element, time );
        }

        var fade_status_in_after = function ( time ) {
            fade_in( status_element, time );
        }

        // Removes the terminal elements from the page, if they are there.
        // Otherwise does nothing.
        var stopTerminal = function () {
            if ( !terminal ) {
                return;
            }
            terminal_dom_element.removeChild( terminal.getDOMObject() );
            terminal_dom_element.removeChild( who_is_playing_box );
            terminal_dom_element.removeChild( title );
            terminal_dom_element.removeChild( br_element );
            terminal_dom_element.removeChild( second_br_element );
            terminal_dom_element.removeChild( go_back_button );
            terminal_dom_element.removeChild( chat_box );
            terminal = undefined;

            resetWhoIsPlaying();
            clearChat();
        }

        // Starts a terminal on the page. Removes the old terminal if it is
        // there.
        var startTerminal = function( w, h ) {
            stopTerminal();
            terminal = dfterm3_terminal.createTerminal( w, h );

            terminal_dom_element.appendChild( title );
            terminal_dom_element.appendChild( who_is_playing_box );
            terminal_dom_element.appendChild( br_element );
            terminal_dom_element.appendChild( terminal.getDOMObject() );
            terminal_dom_element.appendChild( second_br_element );
            terminal_dom_element.appendChild( go_back_button );
            terminal_dom_element.appendChild( chat_box );

            terminal.getDOMObject().setAttribute("tabindex", 1);

            var key = function(event) {
                socket.send("\x05" + JSON.stringify(
                            { which: event.which
                            , code_point: 0
                            , shift: event.shiftKey
                            , alt:  event.altKey
                            , ctrl: event.ctrlKey }));

                if ( event.which == 39 ||
                     event.which == 37 ||
                     event.which == 38 ||
                     event.which == 40 ||
                     event.which == 9 ||
                     event.which == 32 ) {
                     event.preventDefault();
                }
            }

            var keypress = function(event) {
                socket.send("\x05" + JSON.stringify(
                            { which: 0
                            , code_point: event.charCode
                            , shift: event.shiftKey
                            , alt:  event.altKey
                            , ctrl: event.ctrlKey }));
            }

            terminal.getDOMObject().addEventListener("keypress", keypress, true);
            terminal.getDOMObject().addEventListener("keydown", key, true);
        }

        title.setAttribute("class", "dfterm3_terminal_title_bar");

        terminal_dom_element.appendChild( status_element );

        // This function parses the WebSocket data received from Dfterm3 when
        // it is screen data. It updates the terminal (if there is one).
        var handleScreenData = function( array ) {
            stopGameList();
            var array_view = new Uint8Array( array, 1 );
            var w = (array_view[0] << 8) | array_view[1];
            var h = (array_view[2] << 8) | array_view[3];
            if ( !terminal ) {
                startTerminal( w, h );
            } else {
                terminal.resize( w, h );
            }
            var num_elements = (array_view[4] << 24) |
                               (array_view[5] << 16) |
                               (array_view[6] << 8) |
                               (array_view[7]);

            for ( var i = 0; i < num_elements; ++i ) {
                var offset = 8 + i*6;
                var cp437_code = array_view[offset];
                var colors = array_view[offset+1];
                var foreground_color = colors & 0x0f;
                var background_color = (colors & 0xf0) >> 4;
                var x = (array_view[offset+2] << 8) | array_view[offset+3];
                var y = (array_view[offset+4] << 8) | array_view[offset+5];
                terminal.setCP437CellAt( x, y, cp437_code
                                       , foreground_color
                                       , background_color );
            }
        }

        var game_list_ul = undefined;
        var game_list_h1 = undefined;

        var stopGameList = function() {
            if ( !game_list_ul ) {
                return;
            }
            terminal_dom_element.removeChild( game_list_ul );
            terminal_dom_element.removeChild( game_list_h1 );
            game_list_ul = undefined;
        }

        var startGameList = function(msg) {
            stopGameList();

            game_list_ul = document.createElement("ul");
            for ( var i = 0; i < msg.length; ++i ) {
                var li = document.createElement("li");
                var a = document.createElement("a");
                a.setAttribute("href", "#");
                a.textContent = msg[i][0];
                var x = i;
                a.onclick = function () {
                    var choice = msg[x][1];
                    socket.send("\x01" + choice);
                    title.textContent = msg[x][0];
                }
                li.appendChild(a);
                game_list_ul.appendChild(li);
            }

            game_list_h1 = document.createElement("h1");
            game_list_h1.textContent = "Choose your game";

            if ( msg.length == 0 ) {
                game_list_h1.textContent = "No games currently running";
            }

            terminal_dom_element.appendChild( game_list_h1 );
            terminal_dom_element.appendChild( game_list_ul );
        }

        var handleGameListMessage = function(msg) {
            stopTerminal();
            startGameList(msg);
        }

        var handleChatMessage = function(from_who, content) {
            var line = document.createElement("p");
            var from_who_span = document.createElement("span");
            line.appendChild(from_who_span);
            var content_span = document.createElement("span");
            line.appendChild(content_span);

            from_who_span.setAttribute("class", "chat_from_who");
            content_span.setAttribute("class", "chat_content");

            from_who_span.textContent = from_who + ":"
            content_span.textContent = content;
            chat_box.insertBefore( line, chat_hr.nextSibling );
        }

        var jsonMessage = function(msg) {
            if ( msg[0] == "game_list" ) {
                handleGameListMessage( msg[1] );
            } else if ( msg[0] == "chat" ) {
                handleChatMessage( msg[1], msg[2] );
            } else if ( msg[0] == "who_is_playing" ) {
                who_is_playing_box.textContent = "Last played by: " + msg[1];
            }
        }

        showCover();

        var socket = new WebSocket( host );
        socket.onopen = function() {
            status("Connection established.", false );
            hideCover();
        }
        socket.onclose = function() {
            status("No connection to server.", true );
            showCover();
        }
        socket.onmessage = function(event) {
            var reader = new FileReader();
            reader.readAsArrayBuffer(event.data);
            reader.onload = function() {
                var array = reader.result;
                var array_view = new Uint8Array( array, 0, 1 );
                if ( array_view[0] == SCREEN_DATA ) {
                    handleScreenData( array );
                } else if ( array_view[0] == JSON_DATA ) {
                    var reader2 = new FileReader();
                    reader2.readAsText(event.data);
                    reader2.onload = function() {
                        jsonMessage( JSON.parse(reader2.result.substring(1)) );
                    }
                } else if ( array_view[0] == LOGIN_REJECTED ) {
                    status( "Login rejected", true );
                } else if ( array_view[0] == LOGIN_ACCEPTED ) {
                    status( "Logged in", false );
                    chat_text.style.display = "block";
                    login_form.style.display = "none";
                }
            }
        }
    }

    return exports;
}();

<commit_msg>Fix not being able to select more than one game in the web interface.<commit_after>dfterm3_playing = function() {
    // imports
    var do_after = dfterm3_timings.do_after;
    var seconds = dfterm3_timings.seconds;
    var fade_out = dfterm3_timings.fade_out;
    var fade_in = dfterm3_timings.fade_in;

    var exports = { };

    // protocol message types.
    var SCREEN_DATA = 1;
    var JSON_DATA = 2;
    var CHAT_MESSAGE = 3;
    var LOGIN_REJECTED = 3;
    var LOGIN_ACCEPTED = 4;

    var logged_in = false;

    if ( WebSocket === undefined ) {
        exports.websockets_are_supported = false;
        return exports;
    }
    exports.websockets_are_supported = true;

    // Call this when you want to connect to Dfterm3.
    exports.play = function( host           // Where is Dfterm3? This is a
                                            // WebSocket address, e.g.
                                            // "ws://127.0.0.1:8000/"
                           , host_dom_element
                                              // ^ The DOM element inside which
                                              // all the game stuff will be
                                              // put in.
                           ) {
        var terminal_dom_element = document.createElement("div");
        var title = document.createElement("span"); // title above the terminal
                                                    // itself.
        var terminal = undefined;                  // The terminal. Inside this
                                                   // you can see the actual
                                                   // game.
        var br_element = document.createElement("br"); // separates title from
                                                       // the game
        var second_br_element = document.createElement("br"); // separates game
                                                              // from whatever
                                                              // comes after it
        var status_element = document.createElement("span");

        var go_back_button = document.createElement("div");
        var go_back_button_a = document.createElement("a");
        go_back_button_a.textContent = "Go back to game list";
        go_back_button_a.setAttribute("href", "#");
        go_back_button.appendChild(go_back_button_a);
        go_back_button.setAttribute("class", "go_back_button");

        go_back_button_a.onclick = function () {
            socket.send("\x02");
        }

        title.setAttribute("id", "game_title");

        var cover = document.createElement("div");
        cover.setAttribute("class", "cover");

        var showCover = function() {
            cover.style.display = "block";
        }
        var hideCover = function() {
            cover.style.display = "none";
        }

        terminal_dom_element.setAttribute( "class"
                                         , "dfterm3" );

        var who_is_playing_box = document.createElement("h3");
        var resetWhoIsPlaying = function() {
            who_is_playing_box.textContent = "No one is currently playing";
        }
        resetWhoIsPlaying();
        who_is_playing_box.setAttribute("id", "who_is_playing");

        var chat_box = document.createElement("div");
        var login_form = document.createElement("form");
        var login_text = document.createElement("input");
        var login_title = document.createElement("h3");
        var login_label = document.createElement("label");
        login_form.setAttribute("action", "#chat");
        login_text.setAttribute("type", "text");
        login_label.textContent = "User name:";
        login_form.appendChild( login_title );
        login_form.appendChild(login_label);
        login_form.appendChild( login_text );
        login_text.setAttribute("id", "login_text");
        login_text.setAttribute("maxlength", 20);
        login_title.textContent = "Login";

        chat_box.appendChild( login_form );
        chat_box.setAttribute( "class", "chat" );
        var chat_title = document.createElement("h3");
        chat_title.textContent = "Chat";
        chat_box.appendChild( chat_title );
        var chat_form = document.createElement("form");
        var chat_text = document.createElement("input");
        var chat_hr = document.createElement("hr")
        chat_form.setAttribute("action", "#chat");
        chat_text.setAttribute("type", "text");
        chat_text.style.display = "none";
        chat_text.setAttribute("maxlength", 800);
        chat_form.appendChild( chat_text );
        chat_box.appendChild( chat_form );
        chat_box.appendChild( chat_hr );

        var clearChat = function () {
            var start;
            while ( (start = chat_hr.nextSibling) ) {
                chat_box.removeChild(start);
            }
        }

        login_form.onsubmit = function (event) {
            var login_name = login_text.value;
            login_text.value = "";

            socket.send("\x04" + login_name);
            event.preventDefault();
        }

        chat_form.onsubmit = function (event) {
            var msg = chat_text.value;
            chat_text.value = "";

            socket.send("\x03" + msg);
            event.preventDefault();
        }

        host_dom_element.appendChild( cover );
        host_dom_element.appendChild( terminal_dom_element );

        status_element.textContent =
            "Trying to make a connection to Dfterm3 server..."
        status_element.setAttribute( "class"
                                   , "dfterm3_connection_status_element_good" );

        title.textContent = "Unnamed"

        var status = function( str, badness ) {
            fade_status_in_after( 0.0 );
            status_element.textContent = str;
            if ( badness ) {
                status_element.setAttribute(
                          "class"
                        , "dfterm3_connection_status_element_bad" );
            } else {
                status_element.setAttribute(
                          "class"
                        , "dfterm3_connection_status_element_good" );
            }
            fade_status_out_after( 2.0 );
        }

        var fade_status_out_after = function ( time ) {
            fade_out( status_element, time );
        }

        var fade_status_in_after = function ( time ) {
            fade_in( status_element, time );
        }

        // Removes the terminal elements from the page, if they are there.
        // Otherwise does nothing.
        var stopTerminal = function () {
            if ( !terminal ) {
                return;
            }
            terminal_dom_element.removeChild( terminal.getDOMObject() );
            terminal_dom_element.removeChild( who_is_playing_box );
            terminal_dom_element.removeChild( title );
            terminal_dom_element.removeChild( br_element );
            terminal_dom_element.removeChild( second_br_element );
            terminal_dom_element.removeChild( go_back_button );
            terminal_dom_element.removeChild( chat_box );
            terminal = undefined;

            resetWhoIsPlaying();
            clearChat();
        }

        // Starts a terminal on the page. Removes the old terminal if it is
        // there.
        var startTerminal = function( w, h ) {
            stopTerminal();
            terminal = dfterm3_terminal.createTerminal( w, h );

            terminal_dom_element.appendChild( title );
            terminal_dom_element.appendChild( who_is_playing_box );
            terminal_dom_element.appendChild( br_element );
            terminal_dom_element.appendChild( terminal.getDOMObject() );
            terminal_dom_element.appendChild( second_br_element );
            terminal_dom_element.appendChild( go_back_button );
            terminal_dom_element.appendChild( chat_box );

            terminal.getDOMObject().setAttribute("tabindex", 1);

            var key = function(event) {
                socket.send("\x05" + JSON.stringify(
                            { which: event.which
                            , code_point: 0
                            , shift: event.shiftKey
                            , alt:  event.altKey
                            , ctrl: event.ctrlKey }));

                if ( event.which == 39 ||
                     event.which == 37 ||
                     event.which == 38 ||
                     event.which == 40 ||
                     event.which == 9 ||
                     event.which == 32 ) {
                     event.preventDefault();
                }
            }

            var keypress = function(event) {
                socket.send("\x05" + JSON.stringify(
                            { which: 0
                            , code_point: event.charCode
                            , shift: event.shiftKey
                            , alt:  event.altKey
                            , ctrl: event.ctrlKey }));
            }

            terminal.getDOMObject().addEventListener("keypress", keypress, true);
            terminal.getDOMObject().addEventListener("keydown", key, true);
        }

        title.setAttribute("class", "dfterm3_terminal_title_bar");

        terminal_dom_element.appendChild( status_element );

        // This function parses the WebSocket data received from Dfterm3 when
        // it is screen data. It updates the terminal (if there is one).
        var handleScreenData = function( array ) {
            stopGameList();
            var array_view = new Uint8Array( array, 1 );
            var w = (array_view[0] << 8) | array_view[1];
            var h = (array_view[2] << 8) | array_view[3];
            if ( !terminal ) {
                startTerminal( w, h );
            } else {
                terminal.resize( w, h );
            }
            var num_elements = (array_view[4] << 24) |
                               (array_view[5] << 16) |
                               (array_view[6] << 8) |
                               (array_view[7]);

            for ( var i = 0; i < num_elements; ++i ) {
                var offset = 8 + i*6;
                var cp437_code = array_view[offset];
                var colors = array_view[offset+1];
                var foreground_color = colors & 0x0f;
                var background_color = (colors & 0xf0) >> 4;
                var x = (array_view[offset+2] << 8) | array_view[offset+3];
                var y = (array_view[offset+4] << 8) | array_view[offset+5];
                terminal.setCP437CellAt( x, y, cp437_code
                                       , foreground_color
                                       , background_color );
            }
        }

        var game_list_ul = undefined;
        var game_list_h1 = undefined;

        var stopGameList = function() {
            if ( !game_list_ul ) {
                return;
            }
            terminal_dom_element.removeChild( game_list_ul );
            terminal_dom_element.removeChild( game_list_h1 );
            game_list_ul = undefined;
        }

        var startGameList = function(msg) {
            stopGameList();

            game_list_ul = document.createElement("ul");
            for ( var i = 0; i < msg.length; ++i ) {
	        var x = function() {
		    var li = document.createElement("li");
		    var a = document.createElement("a");
		    a.setAttribute("href", "#");
		    a.textContent = msg[i][0];
		    var choice = msg[i][1];
		    var content = msg[i][0];
		    a.onclick = function () {
			socket.send("\x01" + choice);
			title.textContent = content;
		    }
		    li.appendChild(a);
		    game_list_ul.appendChild(li);
		}();
            }

            game_list_h1 = document.createElement("h1");
            game_list_h1.textContent = "Choose your game";

            if ( msg.length == 0 ) {
                game_list_h1.textContent = "No games currently running";
            }

            terminal_dom_element.appendChild( game_list_h1 );
            terminal_dom_element.appendChild( game_list_ul );
        }

        var handleGameListMessage = function(msg) {
            stopTerminal();
            startGameList(msg);
        }

        var handleChatMessage = function(from_who, content) {
            var line = document.createElement("p");
            var from_who_span = document.createElement("span");
            line.appendChild(from_who_span);
            var content_span = document.createElement("span");
            line.appendChild(content_span);

            from_who_span.setAttribute("class", "chat_from_who");
            content_span.setAttribute("class", "chat_content");

            from_who_span.textContent = from_who + ":"
            content_span.textContent = content;
            chat_box.insertBefore( line, chat_hr.nextSibling );
        }

        var jsonMessage = function(msg) {
            if ( msg[0] == "game_list" ) {
                handleGameListMessage( msg[1] );
            } else if ( msg[0] == "chat" ) {
                handleChatMessage( msg[1], msg[2] );
            } else if ( msg[0] == "who_is_playing" ) {
                who_is_playing_box.textContent = "Last played by: " + msg[1];
            }
        }

        showCover();

        var socket = new WebSocket( host );
        socket.onopen = function() {
            status("Connection established.", false );
            hideCover();
        }
        socket.onclose = function() {
            status("No connection to server.", true );
            showCover();
        }
        socket.onmessage = function(event) {
            var reader = new FileReader();
            reader.readAsArrayBuffer(event.data);
            reader.onload = function() {
                var array = reader.result;
                var array_view = new Uint8Array( array, 0, 1 );
                if ( array_view[0] == SCREEN_DATA ) {
                    handleScreenData( array );
                } else if ( array_view[0] == JSON_DATA ) {
                    var reader2 = new FileReader();
                    reader2.readAsText(event.data);
                    reader2.onload = function() {
                        jsonMessage( JSON.parse(reader2.result.substring(1)) );
                    }
                } else if ( array_view[0] == LOGIN_REJECTED ) {
                    status( "Login rejected", true );
                } else if ( array_view[0] == LOGIN_ACCEPTED ) {
                    status( "Logged in", false );
                    chat_text.style.display = "block";
                    login_form.style.display = "none";
                }
            }
        }
    }

    return exports;
}();

<|endoftext|>


<commit_before>
function canvas_init () {
    var graph = new Graph()
    var n1 = new Node(1)
    graph.insert(n1)
    var n2 = new Node(2)
    graph.insert(n2)
    var n3 = new Node(3)
    graph.insert(n3)
    graph.connect(n1, n2)
    var n4 = new Node(4)
    graph.insert(n4)
    graph.connect(n4, n3)
    graph.connect(n1, n4)
    var n5 = new Node(5)
    var n6 = new Node(6)
    var n7 = new Node(7)
    var n8 = new Node(8)
    var n9 = new Node(9)
    graph.insert(n5)
    graph.insert(n6)
    graph.insert(n7)
    graph.insert(n8)
    graph.insert(n9)
    graph.connect(n5, n4)
    graph.connect(n2, n3)
    graph.connect(n1, n5)
    graph.connect(n1, n6)
    graph.connect(n7, n8)
    //graph.connect(n1, n9)
    graph.connect(n9, n8)
    graph.connect(n9, n7)
    //graph.connect(n1, n7)
    //graph.visit(function (x) { console.log("[neighbours] callback of " + x.value) })
    //graph.visit(function (x) { console.log("[up] callback of " + x.value) }, 'up')
    //graph.visit(function (x) { console.log("[down] callback of " + x.value) }, 'down')

    var canvas = document.getElementById('canvas')
    canvas.onclick = function (event) {
        var x = event.pageX - canvas.offsetLeft
        var y = event.pageY - canvas.offsetTop
        var node = graph.find(x, y)
        graph.setselected(node)
    }

    graph.layout(canvas)
    var ctx = canvas.getContext('2d')
    graph.context = ctx
    graph.draw(ctx)
}

Function.prototype.compose  = function (argFunction) {
    var invokingFunction = this
    return function () {
        return  invokingFunction.call(this, argFunction.apply(this, arguments))
    }
}
function getProp (property, object) {
    return object[property]
}
function eq (a, b) {
    return a == b
}

function PolarPoint (theta, rho) {
    this.theta = theta || 0
    this.rho = rho || 0
}
function toPolar (x, y) {
    var pp = new PolarPoint()
    pp.setComplex(x, y)
    return pp
}

PolarPoint.prototype = {
    setComplex: function (x, y) {
        this.rho = Math.sqrt(x * x + y * y)
        this.theta = Math.atan2(y, x)
        if (this.theta < 0)
            this.theta += Math.PI * 2
    },

    toComplex: function () {
        var x = Math.cos(this.theta) * this.rho
        var y = Math.sin(this.theta) * this.rho
        return [x, y]
    },

    add: function (polar) {
        return new PolarPoint(this.theta + polar.theta, this.rho + polar.rho)
    },

    follow: function (polar) {
        var n1 = this.toComplex()
        var n2 = polar.toComplex()
        return toPolar(n1[0] + n2[0], n1[1] + n2[1])

    },

    scale: function (number) {
        return new PolarPoint(this.theta, this.rho * number);
    },

    copy: function () {
        return new PolarPoint(this.theta, this.rho)
    },
}

function Edge (source, destination, properties) {
    this.source = source
    this.destination = destination
    this.strokeStyle = "blue"
    this.directed = true
    if (properties)
        for (var i = 0; i < properties.length; i = i + 2)
            this.properties[i] = properties[i + 1]
}

Edge.prototype.draw = function (ctx, graph) {
    ctx.beginPath()
    var sour = this.source.position
    var dest = this.destination.position

    var spos = sour.toComplex()
    var dpos = dest.toComplex()
    var dx = dpos[0] - spos[0]
    var dy = dpos[1] - spos[1]
    var theta  = toPolar(dx, dy).theta

    var start = sour.follow(new PolarPoint(theta, this.source.radius)).toComplex()

    if (Math.abs(dpos[0] - spos[0]) < 0.1) {
        if (dy > 0) {
            theta = Math.PI / 2
        } else {
            theta = - Math.PI / 2
        }
    }
    var invvec = Math.PI + theta
    //console.log("node " + this.source.value + " to " + this.destination.value + " dx " + dx + " dy " + dy + " theta " + theta + " invvec " + invvec)
    var endp = dest.follow(new PolarPoint(invvec, this.destination.radius))
    var end = endp.toComplex()

    ctx.strokeStyle = this.strokeStyle
    ctx.moveTo(start[0], start[1])
    ctx.lineTo(end[0], end[1])
    ctx.closePath()
    ctx.stroke()

    if (this.directed) {
        var arrowsize = 5
        var oneend = endp.follow(new PolarPoint(invvec + Math.PI / 8, arrowsize)).toComplex()
        var otherend = endp.follow(new PolarPoint(invvec - Math.PI / 8, arrowsize)).toComplex()

        ctx.beginPath()
        ctx.moveTo(end[0], end[1])
        ctx.lineTo(oneend[0], oneend[1])
        ctx.moveTo(end[0], end[1])
        ctx.lineTo(otherend[0], otherend[1])
        ctx.closePath()
        ctx.stroke()
    }
}

function Node (value, properties) {
    this.value = value
    this.position = null
    this.fillStyle = "orange"
    this.textStyle = "black"
    this.radius = 10
    if (properties)
        for (var i = 0; i < properties.length; i = i + 2)
            this.properties[i] = properties[i + 1]
}

Node.prototype = {
    redraw: function (ctx, graph) {
        var pos = this.position.toComplex()
        ctx.beginPath()
        ctx.arc(pos[0], pos[1], this.radius + 0.5, 0, Math.PI * 2, true)
        ctx.fillStyle = this.fillStyle
        ctx.closePath()
        ctx.fill()
        if (this.isselected) {
            var old = ctx.strokeStyle
            ctx.strokeStyle = "red"
            ctx.arc(pos[0], pos[1], this.radius - 0.5, 0, Math.PI * 2, true)
            ctx.arc(pos[0], pos[1], this.radius - 1.5, 0, Math.PI * 2, true)
            ctx.closePath()
            ctx.stroke()
            ctx.strokeStyle = old
        }
        ctx.fillStyle = this.textStyle
        ctx.fillText(this.value, pos[0], pos[1])
    },

    place: function (graph) {
        var childs = graph.children(this)
        for (var i = 0; i < childs.length; i++) {
            if (childs[i].position == null) {
                var vec = new PolarPoint(i * (Math.PI * 2 / (childs.length + 1)), 50)
                childs[i].position = this.position.follow(vec)
            }
        }
    },

    draw: function (ctx, graph) {
        //we better have a position
        this.redraw(ctx, graph)
        graph.outEdges(this).forEach(function (x) { x.draw(ctx, graph) })
    },
}

function Graph () {
    this.nodes = []
    this.edges = []
    this.selectedNode = null
    this.subgraphs = []
}

Graph.prototype = {
    layout: function (canvas) {
        this.findsubgraphs()
        var subgraphs = this.subgraphs
        for (var i = 0; i < subgraphs.length; i++) {
            var roots = this.getRoots(subgraphs[i])
            for (var r = 0; r < roots.length; r++) {
                var root = roots[r]
                var x = canvas.width / (2 * subgraphs.length) + (canvas.width * i / subgraphs.length)
                var y = canvas.height / (2 * roots.length) + (canvas.height * r / roots.length)
                root.position = toPolar(x, y)
            }
        }
        var cb = function (graph, x) { x.place(graph) }
        this.visit(cb.curry(this), 'down')
    },

    draw: function (ctx) {
        var cb = function (ctx, graph, x) { x.draw(ctx, graph) }
        this.visit(cb.curry(ctx, this))
    },

    find: function (x, y) {
        var res = null
        var hit =
            function (nod) {
                var nums = nod.position.toComplex()
                if (nums[0] - 10 < x && nums[0] + 10 > x)
                    if (nums[1] - 10 < y && nums[1] + 10 > y)
                        res = nod
            }

        this.visit(hit)
        return res
    },

    outEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("source")))
    },

    children: function (node) {
        return this.outEdges(node).map(getProp.curry("destination"))
    },

    inEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("destination")))
    },

    parents: function (node) {
        return this.inEdges(node).map(getProp.curry("source"))
    },

    neighbours: function (node) {
        return this.children(node).concat(this.parents(node))
    },

    connect: function (node1, node2) {
        this.edges.push(new Edge(node1, node2))
    },

    insert: function (node) {
        this.nodes.push(node)
    },

    contains: function (node) {
        return (this.nodes.filter(eq.curry(node)).length > 0)
    },

    findNode: function (val) {
        var nodes = this.nodes.filter(function (x) { return x.value == val })
        if (nodes.length == 1)
            return nodes[0]
        else
            return null
    },

    setselected: function (node) {
        var old = this.selectedNode
        if (old && node && old == node) { }
        else {
            if (old) {
                old.isselected = false
                old.redraw(this.context, this)
                this.selectedNode = null
            }

            if (node) {
                this.selectedNode = node
                node.isselected = true
                node.redraw(this.context, this)
            }
        }
    },

    findsubgraphs: function () {
        //implements a breadth-first search
        var todo = [this.nodes[0]]
        var visited = []
        var subgraph = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    subgraph.push(node)
                    visited.push(node)
                    graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
        this.subgraphs.push(subgraph)

        //find remaining, disconnected nodes
        for (var i = 0; i < this.nodes.length; i++)
            if (visited.filter(eq.curry(this.nodes[i])).length == 0) {
                console.log("disconnected graph!")
                subgraph = []
                todo.push(this.nodes[i])
                doVisit(this)
                this.subgraphs.push(subgraph)
            }
    },

    getRoots: function (nodelist) {
        var roots = []
        var cb = function (graph, x) {
            if (graph.inEdges(x).length == 0)
                roots.push(x)
        }
        nodelist.filter(cb.curry(this))
        return roots
    },

    visit: function (callback, direction) {
        //implements a breadth-first search
        var todo = []
        var cb = function (graph, subgraph) {
            graph.getRoots(subgraph).forEach(function (r) { todo.push(r)})
        }
        this.subgraphs.forEach(cb.curry(this))
        var visited = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    callback(node)
                    visited.push(node)
                    if (direction && direction == 'up')
                        graph.parents(node).forEach(function (x) { todo.push(x) })
                    if (direction && direction == 'down')
                        graph.children(node).forEach(function (x) { todo.push(x) })
                    if (direction == null)
                        graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
    },

}
<commit_msg>placing and drawing separated for edges, also distribute children slightly different<commit_after>
function canvas_init () {
    var graph = new Graph()
    var n1 = new Node(1)
    graph.insert(n1)
    var n2 = new Node(2)
    graph.insert(n2)
    var n3 = new Node(3)
    graph.insert(n3)
    graph.connect(n1, n2)
    var n4 = new Node(4)
    graph.insert(n4)
    graph.connect(n4, n3)
    graph.connect(n1, n4)
    var n5 = new Node(5)
    var n6 = new Node(6)
    var n7 = new Node(7)
    var n8 = new Node(8)
    var n9 = new Node(9)
    var n10 = new Node(10)
    var n11 = new Node(11)
    var n12 = new Node(12)
    graph.insert(n5)
    graph.insert(n6)
    graph.insert(n7)
    graph.insert(n8)
    graph.insert(n9)
    graph.insert(n10)
    graph.insert(n11)
    graph.insert(n12)
    graph.connect(n5, n4)
    graph.connect(n2, n3)
    graph.connect(n1, n5)
    graph.connect(n1, n6)
    graph.connect(n1, n10)
    graph.connect(n1, n11)
//    graph.connect(n1, n12)
    graph.connect(n7, n8)
    graph.connect(n1, n9)
    graph.connect(n9, n8)
    graph.connect(n9, n7)
    var n13 = new Node(13)
    var n14 = new Node(14)
    var n15 = new Node(15)
    graph.insert(n13)
    graph.insert(n14)
    graph.insert(n15)
    graph.connect(n9, n13)
    graph.connect(n9, n14)
    graph.connect(n9, n15)
    //graph.connect(n1, n7)
    //graph.visit(function (x) { console.log("[neighbours] callback of " + x.value) })
    //graph.visit(function (x) { console.log("[up] callback of " + x.value) }, 'up')
    //graph.visit(function (x) { console.log("[down] callback of " + x.value) }, 'down')

    var canvas = document.getElementById('canvas')
    canvas.onclick = function (event) {
        var x = event.pageX - canvas.offsetLeft
        var y = event.pageY - canvas.offsetTop
        var node = graph.find(x, y)
        graph.setselected(node)
    }

    graph.layout(canvas)
    var ctx = canvas.getContext('2d')
    graph.context = ctx
    graph.draw(ctx)
}

Function.prototype.compose  = function (argFunction) {
    var invokingFunction = this
    return function () {
        return  invokingFunction.call(this, argFunction.apply(this, arguments))
    }
}
function getProp (property, object) {
    return object[property]
}
function eq (a, b) {
    return a == b
}

function PolarPoint (theta, rho) {
    this.theta = theta || 0
    this.rho = rho || 0
}
function toPolar (x, y) {
    var pp = new PolarPoint()
    pp.setComplex(x, y)
    return pp
}

PolarPoint.prototype = {
    setComplex: function (x, y) {
        this.rho = Math.sqrt(x * x + y * y)
        this.theta = Math.atan2(y, x)
        if (this.theta < 0)
            this.theta += Math.PI * 2
    },

    toComplex: function () {
        var x = Math.cos(this.theta) * this.rho
        var y = Math.sin(this.theta) * this.rho
        return [x, y]
    },

    add: function (polar) {
        return new PolarPoint(this.theta + polar.theta, this.rho + polar.rho)
    },

    follow: function (polar) {
        var n1 = this.toComplex()
        var n2 = polar.toComplex()
        return toPolar(n1[0] + n2[0], n1[1] + n2[1])

    },

    scale: function (number) {
        return new PolarPoint(this.theta, this.rho * number);
    },

    copy: function () {
        return new PolarPoint(this.theta, this.rho)
    },
}

function Edge (source, destination) {
    this.source = source
    this.destination = destination
    this.strokeStyle = "blue"
    this.directed = true
    this.theta = null
    this.invvec = null
    this.startpos = null
    this.endpos = null
}

Edge.prototype = {
    place: function (graph) {
        var sour = this.source.position
        var dest = this.destination.position

        var spos = sour.toComplex()
        var dpos = dest.toComplex()
        var dx = dpos[0] - spos[0]
        var dy = dpos[1] - spos[1]
        this.theta = toPolar(dx, dy).theta

        this.startpos = sour.follow(new PolarPoint(this.theta, this.source.radius))

        var mytheta = this.theta
        if (Math.abs(dpos[0] - spos[0]) < 0.1) {
            if (dy > 0) {
                mytheta = Math.PI / 2
            } else {
                mytheta = - Math.PI / 2
            }
        }
        var invvec = Math.PI + mytheta
        //console.log("node " + this.source.value + " to " + this.destination.value + " dx " + dx + " dy " + dy + " theta " + theta + " invvec " + invvec)
        this.invvec = invvec
        this.endpos = dest.follow(new PolarPoint(invvec, this.destination.radius))
        this.destination.edge = this
    },

    draw: function (ctx, graph) {
        ctx.beginPath()
        var start = this.startpos.toComplex()
        var end = this.endpos.toComplex()

        ctx.strokeStyle = this.strokeStyle
        ctx.moveTo(start[0], start[1])
        ctx.lineTo(end[0], end[1])
        ctx.closePath()
        ctx.stroke()

        if (this.directed) {
            var arrowsize = 5
            var oneend = this.endpos.follow(new PolarPoint(this.invvec + Math.PI / 8, arrowsize)).toComplex()
            var otherend = this.endpos.follow(new PolarPoint(this.invvec - Math.PI / 8, arrowsize)).toComplex()

            ctx.beginPath()
            ctx.moveTo(end[0], end[1])
            ctx.lineTo(oneend[0], oneend[1])
            ctx.moveTo(end[0], end[1])
            ctx.lineTo(otherend[0], otherend[1])
            ctx.closePath()
            ctx.stroke()
        }
    },
}

function Node (value) {
    this.value = value
    this.position = null
    this.fillStyle = "orange"
    this.textStyle = "black"
    this.radius = 10
    this.edge = null
}

Node.prototype = {
    redraw: function (ctx, graph) {
        var pos = this.position.toComplex()
        ctx.beginPath()
        ctx.arc(pos[0], pos[1], this.radius + 0.5, 0, Math.PI * 2, true)
        ctx.fillStyle = this.fillStyle
        ctx.closePath()
        ctx.fill()
        if (this.isselected) {
            var old = ctx.strokeStyle
            ctx.strokeStyle = "red"
            ctx.arc(pos[0], pos[1], this.radius - 0.5, 0, Math.PI * 2, true)
            ctx.arc(pos[0], pos[1], this.radius - 1.5, 0, Math.PI * 2, true)
            ctx.closePath()
            ctx.stroke()
            ctx.strokeStyle = old
        }
        ctx.fillStyle = this.textStyle
        ctx.fillText(this.value, pos[0], pos[1])
    },

    place: function (graph) {
        var childs = graph.children(this)
        for (var i = 0; i < childs.length; i++) {
            if (childs[i].position == null) {
                var fact = childs.length
                if (fact % 2 == 0)
                    fact++

                var stat = 0
                var variance = Math.PI * 2
                if (this.edge) {
                    variance = Math.PI
                    stat = this.edge.theta - (Math.PI / 2)
                }

                var vec = new PolarPoint(stat + i * (variance / fact), 50)
                childs[i].position = this.position.follow(vec)
            }
        }
        graph.outEdges(this).forEach(function (x) { x.place(graph) })
    },

    draw: function (ctx, graph) {
        //we better have a position
        this.redraw(ctx, graph)
        graph.outEdges(this).forEach(function (x) { x.draw(ctx, graph) })
    },
}

function Graph () {
    this.nodes = []
    this.edges = []
    this.selectedNode = null
    this.subgraphs = []
}

Graph.prototype = {
    layout: function (canvas) {
        this.findsubgraphs()
        var subgraphs = this.subgraphs
        for (var i = 0; i < subgraphs.length; i++) {
            var roots = this.getRoots(subgraphs[i])
            for (var r = 0; r < roots.length; r++) {
                var root = roots[r]
                var x = canvas.width / (2 * subgraphs.length) + (canvas.width * i / subgraphs.length)
                var y = canvas.height / (2 * roots.length) + (canvas.height * r / roots.length)
                root.position = toPolar(x, y)
            }
        }
        var cb = function (graph, x) { x.place(graph) }
        this.visit(cb.curry(this), 'down')
    },

    draw: function (ctx) {
        var cb = function (ctx, graph, x) { x.draw(ctx, graph) }
        this.visit(cb.curry(ctx, this))
    },

    find: function (x, y) {
        var res = null
        var hit =
            function (nod) {
                var nums = nod.position.toComplex()
                if (nums[0] - 10 < x && nums[0] + 10 > x)
                    if (nums[1] - 10 < y && nums[1] + 10 > y)
                        res = nod
            }

        this.visit(hit)
        return res
    },

    outEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("source")))
    },

    children: function (node) {
        return this.outEdges(node).map(getProp.curry("destination"))
    },

    inEdges: function (node) {
        return this.edges.filter(eq.curry(node).compose(getProp.curry("destination")))
    },

    parents: function (node) {
        return this.inEdges(node).map(getProp.curry("source"))
    },

    neighbours: function (node) {
        return this.children(node).concat(this.parents(node))
    },

    connect: function (node1, node2) {
        this.edges.push(new Edge(node1, node2))
    },

    insert: function (node) {
        this.nodes.push(node)
    },

    contains: function (node) {
        return (this.nodes.filter(eq.curry(node)).length > 0)
    },

    findNode: function (val) {
        var nodes = this.nodes.filter(function (x) { return x.value == val })
        if (nodes.length == 1)
            return nodes[0]
        else
            return null
    },

    setselected: function (node) {
        var old = this.selectedNode
        if (old && node && old == node) { }
        else {
            if (old) {
                old.isselected = false
                old.redraw(this.context, this)
                this.selectedNode = null
            }

            if (node) {
                this.selectedNode = node
                node.isselected = true
                node.redraw(this.context, this)
            }
        }
    },

    findsubgraphs: function () {
        //implements a breadth-first search
        var todo = [this.nodes[0]]
        var visited = []
        var subgraph = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    subgraph.push(node)
                    visited.push(node)
                    graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
        this.subgraphs.push(subgraph)

        //find remaining, disconnected nodes
        for (var i = 0; i < this.nodes.length; i++)
            if (visited.filter(eq.curry(this.nodes[i])).length == 0) {
                console.log("disconnected graph!")
                subgraph = []
                todo.push(this.nodes[i])
                doVisit(this)
                this.subgraphs.push(subgraph)
            }
    },

    getRoots: function (nodelist) {
        var roots = []
        var cb = function (graph, x) {
            if (graph.inEdges(x).length == 0)
                roots.push(x)
        }
        nodelist.filter(cb.curry(this))
        return roots
    },

    visit: function (callback, direction) {
        //implements a breadth-first search
        var todo = []
        var cb = function (graph, subgraph) {
            graph.getRoots(subgraph).forEach(function (r) { todo.push(r)})
        }
        this.subgraphs.forEach(cb.curry(this))
        var visited = []
        function doVisit (graph) {
            while (todo.length > 0) {
                var node = todo.shift()
                if (visited.filter(eq.curry(node)).length == 0) {
                    callback(node)
                    visited.push(node)
                    if (direction && direction == 'up')
                        graph.parents(node).forEach(function (x) { todo.push(x) })
                    if (direction && direction == 'down')
                        graph.children(node).forEach(function (x) { todo.push(x) })
                    if (direction == null)
                        graph.neighbours(node).forEach(function (x) { todo.push(x) })
                }
            }
        }

        doVisit(this)
    },

}
<|endoftext|>


<commit_before>var express = require('express'),
    request = require('request'),
    async = require('async'),
    FB = require('fb'),
    AggregatorController = require('../AggregatorController'),
    Post = require('../../model/Post');

var session = {};
var searchCriteria = {};

var extractedPosts = [];
var bufferedPages = [];
var bufferedPagesInExec = [];

exports.aggregateData = function(userName, agency) {
    var $that = this;

    AggregatorController.gatherSearchCriteria(userName, agency, 'facebook', function(criteria){
        searchCriteria = criteria;

        $that.extractData(userName, agency.name, criteria);
    });
}

exports.authenticate = function(callback){
    FB.api('oauth/access_token', {
        client_id: config.apps.facebook.key,
        client_secret: config.apps.facebook.secret,
        grant_type: 'client_credentials'
    }, function (res) {
        console.log('authenticate call: ');
        console.log(res);
        logger.log('debug',"Authentication to Facebook was successful!");

        session.access_token    = res.access_token;
        session.expires         = new Date().getTime() + (res.expires || 0) * 1000;

        return callback();
    });

}

exports.ensureAuthenticated = function(callback){
    var $that = this;
    console.log('auth start');
    this.isSessionValid(function(sessionValid){
        console.log('session valid: ' + sessionValid);
        if(sessionValid){
            return callback();
        } else {
            logger.log('debug',"Facebook session not valid");
            $that.authenticate(function(){
                return callback();
            });
        }
    });
}

exports.isSessionValid = function(callback){
    var $that = this;
    var accessTokenNotExpired = session.access_token!=null && new Date().getTime() - session.expires > 0;
    console.log('isSessionValid token ' + accessTokenNotExpired);
    if(accessTokenNotExpired){
        FB.api('facebook?access_token=' + session.access_token, function (res) {
            if(!res || res.error) {
                return callback(false);
            }

            return callback(true);
        });
    } else {
        return callback(false);
    }
}

exports.extractData = function(userName, agencyName, criteria){
    var $that = this;

    $that.ensureAuthenticated(function(){
        logger.log('debug',"Extracting data from Facebook...");
        var asyncTasks = [];

        criteria.profiles.forEach(function(profile){
            console.log(profile);
            asyncTasks.push(function(callback){
                $that.extractProfilePosts(userName, agencyName, profile, callback);
            });
        });

        async.parallel(asyncTasks, function(){
            if(asyncTasks.length < config.app.postLimit) {
                $that.extractPostsFromBufferedPages(userName, agencyName);
            }
        });

    })
}

exports.extractProfilePosts = function(userName, agencyName, profile, callback){
    logger.log('debug',"Extracting data from Facebook profile %s", profile);

    var $that = this;

    $that.getLastPostTime('@' + profile, function(lastPostTime){

        $that.extractPostsInfo(profile, lastPostTime, function(){

            var asyncTasks = [];

            extractedPosts.forEach(function(post){

                asyncTasks.push(function(callback){

                    $that.extractPostsLikes(post, function(post){

                        $that.savePost(userName, agencyName, post, callback);

                    });

                });
            });

            async.parallel(asyncTasks, function(){
                callback();
            });

        });
    });

}

// will query the db to get the laast post datetime for a profile
exports.getLastPostTime = function(match, callback){
    Post.getLastPostTime('facebook', match, function(lastPostTime){
        return callback(lastPostTime);
    });
}

// extracts id, message, creted_time, icon, link
exports.extractPostsInfo = function(profile, lastPostTime, callback){
    var $that = this;
    var url = profile + '/posts?fields=id,message,picture,full_picture,created_time,icon,link';
    url += '&access_token=' + session.access_token;
    url += lastPostTime!=undefined ? "&since=" + lastPostTime : "";
    url += "&limit=" + config.app.postsLimit;

    FB.api(url, function (res) {
        console.log(url);
        console.log(res.error);
        if(!res || res.error) {
            $that.handleError(res.error.code, res.error.message, function(){
                return $that.extractPostsInfo(profile, lastPostTime, callback);
            });
        }

        if(res!=undefined && res.data!=undefined && res.data.length!=0){
            for(var i in res.data){
                var entry = res.data[i];

                entry.service = "facebook";
                entry.profile = profile;
                entry.match = "@" + profile;

                extractedPosts.push(entry);
            }

            if(res.paging!=undefined && res.paging.next!=undefined){
                bufferedPages.push({profile: profile, url: res.paging.next});
            }

            return callback();
        }

        return;

    });
}

exports.extractPostsFromBufferedPages = function(userName, agencyName){
    var $that = this;

    if(bufferedPages.length!=0){
        bufferedPagesInExec = [];
        extractedPosts = [];

        var bufferedPagesTasks = [];

        bufferedPages.forEach(function(bufferedPage){
            bufferedPagesTasks.push(function(callback){

                $that.extractNextInfo(bufferedPage, function(){
                    var asyncTasks = [];

                    extractedPosts.forEach(function(post){

                        asyncTasks.push(function(callback){

                            $that.extractPostsLikes(post, function(post){

                                $that.savePost(userName, agencyName, post, callback);

                            });

                        });
                    });

                    async.parallel(asyncTasks, function(){
                        callback();
                    });

                });

            });
        });

        async.parallel(bufferedPagesTasks, function(){

            bufferedPages = [];
            $that.extractPostsFromBufferedPages(userName, agencyName);

        });
    }
}

exports.extractNextInfo = function(bufferedPage, callback){
    var $that = this;

    request({
        uri: bufferedPage.url,
        method: "GET"
    }, function(error, response, body) {
        if(error) {
            $that.handleError(error.code, error.message, function(){
                return $that.extractNextInfo(bufferedPage, callback);
            });
        }

        body = JSON.parse(body);

        if(body!=undefined && body.data!=undefined && body.data.length!=0){
            for(var i in body.data){
                var entry = body.data[i];

                entry.service = "facebook";
                entry.profile = bufferedPage.profile;
                entry.match = "@" + bufferedPage.profile;

                extractedPosts.push(entry);
            }

            if(body.paging!=undefined && body.paging.next!=undefined){
                bufferedPagesInExec.push({profile: bufferedPage.profile, url: body.paging.next});
            }

            return callback();
        }

        return;

    });
}

// extracts likes
exports.extractPostsLikes = function(post, cb){
    var $that = this;

    FB.api(post.id + '/likes?summary=true&access_token=' + session.access_token, function (res) {

        if(!res || res.error) {
            $that.handleError(res.error.code, res.error.message, function(){
                return $that.extractPostsLikes(post, cb);
            });
        }

        if(res!=undefined && res.summary!=undefined){
            post.likes = res.summary.total_count;
        }

        return cb(post);
    });

}

// saves the post into the db
exports.savePost = function(userName, agencyName, postInfo, callback) {
    console.log('saving post');
    var post = new Post();
    post.userName = userName;
    post.agencyName = agencyName;
    post.id = postInfo.id;
    post.date = new Date(postInfo.created_time);
    post.date_extracted = new Date();
    post.service = postInfo.service;
    post.account = postInfo.profile;
    post.match = postInfo.match;
    post.icon = postInfo.icon;
    post.url = postInfo.link;
    post.text = postInfo.message;
    post.likes = postInfo.likes;
    post.image = postInfo.full_picture || postInfo.picture;

    post.save();
    callback();
}

exports.handleError = function(errCode, errMessage, nextAction){
    var $that = this;
    console.log("facebook errors: " + errCode);
    console.log(errMessage);
    switch (errCode) {
        // access_token expired
        case 102: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 104: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 463: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 467: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 191: {
            logger.log('info',"Error %s occurred: %s",errCode, errMessage);
            return ;
        }

        // Bad search key
        case 803: {
            logger.log('info', errMessage);
            return ;
        }

        default: {
            logger.log('info',"Failed to handle error %s: %s", errCode, errMessage);
        }
    }
}

var $that = this;

exports.errorHandlers = {
    handleExpiredToken: function(errCode, nextAction){
        logger.log('info',"%s: handling expired access_token", errCode);
        $that.authenticate(nextAction);
    }
}<commit_msg>facebook<commit_after>var express = require('express'),
    request = require('request'),
    async = require('async'),
    FB = require('fb'),
    AggregatorController = require('../AggregatorController'),
    Post = require('../../model/Post');

var session = {};
var searchCriteria = {};

var extractedPosts = [];
var bufferedPages = [];
var bufferedPagesInExec = [];

exports.aggregateData = function(userName, agency) {
    var $that = this;

    AggregatorController.gatherSearchCriteria(userName, agency, 'facebook', function(criteria){
        searchCriteria = criteria;

        $that.extractData(userName, agency.name, criteria);
    });
}

exports.authenticate = function(callback){
    FB.api('oauth/access_token', {
        client_id: config.apps.facebook.key,
        client_secret: config.apps.facebook.secret,
        grant_type: 'client_credentials'
    }, function (res) {
        console.log('authenticate call: ');
        console.log(res);
        logger.log('debug',"Authentication to Facebook was successful!");

        session.access_token    = res.access_token;
        session.expires         = new Date().getTime() + (res.expires || 0) * 1000;

        return callback();
    });

}

exports.ensureAuthenticated = function(callback){
    var $that = this;
    console.log('auth start');
    this.isSessionValid(function(sessionValid){
        console.log('session valid: ' + sessionValid);
        if(sessionValid){
            return callback();
        } else {
            logger.log('debug',"Facebook session not valid");
            $that.authenticate(function(){
                return callback();
            });
        }
    });
}

exports.isSessionValid = function(callback){
    var $that = this;
    var accessTokenNotExpired = session.access_token!=null && new Date().getTime() - session.expires > 0;
    console.log('isSessionValid token ' + accessTokenNotExpired);
    if(accessTokenNotExpired){
        FB.api('facebook?access_token=' + session.access_token, function (res) {
            if(!res || res.error) {
                return callback(false);
            }

            return callback(true);
        });
    } else {
        return callback(false);
    }
}

exports.extractData = function(userName, agencyName, criteria){
    var $that = this;

    $that.ensureAuthenticated(function(){
        logger.log('debug',"Extracting data from Facebook...");
        var asyncTasks = [];

        criteria.profiles.forEach(function(profile){
            console.log(profile);
            asyncTasks.push(function(callback){
                $that.extractProfilePosts(userName, agencyName, profile, callback);
            });
        });

        async.parallel(asyncTasks, function(){
            if(asyncTasks.length < config.app.postLimit) {
                $that.extractPostsFromBufferedPages(userName, agencyName);
            }
        });

    })
}

exports.extractProfilePosts = function(userName, agencyName, profile, callback){
    logger.log('debug',"Extracting data from Facebook profile %s", profile);

    var $that = this;

    $that.getLastPostTime('@' + profile, function(lastPostTime){

        $that.extractPostsInfo(profile, lastPostTime, function(){

            var asyncTasks = [];

            extractedPosts.forEach(function(post){

                asyncTasks.push(function(callback){

                    $that.extractPostsLikes(post, function(post){

                        $that.savePost(userName, agencyName, post, callback);

                    });

                });
            });

            async.parallel(asyncTasks, function(){
                callback();
            });

        });
    });

}

// will query the db to get the laast post datetime for a profile
exports.getLastPostTime = function(match, callback){
    Post.getLastPostTime('facebook', match, function(lastPostTime){
        return callback(lastPostTime);
    });
}

// extracts id, message, creted_time, icon, link
exports.extractPostsInfo = function(profile, lastPostTime, callback){
    var $that = this;
    var url = profile + '/posts?fields=id,message,picture,full_picture,created_time,icon,link';
    url += '&access_token=' + session.access_token;
    url += lastPostTime!=undefined ? "&since=" + lastPostTime : "";
    url += "&limit=" + config.app.postsLimit;

    FB.api(url, function (res) {
        console.log(url);
        console.log(res.error);
        if(profile == "goredmond") {
            console.log(res);
        }
        if(!res || res.error) {
            $that.handleError(res.error.code, res.error.message, function(){
                return $that.extractPostsInfo(profile, lastPostTime, callback);
            });
        }

        if(res!=undefined && res.data!=undefined && res.data.length!=0){
            for(var i in res.data){
                var entry = res.data[i];

                entry.service = "facebook";
                entry.profile = profile;
                entry.match = "@" + profile;

                extractedPosts.push(entry);
            }

            if(res.paging!=undefined && res.paging.next!=undefined){
                bufferedPages.push({profile: profile, url: res.paging.next});
            }

            return callback();
        }

        return;

    });
}

exports.extractPostsFromBufferedPages = function(userName, agencyName){
    var $that = this;

    if(bufferedPages.length!=0){
        bufferedPagesInExec = [];
        extractedPosts = [];

        var bufferedPagesTasks = [];

        bufferedPages.forEach(function(bufferedPage){
            bufferedPagesTasks.push(function(callback){

                $that.extractNextInfo(bufferedPage, function(){
                    var asyncTasks = [];

                    extractedPosts.forEach(function(post){

                        asyncTasks.push(function(callback){

                            $that.extractPostsLikes(post, function(post){

                                $that.savePost(userName, agencyName, post, callback);

                            });

                        });
                    });

                    async.parallel(asyncTasks, function(){
                        callback();
                    });

                });

            });
        });

        async.parallel(bufferedPagesTasks, function(){

            bufferedPages = [];
            $that.extractPostsFromBufferedPages(userName, agencyName);

        });
    }
}

exports.extractNextInfo = function(bufferedPage, callback){
    var $that = this;

    request({
        uri: bufferedPage.url,
        method: "GET"
    }, function(error, response, body) {
        if(error) {
            $that.handleError(error.code, error.message, function(){
                return $that.extractNextInfo(bufferedPage, callback);
            });
        }

        body = JSON.parse(body);

        if(body!=undefined && body.data!=undefined && body.data.length!=0){
            for(var i in body.data){
                var entry = body.data[i];

                entry.service = "facebook";
                entry.profile = bufferedPage.profile;
                entry.match = "@" + bufferedPage.profile;

                extractedPosts.push(entry);
            }

            if(body.paging!=undefined && body.paging.next!=undefined){
                bufferedPagesInExec.push({profile: bufferedPage.profile, url: body.paging.next});
            }

            return callback();
        }

        return;

    });
}

// extracts likes
exports.extractPostsLikes = function(post, cb){
    var $that = this;

    FB.api(post.id + '/likes?summary=true&access_token=' + session.access_token, function (res) {

        if(!res || res.error) {
            $that.handleError(res.error.code, res.error.message, function(){
                return $that.extractPostsLikes(post, cb);
            });
        }

        if(res!=undefined && res.summary!=undefined){
            post.likes = res.summary.total_count;
        }

        return cb(post);
    });

}

// saves the post into the db
exports.savePost = function(userName, agencyName, postInfo, callback) {
    console.log('saving post');
    var post = new Post();
    post.userName = userName;
    post.agencyName = agencyName;
    post.id = postInfo.id;
    post.date = new Date(postInfo.created_time);
    post.date_extracted = new Date();
    post.service = postInfo.service;
    post.account = postInfo.profile;
    post.match = postInfo.match;
    post.icon = postInfo.icon;
    post.url = postInfo.link;
    post.text = postInfo.message;
    post.likes = postInfo.likes;
    post.image = postInfo.full_picture || postInfo.picture;

    post.save();
    callback();
}

exports.handleError = function(errCode, errMessage, nextAction){
    var $that = this;
    console.log("facebook errors: " + errCode);
    console.log(errMessage);
    switch (errCode) {
        // access_token expired
        case 102: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 104: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 463: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 467: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 191: {
            logger.log('info',"Error %s occurred: %s",errCode, errMessage);
            return ;
        }

        // Bad search key
        case 803: {
            logger.log('info', errMessage);
            return ;
        }

        default: {
            logger.log('info',"Failed to handle error %s: %s", errCode, errMessage);
        }
    }
}

var $that = this;

exports.errorHandlers = {
    handleExpiredToken: function(errCode, nextAction){
        logger.log('info',"%s: handling expired access_token", errCode);
        $that.authenticate(nextAction);
    }
}<|endoftext|>


<commit_before>var express = require('express'),
    request = require('request'),
    async = require('async'),
    FB = require('fb'),
    AggregatorController = require('../AggregatorController'),
    Post = require('../../model/Post');

var session = {};
var searchCriteria = {};

var extractedPosts = [];
var bufferedPages = [];
var bufferedPagesInExec = [];

exports.aggregateData = function(userName, agency) {
    var $that = this;

    AggregatorController.gatherSearchCriteria(userName, agency, 'facebook', function(criteria){
        searchCriteria = criteria;

        $that.extractData(userName, agency.name, criteria);
    });
}

exports.authenticate = function(callback){
    FB.api('oauth/access_token', {
        client_id: config.apps.facebook.key,
        client_secret: config.apps.facebook.secret,
        grant_type: 'client_credentials'
    }, function (res) {
        logger.log('debug',"Authentication to Facebook was successful!");

        session.access_token    = res.access_token;
        session.expires         = new Date().getTime() + (res.expires || 0) * 1000;

        return callback();
    });

}

exports.ensureAuthenticated = function(callback){
    var $that = this;

    this.isSessionValid(function(sessionValid){
        if(sessionValid){
            return callback();
        } else {
            logger.log('debug',"Facebook session not valid");
            $that.authenticate(function(){
                return callback();
            });
        }
    });
}

exports.isSessionValid = function(callback){
    var $that = this;
    var accessTokenNotExpired = session.access_token!=null && new Date().getTime() - session.expires > 0;

    if(accessTokenNotExpired){
        FB.api('facebook?access_token=' + session.access_token, function (res) {
            if(!res || res.error) {
                return callback(false);
            }

            return callback(true);
        });
    } else {
        return callback(false);
    }
}

exports.extractData = function(userName, agencyName, criteria){
    var $that = this;

    $that.ensureAuthenticated(function(){
        logger.log('debug',"Extracting data from Facebook...");
        var asyncTasks = [];

        searchCriteria.profiles.forEach(function(profile){
            asyncTasks.push(function(callback){
                $that.extractProfilePosts(userName, agencyName, profile, callback);
            });
        });

        async.parallel(asyncTasks, function(){
            if(asyncTasks.length < config.app.postLimit) {
                $that.extractPostsFromBufferedPages(userName, agencyName);
            }
        });

    })
}

exports.extractProfilePosts = function(userName, agencyName, profile, callback){
    logger.log('debug',"Extracting data from Facebook profile %s", profile);

    var $that = this;

    $that.getLastPostTime('@' + profile, function(lastPostTime){

        $that.extractPostsInfo(profile, lastPostTime, function(){

            var asyncTasks = [];

            extractedPosts.forEach(function(post){

                asyncTasks.push(function(callback){

                    $that.extractPostsLikes(post, function(post){

                        $that.savePost(userName, agencyName, post, callback);

                    });

                });
            });

            async.parallel(asyncTasks, function(){
                callback();
            });

        });
    });

}

// will query the db to get the laast post datetime for a profile
exports.getLastPostTime = function(match, callback){
    Post.getLastPostTime('facebook', match, function(lastPostTime){
        return callback(lastPostTime);
    });
}

// extracts id, message, creted_time, icon, link
exports.extractPostsInfo = function(profile, lastPostTime, callback){
    var $that = this;
    var url = profile + '/posts?fields=id,message,picture,full_picture,created_time,icon,link';
    url += '&access_token=' + session.access_token;
    url += lastPostTime!=undefined ? "&since=" + lastPostTime : "";
    url += "&limit=" + config.app.postsLimit;

    FB.api(url, function (res) {
            if(!res || res.error) {
                $that.handleError(res.error.code, res.error.message, function(){
                    return $that.extractPostsInfo(profile, lastPostTime, callback);
                });
            }

            if(res!=undefined && res.data!=undefined && res.data.length!=0){
                for(var i in res.data){
                    var entry = res.data[i];

                    entry.service = "facebook";
                    entry.profile = profile;
                    entry.match = "@" + profile;

                    extractedPosts.push(entry);
                }

                if(res.paging!=undefined && res.paging.next!=undefined){
                    bufferedPages.push({profile: profile, url: res.paging.next});
                }

                return callback();
            }

            return;

    });
}

exports.extractPostsFromBufferedPages = function(userName, agencyName){
    var $that = this;

    if(bufferedPages.length!=0){
        bufferedPagesInExec = [];
        extractedPosts = [];

        var bufferedPagesTasks = [];

        bufferedPages.forEach(function(bufferedPage){
            bufferedPagesTasks.push(function(callback){

                $that.extractNextInfo(bufferedPage, function(){
                    var asyncTasks = [];

                    extractedPosts.forEach(function(post){

                        asyncTasks.push(function(callback){

                            $that.extractPostsLikes(post, function(post){

                                $that.savePost(userName, agencyName, post, callback);

                            });

                        });
                    });

                    async.parallel(asyncTasks, function(){
                        callback();
                    });

                });

            });
        });

        async.parallel(bufferedPagesTasks, function(){

            bufferedPages = [];
            $that.extractPostsFromBufferedPages(userName, agencyName);

        });
    }
}

exports.extractNextInfo = function(bufferedPage, callback){
    var $that = this;

    request({
        uri: bufferedPage.url,
        method: "GET"
    }, function(error, response, body) {
        if(error) {
            $that.handleError(error.code, error.message, function(){
                return $that.extractNextInfo(bufferedPage, callback);
            });
        }

        body = JSON.parse(body);

        if(body!=undefined && body.data!=undefined && body.data.length!=0){
            for(var i in body.data){
                var entry = body.data[i];

                entry.service = "facebook";
                entry.profile = bufferedPage.profile;
                entry.match = "@" + bufferedPage.profile;

                extractedPosts.push(entry);
            }

            if(body.paging!=undefined && body.paging.next!=undefined){
                bufferedPagesInExec.push({profile: bufferedPage.profile, url: body.paging.next});
            }

            return callback();
        }

        return;

    });
}

// extracts likes
exports.extractPostsLikes = function(post, cb){
    var $that = this;

    FB.api(post.id + '/likes?summary=true&access_token=' + session.access_token, function (res) {

        if(!res || res.error) {
            $that.handleError(res.error.code, res.error.message, function(){
                return $that.extractPostsLikes(post, cb);
            });
        }

        if(res!=undefined && res.summary!=undefined){
            post.likes = res.summary.total_count;
        }

        return cb(post);
    });

}

// saves the post into the db
exports.savePost = function(userName, agencyName, postInfo, callback) {
    var post = new Post();
    post.userName = userName;
    post.agencyName = agencyName;
    post.id = postInfo.id;
    post.date = new Date(postInfo.created_time);
    post.date_extracted = new Date();
    post.service = postInfo.service;
    post.account = postInfo.profile;
    post.match = postInfo.match;
    post.icon = postInfo.icon;
    post.url = postInfo.link;
    post.text = postInfo.message;
    post.likes = postInfo.likes;
    post.image = postInfo.full_picture || postInfo.picture;

    post.save();
    callback();
}

exports.handleError = function(errCode, errMessage, nextAction){
    var $that = this;
    console.log("facebook errors: " + errCode);
    console.log(errMessage);
    switch (errCode) {
        // access_token expired
        case 102: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 104: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 463: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 467: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 191: {
            logger.log('info',"Error %s occurred: %s",errCode, errMessage);
            return ;
        }

        // Bad search key
        case 803: {
            logger.log('info', errMessage);
            return ;
        }

        default: {
            logger.log('info',"Failed to handle error %s: %s", errCode, errMessage);
        }
    }
}

var $that = this;

exports.errorHandlers = {
    handleExpiredToken: function(errCode, nextAction){
        logger.log('info',"%s: handling expired access_token", errCode);
        $that.authenticate(nextAction);
    }
}<commit_msg>debugging facebvook<commit_after>var express = require('express'),
    request = require('request'),
    async = require('async'),
    FB = require('fb'),
    AggregatorController = require('../AggregatorController'),
    Post = require('../../model/Post');

var session = {};
var searchCriteria = {};

var extractedPosts = [];
var bufferedPages = [];
var bufferedPagesInExec = [];

exports.aggregateData = function(userName, agency) {
    var $that = this;

    AggregatorController.gatherSearchCriteria(userName, agency, 'facebook', function(criteria){
        searchCriteria = criteria;

        $that.extractData(userName, agency.name, criteria);
    });
}

exports.authenticate = function(callback){
    FB.api('oauth/access_token', {
        client_id: config.apps.facebook.key,
        client_secret: config.apps.facebook.secret,
        grant_type: 'client_credentials'
    }, function (res) {
        console.log('authenticate call: ');
        console.log(res);
        logger.log('debug',"Authentication to Facebook was successful!");

        session.access_token    = res.access_token;
        session.expires         = new Date().getTime() + (res.expires || 0) * 1000;

        return callback();
    });

}

exports.ensureAuthenticated = function(callback){
    var $that = this;
    console.log('auth start');
    this.isSessionValid(function(sessionValid){
        console.log('session valid: ' + sessionValid);
        if(sessionValid){
            return callback();
        } else {
            logger.log('debug',"Facebook session not valid");
            $that.authenticate(function(){
                return callback();
            });
        }
    });
}

exports.isSessionValid = function(callback){
    var $that = this;
    var accessTokenNotExpired = session.access_token!=null && new Date().getTime() - session.expires > 0;
    console.log('isSessionValid token ' + accessTokenNotExpired);
    if(accessTokenNotExpired){
        FB.api('facebook?access_token=' + session.access_token, function (res) {
            if(!res || res.error) {
                return callback(false);
            }

            return callback(true);
        });
    } else {
        return callback(false);
    }
}

exports.extractData = function(userName, agencyName, criteria){
    var $that = this;

    $that.ensureAuthenticated(function(){
        logger.log('debug',"Extracting data from Facebook...");
        var asyncTasks = [];

        searchCriteria.profiles.forEach(function(profile){
            asyncTasks.push(function(callback){
                $that.extractProfilePosts(userName, agencyName, profile, callback);
            });
        });

        async.parallel(asyncTasks, function(){
            if(asyncTasks.length < config.app.postLimit) {
                $that.extractPostsFromBufferedPages(userName, agencyName);
            }
        });

    })
}

exports.extractProfilePosts = function(userName, agencyName, profile, callback){
    logger.log('debug',"Extracting data from Facebook profile %s", profile);

    var $that = this;

    $that.getLastPostTime('@' + profile, function(lastPostTime){

        $that.extractPostsInfo(profile, lastPostTime, function(){

            var asyncTasks = [];

            extractedPosts.forEach(function(post){

                asyncTasks.push(function(callback){

                    $that.extractPostsLikes(post, function(post){

                        $that.savePost(userName, agencyName, post, callback);

                    });

                });
            });

            async.parallel(asyncTasks, function(){
                callback();
            });

        });
    });

}

// will query the db to get the laast post datetime for a profile
exports.getLastPostTime = function(match, callback){
    Post.getLastPostTime('facebook', match, function(lastPostTime){
        return callback(lastPostTime);
    });
}

// extracts id, message, creted_time, icon, link
exports.extractPostsInfo = function(profile, lastPostTime, callback){
    var $that = this;
    var url = profile + '/posts?fields=id,message,picture,full_picture,created_time,icon,link';
    url += '&access_token=' + session.access_token;
    url += lastPostTime!=undefined ? "&since=" + lastPostTime : "";
    url += "&limit=" + config.app.postsLimit;

    FB.api(url, function (res) {
            if(!res || res.error) {
                $that.handleError(res.error.code, res.error.message, function(){
                    return $that.extractPostsInfo(profile, lastPostTime, callback);
                });
            }

            if(res!=undefined && res.data!=undefined && res.data.length!=0){
                for(var i in res.data){
                    var entry = res.data[i];

                    entry.service = "facebook";
                    entry.profile = profile;
                    entry.match = "@" + profile;

                    extractedPosts.push(entry);
                }

                if(res.paging!=undefined && res.paging.next!=undefined){
                    bufferedPages.push({profile: profile, url: res.paging.next});
                }

                return callback();
            }

            return;

    });
}

exports.extractPostsFromBufferedPages = function(userName, agencyName){
    var $that = this;

    if(bufferedPages.length!=0){
        bufferedPagesInExec = [];
        extractedPosts = [];

        var bufferedPagesTasks = [];

        bufferedPages.forEach(function(bufferedPage){
            bufferedPagesTasks.push(function(callback){

                $that.extractNextInfo(bufferedPage, function(){
                    var asyncTasks = [];

                    extractedPosts.forEach(function(post){

                        asyncTasks.push(function(callback){

                            $that.extractPostsLikes(post, function(post){

                                $that.savePost(userName, agencyName, post, callback);

                            });

                        });
                    });

                    async.parallel(asyncTasks, function(){
                        callback();
                    });

                });

            });
        });

        async.parallel(bufferedPagesTasks, function(){

            bufferedPages = [];
            $that.extractPostsFromBufferedPages(userName, agencyName);

        });
    }
}

exports.extractNextInfo = function(bufferedPage, callback){
    var $that = this;

    request({
        uri: bufferedPage.url,
        method: "GET"
    }, function(error, response, body) {
        if(error) {
            $that.handleError(error.code, error.message, function(){
                return $that.extractNextInfo(bufferedPage, callback);
            });
        }

        body = JSON.parse(body);

        if(body!=undefined && body.data!=undefined && body.data.length!=0){
            for(var i in body.data){
                var entry = body.data[i];

                entry.service = "facebook";
                entry.profile = bufferedPage.profile;
                entry.match = "@" + bufferedPage.profile;

                extractedPosts.push(entry);
            }

            if(body.paging!=undefined && body.paging.next!=undefined){
                bufferedPagesInExec.push({profile: bufferedPage.profile, url: body.paging.next});
            }

            return callback();
        }

        return;

    });
}

// extracts likes
exports.extractPostsLikes = function(post, cb){
    var $that = this;

    FB.api(post.id + '/likes?summary=true&access_token=' + session.access_token, function (res) {

        if(!res || res.error) {
            $that.handleError(res.error.code, res.error.message, function(){
                return $that.extractPostsLikes(post, cb);
            });
        }

        if(res!=undefined && res.summary!=undefined){
            post.likes = res.summary.total_count;
        }

        return cb(post);
    });

}

// saves the post into the db
exports.savePost = function(userName, agencyName, postInfo, callback) {
    var post = new Post();
    post.userName = userName;
    post.agencyName = agencyName;
    post.id = postInfo.id;
    post.date = new Date(postInfo.created_time);
    post.date_extracted = new Date();
    post.service = postInfo.service;
    post.account = postInfo.profile;
    post.match = postInfo.match;
    post.icon = postInfo.icon;
    post.url = postInfo.link;
    post.text = postInfo.message;
    post.likes = postInfo.likes;
    post.image = postInfo.full_picture || postInfo.picture;

    post.save();
    callback();
}

exports.handleError = function(errCode, errMessage, nextAction){
    var $that = this;
    console.log("facebook errors: " + errCode);
    console.log(errMessage);
    switch (errCode) {
        // access_token expired
        case 102: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 104: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 463: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // access_token expired
        case 467: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 190: {
            $that.errorHandlers.handleExpiredToken(errCode, nextAction);
        }

        // OAuthException
        case 191: {
            logger.log('info',"Error %s occurred: %s",errCode, errMessage);
            return ;
        }

        // Bad search key
        case 803: {
            logger.log('info', errMessage);
            return ;
        }

        default: {
            logger.log('info',"Failed to handle error %s: %s", errCode, errMessage);
        }
    }
}

var $that = this;

exports.errorHandlers = {
    handleExpiredToken: function(errCode, nextAction){
        logger.log('info',"%s: handling expired access_token", errCode);
        $that.authenticate(nextAction);
    }
}<|endoftext|>


<commit_before>// cannot rely on jQuery being loaded here

var ccm_uiLoaded = false;
var ccm_siteActivated = true;
var ccm_animEffects = false;

ccm_parseJSON = function(resp, onNoError) {
	if (resp.error) {
		alert(resp.message);
	} else {
		onNoError();
	}
}

ccm_deactivateSite = function(onDone) {
	if (ccm_siteActivated == false) {
		return false;
	}

	if ($("#ccm-overlay").length < 1) {
		$(document.body).append('

');
	}

	$("embed,object").each(function() {
		$(this).attr('ccm-style-old-visibility', $(this).css('visibility'));
		$(this).css('visibility', 'hidden');
	});

	if (ccm_animEffects) {
		$("#ccm-overlay").fadeIn(100);
	} else {
		$("#ccm-overlay").show();
	}

	ccm_siteActivated = false;
	if (typeof onDone == 'function') {
		onDone();
	}
}

ccm_activateSite = function() {
	if (ccm_animEffects) {
		$("#ccm-overlay").fadeOut(100);
	} else {
		$("#ccm-overlay").hide();
	}

	$("embed,object").each(function() {
		$(this).css('visibility', $(this).attr('ccm-style-old-visibility'));
	});

	ccm_siteActivated = true;
	ccm_topPaneDeactivated = false;
}

ccm_addHeaderItem = function(item, type) {
	// "item" might already have a "?v=", so avoid invalid query string.
	var qschar = (item.indexOf('?') != -1 ? '' : '?ts=');
	if (type == 'CSS') {
		if (navigator.userAgent.indexOf('MSIE') != -1) {
			// Most reliable way found to force IE to apply dynamically inserted stylesheet across jQuery versions
			var ss = document.createElement('link'), hd = document.getElementsByTagName('head')[0];
			ss.type = 'text/css'; ss.rel = 'stylesheet'; ss.href = item; ss.media = 'screen';
			hd.appendChild(ss);
		} else {
			if (!($('head').children('link[href*="' + item + '"]').length)) {
				$('head').append('<link rel="stylesheet" media="screen" type="text/css" href="' + item + qschar + new Date().getTime() + '" />');
			}
		}
	} else if (type == 'JAVASCRIPT') {
		if (!($('script[src*="' + item + '"]').length)) {
			$('head').append('');
		}
	} else {
		if (!($('head').children(item).length)) {
			$('head').append(item);
		}
	}
}

// called in versions popup
ccm_disableLinks = function() {
	td = document.createElement("DIV");
	td.style.position = "absolute";
	td.style.top = "0px";
	td.style.left = "0px";
	td.style.width = "100%";
	td.style.height = "100%";
	td.style.zIndex = "1000";
	document.body.appendChild(td);
}<commit_msg>Fix adding timestamp in ccm_addHeaderItem<commit_after>// cannot rely on jQuery being loaded here

var ccm_uiLoaded = false;
var ccm_siteActivated = true;
var ccm_animEffects = false;

ccm_parseJSON = function(resp, onNoError) {
	if (resp.error) {
		alert(resp.message);
	} else {
		onNoError();
	}
}

ccm_deactivateSite = function(onDone) {
	if (ccm_siteActivated == false) {
		return false;
	}

	if ($("#ccm-overlay").length < 1) {
		$(document.body).append('

');
	}

	$("embed,object").each(function() {
		$(this).attr('ccm-style-old-visibility', $(this).css('visibility'));
		$(this).css('visibility', 'hidden');
	});

	if (ccm_animEffects) {
		$("#ccm-overlay").fadeIn(100);
	} else {
		$("#ccm-overlay").show();
	}

	ccm_siteActivated = false;
	if (typeof onDone == 'function') {
		onDone();
	}
}

ccm_activateSite = function() {
	if (ccm_animEffects) {
		$("#ccm-overlay").fadeOut(100);
	} else {
		$("#ccm-overlay").hide();
	}

	$("embed,object").each(function() {
		$(this).css('visibility', $(this).attr('ccm-style-old-visibility'));
	});

	ccm_siteActivated = true;
	ccm_topPaneDeactivated = false;
}

ccm_addHeaderItem = function(item, type) {
	// "item" might already have a "?v=", so avoid invalid query string.
	var qschar = (item.indexOf('?') != -1 ? '&ts' : '?ts=');
	if (type == 'CSS') {
		if (navigator.userAgent.indexOf('MSIE') != -1) {
			// Most reliable way found to force IE to apply dynamically inserted stylesheet across jQuery versions
			var ss = document.createElement('link'), hd = document.getElementsByTagName('head')[0];
			ss.type = 'text/css'; ss.rel = 'stylesheet'; ss.href = item; ss.media = 'screen';
			hd.appendChild(ss);
		} else {
			if (!($('head').children('link[href*="' + item + '"]').length)) {
				$('head').append('<link rel="stylesheet" media="screen" type="text/css" href="' + item + qschar + new Date().getTime() + '" />');
			}
		}
	} else if (type == 'JAVASCRIPT') {
		if (!($('script[src*="' + item + '"]').length)) {
			$('head').append('');
		}
	} else {
		if (!($('head').children(item).length)) {
			$('head').append(item);
		}
	}
}

// called in versions popup
ccm_disableLinks = function() {
	td = document.createElement("DIV");
	td.style.position = "absolute";
	td.style.top = "0px";
	td.style.left = "0px";
	td.style.width = "100%";
	td.style.height = "100%";
	td.style.zIndex = "1000";
	document.body.appendChild(td);
}<|endoftext|>