#! /usr/bin/env ruby
# vim: ai ts=2 sts=2 sw=2 expandtab
#
# ruutu-dl - Download video from ruutu.fi and jimtv.fi
# Copyright (C) 2013  Tommi Saviranta <wnd@iki.fi>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


require 'net/http'
require 'time'
require 'pty'

APP_NAME = 'ruutu-dl'
APP_VERSION = '0.1.1'

MAX_TIMEOUT = 10.0
MIN_TIMEOUT = 0.5



class RuutuDownloader
  def self.prerequisites?
    if !has_bin?('flvstreamer')
      raise RuntimeError, 'flvstreamer'
    end
  end

  def self.get(uri, out_file = nil)
    dl = RuutuDownloader.new(uri, out_file)
    dl.get
  end

  def initialize(uri, out_file = nil)
    @web_uri = uri
    @out_file = out_file
  end

  def get
    rtmp = find_rtmp
    generate_output_filename(web_data, rtmp) if @out_file.nil?
    download(rtmp, @out_file)
  end

  private

  def self.has_bin?(exe)
    !ENV['PATH'].split(':').find{|dir| File.executable?("#{dir}/#{exe}")}.nil?
  end

  def download(rtmp, out_file)
    KludgyRTMPDumper.get(rtmp, out_file)
  end

  def web_data
    @web_data ||= Net::HTTP.get(URI(@web_uri))
  end

  def find_rtmp
    config_uri = find_config_uri
    config_data = Net::HTTP.get(URI(config_uri))
    find_rtmp_uri(config_data)
  end

  def find_config_uri
    re = Regexp.new("/media_configurator")
    configs = web_data.each_line.select{|line| re.match(line)}
    if configs.size == 0
      raise RuntimeError, 'Cannot find media_configurator'
    elsif configs.size > 1
      raise RuntimeError, 'Multiple media_configurators'
    else
      configs.first.sub(/.*http/, 'http').sub(/".*/, '').chomp
    end
  end

  def find_rtmp_uri(data)
    re = Regexp.new(/SourceFile/)
    uris = data.each_line.select{|line| re.match(line)}
    if uris.count == 0
      raise RuntimeError, 'Cannot find SourceFile'
    elsif uris.count > 1
      raise RuntimeError, 'Multiple SourceFiles'
    else
      uris.first.sub(/.*<SourceFile>/, '').sub(/<\/SourceFile>/, '').chomp
    end
  end

  def generate_output_filename(html, rtmp_uri)
    re = Regexp.new('<title>')
    titles = web_data.each_line.select{|line| re.match(line)}
    if titles.count == 0
      raise RuntimeError, 'Cannot find title'
    elsif titles.count > 1
      raise RuntimeError, 'Multiple titles'
    else
      src = titles.first.sub(/.*<title>/, '').
        sub(/<.*/, '').sub(/ \| .*/, '').chomp
      suffix = rtmp_uri.sub(/.*\./, '')
      @out_file = "#{src}.#{suffix}"
    end
  end


  class KludgyRTMPDumper
    def self.get(uri, out_file)
      KludgyRTMPDumper.new(uri, out_file).download
    end

    def initialize(uri, out_file)
      @uri = uri
      @out_file = out_file
      @cmd = "flvstreamer -e -r #{@uri} -o '#{@out_file}' 2>&1"
    end

    def download
      timeout = 0.5

      old_size = File.size(@out_file) if File.exists?(@out_file)
      old_size ||= 0

      done = false
      until done
        done = round(timeout)

        new_size = File.size(@out_file)
        if new_size > old_size
          timeout = [timeout / 1.5, MIN_TIMEOUT].max
        else
          timeout = [timeout * 1.5, MAX_TIMEOUT].min
        end
        old_size = new_size
      end
    end

    private

    def round(timeout)
      timeout_at = Time.now + timeout

      PTY.spawn(@cmd) do |stdin, stdout, pid|
        got_data_at = Time.now
        loop do
          begin
            data = stdin.read_nonblock(1024)
            print data
            timeout_at = Time.now + timeout
          rescue Errno::EAGAIN
            sleep 0.1
          rescue EOFError
            $stderr.puts "\nprocess EOF?"
            raise
          rescue Errno::EIO
            $stderr.puts "\nChild exited. All done?"
            return true
          end
          if Time.now >= timeout_at
            $stderr.puts "\nflvstreamer stalled. Restarting..."
            Process.kill('KILL', pid)
            return false
          end
        end
      end

      $stderr.puts "\nChild and loop exited. All done?"
      return true
    end
  end
end


begin
  RuutuDownloader.prerequisites?
rescue RuntimeError => e
  $stderr.puts "#{e} required"
  exit 1
end

if !(1..2).cover?(ARGV.count)
  $stderr.puts <<EOF
#{APP_NAME} #{APP_VERSION} - Download video from ruutu.fi and jimtv.fi
Copyright (C) 2013  Tommi Saviranta <wnd@iki.fi>
http://wnd.katei.fi/ruutu-dl/

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Usage: #{APP_NAME} PROGRAM_WEB_URI [OUTPUT_FILE]
EOF
  exit 1
end

RuutuDownloader.get(ARGV[0], ARGV[1])
