<commit_before>/*
 * This file ties together the IRC and Matrix interfaces into a bridge between
 * the two.
 */
"use strict";
var q = require("q");
var matrixLib = require("./mxlib/matrix");
var ircLib = require("./irclib/irc");
var roomModels = require("./rooms");
var store = require("./store");
var protocols = require("./protocols");
var actions = require("./actions");
var users = require("./users");
var log = require("./logging").get("bridge");

var createMatrixUserForIrcUser = function(ircUser) {
    var defer = q.defer();

    ircLib.checkNickExists(ircUser.server, ircUser.nick).then(function(info) {
        log.info("Creating virtual user for %s on %s",
            ircUser.nick, ircUser.server.domain);
        return protocols.mapToMatrix("users", ircUser);
    }).then(function(user) {
        log.info("Created virtual user %s", user.userId);
        defer.resolve(user);
    }, function(err) {
        log.error("Virtual user creation for %s failed: %s",
            userId, err);
        defer.reject({});
    }).done();

    return defer.promise;
}

module.exports.hooks = {
    matrix: {
        onInvite: function(event) {
            // First, try to map the invitee to an IRC user. If this can be done,
            // create a virtual Matrix user for the IRC user and make them join
            // the room they have been invited to.
            log.info("onInvite: %s", JSON.stringify(event));
            var errFn = function(err) {
                log.error(
                    "onInvite: Failed to handle invite from %s to room %s : %s",
                    userId, event.room_id, err);
            };

            var invitedUser = users.matrix.createUser(
                event.state_key, true
            );
            var realIrcUser;
            var virtualMatrixUser;

            return protocols.mapToIrc("users", invitedUser).then(
            function(ircUser) {
                if (!ircUser.server.allowsPms()) {
                    log.error(
                        "Rejecting invite: This server does not allow PMs."
                    );
                    return q.reject("Server disallows PMs");
                }
                realIrcUser = ircUser;
                return createMatrixUserForIrcUser(ircUser);
            }).then(function(user) {
                virtualMatrixUser = user;
                return matrixLib.joinRoom(event.room_id, virtualMatrixUser);
            }).then(function() {
                log.info(
                    "Joined %s to room %s", invitedUser.userId, event.room_id
                );
                return matrixLib.isPmRoom(
                    invitedUser.userId, event.room_id, event.user_id
                );
            }).then(function(isPmRoom) {
                if (isPmRoom) {
                    // clobber any existing PM room
                    // FIXME: Need a unified room object here
                    var pmRoom = roomModels.createMatrixRoom(event.room_id);
                    pmRoom.server = realIrcUser.server;
                    pmRoom.channel = realIrcUser.nick;
                    return store.storePmRoom(
                        pmRoom, event.user_id, event.state_key
                    );
                }
                else {
                    log.error("This room isn't a 1:1 chat!");
                    // whine that you don't do group chats and leave.
                    matrixLib.sendNoticeRaw(
                        event.room_id, invitedUser.userId,
                        "Group chat not supported."
                    ).finally(function() {
                        matrixLib.leaveRoom(invitedUser.userId, event.room_id).done(
                            undefined, errFn
                        );
                    });
                }
            }).done(function() {
                log.info("onInvite: Processed invite.");
            }, function(err) {
                log.error("onInvite: Failed to handle invite: %s", err);
            });
        },
        onJoin: function(event) {
            // if this is another Matrix user joining a PM room:
            //  - Whine that you don't do group chats and leave (virtual user)
        },
        onMessage: function(event) {
            log.info("[M->I]%s usr=%s rm=%s", event.type, event.user_id,
                event.room_id);

            var ircAction = protocols.mapToIrc(
                "actions", actions.matrix.createAction(event)
            );
            store.getIrcRoomsForRoomId(event.room_id).done(
            function(ircRooms) {
                if (ircRooms.length == 0) {
                    log.info("No mapped channels.");
                    return;
                }
                ircRooms.forEach(function(ircRoom) {
                    log.info("Relaying message in %s on %s",
                        ircRoom.channel, ircRoom.server.domain);

                    if (event.content.msgtype === "m.image") {
                        // sent by the bot
                        var msg = "<"+event.user_id+"> posted an image: "+
                              matrixLib.decodeMxc(event.content.url)+
                              " - "+event.content.body;
                        ircLib.sendBotText(
                            ircRoom.server, ircRoom.channel, msg
                        ).done(function() {
                            log.info("Relayed image text.");
                        });
                        return;
                    }

                    ircLib.getVirtualIrcUser(ircRoom.server, event.user_id).then(
                        function(ircUser) {
                            if (!ircUser) {
                                log.error("Unknown IRC user for user ID %s",
                                    event.user_id);
                                return;
                            }
                            log.info("Obtained virtual IRC user: %s",
                                ircUser.nick);
                            ircUser.sendAction(ircRoom, ircAction);
                    }).done(function() {
                        log.info("[M->I] Sent message.");
                    }, function(err) {
                        log.error("[M->I]Failed to relay Matrix message: %s",
                            JSON.stringify(err));
                    });
                });
            }, function(err) {
                log.error("Failed to get rooms for room ID %s",
                    event.room_id);
            });
        },
        onAliasQuery: function(roomAlias) {
            log.info("onAliasQuery %s", roomAlias);
            var defer = q.defer();
            // check if alias maps to a valid IRC server and channel
            var channelInfo = protocols.mapToIrc(
                "aliases", roomAlias
            );
            if (!channelInfo.channel) {
                log.info("Unknown alias: %s", roomAlias);
                return q.reject();  // bad alias
            }
            if (!channelInfo.server.shouldMapAllRooms()) {
                log.error("This server does not allow alias mappings.");
                return q.reject();
            }

            // join the irc server + channel
            ircLib.trackChannel(channelInfo.server, channelInfo.channel).then(
                function(ircRoom) {
                    return matrixLib.createRoomWithAlias(
                        roomAlias, channelInfo.channel
                    );
                }
            ).then(function(matrixRoom) {
                // TODO set topic, add matrix members f.e. irc user(?) given
                // they are cheap to do.

                // store the mapping and return OK
                matrixRoom.channel = channelInfo.channel;
                matrixRoom.server = channelInfo.server;
                store.storeRoom(matrixRoom);
                defer.resolve({});
            }).done(undefined, function(err) {
                log.error(
                    "onAliasQuery: Failed to create a room for alias %s : %s",
                    roomAlias, err
                );
                defer.reject(err);
            });

            return defer.promise;
        },
        onUserQuery: function(userId) {
            log.info("onUserQuery: %s", userId);
            var matrixUser = users.matrix.createUser(userId, true);

            return protocols.mapToIrc("users", matrixUser).then(
            function(ircUser) {
                return createMatrixUserForIrcUser(ircUser);
            });
        }
    },
    irc: {
        onMessage: function(server, from, to, action) {
            log.info("[I->M]onMessage: from=%s to=%s action=%s",
                from, to, JSON.stringify(action));

            // Attempt to make IRC users for from/to
            var fromUser = users.irc.createUser(
                server, from, ircLib.isNickVirtualUser(server, from)
            );
            var toUser = users.irc.createUser(
                server, to, ircLib.isNickVirtualUser(server, to)
            );

            if (fromUser.isVirtual) {
                return; // don't send stuff which were sent from bots
            }

            var mxAction = protocols.mapToMatrix("actions", action);

            if (!mxAction) {
                log.error("Couldn't map IRC action to matrix action");
                return;
            }

            var errFn = function(err) {
                log.error("[I->M]Failed to relay IRC message: %s",
                    JSON.stringify(err));
            };
            var virtualMatrixUser = undefined; // sender
            var virtualIrcUser = ircLib.getVirtualUser(toUser); // receiver

            // map the sending IRC user to a Matrix user
            protocols.mapToMatrix("users", fromUser).then(function(user) {
                virtualMatrixUser = user;
                log.info("Mapped real IRC user to %s", JSON.stringify(user));
                if (virtualIrcUser) {
                    // this is actually a PM
                    if (!server.allowsPms()) {
                        log.error("Server %s disallows PMs.", server.domain);
                        return;
                    }
                    var pmRoom = undefined;
                    store.getPmRoom(
                        virtualIrcUser.userId, virtualMatrixUser.userId
                    ).then(function(room) {
                        if (room) {
                            log.info("[I->M]Relaying PM in room %s", room.roomId);
                            matrixLib.sendAction(
                                room, virtualMatrixUser, mxAction
                            ).done(undefined, errFn);
                            return;
                        }
                        // make a pm room then send the message
                        log.info("Creating a PM room with %s",
                            virtualIrcUser.userId);
                        return matrixLib.createRoomWithUser(
                            virtualMatrixUser.userId, virtualIrcUser.userId,
                            (from + " (PM on " + server.domain + ")")
                        );
                    }).then(function(room) {
                        room.server = server; // FIXME
                        room.channel = from;
                        pmRoom = room;
                        return store.storePmRoom(
                            pmRoom, virtualIrcUser.userId,
                            virtualMatrixUser.userId
                        );
                    }).then(function() {
                        return matrixLib.sendAction(
                            pmRoom,
                            virtualMatrixUser,
                            mxAction
                        );
                    }).done(undefined, errFn);
                }
                else {
                    // this is directed at a channel
                    store.getRoomsForChannel(server, to).then(function(matrixRooms) {
                        matrixRooms.forEach(function(room) {
                            log.info("[I->M]Relaying in room %s", room.roomId);
                            matrixLib.sendAction(
                                room, virtualMatrixUser, mxAction
                            ).done(undefined, errFn);
                        });
                    })
                }
            }).done(undefined, errFn);
        }
    }
};
<commit_msg>Queue messages for rooms whose invites are being processed.<commit_after>/*
 * This file ties together the IRC and Matrix interfaces into a bridge between
 * the two.
 */
"use strict";
var q = require("q");
var matrixLib = require("./mxlib/matrix");
var ircLib = require("./irclib/irc");
var roomModels = require("./rooms");
var store = require("./store");
var protocols = require("./protocols");
var actions = require("./actions");
var users = require("./users");
var log = require("./logging").get("bridge");

var createMatrixUserForIrcUser = function(ircUser) {
    var defer = q.defer();

    ircLib.checkNickExists(ircUser.server, ircUser.nick).then(function(info) {
        log.info("Creating virtual user for %s on %s",
            ircUser.nick, ircUser.server.domain);
        return protocols.mapToMatrix("users", ircUser);
    }).then(function(user) {
        log.info("Created virtual user %s", user.userId);
        defer.resolve(user);
    }, function(err) {
        log.error("Virtual user creation for %s failed: %s",
            userId, err);
        defer.reject({});
    }).done();

    return defer.promise;
}

// maintain a list of room IDs which are being processed invite-wise. This is
// required because invites are processed asyncly, so you could get invite->msg
// and the message is processed before the room is created.
var processingInvitesForRooms = {
    // roomId: defer
};

var holdEvent = function(event) {
    processingInvitesForRooms[event.room_id].finally(function() {
        log.info("Finished holding event for room %s", event.room_id);
        module.exports.hooks.matrix.onMessage(event);
    });
}

