narou 3.5.0.1 → 3.7.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of narou might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.circleci/config.yml +3 -3
- data/.rubocop.yml +4 -4
- data/ChangeLog.md +37 -0
- data/Gemfile.lock +83 -71
- data/README.md +7 -2
- data/lib/command/browser.rb +1 -1
- data/lib/command/diff.rb +2 -2
- data/lib/command/folder.rb +6 -1
- data/lib/command/setting.rb +3 -3
- data/lib/command/update/general_lastup_updater.rb +2 -1
- data/lib/converterbase.rb +1 -1
- data/lib/device.rb +6 -7
- data/lib/downloader.rb +5 -4
- data/lib/helper.rb +3 -3
- data/lib/illustration.rb +10 -1
- data/lib/inventory.rb +2 -2
- data/lib/mailer.rb +1 -1
- data/lib/narou.rb +276 -270
- data/lib/novelconverter.rb +3 -3
- data/lib/sitesetting.rb +1 -1
- data/lib/template.rb +1 -1
- data/lib/version.rb +1 -1
- data/lib/web/appserver.rb +8 -5
- data/narou.gemspec +16 -8
- metadata +81 -28
data/lib/downloader.rb
CHANGED
@@ -89,7 +89,7 @@ class Downloader
|
|
89
89
|
#
|
90
90
|
def self.get_target_type(target)
|
91
91
|
case target
|
92
|
-
when URI.
|
92
|
+
when URI::DEFAULT_PARSER.make_regexp
|
93
93
|
:url
|
94
94
|
when /^n\d+[a-z]+$/i
|
95
95
|
target.downcase!
|
@@ -157,7 +157,7 @@ class Downloader
|
|
157
157
|
# toc 読込
|
158
158
|
#
|
159
159
|
def self.get_toc_data(archive_path)
|
160
|
-
YAML.
|
160
|
+
YAML.unsafe_load_file(File.join(archive_path, TOC_FILE_NAME))
|
161
161
|
end
|
162
162
|
|
163
163
|
def self.get_toc_by_target(target)
|
@@ -801,6 +801,7 @@ class Downloader
|
|
801
801
|
raise if through_error # エラー処理はしなくていいからそのまま例外を受け取りたい時用
|
802
802
|
if e.message.include?("404")
|
803
803
|
@stream.error "小説が削除されているか非公開な可能性があります"
|
804
|
+
sleep_for_download
|
804
805
|
if database.novel_exists?(@id)
|
805
806
|
Command::Tag.execute!(%W(#{@id} --add 404 --color white --no-overwrite-color), io: Narou::NullIO.new)
|
806
807
|
Command::Freeze.execute!(@id, "--on")
|
@@ -1042,7 +1043,7 @@ class Downloader
|
|
1042
1043
|
def different_section?(old_relative_path, new_subtitle_info)
|
1043
1044
|
path = get_novel_data_dir.join(old_relative_path)
|
1044
1045
|
return true unless path.exist?
|
1045
|
-
YAML.
|
1046
|
+
YAML.unsafe_load_file(path)["element"] != new_subtitle_info["element"]
|
1046
1047
|
end
|
1047
1048
|
|
1048
1049
|
#
|
@@ -1267,7 +1268,7 @@ class Downloader
|
|
1267
1268
|
#
|
1268
1269
|
# 小説データの格納ディレクトリから読み込む
|
1269
1270
|
def load_novel_data(filename)
|
1270
|
-
YAML.
|
1271
|
+
YAML.unsafe_load_file(get_novel_data_dir.join(filename))
|
1271
1272
|
rescue Errno::ENOENT
|
1272
1273
|
nil
|
1273
1274
|
end
|
data/lib/helper.rb
CHANGED
@@ -350,10 +350,10 @@ module Helper
|
|
350
350
|
# 長過ぎるファイルパスを詰める
|
351
351
|
# ファイル名部分のみを詰める。拡張子は維持する
|
352
352
|
#
|
353
|
-
def truncate_path(path, limit = Inventory.load["filename-length-limit"])
|
353
|
+
def truncate_path(path, limit = Inventory.load["filename-length-limit"], extname: nil)
|
354
354
|
limit ||= FILENAME_LENGTH_LIMIT
|
355
355
|
dirname = File.dirname(path)
|
356
|
-
extname
|
356
|
+
extname ||= File.extname(path)
|
357
357
|
basename = File.basename(path, extname)
|
358
358
|
if basename.length > limit
|
359
359
|
basename = basename[0...limit]
|
@@ -375,7 +375,7 @@ module Helper
|
|
375
375
|
#
|
376
376
|
def erb_copy(src, dst, _binding)
|
377
377
|
data = File.read(src, mode: "r:BOM|UTF-8")
|
378
|
-
result = ERB.new(data,
|
378
|
+
result = ERB.new(data, trim_mode: "-").result(_binding)
|
379
379
|
File.write(dst, result)
|
380
380
|
end
|
381
381
|
|
data/lib/illustration.rb
CHANGED
@@ -27,7 +27,7 @@ class Illustration
|
|
27
27
|
source.gsub!(/[#挿絵((.+?))入る]/) do |match|
|
28
28
|
url = $1
|
29
29
|
url = "https:#{url}" if url.start_with?("//")
|
30
|
-
if url =~ URI.
|
30
|
+
if url =~ URI::DEFAULT_PARSER.make_regexp
|
31
31
|
path = download_image(url)
|
32
32
|
path ? block.call(make_illust_chuki(path)) : ""
|
33
33
|
else
|
@@ -53,6 +53,8 @@ class Illustration
|
|
53
53
|
if path = search_image(basename)
|
54
54
|
return path
|
55
55
|
end
|
56
|
+
|
57
|
+
url = transform_mitemin_url(url)
|
56
58
|
URI.open(url, make_open_uri_options(allow_redirections: :safe)) do |fp|
|
57
59
|
content_type = fp.meta["content-type"]
|
58
60
|
ext = MIME[content_type] or raise UnknownMIMEType, content_type
|
@@ -77,6 +79,13 @@ class Illustration
|
|
77
79
|
Dir.glob(path)[0]
|
78
80
|
end
|
79
81
|
|
82
|
+
def transform_mitemin_url(url)
|
83
|
+
uri = URI.parse(url)
|
84
|
+
return url unless uri.host.end_with?(".mitemin.net")
|
85
|
+
|
86
|
+
url.sub("viewimagebig", "viewimage")
|
87
|
+
end
|
88
|
+
|
80
89
|
def create_illust_path(basename)
|
81
90
|
illust_abs_dir = File.join(@setting.archive_path, ILLUST_DIR)
|
82
91
|
Dir.mkdir(illust_abs_dir) unless File.exist?(illust_abs_dir)
|
data/lib/inventory.rb
CHANGED
@@ -44,13 +44,13 @@ module Inventory
|
|
44
44
|
return unless File.exist?(@inventory_file_path)
|
45
45
|
self.merge!(Helper::CacheLoader.memo(@inventory_file_path) { |yaml|
|
46
46
|
begin
|
47
|
-
YAML.
|
47
|
+
YAML.unsafe_load(yaml)
|
48
48
|
rescue Psych::SyntaxError
|
49
49
|
unless restore(@inventory_file_path)
|
50
50
|
error "#{@inventory_file_path} が壊れてるっぽい"
|
51
51
|
raise
|
52
52
|
end
|
53
|
-
YAML.
|
53
|
+
YAML.unsafe_load_file(@inventory_file_path)
|
54
54
|
end
|
55
55
|
})
|
56
56
|
end
|
data/lib/mailer.rb
CHANGED
@@ -23,7 +23,7 @@ class Mailer
|
|
23
23
|
this.clear
|
24
24
|
setting_file_path = File.join(Narou.root_dir, SETTING_FILE)
|
25
25
|
if File.exist?(setting_file_path)
|
26
|
-
options = YAML.
|
26
|
+
options = YAML.unsafe_load_file(setting_file_path)
|
27
27
|
unless options.delete(:complete)
|
28
28
|
raise SettingUncompleteError, "設定ファイルの書き換えが終了していないようです。\n" +
|
29
29
|
"設定ファイルは #{setting_file_path} にあります"
|
data/lib/narou.rb
CHANGED
@@ -38,330 +38,336 @@ module Narou
|
|
38
38
|
|
39
39
|
extend Mixin::Locker
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
class << self
|
42
|
+
extend Memoist
|
43
43
|
|
44
|
-
|
44
|
+
@@is_web = false
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
def last_commit_year
|
47
|
+
2021
|
48
|
+
end
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
50
|
+
def root_dir
|
51
|
+
root = nil
|
52
|
+
path = Dir.pwd
|
53
|
+
drive_letter = ""
|
54
|
+
if Helper.os_windows?
|
55
|
+
path.encode!(Encoding::UTF_8)
|
56
|
+
path.gsub!(/^[a-z]:/i, "")
|
57
|
+
drive_letter = $&
|
58
|
+
end
|
59
|
+
while path != ""
|
60
|
+
if File.directory?("#{drive_letter}#{path}/#{LOCAL_SETTING_DIR_NAME}")
|
61
|
+
root = drive_letter + path
|
62
|
+
break
|
63
|
+
end
|
64
|
+
path.gsub!(%r!/[^/]*$!, "")
|
63
65
|
end
|
64
|
-
|
66
|
+
Pathname(root) if root
|
65
67
|
end
|
66
|
-
|
67
|
-
end
|
68
|
-
memoize :root_dir
|
68
|
+
memoize :root_dir
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
def local_setting_dir
|
71
|
+
root_dir&.join(LOCAL_SETTING_DIR_NAME)
|
72
|
+
end
|
73
|
+
memoize :local_setting_dir
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
75
|
+
def global_setting_dir
|
76
|
+
if root_dir
|
77
|
+
dir = root_dir.join(GLOBAL_SETTING_DIR_NAME)
|
78
|
+
return dir if dir.directory?
|
79
|
+
end
|
80
|
+
dir = Pathname(GLOBAL_SETTING_DIR_NAME).expand_path("~")
|
81
|
+
dir.mkdir unless dir.exist?
|
82
|
+
dir
|
79
83
|
end
|
80
|
-
|
81
|
-
dir.mkdir unless dir.exist?
|
82
|
-
dir
|
83
|
-
end
|
84
|
-
memoize :global_setting_dir
|
84
|
+
memoize :global_setting_dir
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
86
|
+
def script_dir
|
87
|
+
Pathname(__dir__).join("..").expand_path
|
88
|
+
end
|
89
|
+
memoize :script_dir
|
90
90
|
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
def log_dir
|
92
|
+
root_dir&.join(LOG_DIR)
|
93
|
+
end
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
95
|
+
def preset_dir
|
96
|
+
script_dir&.join(PRESET_DIR)
|
97
|
+
end
|
98
|
+
memoize :preset_dir
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
|
100
|
+
def already_init?
|
101
|
+
root_dir.present?
|
102
|
+
end
|
103
103
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
104
|
+
def init
|
105
|
+
return nil if already_init?
|
106
|
+
FileUtils.mkdir(LOCAL_SETTING_DIR_NAME)
|
107
|
+
puts "#{LOCAL_SETTING_DIR_NAME}/ を作成しました"
|
108
|
+
Database.init
|
109
|
+
end
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
111
|
+
#
|
112
|
+
# target が alias だった場合はIDに変換する
|
113
|
+
#
|
114
|
+
# 全てのtarget照合系はこのメソッドを通過するので、ここで小文字にしてしまう
|
115
|
+
#
|
116
|
+
def alias_to_id(target)
|
117
|
+
aliases = Inventory.load("alias")
|
118
|
+
if aliases[target]
|
119
|
+
return aliases[target]
|
120
|
+
end
|
121
|
+
target.is_a?(Numeric) ? target : target.downcase
|
122
|
+
end
|
123
123
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
124
|
+
def novel_frozen?(target)
|
125
|
+
id = Downloader.get_id_by_target(target) or return false
|
126
|
+
Inventory.load("freeze").include?(id)
|
127
|
+
end
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
def create_aozoraepub3_jar_path(*paths)
|
130
|
+
Pathname(File.expand_path(File.join(*paths, AOZORAEPUB3_JAR_NAME)))
|
131
|
+
end
|
132
132
|
|
133
|
-
|
134
|
-
|
135
|
-
|
133
|
+
def aozoraepub3_directory?(path)
|
134
|
+
create_aozoraepub3_jar_path(path).exist?
|
135
|
+
end
|
136
136
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
137
|
+
def parse_replace_txt(text)
|
138
|
+
pattern = []
|
139
|
+
text.each_line do |line|
|
140
|
+
line.sub!(/[\r\n]+\z/, "")
|
141
|
+
next if line[0] == ";" # コメント記号
|
142
|
+
pair = line.split("\t", 2)
|
143
|
+
if pair.length == 2 && pair[0]
|
144
|
+
pattern << pair
|
145
|
+
end
|
145
146
|
end
|
147
|
+
pattern
|
146
148
|
end
|
147
|
-
pattern
|
148
|
-
end
|
149
149
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
150
|
+
def write_replace_txt(path, pairs)
|
151
|
+
buffer = pairs.each_with_object("\t").map(&:join).join("\n")
|
152
|
+
File.write(path, buffer)
|
153
|
+
end
|
154
154
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
155
|
+
def load_global_replace_pattern
|
156
|
+
path = root_dir.join(GLOBAL_REPLACE_NAME)
|
157
|
+
pairs =
|
158
|
+
if path.exist?
|
159
|
+
Helper::CacheLoader.memo(path) do |text|
|
160
|
+
parse_replace_txt(text)
|
161
|
+
end
|
162
|
+
else
|
163
|
+
[]
|
161
164
|
end
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
@@global_replace_pattern_pairs = pairs
|
166
|
-
pairs
|
167
|
-
end
|
165
|
+
@@global_replace_pattern_pairs = pairs
|
166
|
+
pairs
|
167
|
+
end
|
168
168
|
|
169
|
-
|
170
|
-
|
171
|
-
|
169
|
+
def global_replace_pattern
|
170
|
+
@@global_replace_pattern_pairs ||= load_global_replace_pattern
|
171
|
+
end
|
172
172
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
173
|
+
def save_global_replace_pattern
|
174
|
+
path = root_dir.join(GLOBAL_REPLACE_NAME)
|
175
|
+
write_replace_txt(path, @@global_replace_pattern_pairs)
|
176
|
+
end
|
177
177
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
178
|
+
#
|
179
|
+
# AozoraEpub3 の実行ファイル(.jar)のフルパス取得
|
180
|
+
# 検索順序
|
181
|
+
# 1. グローバルセッティング (global_setting aozoraepub3dir)
|
182
|
+
# 2. 小説保存ディレクトリ(Narou.root_dir) 直下の AozoraEpub3
|
183
|
+
# 3. スクリプト保存ディレクトリ(Narou.script_dir) 直下の AozoraEpub3
|
184
|
+
#
|
185
|
+
def aozoraepub3_path
|
186
|
+
global_setting_aozora_path = Inventory.load("global_setting", :global)["aozoraepub3dir"]
|
187
|
+
if global_setting_aozora_path
|
188
|
+
aozora_jar_path = create_aozoraepub3_jar_path(global_setting_aozora_path)
|
189
|
+
if aozora_jar_path.exist?
|
190
|
+
return aozora_jar_path
|
191
|
+
end
|
191
192
|
end
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
return aozora_jar_path if aozora_jar_path.exist?
|
196
|
-
end
|
197
|
-
nil
|
198
|
-
end
|
199
|
-
memoize :aozoraepub3_path
|
200
|
-
|
201
|
-
#
|
202
|
-
# 書籍ファイル名を生成する
|
203
|
-
# convert.filename-to-ncode を設定している場合に novel_data に ncode、domain を
|
204
|
-
# 設定しない場合は id カラムが必須
|
205
|
-
#
|
206
|
-
def create_novel_filename(novel_data, ext = "")
|
207
|
-
filename_to_ncode = Inventory.load("local_setting")["convert.filename-to-ncode"]
|
208
|
-
novel_setting =
|
209
|
-
if novel_data["id"]
|
210
|
-
NovelSetting.load(novel_data["id"])
|
211
|
-
else
|
212
|
-
OpenStruct.new
|
193
|
+
[Narou.root_dir, Narou.script_dir].each do |dir|
|
194
|
+
aozora_jar_path = create_aozoraepub3_jar_path(dir, AOZORAEPUB3_DIR)
|
195
|
+
return aozora_jar_path if aozora_jar_path.exist?
|
213
196
|
end
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
memoize :aozoraepub3_path
|
200
|
+
|
201
|
+
#
|
202
|
+
# 書籍ファイル名を生成する
|
203
|
+
# convert.filename-to-ncode を設定している場合に novel_data に ncode、domain を
|
204
|
+
# 設定しない場合は id カラムが必須
|
205
|
+
#
|
206
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
207
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
208
|
+
def create_novel_filename(novel_data, ext = "")
|
209
|
+
filename_to_ncode = Inventory.load("local_setting")["convert.filename-to-ncode"]
|
210
|
+
novel_setting =
|
211
|
+
if novel_data["id"]
|
212
|
+
NovelSetting.load(novel_data["id"])
|
213
|
+
else
|
214
|
+
OpenStruct.new
|
215
|
+
end
|
216
|
+
if novel_setting.output_filename.present?
|
217
|
+
%!#{novel_setting.output_filename}#{ext}!
|
218
|
+
elsif filename_to_ncode
|
219
|
+
ncode = novel_data["ncode"]
|
220
|
+
domain = novel_data["domain"]
|
221
|
+
if !ncode || !domain
|
222
|
+
id = novel_data["id"]
|
223
|
+
unless id
|
224
|
+
raise ArgumentError, %!novel_data["id"] を設定して下さい!
|
225
|
+
end
|
226
|
+
site_setting = Downloader.get_sitesetting_by_target(id)
|
227
|
+
ncode = site_setting["ncode"]
|
228
|
+
domain = site_setting["domain"]
|
222
229
|
end
|
223
|
-
|
224
|
-
ncode
|
225
|
-
|
230
|
+
serialized_domain = domain.to_s.gsub(".", "_")
|
231
|
+
%!#{serialized_domain}_#{ncode}#{ext}!
|
232
|
+
else
|
233
|
+
author = Helper.replace_filename_special_chars(
|
234
|
+
novel_setting.novel_author.presence || novel_data["author"],
|
235
|
+
true
|
236
|
+
)
|
237
|
+
title = Helper.replace_filename_special_chars(
|
238
|
+
novel_setting.novel_title.presence || novel_data["title"],
|
239
|
+
true
|
240
|
+
)
|
241
|
+
filename = "[#{author}] #{title}#{ext}"
|
242
|
+
length_limit = Inventory.load["ebook-filename-length-limit"]
|
243
|
+
length_limit ? Helper.truncate_path(filename, length_limit, extname: ext) : filename
|
226
244
|
end
|
227
|
-
serialized_domain = domain.to_s.gsub(".", "_")
|
228
|
-
%!#{serialized_domain}_#{ncode}#{ext}!
|
229
|
-
else
|
230
|
-
author = Helper.replace_filename_special_chars(
|
231
|
-
novel_setting.novel_author.presence || novel_data["author"],
|
232
|
-
true
|
233
|
-
)
|
234
|
-
title = Helper.replace_filename_special_chars(
|
235
|
-
novel_setting.novel_title.presence || novel_data["title"],
|
236
|
-
true
|
237
|
-
)
|
238
|
-
filename = "[#{author}] #{title}#{ext}"
|
239
|
-
length_limit = Inventory.load["ebook-filename-length-limit"]
|
240
|
-
length_limit ? Helper.truncate_path(filename, length_limit) : filename
|
241
245
|
end
|
242
|
-
|
246
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
247
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
243
248
|
|
244
|
-
|
245
|
-
|
246
|
-
|
249
|
+
def get_mobi_paths(target)
|
250
|
+
get_ebook_file_paths(target, ".mobi")
|
251
|
+
end
|
247
252
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
dir = Downloader.get_novel_data_dir_by_target(target)
|
252
|
-
fname = create_novel_filename(data, ext)
|
253
|
-
base = File.basename(fname, ext)
|
254
|
-
get_ebook_file_paths_from_components(dir, base, ext)
|
255
|
-
end
|
253
|
+
def get_ebook_file_paths(target, ext)
|
254
|
+
data = Downloader.get_data_by_target(target)
|
255
|
+
return nil unless data
|
256
256
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
paths.push(Pathname(path))
|
262
|
-
index += 1
|
257
|
+
dir = Downloader.get_novel_data_dir_by_target(target)
|
258
|
+
fname = create_novel_filename(data, ext)
|
259
|
+
base = File.basename(fname, ext)
|
260
|
+
get_ebook_file_paths_from_components(dir, base, ext)
|
263
261
|
end
|
264
|
-
paths
|
265
|
-
end
|
266
262
|
|
267
|
-
|
268
|
-
|
269
|
-
|
263
|
+
def get_ebook_file_paths_from_components(dir, base, ext)
|
264
|
+
paths = [File.join(dir, "#{base}#{ext}")]
|
265
|
+
index = 2
|
266
|
+
while File.exist?(path = File.join(dir, "#{base}_#{index}#{ext}"))
|
267
|
+
paths.push(Pathname(path))
|
268
|
+
index += 1
|
269
|
+
end
|
270
|
+
paths
|
271
|
+
end
|
272
|
+
|
273
|
+
def misc_dir
|
274
|
+
root_dir.join(MISC_DIR)
|
275
|
+
end
|
270
276
|
|
271
|
-
|
277
|
+
require_relative "device"
|
272
278
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
279
|
+
def get_device(device_name = nil)
|
280
|
+
device_name ||= Inventory.load("local_setting")["device"]
|
281
|
+
if device_name && Device.exists?(device_name)
|
282
|
+
return Device.create(device_name)
|
283
|
+
end
|
284
|
+
nil
|
277
285
|
end
|
278
|
-
nil
|
279
|
-
end
|
280
286
|
|
281
|
-
|
282
|
-
|
283
|
-
|
287
|
+
def web=(bool)
|
288
|
+
@@is_web = bool
|
289
|
+
end
|
284
290
|
|
285
|
-
|
286
|
-
|
287
|
-
|
291
|
+
def web?
|
292
|
+
@@is_web
|
293
|
+
end
|
288
294
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
295
|
+
def update_sort_key_summaries(left_space = 28)
|
296
|
+
summaries = { "KEY" => " 対象" }.merge(UPDATE_SORT_KEYS)
|
297
|
+
key_max_width = summaries.keys.max_by(&:length).length
|
298
|
+
summaries.map do |(key, summary)|
|
299
|
+
"#{" " * left_space}| #{key.center(key_max_width)} | #{summary}"
|
300
|
+
end.join("\n")
|
301
|
+
end
|
296
302
|
|
297
|
-
|
298
|
-
|
299
|
-
|
303
|
+
def theme
|
304
|
+
Inventory.load("local_setting")["webui.theme"]
|
305
|
+
end
|
300
306
|
|
301
|
-
|
302
|
-
|
303
|
-
|
307
|
+
def get_theme_dir(name = nil)
|
308
|
+
Pathname(File.join([script_dir, "lib/web/public/theme", name].compact))
|
309
|
+
end
|
304
310
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
311
|
+
def theme_names
|
312
|
+
Dir.glob(get_theme_dir("*")).map do |path|
|
313
|
+
name = File.basename(path)
|
314
|
+
name == "fonts" ? nil : name
|
315
|
+
end.compact
|
316
|
+
end
|
317
|
+
memoize :theme_names
|
312
318
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
319
|
+
def economy?(mode)
|
320
|
+
eco_modes = Inventory.load("local_setting")["economy"].to_s.split(",").map(&:strip)
|
321
|
+
eco_modes.include?(mode)
|
322
|
+
end
|
317
323
|
|
318
|
-
|
319
|
-
|
320
|
-
|
324
|
+
def novel_type_text(type)
|
325
|
+
type == 2 ? "短編" : "連載"
|
326
|
+
end
|
321
327
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
328
|
+
#
|
329
|
+
# Narou.rb gem の最新バージョン番号を取得する
|
330
|
+
#
|
331
|
+
# rubygems公式APIによる取得は、WindowsでのSSL証明書問題で取得出来ない
|
332
|
+
# 環境があるため、gemコマンド経由で取得する
|
333
|
+
#
|
334
|
+
def latest_version
|
335
|
+
response = `gem search ^narou$`.split("\n")
|
336
|
+
if response.last =~ /\Anarou \(([0-9.]+).*?\)\z/
|
337
|
+
$1
|
338
|
+
end
|
332
339
|
end
|
333
|
-
end
|
334
340
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
341
|
+
def commit_version
|
342
|
+
cv_path = File.expand_path("commitversion", script_dir)
|
343
|
+
File.read(cv_path) if File.exist?(cv_path)
|
344
|
+
end
|
345
|
+
memoize :commit_version
|
340
346
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
347
|
+
def kindlegen_path
|
348
|
+
postfix = Helper.os_windows? ? ".exe" : ""
|
349
|
+
aozoraepub3_path.dirname.join("kindlegen#{postfix}")
|
350
|
+
end
|
351
|
+
memoize :kindlegen_path
|
346
352
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
353
|
+
def line_height(default: LINE_HEIGHT_DEFAULT)
|
354
|
+
global_setting = Inventory.load("global_setting", :global)
|
355
|
+
global_setting["line-height"] || default
|
356
|
+
end
|
351
357
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
358
|
+
def concurrency_enabled?
|
359
|
+
$stdout != $stdout2
|
360
|
+
end
|
361
|
+
memoize :concurrency_enabled?
|
356
362
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
363
|
+
# 同時実行が有効ならキューに積んで、無効なら普通に実行する
|
364
|
+
def concurrency_call(&block)
|
365
|
+
if concurrency_enabled?
|
366
|
+
Worker.push(&block)
|
367
|
+
EXIT_SUCCESS
|
368
|
+
else
|
369
|
+
block.call
|
370
|
+
end
|
364
371
|
end
|
365
372
|
end
|
366
|
-
end
|
367
373
|
end
|