#!/usr/bin/ruby
#--
# Copyright (c) 2014, Flinders University, South Australia. All rights reserved.
# Contributors: eResearch@Flinders, Library, Information Services, Flinders University.
# See the accompanying LICENSE file (or http://opensource.org/licenses/BSD-3-Clause).
#++ 
#
# Make a SAF import sh/bash script. Expected usage:
# - erasaf_mkimport.rb mitest01 123456789/5055 > my_era_import.sh
# - Review contents of my_era_import.sh
# - Run the import: sh my_era_import.sh
#
##############################################################################

# Add dirs to the library path
$: << File.expand_path("../lib", File.dirname(__FILE__))
$: << "#{ENV['HOME']}/.ds/etc"

require 'date'
require 'rubygems'
require 'pg'
require 'pg_extra'
require 'dbc'

##############################################################################
# A class for representing an ERA Simple Archive Format (SAF) tree
class EraSafTree3
  include DbConnection

  IS_IMPORT_TEST = true		# true = test commands only; false = real import commands
  IS_EXPAND_SOURCE_PATH = true	# true = expand --source path to be absolute; false = don't expand
  IS_EXPAND_MAP_PATH = false	# true = expand --map path to be absolute; false = don't expand

  DSPACE_EXE_PATH = "#{ENV['HOME']}/dspace/bin/dspace"
  DSPACE_EPERSON_EMAIL = ENV['DS_USER_EMAIL']		# Eg. my_user@example.com
  DSPACE_IMPORT_MAP_PREFIX = 'map_'

  TEST_OPT = IS_IMPORT_TEST ? '--test' : ''
  DSPACE_IMPORT_PART = "#{DSPACE_EXE_PATH} import --add #{TEST_OPT} --eperson=#{DSPACE_EPERSON_EMAIL}"

  SCRIPT_HEADER = [
    '#!/bin/sh',
    "# This script was generated by #{File.basename($0)} on #{DateTime.now.strftime('%F %T (%:z)')}",
    '',
    '# It is recommended that you run this script soon after it is generated to',
    '# minimise the risk that any of the following have changed:',
    '# - ERA Simple Archive Format (SAF) directories',
    '# - the relationships between DSpace handles and communities',
  ]

  attr_reader :era_root_dir_path, :era_root_community_handle, :collection_names_by_handle, :sh_commands

  ############################################################################
  # Create this object
  ############################################################################
  def initialize(era_root_dir_path, era_root_community_handle)
    @era_root_dir_path = era_root_dir_path.sub(/\/$/, '')  # Strip trailing slash
    verify_era_root_dir_path
    @era_root_community_handle = era_root_community_handle
    verify_era_root_community
    verify_dspace_eperson_email

    @collection_names_by_handle = {}
    populate_collection_names
    @sh_commands = []
  end

  ############################################################################
  # Make import shell-script
  ############################################################################
  def make_import_script
    # Iterate thru all collections in the ERA SAF tree then lookup
    # their corresponding collection handle in the DSpace database.
    # Add to the list of shell commands.
    Dir.glob("#{@era_root_dir_path}/*").sort.each{|coll_dpath|
      next unless File.directory?(coll_dpath)

      coll_name_part = File.basename(coll_dpath)
      regex_str = "^#{coll_name_part}([^[:alnum:]]|$)"
      db_coll_handle, db_coll_name = @collection_names_by_handle.find{|k,v|
        v.match( Regexp.new(regex_str) )
      }
      unless db_coll_handle && db_coll_name
        STDERR.puts "\nQuitting: No collection found matching reg-ex: #{regex_str}"
        STDERR.puts "  for SAF collection path: '#{coll_dpath}'"
        exit 2
      end

      # Eg. dspace import --add [--test] --eperson=user@example.com --collection=hdl --source=collDir --mapfile=mapFile
      mp = "#{DSPACE_IMPORT_MAP_PREFIX}#{coll_name_part}"
      mpath = IS_EXPAND_MAP_PATH ? File.expand_path(mp) : mp
      cpath = IS_EXPAND_SOURCE_PATH ? File.expand_path(coll_dpath) : coll_dpath
      cmd = "#{DSPACE_IMPORT_PART} --collection=#{db_coll_handle} --source=#{cpath} --mapfile=#{mpath}"
      @sh_commands << "\necho"
      @sh_commands << "echo \"Import #{db_coll_handle}; '#{db_coll_name}'\""
      @sh_commands << "echo \"COMMAND: #{cmd}\""
      @sh_commands << cmd
    }
  end

  ############################################################################
  # Returns the generated bash/sh script.
  ############################################################################
  def script_to_s
    (SCRIPT_HEADER + @sh_commands).join("\n")
  end

  private

  ############################################################################
  # Verify the ERA root directory path.
  ############################################################################
  def verify_era_root_dir_path
    unless File.directory?(@era_root_dir_path)
      STDERR.puts "Quitting: #{@era_root_dir_path} is not a directory"
      exit 3
    end
  end

  ############################################################################
  # Verify the ERA root community.
  ############################################################################
  def verify_era_root_community
    sql = <<-SQL_ERA_ROOT_COMMUNITY_NAME.gsub(/^\t*/, '')
	select community_id,name from community where community_id = 
	  (select resource_id from handle where handle='#{@era_root_community_handle}' and resource_type_id=4)
    SQL_ERA_ROOT_COMMUNITY_NAME
    PG::Connection.connect2(DB_CONNECT_INFO){|conn|
      conn.exec(sql){|result|
        if result.ntuples == 0
          STDERR.puts "Quitting: No community found when looking up handle: '#{@era_root_community_handle}'"
          exit 4
        else
          result.each{|row| STDERR.puts "ERA root community name: #{row['name']}" }
        end
      }
    }
  end

  ############################################################################
  # Verify the DSPACE_EPERSON_EMAIL constant.
  ############################################################################
  def verify_dspace_eperson_email
    unless DSPACE_EPERSON_EMAIL && DSPACE_EPERSON_EMAIL != ''
      STDERR.puts "Quitting: Constant DSPACE_EPERSON_EMAIL is empty. However the email"
      STDERR.puts "address of the DSpace eperson performing the import is expected."
      exit 5
    end
  end

  ############################################################################
  # Populate @collection_names_by_handle from the database.
  ############################################################################
  def populate_collection_names
    # - Collection: resource_type_id=3.  Community: resource_type_id=4.
    # - collection_id is currently used for debugging only. It could be
    #   used in the "dspace import ..." command, but handles are easier
    #   to verify without resorting to a DB query (via the DSpace URL).
    # - The query below assumes that collections are within a sub-community
    #   which is within the ERA-year root-community.
    sql = <<-SQL_COLLECTION_NAMES.gsub(/^\t*/, '')
	select
	  (select handle from handle where resource_id=collection_id and resource_type_id=3) handle,
	  collection_id,
	  name
	from collection
	where collection_id in (
	  select collection_id from community2collection where community_id in (
	    select child_comm_id from community2community where parent_comm_id = (
	      select resource_id from handle where handle='#{@era_root_community_handle}' and resource_type_id=4
	    )
	  )
	)
    SQL_COLLECTION_NAMES
    PG::Connection.connect2(DB_CONNECT_INFO){|conn|
      conn.exec(sql){|result|
        result.each{|row| @collection_names_by_handle[ row['handle'] ] = row['name'] }
      }
    }
  end

  public

  ############################################################################
  # The main method for this class
  ############################################################################
  def self.main
    verify_command_line_args
    era_root_dir = ARGV.shift
    era_root_comm_hdl = ARGV.shift

    STDERR.puts "\nBuilding a bash/sh script for importing ERA SAF tree into DSpace"
    STDERR.puts   "----------------------------------------------------------------"
    STDERR.puts "ERA root directory: #{era_root_dir}"
    STDERR.puts "ERA root community handle: #{era_root_comm_hdl}"

    era_tree = EraSafTree3.new(era_root_dir, era_root_comm_hdl)
    era_tree.make_import_script
    STDERR.puts
    puts era_tree.script_to_s
  end

  private

  ############################################################################
  # Verify the command line arguments
  ############################################################################
  def self.verify_command_line_args
    if ARGV.length < 2 || ARGV.include?('-h') || ARGV.include?('--help')
      STDERR.puts <<-MSG_COMMAND_LINE_ARGS.gsub(/^\t*/, '')
		Usage:  #{File.basename $0}  ERA_ROOT_DIR_PATH  ERA_ROOT_COMMUNITY_HANDLE
		  where
		    ERA_ROOT_DIR_PATH is the root of a directory tree
		    containing multiple DSpace collections which conform to
		    the Simple Archive Format (SAF).

		    ERA_ROOT_COMMUNITY_HANDLE is the DSpace handle
		    corresponding to the ERA-reporting-year community. This
		    community is expected to contain sub-communities which
		    in turn contain collections whose names start with a
		    4-digit FOR code.

		    This script expects the DS_USER_EMAIL shell environment
		    variable to be set to the email address of the DSpace
		    user performing the import. Hence a typical usage might be:

		      DS_USER_EMAIL=user@example.com #{File.basename $0} era2012_saftree 123456789/1 > import.sh

      MSG_COMMAND_LINE_ARGS
      exit 1
    end
  end
end

##############################################################################
# Main
##############################################################################
EraSafTree3.main
exit 0

