#!/usr/bin/env ruby

$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])

require 'rubygems'
require 'rubygems/specification'
require 'sinatra'
require 'timeout'
require 'yaml'
require 'net/http'
require 'safegem/exception'
require 'json'
require 'base64'
require 'zlib'

def time
  t1 = Time.now
  yield
  Time.now - t1
end

post '/' do
  puts "-> #{params.merge('data' => params[:data].size).inspect}"
  r, w = IO.pipe

  pid = nil
  begin
    repo     = params[:repo]
    data     = params[:data]
    callback = params[:callback]
    token    = params[:token]
    tmpdir   = "tmp/#{repo}"
    spec     = nil

    Timeout::timeout(300) do
      t = time { `git clone --depth 1 git://github.com/#{repo} #{tmpdir}` }
      puts "-- cloned #{repo} in #{t}s"

      t1 = Time.now
      pid = fork do
        begin
          r.close

          require 'safegem/security'
          require 'safegem/lazy_dir'
          Dir.chdir(tmpdir) do
            thread = Thread.new do
              eval <<-EOE
                BEGIN { # First in first out. Get this one exec'ed before the code below.
                  Object.class_eval do
                    remove_const :OrigDir rescue nil
                    OrigDir = Dir
                    remove_const :Dir
                    Dir = LazyDir
                  end
                  $SAFE = 3
                  OrigDir.set_safe_level
                }
                BEGIN { # This forces Ruby to ignore nested END {} blocks
                  begin
                    params = tmpdir = data = spec = repo = nil
                    # Pass data out using TLS
                    Thread.current[:spec] = (#{data})
                  ensure
                    Object.class_eval do
                      remove_const :Dir
                      Dir = OrigDir
                    end
                  end
                }
              EOE
            end.join
            Dir.set_safe_level
            spec = thread[:spec]
            spec.rubygems_version = Gem::RubyGemsVersion # make sure validation passes
            spec.validate
          end

          payload = Base64.encode64(Zlib::Deflate.deflate(YAML.dump(spec)))
          w.write payload
          w.close
        rescue Object
          puts $!, $@

          w.write "ERROR: #$!"
          w.close
        end
      end

      w.close
      Process.wait(pid)
      yaml = r.read
      r.close
      puts "-- converted to yaml in #{Time.now - t1}s"

      res = nil
      t = time do
        payload = {'token' => token, 'yaml' => yaml}
        puts "<- [#{callback}] #{payload.merge('yaml' => payload['yaml'].size).inspect}"
        res = Net::HTTP.post_form(URI.parse(callback), payload)
      end
      puts "-> #{res.body.inspect} in #{t}s"

      # uri = URI.parse(callback)
      # http = Net::HTTP.new(uri.host)
      # http.set_debug_output $stdout
      # http.start do |http|
      #   req = Net::HTTP::Post.new(uri.path)
      #   payload = {'token' => token, 'yaml' => yaml}
      #   req.set_form_data(payload)
      #   res = http.request(req)
      #   p res.value
      # end

      packet = {'result' => "Successfully converted #{repo} gemspec to YAML.", 'error' => nil}
      puts "<- #{packet.inspect}"
      packet.to_json
    end
  rescue Exception => e
    Process.kill(9, pid) rescue nil
    packet = {'error' => e.to_hash}
    puts "<- #{packet.inspect}"
    packet.to_json
  ensure
    `rm -rf #{tmpdir}` if tmpdir
  end
end

if $0 != __FILE__
  set :run, false
  disable :reload
  Sinatra::Application.run!
end