module.exports.hooks = {
    matrix: {
        onInvite: function(event) {
            var inviteDefer = q.defer();
            processingInvitesForRooms[event.room_id] = inviteDefer.promise;

            // First, try to map the invitee to an IRC user. If this can be done,
            // create a virtual Matrix user for the IRC user and make them join
            // the room they have been invited to.
            log.info("onInvite: %s", JSON.stringify(event));
            var errFn = function(err) {
                log.error(
                    "onInvite: Failed to handle invite from %s to room %s : %s",
                    event.user_id, event.room_id, err);
                processingInvitesForRooms[event.room_id] = undefined;
                inviteDefer.reject(err);
            };
            var sucFn = function(err) {
                log.info("onInvite: Processed invite from %s to room %s",
                    event.user_id, event.room_id);
                processingInvitesForRooms[event.room_id] = undefined;
                inviteDefer.resolve();
            };

            var invitedUser = users.matrix.createUser(
                event.state_key, true
            );
            var realIrcUser;
            var virtualMatrixUser;

            return protocols.mapToIrc("users", invitedUser).then(
            function(ircUser) {
                if (!ircUser.server.allowsPms()) {
                    log.error(
                        "Rejecting invite: This server does not allow PMs."
                    );
                    return q.reject("Server disallows PMs");
                }
                realIrcUser = ircUser;
                return createMatrixUserForIrcUser(ircUser);
            }).then(function(user) {
                virtualMatrixUser = user;
                return matrixLib.joinRoom(event.room_id, virtualMatrixUser);
            }).then(function() {
                log.info(
                    "Joined %s to room %s", invitedUser.userId, event.room_id
                );
                return matrixLib.isPmRoom(
                    invitedUser.userId, event.room_id, event.user_id
                );
            }).then(function(isPmRoom) {
                if (isPmRoom) {
                    // clobber any existing PM room
                    // FIXME: Need a unified room object here
                    var pmRoom = roomModels.createMatrixRoom(event.room_id);
                    pmRoom.server = realIrcUser.server;
                    pmRoom.channel = realIrcUser.nick;
                    return store.storePmRoom(
                        pmRoom, event.user_id, event.state_key
                    );
                }
                else {
                    log.error("This room isn't a 1:1 chat!");
                    // whine that you don't do group chats and leave.
                    matrixLib.sendNoticeRaw(
                        event.room_id, invitedUser.userId,
                        "Group chat not supported."
                    ).finally(function() {
                        matrixLib.leaveRoom(invitedUser.userId, event.room_id).done(
                            sucFn, errFn
                        );
                    });
                }
            }).done(sucFn, errFn);
        },
        onJoin: function(event) {
            // if this is another Matrix user joining a PM room:
            //  - Whine that you don't do group chats and leave (virtual user)
        },
        onMessage: function(event) {
            log.info("[M->I]%s usr=%s rm=%s", event.type, event.user_id,
                event.room_id);

            if (processingInvitesForRooms[event.room_id]) {
                log.info("Holding message until invite for room %s is done.",
                    event.room_id);
                holdEvent(event);
                return;
            }

            var ircAction = protocols.mapToIrc(
                "actions", actions.matrix.createAction(event)
            );
            store.getIrcRoomsForRoomId(event.room_id).done(
            function(ircRooms) {
                if (ircRooms.length == 0) {
                    log.info("No mapped channels.");
                    return;
                }
                ircRooms.forEach(function(ircRoom) {
                    log.info("Relaying message in %s on %s",
                        ircRoom.channel, ircRoom.server.domain);

                    if (event.content.msgtype === "m.image") {
                        // sent by the bot
                        var msg = "<"+event.user_id+"> posted an image: "+
                              matrixLib.decodeMxc(event.content.url)+
                              " - "+event.content.body;
                        ircLib.sendBotText(
                            ircRoom.server, ircRoom.channel, msg
                        ).done(function() {
                            log.info("Relayed image text.");
                        });
                        return;
                    }

                    ircLib.getVirtualIrcUser(ircRoom.server, event.user_id).then(
                        function(ircUser) {
                            if (!ircUser) {
                                log.error("Unknown IRC user for user ID %s",
                                    event.user_id);
                                return;
                            }
                            log.info("Obtained virtual IRC user: %s",
                                ircUser.nick);
                            ircUser.sendAction(ircRoom, ircAction);
                    }).done(function() {
                        log.info("[M->I] Sent message.");
                    }, function(err) {
                        log.error("[M->I]Failed to relay Matrix message: %s",
                            JSON.stringify(err));
                    });
                });
            }, function(err) {
                log.error("Failed to get rooms for room ID %s",
                    event.room_id);
            });
        },
        onAliasQuery: function(roomAlias) {
            log.info("onAliasQuery %s", roomAlias);
            var defer = q.defer();
            // check if alias maps to a valid IRC server and channel
            var channelInfo = protocols.mapToIrc(
                "aliases", roomAlias
            );
            if (!channelInfo.channel) {
                log.info("Unknown alias: %s", roomAlias);
                return q.reject();  // bad alias
            }
            if (!channelInfo.server.shouldMapAllRooms()) {
                log.error("This server does not allow alias mappings.");
                return q.reject();
            }

            // join the irc server + channel
            ircLib.trackChannel(channelInfo.server, channelInfo.channel).then(
                function(ircRoom) {
                    return matrixLib.createRoomWithAlias(
                        roomAlias, channelInfo.channel
                    );
                }
            ).then(function(matrixRoom) {
                // TODO set topic, add matrix members f.e. irc user(?) given
                // they are cheap to do.

                // store the mapping and return OK
                matrixRoom.channel = channelInfo.channel;
                matrixRoom.server = channelInfo.server;
                store.storeRoom(matrixRoom);
                defer.resolve({});
            }).done(undefined, function(err) {
                log.error(
                    "onAliasQuery: Failed to create a room for alias %s : %s",
                    roomAlias, err
                );
                defer.reject(err);
            });

            return defer.promise;
        },
        onUserQuery: function(userId) {
            log.info("onUserQuery: %s", userId);
            var matrixUser = users.matrix.createUser(userId, true);

            return protocols.mapToIrc("users", matrixUser).then(
            function(ircUser) {
                return createMatrixUserForIrcUser(ircUser);
            });
        }
    },
    irc: {
        onMessage: function(server, from, to, action) {
            log.info("[I->M]onMessage: from=%s to=%s action=%s",
                from, to, JSON.stringify(action));

            // Attempt to make IRC users for from/to
            var fromUser = users.irc.createUser(
                server, from, ircLib.isNickVirtualUser(server, from)
            );
            var toUser = users.irc.createUser(
                server, to, ircLib.isNickVirtualUser(server, to)
            );

            if (fromUser.isVirtual) {
                return; // don't send stuff which were sent from bots
            }

            var mxAction = protocols.mapToMatrix("actions", action);

            if (!mxAction) {
                log.error("Couldn't map IRC action to matrix action");
                return;
            }

            var errFn = function(err) {
                log.error("[I->M]Failed to relay IRC message: %s",
                    JSON.stringify(err));
            };
            var virtualMatrixUser = undefined; // sender
            var virtualIrcUser = ircLib.getVirtualUser(toUser); // receiver
            // map the sending IRC user to a Matrix user
            protocols.mapToMatrix("users", fromUser).then(function(user) {
                virtualMatrixUser = user;
                log.info("Mapped real IRC user to %s", JSON.stringify(user));
                if (virtualIrcUser) {
                    // this is actually a PM
                    if (!server.allowsPms()) {
                        log.error("Server %s disallows PMs.", server.domain);
                        return;
                    }
                    var pmRoom = undefined;
                    store.getPmRoom(
                        virtualIrcUser.userId, virtualMatrixUser.userId
                    ).then(function(room) {
                        if (room) {
                            log.info("[I->M]Relaying PM in room %s", room.roomId);
                            matrixLib.sendAction(
                                room, virtualMatrixUser, mxAction
                            ).done(undefined, errFn);
                            return;
                        }
                        // make a pm room then send the message
                        log.info("Creating a PM room with %s",
                            virtualIrcUser.userId);
                        return matrixLib.createRoomWithUser(
                            virtualMatrixUser.userId, virtualIrcUser.userId,
                            (from + " (PM on " + server.domain + ")")
                        );
                    }).then(function(room) {
                        room.server = server; // FIXME
                        room.channel = from;
                        pmRoom = room;
                        return store.storePmRoom(
                            pmRoom, virtualIrcUser.userId,
                            virtualMatrixUser.userId
                        );
                    }).then(function() {
                        return matrixLib.sendAction(
                            pmRoom,
                            virtualMatrixUser,
                            mxAction
                        );
                    }).done(undefined, errFn);
                }
                else {
                    // this is directed at a channel
                    store.getRoomsForChannel(server, to).then(function(matrixRooms) {
                        matrixRooms.forEach(function(room) {
                            log.info("[I->M]Relaying in room %s", room.roomId);
                            matrixLib.sendAction(
                                room, virtualMatrixUser, mxAction
                            ).done(undefined, errFn);
                        });
                    })
                }
            }).done(undefined, errFn);
        }
    }
};
<|endoftext|>


<commit_before>const {gimmeRobot} = require('./helpers')
const payloads = require('./fixtures/payloads')
const w = any => Promise.resolve({ data: any })
const branch = w([{ head: { ref: 'branch' }, number: 10 }])

const jason = {
  user: { login: 'JasonEtco', type: 'User' },
  body: 'hey'
}

const bot = body => ({
  user: { login: 'todo-dev[bot]', type: 'Bot' },
  body
})

const comment = {
  body: `## Jason!\n\nhttps://github.com/JasonEtco/test/blob/f7d286aa6381bbb5045288496403d9427b0746e2/index.js#L3-L8\n\n---\n\n###### This comment was generated by [todo](https://todo.jasonet.co) based on a \`@todo\` comment in f7d286aa6381bbb5045288496403d9427b0746e2 in #10. cc @JasonEtco.\n\n<!-- probot = {"10000":{"title":"Jason!","file":"index.js","author":"JasonEtco","sha":"f7d286aa6381bbb5045288496403d9427b0746e2"}} -->`,
  number: 10,
  owner: 'JasonEtco',
  repo: 'test'
}

describe('pr comments', () => {
  it('comments on a pull request', async () => {
    const {robot, github} = gimmeRobot()
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive(payloads.pr)
    expect(github.issues.create).toHaveBeenCalledTimes(0)
    expect(github.issues.createComment).toHaveBeenCalledTimes(1)
    expect(github.issues.createComment).toHaveBeenCalledWith(comment)
  })

  it('comments on a pull request with multiple keywords', async () => {
    const {robot, github} = gimmeRobot()
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive({...payloads.pr,
      payload: {
        ...payloads.pr.payload,
        commits: [{
          ...payloads.pr.commits[0],
          modified: ['multiple-keywords.js']
        }]
      }
    })

    expect(github.issues.create).toHaveBeenCalledTimes(0)
    expect(github.issues.createComment).toHaveBeenCalledTimes(2)
  })

  it('comments on a pull request and mentions the assigned user', async () => {
    const {robot, github} = gimmeRobot('autoAssignString.yml')
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive(payloads.pr)
    expect(github.issues.createComment).toHaveBeenCalledTimes(1)
  })

  it('comments on a pull request and mentions the assigned users', async () => {
    const {robot, github} = gimmeRobot('autoAssignArr.yml')
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive(payloads.pr)
    expect(github.issues.createComment).toHaveBeenCalledTimes(1)
  })

  it('does not create duplicate comments', async () => {
    const {robot, github} = gimmeRobot()
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason, bot(comment.body)]))

    await robot.receive(payloads.pr)
    expect(github.issues.createComment).toHaveBeenCalledTimes(0)
  })
})
<commit_msg>Fix that test<commit_after>const {gimmeRobot} = require('./helpers')
const payloads = require('./fixtures/payloads')
const w = any => Promise.resolve({ data: any })
const branch = w([{ head: { ref: 'branch' }, number: 10 }])

const jason = {
  user: { login: 'JasonEtco', type: 'User' },
  body: 'hey'
}

const bot = body => ({
  user: { login: 'todo-dev[bot]', type: 'Bot' },
  body
})

const comment = {
  body: `## Jason!\n\nhttps://github.com/JasonEtco/test/blob/f7d286aa6381bbb5045288496403d9427b0746e2/index.js#L3-L8\n\n---\n\n###### This comment was generated by [todo](https://todo.jasonet.co) based on a \`@todo\` comment in f7d286aa6381bbb5045288496403d9427b0746e2 in #10. cc @JasonEtco.\n\n<!-- probot = {"10000":{"title":"Jason!","file":"index.js","author":"JasonEtco","sha":"f7d286aa6381bbb5045288496403d9427b0746e2"}} -->`,
  number: 10,
  owner: 'JasonEtco',
  repo: 'test'
}

