diff --git a/lib/command/backup.rb b/lib/command/backup.rb
index e1c8acc5..6b9d9095 100644
--- a/lib/command/backup.rb
+++ b/lib/command/backup.rb
@@ -46,7 +46,12 @@ def create_backup(data)
backup_dir = novel_dir.join(BACKUP_DIR_NAME)
backup_dir.mkdir unless backup_dir.exist?
Zip.unicode_names = true unless Helper.os_windows?
- Zip::File.open(backup_dir.join(zipfilename), Zip::File::CREATE) do |zip|
+
+ # Windowsでのスレッド内ファイル操作対策: GCを強制実行してファイルハンドルを解放
+ GC.start
+ sleep 0.1
+
+ Zip::File.open(backup_dir.join(zipfilename), create: true) do |zip|
paths.each do |path|
relative_path = path_to_relative(novel_dir, path).to_s
zipped_filename =
diff --git a/lib/command/update.rb b/lib/command/update.rb
index 44b53896..2d3ebdc8 100644
--- a/lib/command/update.rb
+++ b/lib/command/update.rb
@@ -210,7 +210,19 @@ def execute(argv)
end
convert_argv = [target]
convert_argv << "--no-open" if @options["no-open"]
+
+ # WebUI: 変換メッセージを変換コンソールに出力するため、一時的に$stdout2を切り替え
+ original_stdout2 = $stdout2
+ if $stdout2.respond_to?(:push_server) && $stdout2.respond_to?(:target_console)
+ # WebUIの場合、変換用の別コンソールに出力
+ $stdout2 = Narou::StreamingLogger.new($stdout2.push_server, $stdout2, target_console: "convert")
+ end
+
convert_status = Convert.execute!(convert_argv)
+
+ # $stdout2を元に戻す
+ $stdout2 = original_stdout2 if original_stdout2
+
if convert_status > 0
# 変換が失敗したか、中断された
data["_convert_failure"] = true
diff --git a/lib/command/web.rb b/lib/command/web.rb
index 9bac54dd..42f7a785 100644
--- a/lib/command/web.rb
+++ b/lib/command/web.rb
@@ -180,12 +180,12 @@ def open_browser_when_server_boot(address)
def send_rebooted_event_when_connection_recover(push_server)
return unless @rebooted
- Thread.new do |th|
+ Thread.new do
timeout = Time.now + 20
# WebSocketのコネクションが回復するまで待つ
until push_server.connections.count != 0
sleep 0.2
- th.kill if Time.now > timeout
+ Thread.current.kill if Time.now > timeout
end
puts "再起動が完了しました。".termcolor
push_server.send_all(:"server.rebooted")
diff --git a/lib/device/ibunko.rb b/lib/device/ibunko.rb
index 2c51ba6e..ea4eee89 100644
--- a/lib/device/ibunko.rb
+++ b/lib/device/ibunko.rb
@@ -53,7 +53,12 @@ def create_pure_aozora_zip
zipfile_path = @converted_txt_path.sub(/.txt$/, @device.ebook_file_ext)
File.delete(zipfile_path) if File.exist?(zipfile_path)
- Zip::File.open(zipfile_path, Zip::File::CREATE) do |zip|
+
+ # Windowsでのスレッド内ファイル操作対策: GCを強制実行してファイルハンドルを解放
+ GC.start
+ sleep 0.1
+
+ Zip::File.open(zipfile_path, create: true) do |zip|
# テキスト本体(整形済み)
zip.add(File.basename(@converted_txt_path), sanitized_txt_path) { true }
# 挿絵(Aozora注記のまま。画像ファイルは同梱)
@@ -72,7 +77,7 @@ def create_pure_aozora_zip
end
end
FileUtils.rm_f(sanitized_txt_path)
- output_io = (respond_to?(:stream_io) ? stream_io : nil) || $stdout
+ output_io = $stdout2
output_io.puts File.basename(zipfile_path) + " を出力しました"
output_io.puts "#{@device.display_name}用ファイルを出力しました".termcolor
if Narou.economy?("cleanup_temp") && @argument_target_type == :novel
@@ -98,7 +103,12 @@ def hook_convert_txt_to_ebook_file(&original_func)
translate_illust_chuki_to_img_tag
zipfile_path = @converted_txt_path.sub(/.txt$/, @device.ebook_file_ext)
File.delete(zipfile_path) if File.exist?(zipfile_path)
- Zip::File.open(zipfile_path, Zip::File::CREATE) do |zip|
+
+ # Windowsでのスレッド内ファイル操作対策: GCを強制実行してファイルハンドルを解放
+ GC.start
+ sleep 0.1
+
+ Zip::File.open(zipfile_path, create: true) do |zip|
# テキスト本体
zip.add(File.basename(@converted_txt_path), @converted_txt_path) { true }
# 挿絵
@@ -116,7 +126,7 @@ def hook_convert_txt_to_ebook_file(&original_func)
zip.add(cover_name, File.join(dirpath, cover_name)) { true }
end
end
- output_io = (respond_to?(:stream_io) ? stream_io : nil) || $stdout
+ output_io = $stdout2
output_io.puts File.basename(zipfile_path) + " を出力しました"
output_io.puts "#{@device.display_name}用ファイルを出力しました".termcolor
if Narou.economy?("cleanup_temp") && @argument_target_type == :novel
diff --git a/lib/downloader.rb b/lib/downloader.rb
index 9fb28831..909dcc4b 100644
--- a/lib/downloader.rb
+++ b/lib/downloader.rb
@@ -755,6 +755,7 @@ def get_toc_source
toc_url = @setting["toc_url"]
return nil unless toc_url
max_retry = 5
+ retry_count = LIMIT_TO_RETRY_NETWORK
toc_source = ""
cookie = @setting["cookie"] || ""
open_uri_options = make_open_uri_options("Cookie" => cookie, allow_redirections: :safe)
@@ -787,6 +788,28 @@ def get_toc_source
else
raise
end
+ rescue OpenURI::HTTPError, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::ETIMEDOUT, Net::OpenTimeout, IO::TimeoutError, SocketError => e
+ case e.message
+ when /^503/
+ @stream&.error "server message: #{e.message}"
+ display_hint if @stream
+ raise SuspendDownload
+ when /^404/
+ # 404は上位のget_latest_table_of_contentsで処理させるため、そのまま再raise
+ raise e
+ else
+ if retry_count == 0
+ @stream&.error "上限までリトライしましたが目次がダウンロード出来ませんでした"
+ raise SuspendDownload
+ end
+ retry_count -= 1
+ @stream&.puts <<~MSG
+ server message: #{e.message}
+ リトライ待機中...
+ MSG
+ sleep(WAIT_TIME_TO_RETRY_NETWORK)
+ retry
+ end
end
toc_source
end
@@ -834,7 +857,7 @@ def get_latest_table_of_contents(old_toc, through_error: false)
"subtitles" => subtitles
}
toc_objects
- rescue OpenURI::HTTPError, Errno::ECONNRESET, Errno::ETIMEDOUT, Net::OpenTimeout, IO::TimeoutError => e
+ rescue OpenURI::HTTPError, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::ETIMEDOUT, Net::OpenTimeout, IO::TimeoutError, SocketError => e
raise if through_error # エラー処理はしなくていいからそのまま例外を受け取りたい時用
if e.message.include?("404")
@stream.error "小説が削除されているか非公開な可能性があります"
@@ -1235,7 +1258,7 @@ def download_raw_data(url)
URI.open(url, "r:#{@setting["encoding"]}", open_uri_options) do |fp|
raw = Helper.pretreatment_source(fp.read, @setting["encoding"])
end
- rescue OpenURI::HTTPError, Errno::ECONNRESET, Errno::ETIMEDOUT, Net::OpenTimeout, IO::TimeoutError => e
+ rescue OpenURI::HTTPError, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::ETIMEDOUT, Net::OpenTimeout, IO::TimeoutError, SocketError => e
case e.message
when /^503/
# 503 はアクセス規制やメンテ等でリトライしてもほぼ意味がないことが多いため一度で諦める
diff --git a/lib/novelconverter.rb b/lib/novelconverter.rb
index a120df02..3ce386f1 100644
--- a/lib/novelconverter.rb
+++ b/lib/novelconverter.rb
@@ -280,17 +280,21 @@ def self.add_dc_subject_to_epub(epub_path, subjects, stream_io: $stdout2)
return :error
end
- content = opf_body.dup
+ content = opf_body.dup.force_encoding("UTF-8")
content.gsub!(/.*?<\/dc:subject>\s*\n?\s*/m, "")
dc_subject_lines = subjects.map(&:strip).reject(&:empty?).map { |s|
esc = s.gsub("&","&").gsub("<","<").gsub(">",">").gsub("\"",""").gsub("'","'")
- " #{esc}"
+ "\t\t#{esc}"
}
if dc_subject_lines.any?
dc_subjects_xml = dc_subject_lines.join("\n") + "\n"
content.sub!(/(\s*)<\/metadata>/, "\n#{dc_subjects_xml}\\1")
end
- entries[opf_name] = content
+ entries[opf_name] = content.b
+
+ # Windowsでのスレッド内ファイル操作対策: GCを強制実行してファイルハンドルを解放
+ GC.start
+ sleep 0.1
# 再Zip化 (mimetypeは無圧縮で先頭)
File.delete(epub_path)
@@ -315,7 +319,7 @@ def self.add_dc_subject_to_epub(epub_path, subjects, stream_io: $stdout2)
stream_io.puts "dc:subjectを追加しました: #{subjects.join(', ')}"
:success
rescue => e
- stream_io.error "dc:subject追加中にエラーが発生しました: #{e.message}"
+ stream_io.error "dc:subject追加中にエラーが発生しました: #{e.class} - #{e.message}"
:error
end
end
diff --git a/lib/web/public/resources/js/narou.library.js b/lib/web/public/resources/js/narou.library.js
index e7a70e3e..2854faa5 100644
--- a/lib/web/public/resources/js/narou.library.js
+++ b/lib/web/public/resources/js/narou.library.js
@@ -1902,6 +1902,23 @@ var Narou = (function () {
return matched;
});
+ // 通常単語を追加するためのイベント(テーブル tbody に直接登録)
+ // サーバーサイド/クライアントサイド両方で必要
+ $("#novel-list tbody").on("click", "[data-add-filter]", function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ self.appendWordToFilter($(this).data("addFilter"));
+ return false;
+ });
+ // 追加イベントが発生した時に行選択イベントが起こらないように
+ $("#novel-list tbody").on("mousedown", "[data-add-filter]", function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ return false;
+ });
+
// サーバーサイド処理の場合はカスタムフィルタボックスのイベントを登録しない
// (narou.ui.jsで登録されるハンドラを使用)
if (this.table.settings()[0].oFeatures.bServerSide) {
@@ -1931,17 +1948,6 @@ var Narou = (function () {
self.search();
}
});
-
- this.table
- // 通常単語を追加するためのイベント
- .on("click", "[data-add-filter]", function (e) {
- e.stopPropagation();
- self.appendWordToFilter($(this).data("addFilter"));
- })
- // 追加イベントが発生した時に行選択イベントが起こらないように
- .on("mousedown", "[data-add-filter]", function (e) {
- e.stopPropagation();
- });
},
// フィルタにタグを追加
@@ -2107,6 +2113,7 @@ var Narou = (function () {
$target
.on("click", ".tag", args, function (e) {
if (e.data.stop_bubbling) e.stopPropagation();
+ e.preventDefault(); // テキスト選択を防ぐ
var tag_name = String($(this).data("tag"));
self.search.appendTagToFilter(tag_name, e.altKey);
self.table.$("[data-toggle=tooltip]").tooltip("hide");
@@ -2114,6 +2121,7 @@ var Narou = (function () {
.on("mousedown", ".tag", args, function (e) {
// 範囲選択モードでもクリック出来るように
if (e.data.stop_bubbling) e.stopPropagation();
+ e.preventDefault(); // テキスト選択を防ぐ
});
},
diff --git a/lib/web/public/resources/js/narou.ui.js b/lib/web/public/resources/js/narou.ui.js
index 459ef75a..9c1a6e9d 100644
--- a/lib/web/public/resources/js/narou.ui.js
+++ b/lib/web/public/resources/js/narou.ui.js
@@ -222,7 +222,6 @@ $(function() {
var isLightweightMode = false;
var table, t;
var action; // アクション管理インスタンス
- window.action = action;
var isInitialLoad = true; // 初回ロードフラグ
/*
@@ -1285,7 +1284,12 @@ $(function() {
// ここだけはスマフォ系でも touchstart ではなく click で。
// touchstart だと画面スクロールのためのスワイプでも反応してしまう
table
- .on("click", "tr", function () {
+ .on("click", "tr", function (e) {
+ // data-add-filter要素がクリックされた場合は何もしない
+ if ($(e.target).closest('[data-add-filter]').length > 0) {
+ return;
+ }
+
// DOM の選択状態を変更
$(this).toggleClass("selected");
@@ -1311,6 +1315,10 @@ $(function() {
// リンクをクリックした時は選択処理は行わない
e.stopPropagation();
})
+ .on("click", "[data-add-filter]", function(e) {
+ // フィルタ追加要素をクリックした時は選択処理は行わない
+ e.stopPropagation();
+ })
$("#novel-list tbody").css("cursor", "pointer");
}
diff --git a/lib/web/pushserver.rb b/lib/web/pushserver.rb
index d7ed6487..26aecf50 100644
--- a/lib/web/pushserver.rb
+++ b/lib/web/pushserver.rb
@@ -50,9 +50,16 @@ def run
end
thread = Thread.new do
- while true
- data = que.pop
- ws.send(data)
+ begin
+ while true
+ data = que.pop
+ ws.send(data)
+ end
+ rescue Errno::ECONNRESET, Errno::EPIPE, IOError => e
+ # 接続が切れた場合、スレッドを終了
+ rescue => e
+ # その他のエラーもログに出力してスレッド終了
+ puts "[ERROR] WebSocket send thread error: #{e.class}: #{e.message}" if $DEBUG
end
end