module Md2site # # setupサブコマンドクラス # class Setup # WEBサイトへのアクセス待ち時間(秒単位) ACCESS_WAIT_SEC = 10 # ACCESS_WAIT_SEC=5 # ACCESS_WAIT_SEC=1 require "faraday_middleware" require "faraday" require "date" require "open3" require "uri" require "digest" require "messagex" require "md2site/statusfile" require "md2site/listfile" # # 初期化 # @param env [Env] Envクラスのメソッド # @param mes [Messagex] Messagexクラスのインスタンス def initialize(env, mes) @env = env @mes = mes @category = @env.category @category_x = @env.category_x absolute_path_status_file = env.conf_hs["ABSOLUTE_PATH_STATUS_FILE"] @url = @env.conf_hs["URL"] @res = {} @mes.add_exitcode("EXIT_CODE_BY_EXCEPTION") @mes.add_exitcode("EXIT_CODE_EXECUTE_SETUP_BEFORE_UPDATE_HTMLFILES") @mes.add_exitcode("EXIT_CODE_CANNOT_FIND_DEST_DIR") @mes.add_exitcode("EXIT_CODE_CANNOT_CONVERT_FROM_HTML_TO_MD") @mes.add_exitcode("EXIT_CODE_PANDOC_EXIT_ABNORMALLY") @status_file = StatusFile.new(absolute_path_status_file, @url, @mes) end # # サブコマンド実行 # @param option [String] サブコマンドのオプション def execute_subcommand(option) case option.name when "contentUpdate" update_htmlfiles when "zcontents" dir = @env.get_filepath(option.value) unless dir @mes.exc_make_directory(option.value) { FileUtils.mkdir_p(option.value) } dir = @env.get_filepath(option.value) end unless dir || FileTest.directory?(dir) @mes.output_fatal("Can't find drectory(=#{dir}") exit(@mes.ec("EXIT_CODE_CANNOT_FIND_DIRECTORY")) end get_remote_contents(dir) when "getfiles" dir = @env.get_filepath(option.value) get_remote_file_headers(dir) end end private # # リモートサイトのファイル取得 # @param url [String] 取得先URL # @return [Array] 第0要素:Datetime 第1要素:UNIXタイム 第2要素:HTTPボディ def get_remote_file(url) connection = Faraday.new(url) do |conn| conn.use(FaradayMiddleware::FollowRedirects) conn.adapter(:net_http) end @mes.output_debug("url=#{url}") res = connection.get(url) datetime_str, datetime_unix_time = get_modified_in_datetime_or_date_in_datetime_from_header(res.headers) [datetime_str, datetime_unix_time, res.body] end # # HTTPヘッダの指定フィールドの値を文字列とUNITタイムで取得する # @param headers [Hash] HTTPヘッダを表すハッシュ # @param key [String] HTTPヘッダのフィールドを指定するキー # @return [Array] 第0要素: 指定フィールドの値(String) 第1要素: 指定フィールドの値(UNIXタイム) def get_header_value_of_datetime(headers, key) @res[key] ||= Regexp.compile(Regexp.escape(key)) keys = headers.keys.select {|x| @res[key].match?(x.downcase) } if keys.size == 1 k = keys.first s = headers[k] begin dt = DateTime.httpdate(s) unixtime = dt.to_time.to_i rescue ArgumentError => e @mes.output_exception(e) @mes.output_fatal("s=#{s}") exit(@mes.ec("EXIT_CODE_BY_EXCEPTION")) rescue Error => e @mes.output_exception(e) @mes.output_fatal("s=#{s}") exit(@mes.ec("EXIT_CODE_BY_EXCEPTION")) end else s = nil unixtime = nil end [s, unixtime] end # # HTTPヘッダのlast-modifiedフィールドの値を文字列とUNITタイムで取得 # @param headers [Hash] HTTPヘッダを表すハッシュ # @return [Array] 第0要素: 指定フィールドの値(String) 第1要素: 指定フィールドの値(UNIXタイム) def get_modified_in_datetime_from_header(headers) get_header_value_of_datetime(headers, "last-modified") end # # HTTPヘッダのdateフィールドの値を文字列とUNITタイムで取得 # @param headers [Hash] HTTPヘッダを表すハッシュ # @return [Array] 第0要素: 指定フィールドの値(String) 第1要素: 指定フィールドの値(UNIXタイム) def get_date_in_datetime_from_header(headers) get_header_value_of_datetime(headers, "date") end # # HTTPヘッダから時刻を表すlast-modifiedフィールドまたはdateフィールのの値を文字列とUNIXタイムで取得 # @param headers [Hash] HTTPヘッダを表すハッシュ # @return [Array] 第0要素: 指定フィールドの値(String) 第1要素: 指定フィールドの値(UNIXタイム) def get_modified_in_datetime_or_date_in_datetime_from_header(headers) ary = get_modified_in_datetime_from_header(headers) unless ary.all? ary = get_date_in_datetime_from_header(headers) end ary end # # 指定URLのHTTPヘッダから時刻を表すlast-modifiedフィールドまたはdateフィールのの値を文字列とUNIXタイムで取得 # @param url [String] HTTPヘッダを取得したいURL # @return [Array] 第0要素: 指定フィールドの値(String) 第1要素: 指定フィールドの値(UNIXタイム) def get_modified_in_datetime_or_date_in_datetime(url) connection = Faraday.new(url) do |conn| conn.use(FaradayMiddleware::FollowRedirects) conn.adapter(:net_http) end @mes.output_debug("url=#{url}") res = connection.head(url) get_modified_in_datetime_or_date_in_datetime_from_header(res.headers) end # # 時刻を"年月日-時分秒"の形式の文字列にする # @param datetime [Datetime] Datetimeクラスのインスタンス # @return [String] "年月日-時分秒"の形式の文字列 def get_datestring(datetime) datetime.strftime("%Y%m%d-%H%M%S") end # # 現在時刻をDatetimeクラスのインスタンスと文字列にする # @return [Array] 第0要素: Datetimeクラスのインスタンス 第1要素: "年月日-時分秒"の形式の文字列 def current_time datetime = DateTime.now datetimestr = get_datestring(datetime) [datetime, datetimestr] end # # 指定サイトでダウンロード指定された全URLのHTTPヘッダの時刻を取得して一覧をファイルに保存 # @param dir [String] HTTPヘッダ時刻一覧ファイルの保存先ディレクトリ # @return [Array] 第0要素: Datetimeクラスのインスタンス 第1要素: "年月日-時分秒"の形式の文字列 # @note 一覧を保存するファイルのファイル名はsite-年月日-時分秒.tsvである def get_remote_file_headers(dir) _, datetimestr = current_time fname = %Q(site-#{datetimestr}.tsv) fpath = File.join(dir, fname) @mes.exc_file_write(fpath) do File.open(fpath, "w") do |ofile| @category_x[:category].each do |_k, v| v.each do |_k2, v2| filename = v2[@env.htmlfile_index] next if v2[@env.alias_htmlfile_index] if %r{/$}.match?(@url) file_url = URI.join(@url, filename) else file_url = URI.join(@url, "/", filename) end ary = get_modified_in_datetime_or_date_in_datetime(file_url) unless ary.all? @mes.output_error("Can't find last-modified header from #{@url}") end v2.concat(ary) ofile.puts(v2.join("\t")) end end end end @status_file.fname = fname @status_file.fpath = fpath @status_file.last_datetime = datetimestr @status_file.update end # # 引数で指定したファイルが書込み可能であるように書込み先ディレクトリを作成 # @param dir [String] ディレクトリ # @param filename [String] ファイルへのパス(ファイル名のみも可) def prepare_new_write_path(dir, filename) dirname = File.dirname(filename) if dirname != "." dirpath = File.join(dir, dirname) @mes.exc_make_directory(dirpath) { FileUtils.mkdir_p(dirpath) } basename = File.basename(filename) fpath = File.join(dirpath, basename) else fpath = File.join(dir, filename) end fpath end def get_remote_contents_sub(target_hash, listfile, last_contents_path) target_hash.each do |_, target_def| filename = target_def[@env.htmlfile_index] next if target_def[@env.alias_htmlfile_index] if %r{/$}.match?(@url) file_url = URI.join(@url, filename) else file_url = URI.join(@url, "/", filename) end remote_datetime_str, remote_datetime_unixtime, content = get_remote_file(file_url) unless [remote_datetime_str, remote_datetime_unixtime].all? @mes.output_error("Can't get content from #{file_url}") listfile.add([filename, remote_datetime_str, remote_datetime_unixtime, nil]) sleep(ACCESS_WAIT_SEC) next end fpath = prepare_new_write_path(last_contents_path, filename) @mes.exc_file_write(fpath) do File.open(fpath, "w") do |ofile| if content ofile.puts(content) end end end listfile.add([filename, remote_datetime_str, remote_datetime_unixtime, Digest::MD5.hexdigest(content)]) end end def get_remote_contents(dir) _, datetimestr = current_time @mes.output_info("dir=#{dir}") @mes.output_info("datetimestr=#{datetimestr}") last_contents_path = File.absolute_path(File.join(dir, datetimestr)) @mes.exc_make_directory(last_contents_path) { FileUtils.mkdir_p(last_contents_path) } lf = ListFile.new(last_contents_path, @mes) @category_x[:category].each do |_, target_hash| get_remote_contents_sub(target_hash, lf, last_contents_path) end lf.close @status_file.last_contents_path = last_contents_path @status_file.update end def update_htmlfiles_subtarget(subtarget, src_fpath) dest_fpath = prepare_new_write_path(@env.absolutepath_root, subtarget.filedir.html_input) unless dest_fpath dir = File.dirname(subtarget.filedir.html_input) path = File.join(@env.absolutepath_root, dir) @mes.exc_make_directory(path) { FileUtils.mkdir_p(path) } dest_fpath2 = prepare_new_write_path(@env.absolutepath_root, subtarget.filedir.html_input) if dest_fpath2 dest_fpath = dest_fpath2 else @mes.output_fatal("Can't find dest dir(#{path})") exit(@mes.ec("EXIT_CODE_CANNOT_FIND_DEST_DIR")) end end @mes.exc_file_copy(src_fpath, dest_fpath) { FileUtils.cp(src_fpath, dest_fpath) } @mes.output_info("HtmlUtils.divideHtml dest_fpath=#{dest_fpath}") ary = HTMLUtils.new(dest_fpath, @mes).divide_html dir = File.dirname(dest_fpath) (0..2).each do |x| ofname = File.join(dir, %Q(#{x}.html)) @mes.exc_file_write(ofname) { File.open(ofname, "w") {|f| f.puts(ary[x]) } } end in_html = File.join(dir, %Q(1.html)) unless File.exist?(in_html) @mes.output_fatal("Can't find file(=#{in_html}") exit(@mes.ec("EXIT_CODE_CANNOT_FIND_FILE")) end out_md = prepare_new_write_path(@env.absolutepath_root, subtarget.filedir.input_md) simple_html2md(in_html, out_md) path = File.join(@env.absolutepath_root, subtarget.workDir) @mes.exc_make_directory(subtarget.workDir) { FileUtils.mkdir_p(path) } end def update_htmlfiles if @status_file.nil? || @status_file.last_contents_path.nil? || @status_file.last_contents_path.empty? @mes.output_info("@status_file=#{@status_file}") unless @status_file.nil? @mes.output_info("@status_file.last_contents_path=#{@status_file.last_contents_path}") end exit(@mes.ec("EXIT_CODE_EXECUTE_SETUP_BEFORE_UPDATE_HTMLFILES")) else @category.each do |_name, category_struct| category_struct.subTargets.each do |_k, v| next unless v.aliashtmlfile.empty? filename = v.htmlfile next if filename.empty? src_fpath = File.join(@status_file.last_contents_path, filename) unless File.exist?(src_fpath) @mes.output_info("#{src_fpath} doesn't exist") next end update_htmlfiles_subtarget(v, src_fpath) end end end end def simple_html2md(input_htmlfname, outputmd) ret = false _, _, s = Open3.capture3(%Q(pandoc -o #{outputmd} -t markdown #{input_htmlfname})) if s.exited? if s.exitstatus == 0 ret = true else @mes.output_error("Can't convert from html to md by pandoc(IN:#{input_htmlfname} OUT:#{outputmd}(exit_code=#{s.exitstatus})") exit(@mes.ec("EXIT_CODE_CANNOT_CONVERT_FROM_HTML_TO_MD")) end else @mes.output_error("Pandoc exit abnormally") exit(@mes.ec("EXIT_CODE_PANDOC_EXIT_ABNORMALLY")) end ret end end end