#!/usr/bin/ruby

#
# tdiary-setup --- tDiary Setup Tool
# Author: Daigo Moriwaki <daigo at debian dot org>
# Copyright (c) 2004-2011 Daigo Moriwaki
# License: GPL version 2 or later
#

require 'fileutils'
require 'webrick/httpauth/htpasswd'
require 'debian'

#
# Extend WEBrick
#
class WEBrick::HTTPAuth::Htpasswd
  def path
    @path
  end
end


module TDiarySetup
  # Directory where tdiary is installed on Debian
  TDIARY_DIR = "/usr/share/tdiary"

  #
  # Hold parameters to be used on installation.
  #
  class Parameters
    attr_accessor :user, :home, :data_path, :language
    def initialize(user = ENV['USER'], home = ENV['HOME'])
      @user = user
      @home = home
      @language  = "E"
      @data_path = "#@home/data"
      @target    = "#@home/public_html/diary"
    end

    def language_type
      case @language
      when /e/i
        :en
      when /j/i
        :ja
      when /z/i
        :zh
      else
        raise ArugumentError.new("No such a language type: #@language")
      end
    end

    def target=(t)
      @target = t
    end

    def target
      @target
    end

    def image_dir
      "#@target/images"
    end
  end # Parameters


  #
  # Interact with a user on console.
  #
  module Ask
    def read
      $stdin.gets.strip
    end

    def print(s)
         $stdout.print s
    end

    def read_with_default(default)
      r = read.strip
      return r == "" ? default : r
    end
  end

  #
  # Ask a user to input parameters.
  #
  class Input
    include Ask

    def initialize(params)
      @params = params
      @original = @params.clone
    end

    def ask
      loop do
        ask_data_path()
        ask_language()
        res = confirm()
        break if /^y$/i =~ res
      end
    end

    def ask_language
      loop do
        default = @params.language
        print "Choose English, Japanese, Traditional-Chinese [#{default}/j/z]: "
        @params.language = read_with_default(default)
        break if /^[ejz]$/i =~ @params.language
      end
    end

    def ask_data_path
      default = @params.data_path
      print "Input data_path (default: #{default}): "
      @params.data_path = read_with_default(default)
    end

    def confirm
      print "\ndata_path: %s\nlanguage: %s\n" % [@params.data_path, @params.language]
      res = nil
      loop do
        default = "Y"
        print "Correct? [#{default}/n]: "
        res = read_with_default(default)
        break if /^[yn]$/i =~ res
      end
      if /n/i =~ res
        @params = @original.clone
      end
      return res
    end
  end # Input

  #
  # Install .htpasswd file with id and password.
  #
  class Installhtpasswd
      include Ask

    def initialize(params, htpasswdOfWEBrick)
      @params = params
      @htpasswd = htpasswdOfWEBrick
    end

    def ask
      return if user_exists?(@params.user)

      first, second  = "", ""
      loop do
        print "Input password for #{@params.user} in #{@params.home}/.htpasswd: "
        first = read
        print "Input again to confirm: "
        second = read
        break if first == second
      end

      create_entry(@params.user, first)
      return first
    end

    def user_exists?(user)
       return true & @htpasswd.get_passwd("", user, false)
    end

    def create_entry(user, passwd)
      @htpasswd.set_passwd("", user, passwd)
       @htpasswd.flush
      FileUtils.chmod(0644, @htpasswd.path)
    end
  end # Htpasswd


  #
  # Install tDiary files.
  #
  class AbstractSetup
    def initialize(params)
      @params = params
    end

    def mkdir(mode, dir)
      if !File.directory?(dir)
        FileUtils.mkdir_p dir
        FileUtils.chmod(mode, dir)
      end
    end

    def hasPluginPackage?
      !Debian::Dpkg.status(["tdiary-plugin"]).packages.find_all {|pkg| pkg.installed?}.empty?
    end

    def puts(s)
      $stdout.print "#{s}\n"
    end
  end

  #
  # 'Default' mode
  #
  class DefaultSetup < AbstractSetup
    def install
      mkdir(0701, @params.data_path)
      mkdir(0701, @params.target)
      mkdir(0701, @params.image_dir)
      setup_base()
      if hasPluginPackage?
        setup_with_plugin_package()
        install_makerss
      end
    end

    def setup_base
      puts "Install tdiary files into #{@params.target}."
      install_files( ["index", "update"] )
      FileUtils.ln_sf "#{TDIARY_DIR}/js",    "#{@params.target}/"
      FileUtils.ln_sf "#{TDIARY_DIR}/theme", "#{@params.target}/"
    end

    def setup_with_plugin_package
      install_files( ["squeeze"] )
    end

    def install_files(files)
      files.each do |f|
        FileUtils.install("#{TDIARY_DIR}/debian-tools/#{f}-debian.rb", "#{@params.target}/#{f}.rb", :mode => 0701)
      end
    end

    def install_makerss
      FileUtils::touch(["#{@params.target}/index.rdf"])
      FileUtils::chmod(0701, "#{@params.target}/index.rdf")
    end
  end

  #
  # 'Symlink' mode
  #
  class SymlinkSetup < AbstractSetup
    def install
      mkdir(0777, @params.data_path)
      mkdir(0755, @params.target)
      mkdir(0777, @params.image_dir)
      setup_base()
      if hasPluginPackage?
        setup_with_plugin_package()
        install_makerss()
      end
    end

    def setup_base
      puts "Make symlinks into #{@params.target}."
      FileUtils.ln_sf "#{TDIARY_DIR}/index.rb",    "#{@params.target}/"
      FileUtils.ln_sf "#{TDIARY_DIR}/index.fcgi",  "#{@params.target}/"
      FileUtils.ln_sf "#{TDIARY_DIR}/js",          "#{@params.target}/"
      FileUtils.ln_sf "#{TDIARY_DIR}/theme",       "#{@params.target}/"
      FileUtils.ln_sf "#{TDIARY_DIR}/update.rb",   "#{@params.target}/"
      FileUtils.ln_sf "#{TDIARY_DIR}/update.fcgi", "#{@params.target}/"
    end

    def setup_with_plugin_package
      FileUtils.ln_sf "#{TDIARY_DIR}/misc/plugin/squeeze.rb", "#{@params.target}/"
      FileUtils.ln_sf "#{TDIARY_DIR}/tb.rb",                  "#{@params.target}/"
    end

    def install_makerss
      FileUtils::touch(["#{@params.target}/index.rdf"])
      FileUtils::chmod(0777, "#{@params.target}/index.rdf")
    end
  end

  #
  # 'Copy' mode
  #
  class CopySetup < AbstractSetup
    def install
      mkdir(0777, @params.data_path)
      mkdir(0755, @params.target)
      mkdir(0777, @params.image_dir)
      puts "Copy all flies. You should setup CGI files by yourself."
      FileUtils.cp_r Dir.glob("#{TDIARY_DIR}/*"), "#{@params.target}/"
    end
  end


  #
  # Install a config file.
  #
  class AbstractInstallConf
    def initialize(params)
      @params = params
    end

    # Template mothod
    def install
      lines = File.open(file_name()){|f| f.read}
      lines = sub(lines)
      write(lines)
    end

    def file_name
      raise "Not implemented yet."
    end

    def sub(lines)
      raise "Not implemented yet."
    end

    def write(lines)
      raise "Not implemented yet."
    end
  end

  #
  # Install /usr/share/tdiary/dot.htaccess to the target directory.
  #
  class Installhtaccess < AbstractInstallConf
    def file_name
      "#{TDIARY_DIR}/dot.htaccess"
    end

    def sub(lines)
      lines.gsub!(/^#Options /, "Options ")
      lines.gsub!(/foo/, @params.user)
      return lines
    end

    def write(lines)
      File.open("#{@params.target}/.htaccess", "w") {|f| f.print lines}
    end
  end

  #
  # Install tdiary.conf.sample to the target directory.
  #
  class Installtdiaryconf < AbstractInstallConf
    def write(lines)
      File.open("#{@params.target}/tdiary.conf", "w") {|f| f.print lines}
    end

    def file_name
      path = ""
      case @params.language_type
      when :en
        path << TDIARY_DIR + "/misc/i18n/tdiary.conf.sample-en"
      when :ja
        path << TDIARY_DIR + "/tdiary.conf.sample"
      when :zh
        path << TDIARY_DIR + "/misc/i18n/tdiary.conf.sample-en"
      else
        raise "No such language: #{@params.language}"
      end
      return path
    end

    def sub(lines)
      lines.gsub!(%r!a href="doc/!,     'a href="/doc/tdiary/')
      lines.gsub!(/^@data_path =.*$/,   "@data_path = '#{@params.data_path}'")
      lines.gsub!(/^#@cache_path =.*$/, "@cache_path = '/tmp/#{@params.user}_#{unique_id()}'")
      lines.gsub!(/^@options\['sp\.path'\] =.*$/, "")
      lines.gsub!(/^load_cgi_conf.*$/,  "@options['sp.path'] = '#{TDIARY_DIR}/misc/plugin'\nload_cgi_conf\n")
      case @params.language_type
      when :zh
        lines.gsub!(/^@lang = 'en'.*$/,   "# @lang = 'en'")
        lines.gsub!(/^# @lang = 'zh'.*$/, "@lang = 'zh'")
      end
      return lines
    end

    def unique_id
      return Time.now.strftime("%Y%m%d%H%M%S")
    end
  end

  #
  # Check the installed mode
  #
  class CheckMode
    def initialize(params)
      @index = "#{params.target}/index.rb"
    end

    def mode
      if !FileTest.exist?(@index)
        raise "#{@index} file not found. Is it really the installed directory?"
      end

      if symlink?
        return :symlink
      elsif copy?
        return :copy
      elsif default?
        return :default
      else
        raise "No such a mode. Check #{@index} file."
      end
    end

    def symlink?
      return FileTest.symlink?(@index)
    end

    def copy?
      return FileTest.size?(@index) > 200
    end

    def default?
      return !copy?
    end
  end
end



#
# MAIN
#

def usage
   out = <<EOF
Usage: #{$0} {default|symlink|copy|update} directory
example) #{$0} default /home/#{ENV['USER']}/public_html/diary

directory is an absolute path where CGI files of tDiary will be put.

If you set up tDiary for the fist time, select
  * default if your httpd runs under suEXEC mode, or
  * symlink, or
  * copy for manual installation.

If you update tDiary that you are using already, select update.
EOF
   $stderr.print out
end

def first_install(params, setup)
  input = TDiarySetup::Input.new(params)
  input.ask
  webrick  = WEBrick::HTTPAuth::Htpasswd.new("#{params.home}/.htpasswd")
  htpasswd = TDiarySetup::Installhtpasswd.new(params, webrick)
  htpasswd.ask

  setup.install
  htaccess = TDiarySetup::Installhtaccess.new(params)
  htaccess.install
  tdiaryconf = TDiarySetup::Installtdiaryconf.new(params)
  tdiaryconf.install
end

def update(params)
  check = TDiarySetup::CheckMode.new(params)
  case check.mode
  when :symlink
    TDiarySetup::SymlinkSetup.new(params).install
  when :copy
    TDiarySetup::CopySetup.new(params).install
  when :default
    TDiarySetup::DefaultSetup.new(params).install
  else
    raise "No such a mode: #{check.mode}"
  end
end

def check_args(args)
  if args.length != 2
    usage()
    exit 1
  end
end

def main(args)
  check_args(args)
  mode, target = args[0], args[1]

  params = TDiarySetup::Parameters.new()
  params.target = target
  case mode
  when "default"
    first_install(params, TDiarySetup::DefaultSetup.new(params))
  when "symlink"
    first_install(params, TDiarySetup::SymlinkSetup.new(params))
  when "copy"
    first_install(params, TDiarySetup::CopySetup.new(params))
  when "update"
    update(params)
  else
    usage()
    exit 1
  end
end

if __FILE__ == $0
   main(ARGV)
end