describe('pr comments', () => {
  it('comments on a pull request', async () => {
    const {robot, github} = gimmeRobot()
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive(payloads.pr)
    expect(github.issues.create).toHaveBeenCalledTimes(0)
    expect(github.issues.createComment).toHaveBeenCalledTimes(1)
    expect(github.issues.createComment).toHaveBeenCalledWith(comment)
  })

  it('comments on a pull request with multiple keywords', async () => {
    const {robot, github} = gimmeRobot()
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive({...payloads.pr,
      payload: {
        ...payloads.pr.payload,
        commits: [{
          ...payloads.pr.payload.commits[0],
          modified: ['multiple-keywords.js']
        }]
      }
    })

    expect(github.issues.create).toHaveBeenCalledTimes(0)
    expect(github.issues.createComment).toHaveBeenCalledTimes(2)
  })

  it('comments on a pull request and mentions the assigned user', async () => {
    const {robot, github} = gimmeRobot('autoAssignString.yml')
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive(payloads.pr)
    expect(github.issues.createComment).toHaveBeenCalledTimes(1)
  })

  it('comments on a pull request and mentions the assigned users', async () => {
    const {robot, github} = gimmeRobot('autoAssignArr.yml')
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason]))

    await robot.receive(payloads.pr)
    expect(github.issues.createComment).toHaveBeenCalledTimes(1)
  })

  it('does not create duplicate comments', async () => {
    const {robot, github} = gimmeRobot()
    github.pullRequests.getAll.mockReturnValue(branch)
    github.issues.getComments.mockReturnValueOnce(w([jason, bot(comment.body)]))

    await robot.receive(payloads.pr)
    expect(github.issues.createComment).toHaveBeenCalledTimes(0)
  })
})
<|endoftext|>


<commit_before>var check = require('./util/check')
var isTypedArray = require('./util/is-typed-array')
var isNDArrayLike = require('./util/is-ndarray')
var values = require('./util/values')
var flatten = require('./util/flatten')
var transpose = require('./util/transpose')
var pool = require('./util/pool')

var arrayTypes = require('./constants/arraytypes.json')
var bufferTypes = require('./constants/dtypes.json')
var usageTypes = require('./constants/usage.json')

var GL_STATIC_DRAW = 0x88E4
var GL_STREAM_DRAW = 0x88E0

var GL_UNSIGNED_BYTE = 5121
var GL_FLOAT = 5126

function typedArrayCode (data) {
  return arrayTypes[Object.prototype.toString.call(data)] | 0
}

function copyArray (out, inp) {
  for (var i = 0; i < inp.length; ++i) {
    out[i] = inp[i]
  }
}

module.exports = function wrapBufferState (gl) {
  var bufferCount = 0
  var bufferSet = {}

  function REGLBuffer (type) {
    this.id = bufferCount++
    this.buffer = gl.createBuffer()
    this.type = type
    this.usage = GL_STATIC_DRAW
    this.byteLength = 0
    this.dimension = 1
    this.dtype = GL_UNSIGNED_BYTE
  }

  REGLBuffer.prototype.bind = function () {
    gl.bindBuffer(this.type, this.buffer)
  }

  var streamPool = []

  function createStream (type, data) {
    var buffer = streamPool.pop()
    if (!buffer) {
      buffer = new REGLBuffer(type)
      buffer.buffer = gl.createBuffer()
    }
    buffer.bind()
    initBufferFromData(buffer, data, GL_STREAM_DRAW, 0, 1)
    return buffer
  }

  function destroyStream (stream) {
    streamPool.push(stream)
  }

  function initBufferFromTypedArray (buffer, data, usage) {
    buffer.byteLength = data.byteLength
    gl.bufferData(buffer.type, data, usage)
  }

  function initBufferFromData (buffer, data, usage, dtype, dimension) {
    buffer.usage = usage
    if (Array.isArray(data)) {
      buffer.dtype = dtype || GL_FLOAT
      if (data.length > 0 && Array.isArray(data[0])) {
        buffer.dimension = data[0].length
        var flatData = pool.allocType(
          buffer.dtype,
          data.length * buffer.dimension)
        flatten(flatData, data, buffer.dimension)
        initBufferFromTypedArray(buffer, flatData, usage)
        pool.freeType(flatData)
      } else {
        buffer.dimension = dimension
        var typedData = pool.allocType(buffer.dtype, data.length)
        copyArray(typedData, data)
        initBufferFromTypedArray(buffer, typedData, usage)
        pool.freeType(typedData)
      }
    } else if (isTypedArray(data)) {
      buffer.dtype = dtype || typedArrayCode(data)
      buffer.dimension = dimension
      initBufferFromTypedArray(buffer, data, usage)
    } else if (isNDArrayLike(data)) {
      var shape = data.shape
      var stride = data.stride
      var offset = data.offset

      var shapeX = 0
      var shapeY = 0
      var strideX = 0
      var strideY = 0
      if (shape.length === 1) {
        shapeX = shape[0]
        shapeY = 1
        strideX = stride[0]
        strideY = 0
      } else if (shape.length === 2) {
        shapeX = shape[0]
        shapeY = shape[1]
        strideX = stride[0]
        strideY = stride[1]
      } else {
        check.raise('invalid shape')
      }

      buffer.dtype = dtype || typedArrayCode(data.data) || GL_FLOAT
      buffer.dimension = shapeY

      var transposeData = pool.allocType(buffer.dtype, shapeX * shapeY)
      transpose(transposeData,
        data.data,
        shapeX, shapeY,
        strideX, strideY,
        offset)
      initBufferFromTypedArray(buffer, transposeData, usage)
      pool.freeType(transposeData)
    } else {
      check.raise('invalid buffer data')
    }
  }

  function destroy (buffer) {
    var handle = buffer.buffer
    check(handle, 'buffer must not be deleted already')
    if (gl.isBuffer(handle)) {
      gl.deleteBuffer(handle)
    }
    buffer.buffer = null
    delete bufferSet[buffer.id]
  }

  function createBuffer (options, type, deferInit) {
    var buffer = new REGLBuffer(type)
    bufferSet[buffer.id] = buffer

    function reglBuffer (options) {
      var usage = GL_STATIC_DRAW
      var data = null
      var byteLength = 0
      var dtype = 0
      var dimension = 1
      if (Array.isArray(options) ||
          isTypedArray(options) ||
          isNDArrayLike(options)) {
        data = options
      } else if (typeof options === 'number') {
        byteLength = options | 0
      } else if (options) {
        check.type(
          options, 'object',
          'buffer arguments must be an object, a number or an array')

        if ('data' in options) {
          check(
            data === null ||
            Array.isArray(data) ||
            isTypedArray(data) ||
            isNDArrayLike(data),
            'invalid data for buffer')
          data = options.data
        }

        if ('usage' in options) {
          check.parameter(options.usage, usageTypes, 'invalid buffer usage')
          usage = usageTypes[options.usage]
        }

        if ('type' in options) {
          check.parameter(options.type, bufferTypes, 'invalid buffer type')
          dtype = bufferTypes[options.type]
        }

        if ('dimension' in options) {
          check.type(options.dimension, 'number', 'invalid dimension')
          dimension = options.dimension | 0
        }

        if ('length' in options) {
          check.nni(byteLength, 'buffer length must be a nonnegative integer')
          byteLength = options.length | 0
        }
      }

      buffer.bind()
      if (!data) {
        gl.bufferData(buffer.type, byteLength, usage)
        buffer.dtype = dtype || GL_UNSIGNED_BYTE
        buffer.usage = usage
        buffer.dimension = dimension
        buffer.byteLength = byteLength
      } else {
        initBufferFromData(buffer, data, usage, dtype, dimension)
      }

      return reglBuffer
    }

    function setSubData (data, offset) {
      check(offset + data.byteLength <= buffer.byteLength,
        'invalid buffer subdata call, buffer is too small')
      gl.bufferSubData(buffer.type, offset, data)
    }

    function subdata (data, offset_) {
      var offset = (offset_ || 0) | 0
      buffer.bind()
      if (Array.isArray(data)) {
        if (data.length > 0 && Array.isArray(data[0])) {
          var dimension = data[0].length
          var flatData = pool.allocType(buffer.dtype, data.length * dimension)
          flatten(flatData, data, dimension)
          setSubData(flatData, offset)
          pool.freeType(flatData)
        } else {
          var converted = pool.allocType(buffer.dtype, data.length)
          copyArray(converted, data)
          setSubData(converted, offset)
          pool.freeType(converted)
        }
      } else if (isTypedArray(data)) {
        setSubData(data, offset)
      } else if (isNDArrayLike(data)) {
        var shape = data.shape
        var stride = data.stride

        var shapeX = 0
        var shapeY = 0
        var strideX = 0
        var strideY = 0
        if (shape.length === 1) {
          shapeX = shape[0]
          shapeY = 1
          strideX = stride[0]
          strideY = 0
        } else if (shape.length === 2) {
          shapeX = shape[0]
          shapeY = shape[1]
          strideX = stride[0]
          strideY = stride[1]
        } else {
          check.raise('invalid shape')
        }
        var dtype = Array.isArray(data.data)
          ? buffer.dtype
          : typedArrayCode(data.data)

        var transposeData = pool.allocType(dtype, shapeX * shapeY)
        transpose(transposeData,
          data.data,
          shapeX, shapeY,
          strideX, strideY,
          data.offset)
        setSubData(transposeData, offset)
        pool.freeType(transposeData)
      } else {
        check.raise('invalid data for buffer subdata')
      }
      return reglBuffer
    }

    if (!deferInit) {
      reglBuffer(options)
    }

    reglBuffer._reglType = 'buffer'
    reglBuffer._buffer = buffer
    reglBuffer.subdata = subdata
    reglBuffer.destroy = function () { destroy(buffer) }

    return reglBuffer
  }

  return {
    create: createBuffer,

    createStream: createStream,
    destroyStream: destroyStream,

    clear: function () {
      values(bufferSet).forEach(destroy)
    },

    getBuffer: function (wrapper) {
      if (wrapper && wrapper._buffer instanceof REGLBuffer) {
        return wrapper._buffer
      }
      return null
    },

    _initBuffer: initBufferFromData
  }
}
<commit_msg>Made the error message generated by setSubData much more clear and useful for debugging.<commit_after>var check = require('./util/check')
var isTypedArray = require('./util/is-typed-array')
var isNDArrayLike = require('./util/is-ndarray')
var values = require('./util/values')
var flatten = require('./util/flatten')
var transpose = require('./util/transpose')
var pool = require('./util/pool')

var arrayTypes = require('./constants/arraytypes.json')
var bufferTypes = require('./constants/dtypes.json')
var usageTypes = require('./constants/usage.json')

var GL_STATIC_DRAW = 0x88E4
var GL_STREAM_DRAW = 0x88E0

var GL_UNSIGNED_BYTE = 5121
var GL_FLOAT = 5126

function typedArrayCode (data) {
  return arrayTypes[Object.prototype.toString.call(data)] | 0
}

function copyArray (out, inp) {
  for (var i = 0; i < inp.length; ++i) {
    out[i] = inp[i]
  }
}

