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