lib/downloader.rb in narou-2.5.2 vs lib/downloader.rb in narou-2.6.0

- old
+ new

@@ -10,18 +10,21 @@ require_relative "helper" require_relative "sitesetting" require_relative "template" require_relative "database" require_relative "inventory" +require_relative "eventable" require_relative "narou/api" require_relative "html" require_relative "input" # # 小説サイトからのダウンロード # class Downloader + include Narou::Eventable + NOVEL_SITE_SETTING_DIR = "webnovel/" SECTION_SAVE_DIR_NAME = "本文" # 本文を保存するディレクトリ名 CACHE_SAVE_DIR_NAME = "cache" # 差分用キャッシュ保存用ディレクトリ名 RAW_DATA_DIR_NAME = "raw" # 本文の生データを保存するディレクトリ名 TOC_FILE_NAME = "toc.yaml" @@ -32,48 +35,32 @@ NOVEL_TYPE_SS = 2 # 短編 DISPLAY_LIMIT_DIGITS = 4 # indexの表示桁数限界 attr_reader :id - # - # ターゲット(ID、URL、Nコード、小説名)を指定して小説データのダウンロードを開始する - # - # force が true なら全話強制ダウンロード - # - def self.start(target, force = false, from_download = false) - setting = nil - target = Narou.alias_to_id(target) - fail_status = OpenStruct.new(:id => nil, :new_arrivals => nil, - :status => false).freeze - case type = get_target_type(target) - when :url, :ncode - setting = get_sitesetting_by_target(target) - unless setting - error "対応外の#{type}です(#{target})" - return fail_status + class InvalidTarget < StandardError; end + + def initialize(target, options = {}) + id = Downloader.get_id_by_target(target) + options = { + force: false, from_download: false, + stream: $stdout + }.merge(options) + setting = Downloader.get_sitesetting_by_target(target) + + unless setting + case type = Downloader.get_target_type(target) + when :url, :ncode + raise InvalidTarget, "対応外の#{type}です(#{target})" + when :id + raise InvalidTarget, "指定のID(#{target})は存在しません" + when :other + raise InvalidTarget, "指定の小説(#{target})は存在しません" end - when :id - data = @@database[target.to_i] - unless data - error "指定のID(#{target})は存在しません" - return fail_status - end - setting = get_sitesetting_by_sitename(data["sitename"]) - setting.multi_match(data["toc_url"], "url") - when :other - data = @@database.get_data("title", target) - if data - setting = get_sitesetting_by_sitename(data["sitename"]) - setting.multi_match(data["toc_url"], "url") - else - error "指定の小説(#{target})は存在しません" - return fail_status - end end - downloader = Downloader.new(setting, force, from_download) - result = downloader.start_download - result + + initialize_variables(id, setting, options) end # # 小説サイト設定を取得する # @@ -137,12 +124,12 @@ if File.exist?(path) return path else @@database.delete(id) @@database.save_database - error "#{path} が見つかりません。" - warn "保存フォルダが消去されていたため、データベースのインデックスを削除しました。" + error "#{path} が見つかりません。\n" \ + "保存フォルダが消去されていたため、データベースのインデックスを削除しました。" return nil end end # @@ -284,41 +271,47 @@ @@settings = load_settings @@database = Database.instance end # - # コンストラクタ + # 変数初期化 # - def initialize(site_setting, force = false, from_download = false) + def initialize_variables(id, setting, options) @title = nil @file_title = nil - @setting = site_setting - @force = force - @from_download = from_download + @setting = setting + @force = options[:force] + @from_download = options[:from_download] + @stream = options[:stream] @cache_dir = nil @new_arrivals = false @novel_data_dir = nil @novel_status = nil - @id = @@database.get_id("toc_url", @setting["toc_url"]) || @@database.get_new_id + @id = id || @@database.create_new_id @new_novel = @@database[@id].! @section_download_cache = {} + @download_wait_steps = Inventory.load("local_setting", :local)["download.wait-steps"] || 0 + @download_use_subdirectory = use_subdirectory? + if @setting["is_narou"] && (@download_wait_steps > 10 || @download_wait_steps == 0) + @download_wait_steps = 10 + end + initialize_wait_counter + end - # ウェイト管理関係初期化 + # + # ウェイト関係初期化 + # + def initialize_wait_counter @@__run_once ||= false unless @@__run_once @@__run_once = true @@__wait_counter = 0 @@__last_download_time = Time.now - 20 - @@interval_sleep_time = Inventory.load("local_setting", :local)["download.interval"] || 0 - @@interval_sleep_time = 0 if @@interval_sleep_time < 0 - @@max_steps_wait_time = [STEPS_WAIT_TIME, @@interval_sleep_time].max end - @download_wait_steps = Inventory.load("local_setting", :local)["download.wait-steps"] || 0 - @download_use_subdirectory = use_subdirectory? - if @setting["is_narou"] && (@download_wait_steps > 10 || @download_wait_steps == 0) - @download_wait_steps = 10 - end + @@interval_sleep_time = Inventory.load("local_setting", :local)["download.interval"] || 0 + @@interval_sleep_time = 0 if @@interval_sleep_time < 0 + @@max_steps_wait_time = [STEPS_WAIT_TIME, @@interval_sleep_time].max end # # サブディレクトリに保存してあるかどうか # @@ -370,16 +363,16 @@ # def run_download old_toc = @new_novel ? nil : load_toc_file latest_toc = get_latest_table_of_contents(old_toc) unless latest_toc - error @setting["toc_url"] + " の目次データが取得出来ませんでした" + @stream.error @setting["toc_url"] + " の目次データが取得出来ませんでした" return :failed end if @setting["confirm_over18"] unless confirm_over18? - puts "18歳以上のみ閲覧出来る小説です。ダウンロードを中止しました" + @stream.puts "18歳以上のみ閲覧出来る小説です。ダウンロードを中止しました" return :canceled end end unless old_toc init_novel_dir @@ -392,11 +385,11 @@ else update_subtitles = update_body_check(old_toc["subtitles"], latest_toc["subtitles"]) end if old_toc.empty? && update_subtitles.count == 0 - error "#{@setting['title']} の目次がありません" + @stream.error "#{@setting['title']} の目次がありません" return :failed end unless @force if process_digest(old_toc, latest_toc) @@ -415,27 +408,27 @@ if @cache_dir && Dir.glob(File.join(@cache_dir, "*")).count == 0 remove_cache_dir end rescue Interrupt remove_cache_dir - puts "ダウンロードを中断しました" + @stream.puts "ダウンロードを中断しました" exit Narou::EXIT_ERROR_CODE end update_database :ok when old_toc["subtitles"].size > latest_toc["subtitles"].size # 削除された節がある(かつ更新がない)場合 - puts "#{id_and_title} は一部の話が削除されています" + @stream.puts "#{id_and_title} は一部の話が削除されています" :ok when old_toc["title"] != latest_toc["title"] # タイトルが更新されている場合 - puts "#{id_and_title} のタイトルが更新されています" + @stream.puts "#{id_and_title} のタイトルが更新されています" update_database :ok when old_toc["story"] != latest_toc["story"] # あらすじが更新されている場合 - puts "#{id_and_title} のあらすじが更新されています" + @stream.puts "#{id_and_title} のあらすじが更新されています" :ok else :none end save_novel_data(TOC_FILE_NAME, latest_toc) @@ -445,20 +438,20 @@ update_database if update_subtitles.count == 0 $stdout.silence do Command::Tag.execute!([@id, "--add", "end", "--color", "white"]) end msg = old_toc.empty? ? "完結しているようです" : "完結したようです" - puts "<cyan>#{id_and_title.escape} は#{msg}</cyan>".termcolor + @stream.puts "<cyan>#{id_and_title.escape} は#{msg}</cyan>".termcolor return_status = :ok end else if tags.include?("end") update_database if update_subtitles.size == 0 $stdout.silence do Command::Tag.execute!([@id, "--delete", "end"]) end - puts "<cyan>#{id_and_title.escape} は連載を再開したようです</cyan>".termcolor + @stream.puts "<cyan>#{id_and_title.escape} は連載を再開したようです</cyan>".termcolor return_status = :ok end end return_status ensure @@ -706,11 +699,11 @@ raise DownloaderNotFoundError unless s # 非公開や削除等でトップページへリダイレクトされる場合がある @setting.clear # 今まで使っていたのは一旦クリア @setting = s toc_url = @setting["toc_url"] end - toc_source = Helper.pretreatment_source(toc_fp.read, @setting["encoding"]) + toc_source = Helper.restor_entity(Helper.pretreatment_source(toc_fp.read, @setting["encoding"])) raise DownloaderNotFoundError if Downloader.detect_error_message(@setting, toc_source) end toc_source end @@ -760,19 +753,19 @@ } toc_objects rescue OpenURI::HTTPError, Errno::ECONNRESET => e raise if through_error # エラー処理はしなくていいからそのまま例外を受け取りたい時用 if e.message.include?("404") - error "小説が削除されているか非公開な可能性があります" + @stream.error "小説が削除されているか非公開な可能性があります" if @@database.novel_exists?(@id) $stdout.silence do Command::Tag.execute!(%W(#{@id} --add 404 --color white)) end Command::Freeze.execute!([@id]) end else - error "何らかの理由により目次が取得できませんでした(#{e.message})" + @stream.error "何らかの理由により目次が取得できませんでした(#{e.message})" end false end def __search_index_in_subtitles(subtitles, index) @@ -928,52 +921,57 @@ # subtitles にダウンロードしたいものをまとめた subtitle info を渡す # def sections_download_and_save(subtitles) max = subtitles.size return if max == 0 - puts "<bold><green>#{"ID:#{@id} #{get_title}".escape} のDL開始</green></bold>".termcolor + @stream.puts "<bold><green>#{"ID:#{@id} #{get_title}".escape} のDL開始</green></bold>".termcolor save_least_one = false subtitles.each_with_index do |subtitle_info, i| index, subtitle, file_subtitle, chapter = %w(index subtitle file_subtitle chapter).map { |k| subtitle_info[k] } info = subtitle_info.dup info["element"] = a_section_download(subtitle_info) unless chapter.empty? - puts "#{chapter}" + @stream.puts "#{chapter}" end if get_novel_type == NOVEL_TYPE_SERIES if index.to_s.length <= DISPLAY_LIMIT_DIGITS # indexの数字がでかいと見た目がみっともないので特定の桁以内だけ表示する - print "第#{index}部分 " + @stream.print "第#{index}部分 " end else - print "短編 " + @stream.print "短編 " end - print "#{subtitle} (#{i+1}/#{max})" + @stream.print "#{subtitle} (#{i+1}/#{max})" section_file_name = "#{index} #{file_subtitle}.yaml" section_file_relative_path = File.join(SECTION_SAVE_DIR_NAME, section_file_name) - if File.exist?(File.join(get_novel_data_dir, section_file_relative_path)) + section_file_full_path = File.join(get_novel_data_dir, section_file_relative_path) + if File.exist?(section_file_full_path) if @force if different_section?(section_file_relative_path, info) - print " (更新あり)" + @stream.print " (更新あり)" move_to_cache_dir(section_file_relative_path) end else move_to_cache_dir(section_file_relative_path) end else if !@from_download || (@from_download && @force) - print " <bold><magenta>(新着)</magenta></bold>".termcolor + @stream.print " <bold><magenta>(新着)</magenta></bold>".termcolor + trigger(:newarrival, { + id: @id, + subtitle_info: subtitle_info + }) end @new_arrivals = true end save_novel_data(section_file_relative_path, info) save_least_one = true - puts + @stream.puts end remove_cache_dir unless save_least_one end # @@ -1031,10 +1029,11 @@ else subtitle_url = @setting["toc_url"] + href end raw = download_raw_data(subtitle_url) if @setting["is_narou"] + raw = Helper.restor_entity(raw) save_raw_data(raw, subtitle_info) element = extract_elements_in_section(raw, subtitle_info["subtitle"]) element["data_type"] = "text" else save_raw_data(raw, subtitle_info, ".html") @@ -1053,28 +1052,29 @@ # 指定したURLからデータをダウンロード # def download_raw_data(url) raw = nil retry_count = RETRY_MAX_FOR_503 + cookie = @setting["cookie"] || "" begin - open(url) do |fp| + open(url, "Cookie" => cookie) do |fp| raw = Helper.pretreatment_source(fp.read, @setting["encoding"]) end rescue OpenURI::HTTPError => e if e.message =~ /^503/ if retry_count == 0 - error "上限までリトライしましたがファイルがダウンロード出来ませんでした" + @stream.error "上限までリトライしましたがファイルがダウンロード出来ませんでした" exit Narou::EXIT_ERROR_CODE end retry_count -= 1 - puts - warn "server message: #{e.message}" - warn "リトライ待機中……" + @stream.puts + @stream.warn "server message: #{e.message}" + @stream.warn "リトライ待機中……" @@display_hint_once ||= false unless @@display_hint_once - warn "ヒント: narou s download.wait-steps=10 とすることで、" \ - "10話ごとにウェイトをいれられます" + @stream.warn "ヒント: narou s download.wait-steps=10 とすることで、" \ + "10話ごとにウェイトをいれられます" @@display_hint_once = true end sleep(WAITING_TIME_FOR_503) retry else @@ -1191,14 +1191,16 @@ # def init_novel_dir novel_dir_path = get_novel_data_dir file_title = File.basename(novel_dir_path) FileUtils.mkdir_p(novel_dir_path) unless File.exist?(novel_dir_path) - original_settings = NovelSetting::ORIGINAL_SETTINGS + original_settings = NovelSetting.get_original_settings + default_settings = NovelSetting.load_default_settings + novel_setting = NovelSetting.new(@id, true, true) special_preset_dir = File.join(Narou.get_preset_dir, @setting["domain"], @setting["ncode"]) exists_special_preset_dir = File.exist?(special_preset_dir) templates = [ - [NovelSetting::INI_NAME, 1.1], + [NovelSetting::INI_NAME, NovelSetting::INI_ERB_BINARY_VERSION], ["converter.rb", 1.0], [NovelSetting::REPLACE_NAME, 1.0] ] templates.each do |(filename, binary_version)| if exists_special_preset_dir