class Libreconv::Converter

Attributes

soffice_command[RW]

@return [String]

Public Class Methods

new(source, target, soffice_command = nil, convert_to = nil) click to toggle source

@param [String] source Path or URL of the source file. @param [String] target Target file path. @param [String] soffice_command Path to the soffice binary. @param [String] convert_to Format to convert to (default: 'pdf'). @raise [IOError] If invalid source file/URL or soffice command not found. @raise [URI::Error] When URI parsing error. @raise [Net::ProtocolError] If source URL checking failed.

# File lib/libreconv.rb, line 37
def initialize(source, target, soffice_command = nil, convert_to = nil)
  @source = check_source_type(source)
  @target = target
  @soffice_command = soffice_command || which('soffice') || which('soffice.bin')
  @convert_to = convert_to || 'pdf'

  ensure_soffice_exists
end

Public Instance Methods

convert() click to toggle source

@raise [ConversionFailedError] When soffice command execution error.

# File lib/libreconv.rb, line 47
def convert
  tmp_pipe_path = File.join(Dir.tmpdir, "soffice-pipe-#{SecureRandom.uuid}")

  Dir.mktmpdir do |target_path|
    command = build_command(tmp_pipe_path, target_path)
    target_tmp_file = execute_command(command, target_path)

    FileUtils.cp target_tmp_file, @target
  end
ensure
  FileUtils.rm_rf tmp_pipe_path if File.exist?(tmp_pipe_path)
end

Private Instance Methods

build_command(tmp_pipe_path, target_path) click to toggle source

@param [String] tmp_pipe_path @param [String] target_path @return [Array<String>]

# File lib/libreconv.rb, line 89
def build_command(tmp_pipe_path, target_path)
  [
    soffice_command,
    "--accept=\"pipe,name=#{File.basename(tmp_pipe_path)};url;StarOffice.ServiceManager\"",
    "-env:UserInstallation=#{build_file_uri(tmp_pipe_path)}",
    '--headless',
    '--convert-to', @convert_to,
    escaped_source,
    '--outdir', target_path
  ]
end
build_file_uri(path) click to toggle source

@param [String] path @return [String]

# File lib/libreconv.rb, line 174
def build_file_uri(path)
  separators = /[#{Regexp.quote "#{File::SEPARATOR}#{File::ALT_SEPARATOR}"}]/
  unsafe = Regexp.new("[^#{URI::PATTERN::UNRESERVED}/?:]")

  'file:///' + URI::DEFAULT_PARSER.escape(path.gsub(separators, '/').sub(%r{^/+}, ''), unsafe)
end
check_source_type(source) click to toggle source

@param [String] source @return [String, URI::HTTP] @raise [IOError] If invalid source file/URL. @raise [URI::Error] When URI parsing error. @raise [Net::ProtocolError] If source URL checking failed.

# File lib/libreconv.rb, line 146
def check_source_type(source)
  if File.exist?(source)
    return source unless File.directory?(source)
  elsif (uri = check_valid_url(source))
    return uri
  end

  raise IOError, "Source (#{source}) is neither a file nor a URL."
end
check_valid_url(url) click to toggle source

@param [String] url @return [URI::HTTP, false, nil] @raise [URI::Error] When URI parsing error. @raise [Net::ProtocolError] If source URL checking failed.

# File lib/libreconv.rb, line 160
def check_valid_url(url)
  uri = URI(url)
  return false unless uri.is_a?(URI::HTTP)

  Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
    response = http.head(uri.request_uri)
    return check_valid_url(response['location']) if response.is_a?(Net::HTTPRedirection)

    return response.is_a?(Net::HTTPSuccess) ? uri : nil
  end
end
command_env() click to toggle source

@return [Hash]

# File lib/libreconv.rb, line 82
def command_env
  Hash[%w[HOME PATH LANG LD_LIBRARY_PATH SYSTEMROOT TEMP].map { |k| [k, ENV[k]] }]
end
ensure_soffice_exists() click to toggle source

@raise [IOError] If soffice headless command line tool not found.

# File lib/libreconv.rb, line 120
def ensure_soffice_exists
  return if soffice_command && File.exist?(soffice_command)

  raise IOError, 'Can\'t find LibreOffice or OpenOffice executable.'
end
escaped_source() click to toggle source

If the URL contains GET params, the '&' could break when being used as an argument to soffice. Wrap it in single quotes to escape it. Then strip them from the target temp file name. @return [String]

# File lib/libreconv.rb, line 104
def escaped_source
  # TODO: @source.is_a?(URI::Generic) ? "'#{@source}'" : @source
  @source.to_s
end
escaped_source_path() click to toggle source

@return [String]

# File lib/libreconv.rb, line 110
def escaped_source_path
  @source.is_a?(URI::Generic) ? @source.path : @source
end
execute_command(command, target_path) click to toggle source

@param [Array<String>] command @param [String] target_path @return [String] @raise [ConversionFailedError] When soffice command execution error.

# File lib/libreconv.rb, line 66
def execute_command(command, target_path)
  output, error, status =
    if RUBY_PLATFORM =~ /java/
      Open3.capture3(*command)
    else
      Open3.capture3(command_env, *command, unsetenv_others: true)
    end

  target_tmp_file = File.join(target_path, target_filename)
  return target_tmp_file if status.success? && File.exist?(target_tmp_file)

  raise ConversionFailedError,
        "Conversion failed! Output: #{output.strip.inspect}, Error: #{error.strip.inspect}"
end
target_filename() click to toggle source

@return [String]

# File lib/libreconv.rb, line 115
def target_filename
  File.basename(escaped_source_path, '.*') + '.' + File.basename(@convert_to, ':*')
end
which(cmd) click to toggle source

@param [String] cmd @return [String, nil]

# File lib/libreconv.rb, line 128
def which(cmd)
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']

  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each do |ext|
      exe = File.expand_path("#{cmd}#{ext}", path)
      return exe if File.executable? exe
    end
  end

  nil
end