module.exports = function wrapBufferState (gl) {
  var bufferCount = 0
  var bufferSet = {}

  function REGLBuffer (type) {
    this.id = bufferCount++
    this.buffer = gl.createBuffer()
    this.type = type
    this.usage = GL_STATIC_DRAW
    this.byteLength = 0
    this.dimension = 1
    this.dtype = GL_UNSIGNED_BYTE
  }

  REGLBuffer.prototype.bind = function () {
    gl.bindBuffer(this.type, this.buffer)
  }

  var streamPool = []

  function createStream (type, data) {
    var buffer = streamPool.pop()
    if (!buffer) {
      buffer = new REGLBuffer(type)
      buffer.buffer = gl.createBuffer()
    }
    buffer.bind()
    initBufferFromData(buffer, data, GL_STREAM_DRAW, 0, 1)
    return buffer
  }

  function destroyStream (stream) {
    streamPool.push(stream)
  }

  function initBufferFromTypedArray (buffer, data, usage) {
    buffer.byteLength = data.byteLength
    gl.bufferData(buffer.type, data, usage)
  }

  function initBufferFromData (buffer, data, usage, dtype, dimension) {
    buffer.usage = usage
    if (Array.isArray(data)) {
      buffer.dtype = dtype || GL_FLOAT
      if (data.length > 0 && Array.isArray(data[0])) {
        buffer.dimension = data[0].length
        var flatData = pool.allocType(
          buffer.dtype,
          data.length * buffer.dimension)
        flatten(flatData, data, buffer.dimension)
        initBufferFromTypedArray(buffer, flatData, usage)
        pool.freeType(flatData)
      } else {
        buffer.dimension = dimension
        var typedData = pool.allocType(buffer.dtype, data.length)
        copyArray(typedData, data)
        initBufferFromTypedArray(buffer, typedData, usage)
        pool.freeType(typedData)
      }
    } else if (isTypedArray(data)) {
      buffer.dtype = dtype || typedArrayCode(data)
      buffer.dimension = dimension
      initBufferFromTypedArray(buffer, data, usage)
    } else if (isNDArrayLike(data)) {
      var shape = data.shape
      var stride = data.stride
      var offset = data.offset

      var shapeX = 0
      var shapeY = 0
      var strideX = 0
      var strideY = 0
      if (shape.length === 1) {
        shapeX = shape[0]
        shapeY = 1
        strideX = stride[0]
        strideY = 0
      } else if (shape.length === 2) {
        shapeX = shape[0]
        shapeY = shape[1]
        strideX = stride[0]
        strideY = stride[1]
      } else {
        check.raise('invalid shape')
      }

      buffer.dtype = dtype || typedArrayCode(data.data) || GL_FLOAT
      buffer.dimension = shapeY

      var transposeData = pool.allocType(buffer.dtype, shapeX * shapeY)
      transpose(transposeData,
        data.data,
        shapeX, shapeY,
        strideX, strideY,
        offset)
      initBufferFromTypedArray(buffer, transposeData, usage)
      pool.freeType(transposeData)
    } else {
      check.raise('invalid buffer data')
    }
  }

  function destroy (buffer) {
    var handle = buffer.buffer
    check(handle, 'buffer must not be deleted already')
    if (gl.isBuffer(handle)) {
      gl.deleteBuffer(handle)
    }
    buffer.buffer = null
    delete bufferSet[buffer.id]
  }

  function createBuffer (options, type, deferInit) {
    var buffer = new REGLBuffer(type)
    bufferSet[buffer.id] = buffer

    function reglBuffer (options) {
      var usage = GL_STATIC_DRAW
      var data = null
      var byteLength = 0
      var dtype = 0
      var dimension = 1
      if (Array.isArray(options) ||
          isTypedArray(options) ||
          isNDArrayLike(options)) {
        data = options
      } else if (typeof options === 'number') {
        byteLength = options | 0
      } else if (options) {
        check.type(
          options, 'object',
          'buffer arguments must be an object, a number or an array')

        if ('data' in options) {
          check(
            data === null ||
            Array.isArray(data) ||
            isTypedArray(data) ||
            isNDArrayLike(data),
            'invalid data for buffer')
          data = options.data
        }

        if ('usage' in options) {
          check.parameter(options.usage, usageTypes, 'invalid buffer usage')
          usage = usageTypes[options.usage]
        }

        if ('type' in options) {
          check.parameter(options.type, bufferTypes, 'invalid buffer type')
          dtype = bufferTypes[options.type]
        }

        if ('dimension' in options) {
          check.type(options.dimension, 'number', 'invalid dimension')
          dimension = options.dimension | 0
        }

        if ('length' in options) {
          check.nni(byteLength, 'buffer length must be a nonnegative integer')
          byteLength = options.length | 0
        }
      }

      buffer.bind()
      if (!data) {
        gl.bufferData(buffer.type, byteLength, usage)
        buffer.dtype = dtype || GL_UNSIGNED_BYTE
        buffer.usage = usage
        buffer.dimension = dimension
        buffer.byteLength = byteLength
      } else {
        initBufferFromData(buffer, data, usage, dtype, dimension)
      }

      return reglBuffer
    }

    function setSubData (data, offset) {
      check(offset + data.byteLength <= buffer.byteLength,
        'invalid buffer subdata call, buffer is too small. ' + ' Can\'t write data of size ' + data.byteLength + ' starting from offset ' + offset + ' to a buffer of size ' + buffer.byteLength)

      gl.bufferSubData(buffer.type, offset, data)
    }

    function subdata (data, offset_) {
      var offset = (offset_ || 0) | 0
      buffer.bind()
      if (Array.isArray(data)) {
        if (data.length > 0 && Array.isArray(data[0])) {
          var dimension = data[0].length
          var flatData = pool.allocType(buffer.dtype, data.length * dimension)
          flatten(flatData, data, dimension)
          setSubData(flatData, offset)
          pool.freeType(flatData)
        } else {
          var converted = pool.allocType(buffer.dtype, data.length)
          copyArray(converted, data)
          setSubData(converted, offset)
          pool.freeType(converted)
        }
      } else if (isTypedArray(data)) {
        setSubData(data, offset)
      } else if (isNDArrayLike(data)) {
        var shape = data.shape
        var stride = data.stride

        var shapeX = 0
        var shapeY = 0
        var strideX = 0
        var strideY = 0
        if (shape.length === 1) {
          shapeX = shape[0]
          shapeY = 1
          strideX = stride[0]
          strideY = 0
        } else if (shape.length === 2) {
          shapeX = shape[0]
          shapeY = shape[1]
          strideX = stride[0]
          strideY = stride[1]
        } else {
          check.raise('invalid shape')
        }
        var dtype = Array.isArray(data.data)
          ? buffer.dtype
          : typedArrayCode(data.data)

        var transposeData = pool.allocType(dtype, shapeX * shapeY)
        transpose(transposeData,
          data.data,
          shapeX, shapeY,
          strideX, strideY,
          data.offset)
        setSubData(transposeData, offset)
        pool.freeType(transposeData)
      } else {
        check.raise('invalid data for buffer subdata')
      }
      return reglBuffer
    }

    if (!deferInit) {
      reglBuffer(options)
    }

    reglBuffer._reglType = 'buffer'
    reglBuffer._buffer = buffer
    reglBuffer.subdata = subdata
    reglBuffer.destroy = function () { destroy(buffer) }

    return reglBuffer
  }

  return {
    create: createBuffer,

    createStream: createStream,
    destroyStream: destroyStream,

    clear: function () {
      values(bufferSet).forEach(destroy)
    },

    getBuffer: function (wrapper) {
      if (wrapper && wrapper._buffer instanceof REGLBuffer) {
        return wrapper._buffer
      }
      return null
    },

    _initBuffer: initBufferFromData
  }
}
<|endoftext|>


<commit_before>// @flow

import type {
  NavigationScreenProp,
  NavigationState,
  NavigationDescriptor,
  NavigationStackScreenOptions,
  NavigationRouteConfigMap,
  NavigationTransitionProps,
  NavigationScene,
} from '@react-navigation/core';
import type {
  StackNavigatorConfig,
} from 'react-navigation';

import * as React from 'react';
import {
  View,
  StyleSheet,
  Animated as BaseAnimated,
  Easing as BaseEasing,
} from 'react-native';
import {
  StackRouter,
  createNavigator,
  StackActions,
} from '@react-navigation/core';
import { Transitioner } from 'react-navigation-stack';
import Animated, { Easing } from 'react-native-reanimated';

const LightboxPositionContext: React.Context<Animated.Value>
  = React.createContext(null);

function createLightboxNavigator(
  routeConfigMap: NavigationRouteConfigMap,
  stackConfig: StackNavigatorConfig = {},
) {
  const {
    initialRouteName,
    initialRouteParams,
    paths,
    defaultNavigationOptions,
    initialRouteKey,
  } = stackConfig;
  const stackRouterConfig = {
    initialRouteName,
    initialRouteParams,
    paths,
    defaultNavigationOptions,
    initialRouteKey,
  };
  return createNavigator<
    NavigationStackScreenOptions,
    NavigationState,
    StackNavigatorConfig,
  >(
    Lightbox,
    StackRouter(routeConfigMap, stackRouterConfig),
    stackConfig,
  );
}

type Props = $ReadOnly<{
  navigation: NavigationScreenProp<NavigationState>,
  descriptors: { [key: string]: NavigationDescriptor },
  navigationConfig: StackNavigatorConfig,
}>;
class Lightbox extends React.PureComponent<Props> {

  position: Animated.Value;

  constructor(props: Props) {
    super(props);
    this.position = new Animated.Value(props.navigation.state.index);
  }

  render() {
    return (
      <Transitioner
        render={this.renderScenes}
        configureTransition={this.configureTransition}
        navigation={this.props.navigation}
        descriptors={this.props.descriptors}
        onTransitionStart={this.onTransitionStart}
        onTransitionEnd={this.onTransitionEnd}
      />
    );
  }

  configureTransition = () => ({
    duration: 250,
    easing: BaseEasing.inOut(BaseEasing.ease),
    timing: BaseAnimated.timing,
    useNativeDriver: true,
  })

  onTransitionStart = (transitionProps: NavigationTransitionProps) => {
    const { index } = transitionProps.navigation.state;
    Animated.timing(
      this.position,
      {
        duration: 250,
        easing: Easing.inOut(Easing.ease),
        toValue: index,
      },
    ).start();
  }

  onTransitionEnd = (transitionProps: NavigationTransitionProps) => {
    if (!transitionProps.navigation.state.isTransitioning) {
      return;
    }
    const { navigation } = this.props;
    const transitionDestKey = transitionProps.scene.route.key;
    const isCurrentKey =
      navigation.state.routes[navigation.state.index].key === transitionDestKey;
    if (!isCurrentKey) {
      return;
    }
    navigation.dispatch(
      StackActions.completeTransition({ toChildKey: transitionDestKey }),
    );
  }

  renderScenes = (transitionProps: NavigationTransitionProps) => {
    const { scenes } = transitionProps;
    const renderScene =
      (scene: NavigationScene) => this.renderScene(
        scene,
        transitionProps,
      );
    return (
      <LightboxPositionContext.Provider value={this.position}>
        {scenes.map(renderScene)}
      </LightboxPositionContext.Provider>
    );
  }

  renderScene(scene: NavigationScene, transitionProps: NavigationTransitionProps) {
    if (!scene.descriptor) {
      return null;
    }
    const { navigation, getComponent } = scene.descriptor;
    const SceneComponent = getComponent();
    return (
      <View style={styles.scene} key={scene.key}>
        <SceneComponent
          navigation={navigation}
          scene={scene}
          transitionProps={transitionProps}
          position={this.position}
        />
      </View>
    );
  }

}

const styles = StyleSheet.create({
  scene: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
});

function withLightboxPositionContext<
  AllProps: {},
  ComponentType: React.ComponentType<AllProps>,
>(Component: ComponentType): React.ComponentType<$Diff<
  React.ElementConfig<ComponentType>,
  { lightboxPosition: ?Animated.Value },
