class PingPongPear

Constants

SERVICE
VERSION

Attributes

logger[R]
peers[R]
pull_requests[R]

Public Class Methods

new() click to toggle source
# File lib/ping_pong_pear.rb, line 26
def initialize
  @pull_requests      = Queue.new
  @send_pull_requests = []
  @peers              = Set.new
  @logger             = Logger.new $stdout
end
run(args) click to toggle source
# File lib/ping_pong_pear.rb, line 14
def self.run args
  new.public_send args.first, *args.drop(1)
rescue
  cmds = public_instance_methods(false).find_all { |x|
    instance_method(x).arity < 0
  }
  $stderr.puts "USAGE: pingpongpear #{cmds}"
  exit 1
end

Public Instance Methods

clone(name, dir = nil) click to toggle source
# File lib/ping_pong_pear.rb, line 82
def clone name, dir = nil
  browser = DNSSD::Service.browse SERVICE
  browser.each do |response|
    r = response.resolve
    if r.text_record['project'] == name
      url = "http://#{r.target}:#{r.port}"
      system "git clone #{Shellwords.escape(url)} #{dir || Shellwords.escape(name)}"
      break
    end
  end
end
start(name = File.basename(Dir.pwd)) click to toggle source
# File lib/ping_pong_pear.rb, line 33
  def start name = File.basename(Dir.pwd)
    post_commit_hook = '.git/hooks/post-commit'
    pidfile = '.git/pingpongpear.pid'

    if File.exist? pidfile
      raise "Another instance of Ping Pong Pear is running"
    else
      File.open(pidfile, 'w') { |f| f.write $$ }
    end

    File.open(post_commit_hook, 'w') { |f|
      f.write <<-eof
#!/bin/sh

git update-server-info
kill -INFO $(cat #{pidfile})
      eof
    }
    File.chmod 0755, post_commit_hook

    system "git update-server-info"

    at_exit {
      File.unlink pidfile
      File.unlink post_commit_hook
    }

    identifier    = make_ident name

    logger.info "SEND THIS TO YOUR PEAR `git clone #{name}`"
    logger.info "CTRL-T sends pull requests to all peers"
    logger.debug "MY PROJECT NAME: #{name} IDENT: #{identifier}"

    server        = start_server pull_requests
    http_port     = server.listeners.map { |x| x.addr[1] }.first
    hostname      = Socket.gethostname

    discover identifier, name, peers
    process_pull_requests pull_requests
    t = process_send_pull_requests @send_pull_requests

    trap('INFO') {
      send_pull_requests peers, hostname, http_port
      t.wakeup
    }

    advertise(identifier, name, hostname, http_port).each { |x| x }
  end

Private Instance Methods

advertise(ident, name, hostname, http_port) click to toggle source
discover(ident, name, peers) click to toggle source
# File lib/ping_pong_pear.rb, line 122
def discover ident, name, peers
  browser = DNSSD::Service.browse SERVICE
  browser.async_each do |response|
    if response.flags.to_i > 0
      logger.debug "SAW: #{response.name}"
      unless response.name == ident
        r = response.resolve
        if r.text_record['project'] == name
          logger.info "PEER: #{response.name}"
          peers << [response.name, r.target, r.port]
        end
      end
    else
      peers.delete_if { |id, _, _| id == response.name }
      logger.info "REMOVED: #{response.name}"
    end
  end
end
make_ident(name) click to toggle source
# File lib/ping_pong_pear.rb, line 118
def make_ident name
  "#{name} (#{SecureRandom.hex.slice(0, 4)})"
end
process_pull_requests(pull_requests) click to toggle source
# File lib/ping_pong_pear.rb, line 163
def process_pull_requests pull_requests
  Thread.new do
    while pr = pull_requests.pop
      url = "http://#{pr.join(':')}"
      logger.debug "git pull #{Shellwords.escape(url)}"
      system "git pull #{Shellwords.escape(url)}"
    end
  end
end
process_send_pull_requests(requests) click to toggle source
# File lib/ping_pong_pear.rb, line 147
def process_send_pull_requests requests
  Thread.new do
    loop do
      requests.each do |pr|
        host, port, http_host, http_port = *pr
        http    = Net::HTTP.new host, port
        request = Net::HTTP::Post.new '/pull'
        request.set_form_data 'host' => http_host, 'port' => http_port
        http.request request
      end
      requests.clear
      Thread.stop
    end
  end
end
send_pull_requests(peers, http_host, http_port) click to toggle source
# File lib/ping_pong_pear.rb, line 141
def send_pull_requests peers, http_host, http_port
  peers.each do |_, host, port|
    @send_pull_requests << [host, port, http_host, http_port]
  end
end
start_server(pull_requests) click to toggle source
# File lib/ping_pong_pear.rb, line 96
def start_server pull_requests
  server = WEBrick::HTTPServer.new Port: 0,
                                   DocumentRoot: '.git',
                                   Logger: logger
  server.mount_proc '/pull' do |req, res|
    host = req.query['host']
    port = req.query['port']
    if host && port
      logger.info "ADDED PR: #{host}:#{port}"
      pull_requests << [host, port.to_i]
    end
    res.body = ''
  end
  Thread.new { server.start }
  server
end