>> {
  class LightboxPositionHOC extends React.PureComponent<$Diff<
    React.ElementConfig<ComponentType>,
    { lightboxPosition: ?Animated.Value },
  >> {
    static contextType = LightboxPositionContext;
    render() {
      const lightboxPosition = this.context;
      return <Component {...this.props} lightboxPosition={lightboxPosition} />;
    }
  }
  return LightboxPositionHOC;
}

export {
  createLightboxNavigator,
  withLightboxPositionContext,
};
<commit_msg>[native] Avoid contextType because of uncertainty around PureComponent<commit_after>// @flow

import type {
  NavigationScreenProp,
  NavigationState,
  NavigationDescriptor,
  NavigationStackScreenOptions,
  NavigationRouteConfigMap,
  NavigationTransitionProps,
  NavigationScene,
} from '@react-navigation/core';
import type {
  StackNavigatorConfig,
} from 'react-navigation';

import * as React from 'react';
import {
  View,
  StyleSheet,
  Animated as BaseAnimated,
  Easing as BaseEasing,
} from 'react-native';
import {
  StackRouter,
  createNavigator,
  StackActions,
} from '@react-navigation/core';
import { Transitioner } from 'react-navigation-stack';
import Animated, { Easing } from 'react-native-reanimated';

const LightboxPositionContext: React.Context<Animated.Value>
  = React.createContext(null);

function createLightboxNavigator(
  routeConfigMap: NavigationRouteConfigMap,
  stackConfig: StackNavigatorConfig = {},
) {
  const {
    initialRouteName,
    initialRouteParams,
    paths,
    defaultNavigationOptions,
    initialRouteKey,
  } = stackConfig;
  const stackRouterConfig = {
    initialRouteName,
    initialRouteParams,
    paths,
    defaultNavigationOptions,
    initialRouteKey,
  };
  return createNavigator<
    NavigationStackScreenOptions,
    NavigationState,
    StackNavigatorConfig,
  >(
    Lightbox,
    StackRouter(routeConfigMap, stackRouterConfig),
    stackConfig,
  );
}

type Props = $ReadOnly<{
  navigation: NavigationScreenProp<NavigationState>,
  descriptors: { [key: string]: NavigationDescriptor },
  navigationConfig: StackNavigatorConfig,
}>;
class Lightbox extends React.PureComponent<Props> {

  position: Animated.Value;

  constructor(props: Props) {
    super(props);
    this.position = new Animated.Value(props.navigation.state.index);
  }

  render() {
    return (
      <Transitioner
        render={this.renderScenes}
        configureTransition={this.configureTransition}
        navigation={this.props.navigation}
        descriptors={this.props.descriptors}
        onTransitionStart={this.onTransitionStart}
        onTransitionEnd={this.onTransitionEnd}
      />
    );
  }

  configureTransition = () => ({
    duration: 250,
    easing: BaseEasing.inOut(BaseEasing.ease),
    timing: BaseAnimated.timing,
    useNativeDriver: true,
  })

  onTransitionStart = (transitionProps: NavigationTransitionProps) => {
    const { index } = transitionProps.navigation.state;
    Animated.timing(
      this.position,
      {
        duration: 250,
        easing: Easing.inOut(Easing.ease),
        toValue: index,
      },
    ).start();
  }

  onTransitionEnd = (transitionProps: NavigationTransitionProps) => {
    if (!transitionProps.navigation.state.isTransitioning) {
      return;
    }
    const { navigation } = this.props;
    const transitionDestKey = transitionProps.scene.route.key;
    const isCurrentKey =
      navigation.state.routes[navigation.state.index].key === transitionDestKey;
    if (!isCurrentKey) {
      return;
    }
    navigation.dispatch(
      StackActions.completeTransition({ toChildKey: transitionDestKey }),
    );
  }

  renderScenes = (transitionProps: NavigationTransitionProps) => {
    const { scenes } = transitionProps;
    const renderScene =
      (scene: NavigationScene) => this.renderScene(
        scene,
        transitionProps,
      );
    return (
      <LightboxPositionContext.Provider value={this.position}>
        {scenes.map(renderScene)}
      </LightboxPositionContext.Provider>
    );
  }

  renderScene(scene: NavigationScene, transitionProps: NavigationTransitionProps) {
    if (!scene.descriptor) {
      return null;
    }
    const { navigation, getComponent } = scene.descriptor;
    const SceneComponent = getComponent();
    return (
      <View style={styles.scene} key={scene.key}>
        <SceneComponent
          navigation={navigation}
          scene={scene}
          transitionProps={transitionProps}
          position={this.position}
        />
      </View>
    );
  }

}

const styles = StyleSheet.create({
  scene: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
});

function withLightboxPositionContext<
  AllProps: {},
  ComponentType: React.ComponentType<AllProps>,
>(Component: ComponentType): React.ComponentType<$Diff<
  React.ElementConfig<ComponentType>,
  { lightboxPosition: ?Animated.Value },
>> {
  class LightboxPositionHOC extends React.PureComponent<$Diff<
    React.ElementConfig<ComponentType>,
    { lightboxPosition: ?Animated.Value },
  >> {
    render() {
      return (
        <LightboxPositionContext.Consumer>
          {value => (<Component {...this.props} lightboxPosition={value} />)}
        </LightboxPositionContext.Consumer>
      );
    }
  }
  return LightboxPositionHOC;
}

export {
  createLightboxNavigator,
  withLightboxPositionContext,
};
<|endoftext|>


<commit_before>
/**
 * Buffer utility functions
 * @type {Object}
 */
module.exports = {

  /**
   * [indexOf description]
   *
   * @param  {[type]} needle   [description]
   * @param  {[type]} haystack [description]
   * @return {[type]}          [description]
   */
  indexOf: function( needle, haystack ) {

    var i, k;
    var n = needle.length;
    var m = haystack.length;

    if( n === 0 ) return n;

    var charTable = moore.makeCharTable( needle );
    var offsetTable = moore.makeOffsetTable( needle );

    for( i = n - 1; i < m; ) {
      for( k = n - 1; needle[k] === haystack[i]; --i, --k ) {
        if( k === 0 ) return i;
      }
      // i += n - k; // for naive method
      i += Math.max(
        offsetTable[ n - 1 - k ],
        charTable[ haystack[i] ]
      );
    }

    return -1;

  }

};

/**
 * Boyer-Moore specific functions
 * @type {Object}
 */
var moore = {

  /**
   * Makes the jump table based on the
   * mismatched character information.
   *
   * @param  {Buffer} needle
   * @return {Uint32Array}
   */
  makeCharTable: function( needle ) {

    var table = new Uint32Array( this.alphabetSize );
    var n = needle.length;
    var t = table.length;
    var i = 0;

    for( ; i < t; ++i ) {
      table[i] = n;
    }

    n--;

    for( i = 0; i < n; ++i ) {
      table[ needle[i] ] = n - i;
    }

    return table;

  },

  /**
   * Makes the jump table based on the
   * scan offset which mismatch occurs.
   *
   * @param  {Buffer} needle
   * @return {Uint32Array}
   */
  makeOffsetTable: function( needle ) {

    var i, suffix;
    var n = needle.length;
    var m = n - 1;
    var lastPrefix = n;
    var table = new Uint32Array( n );

    for( i = m; i >= 0; --i ) {
      if( this.isPrefix( needle, i + 1 ) ) {
        lastPrefix = i + 1;
      }
      table[ m - i ] = lastPrefix - i + m;
    }

    for( i = 0; i < n; ++i ) {
      suffix = this.suffixLength( needle, i );
      table[ suffix ] = m - i + suffix;
    }

    return table;

  },

  /**
   * Is `needle[i:end]` a prefix of `needle`?
   *
   * @param  {Buffer}  needle
   * @param  {Number}  i
   * @return {Boolean}
   */
  isPrefix: function( needle, i ) {

    var k = 0;
    var n = needle.length;

    for( ; i < n; ++i, ++k ) {
      if( needle[i] !== needle[k] ) {
        return false;
      }
    }

    return true;

  },

  /**
   * Determines the maximum length of the
   * substring that ends at `i` and is a suffix.
   *
   * @param  {Buffer} needle
   * @param  {Number} i
   * @return {Number}
   */
  suffixLength: function( needle, i ) {

    var k = 0;
    var n = needle.length;
    var m = n - 1;

    for( ; i >= 0 && needle[i] === needle[m]; --i, --m ) {
      k += 1;
    }

    return k;

  }

};
<commit_msg>Added buffer.split()<commit_after>
/**
 * Buffer utility functions
 * @type {Object}
 */
module.exports = {

  /**
   * [indexOf description]
   *
   * @param  {[type]} needle   [description]
   * @param  {[type]} haystack [description]
   * @return {[type]}          [description]
   */
  indexOf: function( needle, haystack ) {

    var i, k;
    var n = needle.length;
    var m = haystack.length;

    if( n === 0 ) return n;

    var charTable = moore.makeCharTable( needle );
    var offsetTable = moore.makeOffsetTable( needle );

    for( i = n - 1; i < m; ) {
      for( k = n - 1; needle[k] === haystack[i]; --i, --k ) {
        if( k === 0 ) return i;
      }
      // i += n - k; // for naive method
      i += Math.max(
        offsetTable[ n - 1 - k ],
        charTable[ haystack[i] ]
      );
    }

    return -1;

  },

  /**
   * [split description]
   *
   * @param  {[type]} needle   [description]
   * @param  {[type]} haystack [description]
   * @return {[type]}          [description]
   */
  split: function( needle, haystack ) {

    var i, k;
    var n = needle.length;
    var m = haystack.length;
    var r = [];

    if( n === 0 ) return n;

    var charTable = moore.makeCharTable( needle );
    var offsetTable = moore.makeOffsetTable( needle );

    for( i = n - 1; i < m; ) {
      for( k = n - 1; needle[k] === haystack[i]; --i, --k ) {
        if( k === 0 ) {
          r.push( i );
          break;
        }
      }
      i += Math.max(
        offsetTable[ n - 1 - k ],
        charTable[ haystack[i] ]
      );
    }

    for( i = 0, m = r.length; i < m; i++ ) {
      if( i === 0 ) r[i] = haystack.slice( 0, r[i] );
      else r[i] = haystack.slice( r[i], r[ i + 1 ] );
    }

    return r;

  }

};

/**
 * Boyer-Moore specific functions
 * @type {Object}
 */
var moore = {

  /**
   * Makes the jump table based on the
   * mismatched character information.
   *
   * @param  {Buffer} needle
   * @return {Uint32Array}
   */
  makeCharTable: function( needle ) {

    var table = new Uint32Array( this.alphabetSize );
    var n = needle.length;
    var t = table.length;
    var i = 0;

    for( ; i < t; ++i ) {
      table[i] = n;
    }

    n--;

    for( i = 0; i < n; ++i ) {
      table[ needle[i] ] = n - i;
    }

    return table;

  },

  /**
   * Makes the jump table based on the
   * scan offset which mismatch occurs.
   *
   * @param  {Buffer} needle
   * @return {Uint32Array}
   */
  makeOffsetTable: function( needle ) {

    var i, suffix;
    var n = needle.length;
    var m = n - 1;
    var lastPrefix = n;
    var table = new Uint32Array( n );

    for( i = m; i >= 0; --i ) {
      if( this.isPrefix( needle, i + 1 ) ) {
        lastPrefix = i + 1;
      }
      table[ m - i ] = lastPrefix - i + m;
    }

    for( i = 0; i < n; ++i ) {
      suffix = this.suffixLength( needle, i );
      table[ suffix ] = m - i + suffix;
    }

    return table;

  },

  /**
   * Is `needle[i:end]` a prefix of `needle`?
   *
   * @param  {Buffer}  needle
   * @param  {Number}  i
   * @return {Boolean}
   */
  isPrefix: function( needle, i ) {

    var k = 0;
    var n = needle.length;

    for( ; i < n; ++i, ++k ) {
      if( needle[i] !== needle[k] ) {
        return false;
      }
    }

    return true;

  },

  /**
   * Determines the maximum length of the
   * substring that ends at `i` and is a suffix.
   *
   * @param  {Buffer} needle
   * @param  {Number} i
   * @return {Number}
   */
  suffixLength: function( needle, i ) {

    var k = 0;
    var n = needle.length;
    var m = n - 1;

    for( ; i >= 0 && needle[i] === needle[m]; --i, --m ) {
      k += 1;
    }

    return k;

  }

};
<|endoftext|>


<commit_before>// TODO this is a file of common android helpers that should be moved into
// appium-android-driver or similar
import _ from 'lodash';
import path from 'path';
import { exec } from 'teen_process';
import { retry, retryInterval } from 'asyncbox';
import logger from './logger';
import { fs } from 'appium-support';
import { path as unicodeIMEPath } from 'appium-android-ime';
import { path as settingsApkPath } from 'io.appium.settings';
import { path as unlockApkPath } from 'appium-unlock';

const REMOTE_TEMP_PATH = "/data/local/tmp";

let helpers = {};

helpers.parseJavaVersion = function (stderr) {
  let lines = stderr.split("\n");
  for (let line of lines) {
    if (new RegExp("java version").test(line)) {
      return line.split(" ")[2].replace(/"/g, '');
    }
  }
  return null;
};

helpers.getJavaVersion = async function () {
  logger.debug("Getting Java version");

  let {stderr} = await exec('java', ['-version']);
  let javaVer = helpers.parseJavaVersion(stderr);
  if (javaVer === null) {
    throw new Error("Could not get the Java version. Is Java installed?");
  }
  logger.info(`Java version is: ${javaVer}`);
  return javaVer;
};

helpers.prepareEmulator = async function (adb, opts) {
  let {avd, avdArgs, language, locale, avdLaunchTimeout,
       avdReadyTimeout} = opts;
  let avdName = avd.replace('@', '');
  let runningAVD = await adb.getRunningAVD(avdName);
  if (runningAVD !== null) {
    logger.debug("Not launching AVD because it is already running.");
    return;
  }
  await adb.launchAVD(avd, avdArgs, language, locale, avdLaunchTimeout,
                      avdReadyTimeout);
};

helpers.ensureDeviceLocale = async function (adb, language, locale) {
  let haveLanguage = language && typeof language === "string";
  let haveCountry = locale && typeof locale === "string";
  if (!haveLanguage && !haveCountry) {
    return;
  }
  let curLanguage = await adb.getDeviceLanguage();
  let country = await adb.getDeviceCountry();
  let changed = false;
  if (haveLanguage && language !== curLanguage) {
    await adb.setDeviceLanguage(language);
    changed = true;
  }
  if (haveCountry && locale !== country) {
    await adb.setDeviceCountry(locale);
    changed = true;
  }
  if (changed) {
    await adb.reboot();
  }
};

helpers.getActiveDevice = async function (adb, udid) {
  logger.info('Retrieving device list');
  let devices = await adb.getDevicesWithRetry();
  let deviceId = null, emPort = null;
  if (udid) {
    if (!_.contains(_.pluck(devices, 'udid'), udid)) {
      logger.errorAndThrow(`Device ${udid} was not in the list ` +
                           `of connected devices`);
    }
    deviceId = udid;
  } else {
    deviceId = devices[0].udid;
    emPort = adb.getPortFromEmulatorString(deviceId);
  }
  logger.info(`Found device: ${deviceId}`);
  return [deviceId, emPort];
};

helpers.getLaunchInfo = async function (adb, opts) {
  let {app, appPackage, appActivity, appWaitPackage, appWaitActivity} = opts;
  if (!app) {
    logger.warn("No app sent in, not parsing package/activity");
    return;
  }
  if (appPackage && appActivity) {
    return;
  }

  logger.debug("Parsing package and activity from app manifest");
  let {apkPackage, apkActivity} =
    await adb.packageAndLaunchActivityFromManifest(app);
  if (apkPackage && !appPackage) {
    appPackage = apkPackage;
  }
  if (!appWaitPackage) {
    appWaitPackage = appPackage;
  }
  if (apkActivity && !appActivity) {
    appActivity = apkActivity;
  }
  if (!appWaitActivity) {
    appWaitActivity = appActivity;
  }
  logger.debug(`Parsed package and activity are: ${apkPackage}/${apkActivity}`);
  return {appPackage, appWaitPackage, appActivity, appWaitActivity};
};

helpers.getRemoteApkPath = async function (localApkMd5) {
  let remotePath = path.resolve(REMOTE_TEMP_PATH, `${localApkMd5}.apk`);
  logger.info(`Remote apk path is ${remotePath}`);
  return remotePath;
};

helpers.resetApp = async function (adb, localApkPath, pkg, fastReset) {
  if (fastReset) {
    logger.debug("Running fast reset (stop and clear)");
    await adb.stopAndClear(pkg);
  } else {
    logger.debug("Running old fashion reset (reinstall)");
    let apkMd5 = await fs.md5(localApkPath);
    let remotePath = await helpers.getRemoteApkPath(apkMd5, localApkPath);
    if (await adb.fileExists(remotePath)) {
    } else {
      throw new Error("Can't run slow reset without a remote apk!");
    }
    await helpers.reinstallRemoteApk(adb, localApkPath, pkg, remotePath);
  }
};

helpers.reinstallRemoteApk = async function (adb, localApkPath, pkg,
                                             remotePath, tries = 2) {
  await retry(tries, async () => {
    try {
      // first do an uninstall of the package to make sure it's not there
      await adb.uninstallApk(pkg);
    } catch (e) {
      logger.warn("Uninstalling remote APK failed, maybe it wasn't installed");
    }
    try {
      await adb.installRemote(remotePath);
    } catch (e) {
      logger.warn("Installing remote APK failed, going to uninstall and try " +
                  "again");
      // if remote install failed, remove ALL the apks and re-push ours
      // to the remote cache
      await helpers.removeTempApks([]);
      await adb.push(localApkPath, remotePath);
      throw e; // throw an error to trigger the retry
    }
  });
};

helpers.installApkRemotely = async function (adb, localApkPath, pkg, fastReset) {
  let apkMd5 = await fs.md5(localApkPath);
  let remotePath = await helpers.getRemoteApkPath(apkMd5, localApkPath);
  let remoteApkExists = await adb.fileExists(remotePath);
  let installed = await adb.isAppInstalled(pkg);
  if (installed && remoteApkExists && fastReset) {
    logger.info("Apk is already on remote and installed, resetting");
    await helpers.resetApp(adb, localApkPath, pkg, fastReset);
  } else if (!installed || (!remoteApkExists && fastReset)) {
    if (!installed) {
      logger.info("Apk is not yet installed");
    } else {
      logger.info("Apk was already installed but not from our remote path");
    }
    logger.info("(Re)installing apk from remote");
    await adb.mkdir(REMOTE_TEMP_PATH);
    logger.info("Clearing out any existing remote apks with the same hash");
    await helpers.removeTempApks(adb, [apkMd5]);
    if (!remoteApkExists) {
      // push from local to remote
      await adb.push(localApkPath, remotePath);
    }

    // Next, install from the remote path. This can be flakey. If it doesn't
    // work, clear out any cached apks, re-push from local, and try again
    await helpers.reinstallRemoteApk(adb, localApkPath, pkg, remotePath);
  }
};

helpers.removeRemoteApks = async function (adb, exceptMd5s = null) {
  logger.debug("Removing any old apks");
  if (exceptMd5s) {
    logger.debug(`Except ${JSON.stringify(exceptMd5s)}`);
  } else {
    exceptMd5s = [];
  }
  let apks = await adb.ls(`${REMOTE_TEMP_PATH}/*.apk`);
  if (apks.length < 1) {
    logger.debug("No apks to examine");
    return;
  }
  apks = apks.filter(apk => {
    for (let md5 of exceptMd5s) {
      return apk.indexOf(md5) === -1;
    }
  });
  for (let apk of apks) {
    logger.info(`Will remove {$apk}`);
    await this.adb.shell(['rm', apk]);
  }
};

helpers.initUnicodeKeyboard = async function (adb) {
  logger.debug('Enabling Unicode keyboard support');
  logger.debug("Pushing unicode ime to device...");
  await adb.install(unicodeIMEPath, false);

  // get the default IME so we can return back to it later if we want
  let defaultIME = await adb.defaultIME();

  logger.debug(`Unsetting previous IME ${defaultIME}`);
  const appiumIME = 'io.appium.android.ime/.UnicodeIME';
  logger.debug(`Setting IME to '${appiumIME}'`);
  await adb.enableIME(appiumIME);
  await adb.setIME(appiumIME);
  return defaultIME;
};

helpers.pushSettingsApp = async function (adb) {
  logger.debug("Pushing settings apk to device...");
  await adb.install(settingsApkPath, false);
};

helpers.pushUnlock = async function (adb) {
  logger.debug("Pushing unlock helper app to device...");
  await adb.install(unlockApkPath, false);
};

helpers.unlock = async function (adb) {
  if (!(await adb.isScreenLocked())) {
    logger.info("Screen already unlocked, doing nothing");
    return;
  }
  logger.info("Unlocking screen");
  await retryInterval(10, 1000, async () => {
    logger.debug("Screen is locked, trying to unlock");
    await adb.startApp({
      pkg: "io.appium.unlock",
      activity: ".Unlock",
      action: "android.intent.action.MAIN",
      category: "android.intent.category.LAUNCHER",
      flags: "0x10200000"
    });
    if (await adb.screenIsLocked()) {
      logger.debug("Screen unlocked successfully");
    } else {
      throw new Error("Screen did not unlock successfully, retrying");
    }
  });
};

export default helpers;
<commit_msg>fix non-existent use of removeTempApks to removeRemoteApks<commit_after>// TODO this is a file of common android helpers that should be moved into
// appium-android-driver or similar
import _ from 'lodash';
import path from 'path';
import { exec } from 'teen_process';
import { retry, retryInterval } from 'asyncbox';
import logger from './logger';
import { fs } from 'appium-support';
import { path as unicodeIMEPath } from 'appium-android-ime';
import { path as settingsApkPath } from 'io.appium.settings';
import { path as unlockApkPath } from 'appium-unlock';

const REMOTE_TEMP_PATH = "/data/local/tmp";

let helpers = {};

helpers.parseJavaVersion = function (stderr) {
  let lines = stderr.split("\n");
  for (let line of lines) {
    if (new RegExp("java version").test(line)) {
      return line.split(" ")[2].replace(/"/g, '');
    }
  }
  return null;
};

helpers.getJavaVersion = async function () {
  logger.debug("Getting Java version");

  let {stderr} = await exec('java', ['-version']);
  let javaVer = helpers.parseJavaVersion(stderr);
  if (javaVer === null) {
    throw new Error("Could not get the Java version. Is Java installed?");
  }
  logger.info(`Java version is: ${javaVer}`);
  return javaVer;
};

helpers.prepareEmulator = async function (adb, opts) {
  let {avd, avdArgs, language, locale, avdLaunchTimeout,
       avdReadyTimeout} = opts;
  let avdName = avd.replace('@', '');
  let runningAVD = await adb.getRunningAVD(avdName);
  if (runningAVD !== null) {
    logger.debug("Not launching AVD because it is already running.");
    return;
  }
  await adb.launchAVD(avd, avdArgs, language, locale, avdLaunchTimeout,
                      avdReadyTimeout);
};

helpers.ensureDeviceLocale = async function (adb, language, locale) {
  let haveLanguage = language && typeof language === "string";
  let haveCountry = locale && typeof locale === "string";
  if (!haveLanguage && !haveCountry) {
    return;
  }
  let curLanguage = await adb.getDeviceLanguage();
  let country = await adb.getDeviceCountry();
  let changed = false;
  if (haveLanguage && language !== curLanguage) {
    await adb.setDeviceLanguage(language);
    changed = true;
  }
  if (haveCountry && locale !== country) {
    await adb.setDeviceCountry(locale);
    changed = true;
  }
  if (changed) {
    await adb.reboot();
  }
};

helpers.getActiveDevice = async function (adb, udid) {
  logger.info('Retrieving device list');
  let devices = await adb.getDevicesWithRetry();
  let deviceId = null, emPort = null;
  if (udid) {
    if (!_.contains(_.pluck(devices, 'udid'), udid)) {
      logger.errorAndThrow(`Device ${udid} was not in the list ` +
                           `of connected devices`);
    }
    deviceId = udid;
  } else {
    deviceId = devices[0].udid;
    emPort = adb.getPortFromEmulatorString(deviceId);
  }
  logger.info(`Found device: ${deviceId}`);
  return [deviceId, emPort];
};

helpers.getLaunchInfo = async function (adb, opts) {
  let {app, appPackage, appActivity, appWaitPackage, appWaitActivity} = opts;
  if (!app) {
    logger.warn("No app sent in, not parsing package/activity");
    return;
  }
  if (appPackage && appActivity) {
    return;
  }

  logger.debug("Parsing package and activity from app manifest");
  let {apkPackage, apkActivity} =
    await adb.packageAndLaunchActivityFromManifest(app);
  if (apkPackage && !appPackage) {
    appPackage = apkPackage;
  }
  if (!appWaitPackage) {
    appWaitPackage = appPackage;
  }
  if (apkActivity && !appActivity) {
    appActivity = apkActivity;
  }
  if (!appWaitActivity) {
    appWaitActivity = appActivity;
  }
  logger.debug(`Parsed package and activity are: ${apkPackage}/${apkActivity}`);
  return {appPackage, appWaitPackage, appActivity, appWaitActivity};
};

helpers.getRemoteApkPath = async function (localApkMd5) {
  let remotePath = path.resolve(REMOTE_TEMP_PATH, `${localApkMd5}.apk`);
  logger.info(`Remote apk path is ${remotePath}`);
  return remotePath;
};

helpers.resetApp = async function (adb, localApkPath, pkg, fastReset) {
  if (fastReset) {
    logger.debug("Running fast reset (stop and clear)");
    await adb.stopAndClear(pkg);
  } else {
    logger.debug("Running old fashion reset (reinstall)");
    let apkMd5 = await fs.md5(localApkPath);
    let remotePath = await helpers.getRemoteApkPath(apkMd5, localApkPath);
    if (await adb.fileExists(remotePath)) {
    } else {
      throw new Error("Can't run slow reset without a remote apk!");
    }
    await helpers.reinstallRemoteApk(adb, localApkPath, pkg, remotePath);
  }
};

helpers.reinstallRemoteApk = async function (adb, localApkPath, pkg,
                                             remotePath, tries = 2) {
  await retry(tries, async () => {
    try {
      // first do an uninstall of the package to make sure it's not there
      await adb.uninstallApk(pkg);
    } catch (e) {
      logger.warn("Uninstalling remote APK failed, maybe it wasn't installed");
    }
    try {
      await adb.installRemote(remotePath);
    } catch (e) {
      logger.warn("Installing remote APK failed, going to uninstall and try " +
                  "again");
      // if remote install failed, remove ALL the apks and re-push ours
      // to the remote cache
      await helpers.removeRemoteApks(adb);
      await adb.push(localApkPath, remotePath);
      throw e; // throw an error to trigger the retry
    }
  });
};

helpers.installApkRemotely = async function (adb, localApkPath, pkg, fastReset) {
  let apkMd5 = await fs.md5(localApkPath);
  let remotePath = await helpers.getRemoteApkPath(apkMd5, localApkPath);
  let remoteApkExists = await adb.fileExists(remotePath);
  let installed = await adb.isAppInstalled(pkg);
  if (installed && remoteApkExists && fastReset) {
    logger.info("Apk is already on remote and installed, resetting");
    await helpers.resetApp(adb, localApkPath, pkg, fastReset);
  } else if (!installed || (!remoteApkExists && fastReset)) {
    if (!installed) {
      logger.info("Apk is not yet installed");
    } else {
      logger.info("Apk was already installed but not from our remote path");
    }
    logger.info("(Re)installing apk from remote");
    await adb.mkdir(REMOTE_TEMP_PATH);
    logger.info("Clearing out any existing remote apks with the same hash");
    await helpers.removeRemoteApks(adb, [apkMd5]);
    if (!remoteApkExists) {
      // push from local to remote
      await adb.push(localApkPath, remotePath);
    }

    // Next, install from the remote path. This can be flakey. If it doesn't
    // work, clear out any cached apks, re-push from local, and try again
    await helpers.reinstallRemoteApk(adb, localApkPath, pkg, remotePath);
  }
};

helpers.removeRemoteApks = async function (adb, exceptMd5s = null) {
  logger.debug("Removing any old apks");
  if (exceptMd5s) {
    logger.debug(`Except ${JSON.stringify(exceptMd5s)}`);
  } else {
    exceptMd5s = [];
  }
  let apks = await adb.ls(`${REMOTE_TEMP_PATH}/*.apk`);
  if (apks.length < 1) {
    logger.debug("No apks to examine");
    return;
  }
  apks = apks.filter(apk => {
    for (let md5 of exceptMd5s) {
      return apk.indexOf(md5) === -1;
    }
  });
  for (let apk of apks) {
    logger.info(`Will remove {$apk}`);
    await this.adb.shell(['rm', apk]);
  }
};

helpers.initUnicodeKeyboard = async function (adb) {
  logger.debug('Enabling Unicode keyboard support');
  logger.debug("Pushing unicode ime to device...");
  await adb.install(unicodeIMEPath, false);

  // get the default IME so we can return back to it later if we want
  let defaultIME = await adb.defaultIME();

  logger.debug(`Unsetting previous IME ${defaultIME}`);
  const appiumIME = 'io.appium.android.ime/.UnicodeIME';
  logger.debug(`Setting IME to '${appiumIME}'`);
  await adb.enableIME(appiumIME);
  await adb.setIME(appiumIME);
  return defaultIME;
};

helpers.pushSettingsApp = async function (adb) {
  logger.debug("Pushing settings apk to device...");
  await adb.install(settingsApkPath, false);
};

helpers.pushUnlock = async function (adb) {
  logger.debug("Pushing unlock helper app to device...");
  await adb.install(unlockApkPath, false);
};

helpers.unlock = async function (adb) {
  if (!(await adb.isScreenLocked())) {
    logger.info("Screen already unlocked, doing nothing");
    return;
  }
  logger.info("Unlocking screen");
  await retryInterval(10, 1000, async () => {
    logger.debug("Screen is locked, trying to unlock");
    await adb.startApp({
      pkg: "io.appium.unlock",
      activity: ".Unlock",
      action: "android.intent.action.MAIN",
      category: "android.intent.category.LAUNCHER",
      flags: "0x10200000"
    });
    if (await adb.screenIsLocked()) {
      logger.debug("Screen unlocked successfully");
    } else {
      throw new Error("Screen did not unlock successfully, retrying");
    }
  });
};

export default helpers;
<|endoftext|>


<commit_before>/* 17jan14abu
 * (c) Software Lab. Alexander Burger
 */

function csMouseDn(cvs, event) {
   cvs.csDn = true;
   cvs.csDnX = event.layerX - cvs.offsetLeft;
   cvs.csDnY = event.layerY - cvs.offsetTop;
   cvs.csMv = false;
   return false;
}

function csMouseMv(cvs, event) {
   if (!cvs.csDn)
      return true;
   if (drawCanvas(cvs.id, -1, cvs.csMv? -1 : 0, cvs.csDnX, cvs.csDnY,
         event.layerX - cvs.offsetLeft, event.layerY - cvs.offsetTop ) )
      return true;
   cvs.csMv = true;
   return false;
}

function csMouseUp(cvs) {
   cvs.csDn = false;
   if (cvs.clicked) {
      clearTimeout(cvs.clicked);
      cvs.clicked = false;
      return drawCanvas(cvs.id, -1, 2, cvs.csDnX, cvs.csDnY);
   }
   if (cvs.csMv)
      return drawCanvas(cvs.id, -1);
   cvs.clicked = setTimeout(
      function() {
         cvs.clicked = false;
         drawCanvas(cvs.id, -1, 1, cvs.csDnX, cvs.csDnY);
      },
      200 );
   return false;
}

function drawCanvas(id, dly) {
   var req = new XMLHttpRequest();
   var url = document.getElementsByTagName("BASE")[0].href + SesId + "!jsDraw?" + id + "&+" + dly;
   var len = arguments.length;

   for (var i = 2; i < len; ++i)
      url += "&+" + arguments[i];
   try {req.open("POST", url);}
   catch (e) {return true;}
   req.responseType = "arraybuffer";
   req.onload = function() {
      var ele = document.getElementById(id);

      renderCanvas(ele, plio(new Uint8Array(req.response)));
      if (len > 2)
         while (ele = ele.parentNode) {
            if (ele.tagName == "FORM") {
               post(ele, false, null, null);
               break;
            }
         }
      // if (dly == 0)
      //    requestAnimationFrame(function() {drawCanvas(id, dly)});
      // else if (dly > 0)
      //    setTimeout(function() {drawCanvas(id, dly)}, dly);
      if (dly >= 0)
         setTimeout(function() {drawCanvas(id, dly)}, dly);
   }
   try {req.send(null);}
   catch (e) {
      req.abort();
      return true;
   }
   return false;
}

function renderCanvas(cvs, lst) {
   var ctx = cvs.getContext("2d");
   var cmd, i, j;

   for (i = 0; i < lst.length; ++i) {
      switch ((cmd = lst[i])[0]) {  // Sync with "@lib/canvas.l"
      /*** Functions ***/
      case 1:  // (csFillText Str X Y)
         ctx.fillText(cmd[1], cmd[2], cmd[3]);
         break;
      case 2:  // (csStrokeLine X1 Y1 X2 Y2)
         ctx.beginPath();
         ctx.moveTo(cmd[1], cmd[2]);
         ctx.lineTo(cmd[3], cmd[4]);
         ctx.closePath();
         ctx.stroke();
         break;
      case 3:  // (csClearRect X Y DX DY)
         ctx.clearRect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 4:  // (csStrokeRect X Y DX DY)
         ctx.strokeRect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 5:  // (csFillRect X Y DX DY)
         ctx.fillRect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 6:  // (csBeginPath)
         ctx.beginPath();
         break;
      case 7:  // (csClosePath)
         ctx.closePath();
         break;
      case 8:  // (csMoveTo X Y)
         ctx.moveTo(cmd[1], cmd[2]);
         break;
      case 9:  // (csLineTo X Y)
         ctx.lineTo(cmd[1], cmd[2]);
         break;
      case 10:  // (csBezierCurveTo X1 Y1 X2 Y2 X Y)
         ctx.bezierCurveTo(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
         break;
      case 11:  // (csLine X1 Y1 X2 Y2)
         ctx.moveTo(cmd[1], cmd[2]);
         ctx.lineTo(cmd[3], cmd[4]);
         break;
      case 12:  // (csRect X Y DX DY)
         ctx.rect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 13:  // (csArc X Y R A B F)
         ctx.arc(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
         break;
      case 14:  // (csStroke)
         ctx.stroke();
         break;
      case 15:  // (csFill)
         ctx.fill();
         break;
      case 16:  // (csClip)
         ctx.clip();
         break;
      case 17:  // (csDef Key DX DY Lst)
         if (!cvs.pre)
            cvs.pre = new Array();
         var buf = cvs.pre[cmd[1]] = document.createElement('canvas');
         buf.width = cmd[2];
         buf.height = cmd[3];
         renderCanvas(buf, cmd[4]);
         break;
      case 18:  // (csDraw Key X Y)
         ctx.drawImage(cvs.pre[cmd[1]], cmd[2], cmd[3]);
         break;
      case 19:  // (csDrawDots DX DY Lst)
         if (cmd[3])
            for (j = 0; j < cmd[3].length; j += 2)
               ctx.fillRect(cmd[3][j], cmd[3][j+1], cmd[1], cmd[2]);
         break;
      case 20:  // (csDrawImage Img X Y)
         var img = new Image();
         img.src = cmd[1];
         (function (img, cmd) {img.onload = function () {ctx.drawImage(img, cmd[2], cmd[3])}})(img, cmd);
         break;
      case 21:  // (csTranslate X Y)
         ctx.translate(cmd[1], cmd[2]);
         break;
      case 22:  // (csRotate A)
         ctx.rotate(cmd[1]);
         break;
      case 23:  // (csScale X Y)
         ctx.scale(cmd[1], cmd[2]);
         break;
      case 24:  // (csSave)
         ctx.save();
         break;
      case 25:  // (csRestore)
         ctx.restore();
         break;
      /*** Variables ***/
      case 26:  // (csFillStyle V)
         ctx.fillStyle = cmd[1];
         break;
      case 27:  // (csStrokeStyle V)
         ctx.strokeStyle = cmd[1];
         break;
      case 28:  // (csGlobalAlpha V)
         ctx.globalAlpha = cmd[1];
         break;
      case 29:  // (csLineWidth V)
         ctx.lineWidth = cmd[1];
         break;
      case 30:  // (csLineCap V)
         ctx.lineCap = cmd[1];
         break;
      case 31:  // (csLineJoin V)
         ctx.lineJoin = cmd[1];
         break;
      case 32:  // (csMiterLimit V)
         ctx.miterLimit = cmd[1];
         break;
      case 33:  // (csGlobalCompositeOperation V) )
         ctx.globalCompositeOperation = cmd[1];
         break;
      }
   }
}
<commit_msg>Extended 'csDef'<commit_after>/* 19jan14abu
 * (c) Software Lab. Alexander Burger
 */

function csMouseDn(cvs, event) {
   cvs.csDn = true;
   cvs.csDnX = event.layerX - cvs.offsetLeft;
   cvs.csDnY = event.layerY - cvs.offsetTop;
   cvs.csMv = false;
   return false;
}

function csMouseMv(cvs, event) {
   if (!cvs.csDn)
      return true;
   if (drawCanvas(cvs.id, -1, cvs.csMv? -1 : 0, cvs.csDnX, cvs.csDnY,
         event.layerX - cvs.offsetLeft, event.layerY - cvs.offsetTop ) )
      return true;
   cvs.csMv = true;
   return false;
}

function csMouseUp(cvs) {
   cvs.csDn = false;
   if (cvs.clicked) {
      clearTimeout(cvs.clicked);
      cvs.clicked = false;
      return drawCanvas(cvs.id, -1, 2, cvs.csDnX, cvs.csDnY);
   }
   if (cvs.csMv)
      return drawCanvas(cvs.id, -1);
   cvs.clicked = setTimeout(
      function() {
         cvs.clicked = false;
         drawCanvas(cvs.id, -1, 1, cvs.csDnX, cvs.csDnY);
      },
      200 );
   return false;
}

function drawCanvas(id, dly) {
   var req = new XMLHttpRequest();
   var url = document.getElementsByTagName("BASE")[0].href + SesId + "!jsDraw?" + id + "&+" + dly;
   var len = arguments.length;

   for (var i = 2; i < len; ++i)
      url += "&+" + arguments[i];
   try {req.open("POST", url);}
   catch (e) {return true;}
   req.responseType = "arraybuffer";
   req.onload = function() {
      var ele = document.getElementById(id);

      renderCanvas(ele, plio(new Uint8Array(req.response)));
      if (len > 2)
         while (ele = ele.parentNode) {
            if (ele.tagName == "FORM") {
               post(ele, false, null, null);
               break;
            }
         }
      // if (dly == 0)
      //    requestAnimationFrame(function() {drawCanvas(id, dly)});
      // else if (dly > 0)
      //    setTimeout(function() {drawCanvas(id, dly)}, dly);
      if (dly >= 0)
         setTimeout(function() {drawCanvas(id, dly)}, dly);
   }
   try {req.send(null);}
   catch (e) {
      req.abort();
      return true;
   }
   return false;
}

function renderCanvas(cvs, lst) {
   var ctx = cvs.getContext("2d");
   var cmd, i, j;

   for (i = 0; i < lst.length; ++i) {
      switch ((cmd = lst[i])[0]) {  // Sync with "@lib/canvas.l"
      /*** Functions ***/
      case 1:  // (csFillText Str X Y)
         ctx.fillText(cmd[1], cmd[2], cmd[3]);
         break;
      case 2:  // (csStrokeLine X1 Y1 X2 Y2)
         ctx.beginPath();
         ctx.moveTo(cmd[1], cmd[2]);
         ctx.lineTo(cmd[3], cmd[4]);
         ctx.closePath();
         ctx.stroke();
         break;
      case 3:  // (csClearRect X Y DX DY)
         ctx.clearRect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 4:  // (csStrokeRect X Y DX DY)
         ctx.strokeRect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 5:  // (csFillRect X Y DX DY)
         ctx.fillRect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 6:  // (csBeginPath)
         ctx.beginPath();
         break;
      case 7:  // (csClosePath)
         ctx.closePath();
         break;
      case 8:  // (csMoveTo X Y)
         ctx.moveTo(cmd[1], cmd[2]);
         break;
      case 9:  // (csLineTo X Y)
         ctx.lineTo(cmd[1], cmd[2]);
         break;
      case 10:  // (csBezierCurveTo X1 Y1 X2 Y2 X Y)
         ctx.bezierCurveTo(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
         break;
      case 11:  // (csLine X1 Y1 X2 Y2)
         ctx.moveTo(cmd[1], cmd[2]);
         ctx.lineTo(cmd[3], cmd[4]);
         break;
      case 12:  // (csRect X Y DX DY)
         ctx.rect(cmd[1], cmd[2], cmd[3], cmd[4]);
         break;
      case 13:  // (csArc X Y R A B F)
         ctx.arc(cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]);
         break;
      case 14:  // (csStroke)
         ctx.stroke();
         break;
      case 15:  // (csFill)
         ctx.fill();
         break;
      case 16:  // (csClip)
         ctx.clip();
         break;
      case 17:  // (csDef Key DX DY Lst), (csDef Key)
         if (!cvs.pre)
            cvs.pre = new Array();
         var buf = cvs.pre[cmd[1]] = document.createElement('canvas');
         if (cmd[2]) {
            buf.width = cmd[2];
            buf.height = cmd[3];
            renderCanvas(buf, cmd[4]);
         }
         else {
            buf.width = cvs.width;
            buf.height = cvs.height;
            buf.getContext("2d").drawImage(cvs, 0, 0);
         }
         break;
      case 18:  // (csDraw Key X Y)
         ctx.drawImage(cvs.pre[cmd[1]], cmd[2], cmd[3]);
         break;
      case 19:  // (csDrawDots DX DY Lst)
         if (cmd[3])
            for (j = 0; j < cmd[3].length; j += 2)
               ctx.fillRect(cmd[3][j], cmd[3][j+1], cmd[1], cmd[2]);
         break;
      case 20:  // (csDrawImage Img X Y)
         var img = new Image();
         img.src = cmd[1];
         (function (img, cmd) {img.onload = function () {ctx.drawImage(img, cmd[2], cmd[3])}})(img, cmd);
         break;
      case 21:  // (csTranslate X Y)
         ctx.translate(cmd[1], cmd[2]);
         break;
      case 22:  // (csRotate A)
         ctx.rotate(cmd[1]);
         break;
      case 23:  // (csScale X Y)
         ctx.scale(cmd[1], cmd[2]);
         break;
      case 24:  // (csSave)
         ctx.save();
         break;
      case 25:  // (csRestore)
         ctx.restore();
         break;
      /*** Variables ***/
      case 26:  // (csFillStyle V)
         ctx.fillStyle = cmd[1];
         break;
      case 27:  // (csStrokeStyle V)
         ctx.strokeStyle = cmd[1];
         break;
      case 28:  // (csGlobalAlpha V)
         ctx.globalAlpha = cmd[1];
         break;
      case 29:  // (csLineWidth V)
         ctx.lineWidth = cmd[1];
         break;
      case 30:  // (csLineCap V)
         ctx.lineCap = cmd[1];
         break;
      case 31:  // (csLineJoin V)
         ctx.lineJoin = cmd[1];
         break;
      case 32:  // (csMiterLimit V)
         ctx.miterLimit = cmd[1];
         break;
      case 33:  // (csGlobalCompositeOperation V) )
         ctx.globalCompositeOperation = cmd[1];
         break;
      }
   }
}
<|endoftext|>


<commit_before>'use babel';

import { CompositeDisposable } from 'atom';
import ExtendedBase from './extended-base';
import OpenFilesPaneView from './open-files-pane-view';

export default class OpenFilesView extends ExtendedBase {
  constructor() {
    super();
    this.createObservers({ stickyKey: 'stickyOpenFiles' });

    this.paneViews = [];
    this.paneSubs = new CompositeDisposable();
    this.paneSubs.add(atom.workspace.observePanes(this.addPane.bind(this)));
  }

  // Tear down any state and detach
  destroy() {
    super.destroy();
		this.paneSubs.dispose();
  }

  addPane(pane) {
		const paneView = new OpenFilesPaneView(pane);
		this.paneViews.push(paneView);
		this.element.appendChild(paneView.element);

    const destroySub = pane.onDidDestroy(() => {
      destroySub.dispose();
      this.removePane(pane);
    });

    this.paneSubs.add(destroySub);
	}

	removePane(pane) {
    const index = this.paneViews.findIndex(view => view.pane === pane);
    if (index === -1) {
      return;
    }
		this.paneViews[index].destroy();
		this.paneViews.splice(index, 1);
	}
}
<commit_msg>fix: When switching projects keep open files in sync with tabs on display.<commit_after>'use babel';

import { CompositeDisposable } from 'atom';
import ExtendedBase from './extended-base';
import OpenFilesPaneView from './open-files-pane-view';

export default class OpenFilesView extends ExtendedBase {
  constructor() {
    super();
    this.createObservers({ stickyKey: 'stickyOpenFiles' });

    this.paneViews = [];
    this.paneSubs = new CompositeDisposable();
    this.paneSubs.add(atom.workspace.observePanes(this.addPane.bind(this)));
  }

  // Tear down any state and detach
  destroy() {
    super.destroy();
		this.paneSubs.dispose();
  }

  addPane(pane) {
    this.syncPanes();

		const paneView = new OpenFilesPaneView(pane);
		this.paneViews.push(paneView);
		this.element.appendChild(paneView.element);

    const destroySub = pane.onDidDestroy(() => {
      destroySub.dispose();
      this.removePane(pane);
    });

    this.paneSubs.add(destroySub);
	}

  // When a user is switching project folders (usually with project manager)
  // panes are added and removed.
  // This file syncs the views against what panes are currently displayed.
  syncPanes() {
    const currentPanes = atom.workspace.getPanes();
    // Use slice to create a copy of paneViews as we mutate that array.
    this.paneViews.slice().forEach(view => {
      const currentPaneIndex = currentPanes.indexOf(view.pane);
      const currentPane = currentPanes[currentPaneIndex];
      if (currentPaneIndex === -1 ||
          currentPane.activeItem !== view.pane.activeItem) {
        this.removePane(view.pane);
      }
    });
  }

	removePane(pane) {
    const index = this.paneViews.findIndex(view => view.pane === pane);
    if (index === -1) {
      return;
    }
		this.paneViews[index].destroy();
		this.paneViews.splice(index, 1);
	}
}
<|endoftext|>