rufio 1.1.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +26 -297
- data/README_ja.md +24 -261
- data/Rakefile +14 -1
- data/bin/rufio +31 -2
- data/docs/fast_zip_design_ja.md +167 -0
- data/docs/guide.md +410 -0
- data/docs/guide_ja.md +410 -0
- data/info/help.md +54 -17
- data/info/keybindings.md +50 -22
- data/info/welcome.md +14 -5
- data/lib/rufio/application.rb +12 -1
- data/lib/rufio/archive_controller.rb +211 -0
- data/lib/rufio/background_command_executor.rb +9 -0
- data/lib/rufio/background_status.rb +49 -0
- data/lib/rufio/bookmark_controller.rb +38 -121
- data/lib/rufio/bookmark_manager.rb +77 -331
- data/lib/rufio/command_completion.rb +28 -2
- data/lib/rufio/command_history.rb +5 -2
- data/lib/rufio/command_mode.rb +60 -134
- data/lib/rufio/command_mode_ui.rb +4 -42
- data/lib/rufio/config.rb +88 -136
- data/lib/rufio/config_loader.rb +4 -11
- data/lib/rufio/dialog_renderer.rb +90 -152
- data/lib/rufio/dialog_support.rb +69 -0
- data/lib/rufio/document_text_extractor.rb +167 -0
- data/lib/rufio/file_operation_controller.rb +49 -222
- data/lib/rufio/file_operations.rb +28 -6
- data/lib/rufio/file_preview.rb +46 -2
- data/lib/rufio/global_search.rb +125 -13
- data/lib/rufio/help_menu.rb +19 -7
- data/lib/rufio/interpreter_resolver.rb +6 -11
- data/lib/rufio/job_manager.rb +2 -5
- data/lib/rufio/key_input.rb +77 -0
- data/lib/rufio/keybind_handler.rb +83 -116
- data/lib/rufio/local_script_scanner.rb +6 -18
- data/lib/rufio/native/aarch64-linux-gnu/librufio_zig.so +0 -0
- data/lib/rufio/native/aarch64-linux-musl/librufio_zig.so +0 -0
- data/lib/rufio/native/aarch64-macos/librufio_zig.dylib +0 -0
- data/lib/rufio/native/aarch64-windows/rufio_zig.dll +0 -0
- data/lib/rufio/native/x86_64-linux-gnu/librufio_zig.so +0 -0
- data/lib/rufio/native/x86_64-linux-musl/librufio_zig.so +0 -0
- data/lib/rufio/native/x86_64-macos/librufio_zig.dylib +0 -0
- data/lib/rufio/native/x86_64-windows/rufio_zig.dll +0 -0
- data/lib/rufio/native_content_search.rb +153 -0
- data/lib/rufio/native_scanner.rb +13 -68
- data/lib/rufio/native_scanner_zig.rb +106 -56
- data/lib/rufio/native_zip.rb +121 -0
- data/lib/rufio/navigation_controller.rb +20 -26
- data/lib/rufio/path_jump.rb +55 -0
- data/lib/rufio/screen.rb +2 -12
- data/lib/rufio/script_executor.rb +6 -73
- data/lib/rufio/script_file.rb +44 -0
- data/lib/rufio/script_path_manager.rb +9 -43
- data/lib/rufio/script_runner.rb +13 -50
- data/lib/rufio/search_controller.rb +1 -1
- data/lib/rufio/shell_command_completion.rb +1 -5
- data/lib/rufio/shell_init.rb +90 -0
- data/lib/rufio/task_status.rb +5 -1
- data/lib/rufio/terminal_ui.rb +84 -298
- data/lib/rufio/text_utils.rb +26 -3
- data/lib/rufio/ui_renderer.rb +73 -45
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio/zip_archive.rb +314 -0
- data/lib/rufio/zip_extractor.rb +346 -0
- data/lib/rufio/zip_writer.rb +268 -0
- data/lib/rufio/zoxide_integration.rb +5 -43
- data/lib/rufio.rb +6 -0
- data/lib_zig/rufio_native/Makefile +54 -17
- data/lib_zig/rufio_native/bench.sh +36 -0
- data/lib_zig/rufio_native/build.zig +32 -35
- data/lib_zig/rufio_native/src/cli.zig +99 -0
- data/lib_zig/rufio_native/src/main.zig +187 -83
- data/lib_zig/rufio_native/src/os.zig +889 -0
- data/lib_zig/rufio_native/src/search.zig +919 -0
- data/lib_zig/rufio_native/src/search_test.zig +88 -0
- data/lib_zig/rufio_native/src/zip.zig +482 -0
- metadata +128 -3
- data/lib/rufio/native/rufio_zig.bundle +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'dialog_support'
|
|
4
|
+
|
|
3
5
|
require_relative 'file_operations'
|
|
4
6
|
require_relative 'dialog_renderer'
|
|
5
7
|
require_relative 'logger'
|
|
@@ -8,6 +10,8 @@ module Rufio
|
|
|
8
10
|
# ファイル操作専用コントローラ
|
|
9
11
|
# KeybindHandler からファイル作成・削除・リネーム・移動・コピー系メソッドを分離し、単一責任原則に準拠
|
|
10
12
|
class FileOperationController
|
|
13
|
+
include DialogSupport
|
|
14
|
+
|
|
11
15
|
# Dialog size constants
|
|
12
16
|
CONFIRMATION_DIALOG_WIDTH = 45
|
|
13
17
|
DIALOG_BORDER_HEIGHT = 4
|
|
@@ -35,51 +39,15 @@ module Rufio
|
|
|
35
39
|
# ============================
|
|
36
40
|
|
|
37
41
|
def create_file
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
filename = @dialog_renderer.show_input_dialog("Create File", "Enter file name:", {
|
|
41
|
-
border_color: "\e[32m",
|
|
42
|
-
title_color: "\e[1;32m",
|
|
43
|
-
content_color: "\e[37m"
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
return false if filename.nil? || filename.empty?
|
|
47
|
-
|
|
48
|
-
result = @file_operations.create_file(current_path, filename)
|
|
49
|
-
|
|
50
|
-
if result.success
|
|
51
|
-
@directory_listing.refresh
|
|
52
|
-
entries = @directory_listing.list_entries
|
|
53
|
-
new_file_index = entries.find_index { |entry| entry[:name] == filename }
|
|
54
|
-
@nav_controller.select_index(new_file_index) if new_file_index
|
|
42
|
+
prompt_and_select_entry("Create File", "Enter file name:", "\e[32m", "\e[1;32m") do |path, name|
|
|
43
|
+
@file_operations.create_file(path, name)
|
|
55
44
|
end
|
|
56
|
-
|
|
57
|
-
@terminal_ui&.refresh_display
|
|
58
|
-
result.success
|
|
59
45
|
end
|
|
60
46
|
|
|
61
47
|
def create_directory
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
dirname = @dialog_renderer.show_input_dialog("Create Directory", "Enter directory name:", {
|
|
65
|
-
border_color: "\e[34m",
|
|
66
|
-
title_color: "\e[1;34m",
|
|
67
|
-
content_color: "\e[37m"
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
return false if dirname.nil? || dirname.empty?
|
|
71
|
-
|
|
72
|
-
result = @file_operations.create_directory(current_path, dirname)
|
|
73
|
-
|
|
74
|
-
if result.success
|
|
75
|
-
@directory_listing.refresh
|
|
76
|
-
entries = @directory_listing.list_entries
|
|
77
|
-
new_dir_index = entries.find_index { |entry| entry[:name] == dirname }
|
|
78
|
-
@nav_controller.select_index(new_dir_index) if new_dir_index
|
|
48
|
+
prompt_and_select_entry("Create Directory", "Enter directory name:", "\e[34m", "\e[1;34m") do |path, name|
|
|
49
|
+
@file_operations.create_directory(path, name)
|
|
79
50
|
end
|
|
80
|
-
|
|
81
|
-
@terminal_ui&.refresh_display
|
|
82
|
-
result.success
|
|
83
51
|
end
|
|
84
52
|
|
|
85
53
|
# ============================
|
|
@@ -91,27 +59,9 @@ module Rufio
|
|
|
91
59
|
return false unless entry
|
|
92
60
|
|
|
93
61
|
current_name = entry[:name]
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
new_name = @dialog_renderer.show_input_dialog("Rename: #{current_name}", "Enter new name:", {
|
|
97
|
-
border_color: "\e[33m",
|
|
98
|
-
title_color: "\e[1;33m",
|
|
99
|
-
content_color: "\e[37m"
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
return false if new_name.nil? || new_name.empty?
|
|
103
|
-
|
|
104
|
-
result = @file_operations.rename(current_path, current_name, new_name)
|
|
105
|
-
|
|
106
|
-
if result.success
|
|
107
|
-
@directory_listing.refresh
|
|
108
|
-
entries = @directory_listing.list_entries
|
|
109
|
-
new_index = entries.find_index { |e| e[:name] == new_name }
|
|
110
|
-
@nav_controller.select_index(new_index) if new_index
|
|
62
|
+
prompt_and_select_entry("Rename: #{current_name}", "Enter new name:", "\e[33m", "\e[1;33m") do |path, new_name|
|
|
63
|
+
@file_operations.rename(path, current_name, new_name)
|
|
111
64
|
end
|
|
112
|
-
|
|
113
|
-
@terminal_ui&.refresh_display
|
|
114
|
-
result.success
|
|
115
65
|
end
|
|
116
66
|
|
|
117
67
|
# ============================
|
|
@@ -154,31 +104,13 @@ module Rufio
|
|
|
154
104
|
''
|
|
155
105
|
]
|
|
156
106
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
confirmed = false
|
|
162
|
-
show_overlay_dialog(title, content_lines, {
|
|
163
|
-
width: width,
|
|
164
|
-
height: height,
|
|
107
|
+
confirmed = confirm_dialog('Confirm Delete', content_lines, {
|
|
108
|
+
width: [50, current_name.length + 10].max,
|
|
109
|
+
height: content_lines.length + 4,
|
|
165
110
|
border_color: "\e[31m",
|
|
166
111
|
title_color: "\e[1;31m",
|
|
167
112
|
content_color: "\e[37m"
|
|
168
|
-
})
|
|
169
|
-
loop do
|
|
170
|
-
input = STDIN.getch.downcase
|
|
171
|
-
case input
|
|
172
|
-
when 'y'
|
|
173
|
-
confirmed = true
|
|
174
|
-
break
|
|
175
|
-
when 'n', "\e"
|
|
176
|
-
confirmed = false
|
|
177
|
-
break
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
nil
|
|
181
|
-
end
|
|
113
|
+
})
|
|
182
114
|
|
|
183
115
|
return false unless confirmed
|
|
184
116
|
|
|
@@ -260,69 +192,39 @@ module Rufio
|
|
|
260
192
|
''
|
|
261
193
|
]
|
|
262
194
|
|
|
263
|
-
|
|
264
|
-
show_overlay_dialog('Exit Confirmation', content_lines, {
|
|
195
|
+
confirm_dialog('Exit Confirmation', content_lines, {
|
|
265
196
|
width: CONFIRMATION_DIALOG_WIDTH,
|
|
266
197
|
height: DIALOG_BORDER_HEIGHT + content_lines.length,
|
|
267
198
|
border_color: "\e[33m",
|
|
268
199
|
title_color: "\e[1;33m",
|
|
269
200
|
content_color: "\e[37m"
|
|
270
|
-
})
|
|
271
|
-
loop do
|
|
272
|
-
input = STDIN.getch.downcase
|
|
273
|
-
case input
|
|
274
|
-
when 'y'
|
|
275
|
-
confirmed = true
|
|
276
|
-
break
|
|
277
|
-
when 'n', "\e", "\x03"
|
|
278
|
-
confirmed = false
|
|
279
|
-
break
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
nil
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
confirmed
|
|
201
|
+
}, cancel_on_quit: false)
|
|
286
202
|
end
|
|
287
203
|
|
|
288
204
|
private
|
|
289
205
|
|
|
290
|
-
def
|
|
291
|
-
|
|
292
|
-
@terminal_ui.respond_to?(:screen) &&
|
|
293
|
-
@terminal_ui.respond_to?(:renderer) &&
|
|
294
|
-
@terminal_ui.screen &&
|
|
295
|
-
@terminal_ui.renderer
|
|
296
|
-
|
|
297
|
-
if use_overlay
|
|
298
|
-
@terminal_ui.show_overlay_dialog(title, content_lines, options, &block)
|
|
299
|
-
else
|
|
300
|
-
width = options[:width]
|
|
301
|
-
height = options[:height]
|
|
302
|
-
|
|
303
|
-
unless width && height
|
|
304
|
-
width, height = @dialog_renderer.calculate_dimensions(content_lines, {
|
|
305
|
-
title: title,
|
|
306
|
-
min_width: options[:min_width] || 40,
|
|
307
|
-
max_width: options[:max_width] || 80
|
|
308
|
-
})
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
x, y = @dialog_renderer.calculate_center(width, height)
|
|
206
|
+
def prompt_and_select_entry(title, prompt, border_color, title_color)
|
|
207
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
312
208
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
209
|
+
name = @dialog_renderer.show_input_dialog(title, prompt, {
|
|
210
|
+
border_color: border_color,
|
|
211
|
+
title_color: title_color,
|
|
212
|
+
content_color: "\e[37m"
|
|
213
|
+
})
|
|
318
214
|
|
|
319
|
-
|
|
215
|
+
return false if name.nil? || name.empty?
|
|
320
216
|
|
|
321
|
-
|
|
322
|
-
@terminal_ui&.refresh_display
|
|
217
|
+
result = yield(current_path, name)
|
|
323
218
|
|
|
324
|
-
|
|
219
|
+
if result.success
|
|
220
|
+
@directory_listing.refresh
|
|
221
|
+
entries = @directory_listing.list_entries
|
|
222
|
+
new_index = entries.find_index { |e| e[:name] == name }
|
|
223
|
+
@nav_controller.select_index(new_index) if new_index
|
|
325
224
|
end
|
|
225
|
+
|
|
226
|
+
@terminal_ui&.refresh_display
|
|
227
|
+
result.success
|
|
326
228
|
end
|
|
327
229
|
|
|
328
230
|
def show_delete_confirmation(count, source_path = nil)
|
|
@@ -342,111 +244,37 @@ module Rufio
|
|
|
342
244
|
|
|
343
245
|
print "\a"
|
|
344
246
|
|
|
345
|
-
|
|
346
|
-
show_overlay_dialog(title, content_lines, {
|
|
247
|
+
confirm_dialog(title, content_lines, {
|
|
347
248
|
width: dialog_width,
|
|
348
249
|
height: dialog_height,
|
|
349
250
|
border_color: "\e[31m",
|
|
350
251
|
title_color: "\e[1;31m",
|
|
351
252
|
content_color: "\e[37m"
|
|
352
|
-
})
|
|
353
|
-
loop do
|
|
354
|
-
input = STDIN.getch.downcase
|
|
355
|
-
case input
|
|
356
|
-
when 'y'
|
|
357
|
-
confirmed = true
|
|
358
|
-
break
|
|
359
|
-
when 'n', "\e", "\x03", 'q'
|
|
360
|
-
confirmed = false
|
|
361
|
-
break
|
|
362
|
-
end
|
|
363
|
-
end
|
|
364
|
-
nil
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
confirmed
|
|
253
|
+
})
|
|
368
254
|
end
|
|
369
255
|
|
|
370
256
|
def show_move_confirmation(count, source_path, dest_path)
|
|
371
|
-
|
|
372
|
-
dest_display = shorten_path(dest_path, 35)
|
|
373
|
-
|
|
374
|
-
content_lines = [
|
|
375
|
-
'',
|
|
376
|
-
"Move #{count} item(s)?",
|
|
377
|
-
'',
|
|
378
|
-
"From: #{source_display}",
|
|
379
|
-
"To: #{dest_display}",
|
|
380
|
-
'',
|
|
381
|
-
' [Y]es - Move',
|
|
382
|
-
' [N]o - Cancel',
|
|
383
|
-
''
|
|
384
|
-
]
|
|
385
|
-
|
|
386
|
-
confirmed = false
|
|
387
|
-
show_overlay_dialog('Move Confirmation', content_lines, {
|
|
388
|
-
width: CONFIRMATION_DIALOG_WIDTH,
|
|
389
|
-
height: DIALOG_BORDER_HEIGHT + content_lines.length,
|
|
390
|
-
border_color: "\e[34m",
|
|
391
|
-
title_color: "\e[1;34m",
|
|
392
|
-
content_color: "\e[37m"
|
|
393
|
-
}) do
|
|
394
|
-
loop do
|
|
395
|
-
input = STDIN.getch.downcase
|
|
396
|
-
case input
|
|
397
|
-
when 'y'
|
|
398
|
-
confirmed = true
|
|
399
|
-
break
|
|
400
|
-
when 'n', "\e", "\x03", 'q'
|
|
401
|
-
confirmed = false
|
|
402
|
-
break
|
|
403
|
-
end
|
|
404
|
-
end
|
|
405
|
-
nil
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
confirmed
|
|
257
|
+
show_transfer_confirmation('Move', count, source_path, dest_path, '34')
|
|
409
258
|
end
|
|
410
259
|
|
|
411
260
|
def show_copy_confirmation(count, source_path, dest_path)
|
|
412
|
-
|
|
413
|
-
|
|
261
|
+
show_transfer_confirmation('Copy', count, source_path, dest_path, '32')
|
|
262
|
+
end
|
|
414
263
|
|
|
264
|
+
def show_transfer_confirmation(action, count, source_path, dest_path, color)
|
|
415
265
|
content_lines = [
|
|
416
|
-
'',
|
|
417
|
-
"
|
|
418
|
-
'',
|
|
419
|
-
"
|
|
420
|
-
"To: #{dest_display}",
|
|
421
|
-
'',
|
|
422
|
-
' [Y]es - Copy',
|
|
423
|
-
' [N]o - Cancel',
|
|
424
|
-
''
|
|
266
|
+
'', "#{action} #{count} item(s)?", '',
|
|
267
|
+
"From: #{shorten_path(source_path, 35)}",
|
|
268
|
+
"To: #{shorten_path(dest_path, 35)}", '',
|
|
269
|
+
" [Y]es - #{action}", ' [N]o - Cancel', ''
|
|
425
270
|
]
|
|
426
|
-
|
|
427
|
-
confirmed = false
|
|
428
|
-
show_overlay_dialog('Copy Confirmation', content_lines, {
|
|
271
|
+
confirm_dialog("#{action} Confirmation", content_lines, {
|
|
429
272
|
width: CONFIRMATION_DIALOG_WIDTH,
|
|
430
273
|
height: DIALOG_BORDER_HEIGHT + content_lines.length,
|
|
431
|
-
border_color: "\e[
|
|
432
|
-
title_color: "\e[1
|
|
274
|
+
border_color: "\e[#{color}m",
|
|
275
|
+
title_color: "\e[1;#{color}m",
|
|
433
276
|
content_color: "\e[37m"
|
|
434
|
-
})
|
|
435
|
-
loop do
|
|
436
|
-
input = STDIN.getch.downcase
|
|
437
|
-
case input
|
|
438
|
-
when 'y'
|
|
439
|
-
confirmed = true
|
|
440
|
-
break
|
|
441
|
-
when 'n', "\e", "\x03", 'q'
|
|
442
|
-
confirmed = false
|
|
443
|
-
break
|
|
444
|
-
end
|
|
445
|
-
end
|
|
446
|
-
nil
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
confirmed
|
|
277
|
+
})
|
|
450
278
|
end
|
|
451
279
|
|
|
452
280
|
def show_deletion_result(success_count, total_count, error_messages = [])
|
|
@@ -484,14 +312,13 @@ module Rufio
|
|
|
484
312
|
title_color: title_color,
|
|
485
313
|
content_color: "\e[37m"
|
|
486
314
|
}) do
|
|
487
|
-
|
|
315
|
+
KeyInput.getch
|
|
488
316
|
nil
|
|
489
317
|
end
|
|
490
318
|
end
|
|
491
319
|
|
|
492
320
|
def shorten_path(path, max_length)
|
|
493
|
-
|
|
494
|
-
"...#{path[-(max_length - 3)..-1]}"
|
|
321
|
+
TextUtils.shorten_path_from_end(path, max_length)
|
|
495
322
|
end
|
|
496
323
|
end
|
|
497
324
|
end
|
|
@@ -246,16 +246,17 @@ module Rufio
|
|
|
246
246
|
dest_path = File.join(destination, item_name)
|
|
247
247
|
|
|
248
248
|
begin
|
|
249
|
-
if File.exist?(dest_path)
|
|
250
|
-
error_messages << "#{item_name}: Already exists in destination"
|
|
251
|
-
next
|
|
252
|
-
end
|
|
253
|
-
|
|
254
249
|
case operation
|
|
255
250
|
when :move
|
|
251
|
+
if File.exist?(dest_path)
|
|
252
|
+
error_messages << "#{item_name}: Already exists in destination"
|
|
253
|
+
next
|
|
254
|
+
end
|
|
256
255
|
FileUtils.mv(source_path, dest_path)
|
|
257
256
|
when :copy
|
|
258
|
-
|
|
257
|
+
is_directory = File.directory?(source_path)
|
|
258
|
+
dest_path = resolve_copy_destination(destination, item_name, is_directory)
|
|
259
|
+
if is_directory
|
|
259
260
|
FileUtils.cp_r(source_path, dest_path)
|
|
260
261
|
else
|
|
261
262
|
FileUtils.cp(source_path, dest_path)
|
|
@@ -281,6 +282,27 @@ module Rufio
|
|
|
281
282
|
)
|
|
282
283
|
end
|
|
283
284
|
|
|
285
|
+
def resolve_copy_destination(destination, item_name, is_directory)
|
|
286
|
+
dest_path = File.join(destination, item_name)
|
|
287
|
+
return dest_path unless File.exist?(dest_path)
|
|
288
|
+
|
|
289
|
+
if is_directory
|
|
290
|
+
base = item_name
|
|
291
|
+
ext = ''
|
|
292
|
+
else
|
|
293
|
+
ext = File.extname(item_name)
|
|
294
|
+
base = File.basename(item_name, ext)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
index = 1
|
|
298
|
+
loop do
|
|
299
|
+
candidate = File.join(destination, "#{base}_#{index}#{ext}")
|
|
300
|
+
return candidate unless File.exist?(candidate)
|
|
301
|
+
|
|
302
|
+
index += 1
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
284
306
|
# Build delete operation message
|
|
285
307
|
# @param success_count [Integer] Number of successfully deleted items
|
|
286
308
|
# @param total_count [Integer] Total number of items
|
data/lib/rufio/file_preview.rb
CHANGED
|
@@ -13,6 +13,9 @@ module Rufio
|
|
|
13
13
|
CONTROL_CHAR_NEWLINE = 10
|
|
14
14
|
CONTROL_CHAR_CARRIAGE_RETURN = 13
|
|
15
15
|
|
|
16
|
+
# ZIPプレビューで一覧表示するエントリ数の上限
|
|
17
|
+
ARCHIVE_PREVIEW_ENTRIES = 200
|
|
18
|
+
|
|
16
19
|
def initialize
|
|
17
20
|
# future: hold syntax highlight settings etc.
|
|
18
21
|
end
|
|
@@ -25,6 +28,8 @@ module Rufio
|
|
|
25
28
|
return empty_response if file_size == 0
|
|
26
29
|
|
|
27
30
|
begin
|
|
31
|
+
return archive_response(file_path, max_lines) if zip_archive?(file_path)
|
|
32
|
+
|
|
28
33
|
# binary file detection
|
|
29
34
|
sample = File.binread(file_path, [file_size, BINARY_SAMPLE_SIZE].min)
|
|
30
35
|
return binary_response(file_path) if binary_file?(sample)
|
|
@@ -49,6 +54,45 @@ module Rufio
|
|
|
49
54
|
|
|
50
55
|
private
|
|
51
56
|
|
|
57
|
+
def zip_archive?(file_path)
|
|
58
|
+
File.extname(file_path).casecmp?('.zip') || ZipArchive.zip_signature?(file_path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# unzip -l 相当のエントリ一覧をプレビュー行として返す
|
|
62
|
+
def archive_response(file_path, max_lines)
|
|
63
|
+
limit = [max_lines, ARCHIVE_PREVIEW_ENTRIES].max
|
|
64
|
+
listing = ZipArchive.list(file_path, limit: limit)
|
|
65
|
+
total_size = listing.entries.sum(&:size)
|
|
66
|
+
|
|
67
|
+
lines = ["ZIP archive: #{listing.total_entries} entries#{' (ZIP64)' if listing.zip64?}", '']
|
|
68
|
+
lines << format('%12s %-8s %-16s %s', 'Length', 'Method', 'Modified', 'Name')
|
|
69
|
+
lines << format('%12s %-8s %-16s %s', '-' * 10, '-' * 8, '-' * 16, '-' * 4)
|
|
70
|
+
listing.entries.each do |entry|
|
|
71
|
+
modified = entry.mtime ? entry.mtime.strftime('%Y-%m-%d %H:%M') : '-'
|
|
72
|
+
method = entry.encrypted? ? "#{ZipArchive.method_name(entry.method)}*" : ZipArchive.method_name(entry.method)
|
|
73
|
+
lines << format('%12d %-8s %-16s %s', entry.size, method, modified, entry.name)
|
|
74
|
+
end
|
|
75
|
+
lines << format('%12s', '-' * 10)
|
|
76
|
+
if listing.truncated?
|
|
77
|
+
lines << "... #{listing.total_entries - listing.entries.length} more entries"
|
|
78
|
+
else
|
|
79
|
+
lines << format('%12d %d files', total_size, listing.entries.length)
|
|
80
|
+
end
|
|
81
|
+
lines << "Comment: #{listing.comment}" unless listing.comment.empty?
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
type: "archive",
|
|
85
|
+
language: nil,
|
|
86
|
+
lines: lines,
|
|
87
|
+
truncated: listing.truncated?,
|
|
88
|
+
size: File.size(file_path),
|
|
89
|
+
modified: File.mtime(file_path),
|
|
90
|
+
encoding: "UTF-8"
|
|
91
|
+
}
|
|
92
|
+
rescue ZipArchive::FormatError => e
|
|
93
|
+
error_response("#{ConfigLoader.message('file.invalid_archive')}: #{e.message}")
|
|
94
|
+
end
|
|
95
|
+
|
|
52
96
|
def binary_file?(sample)
|
|
53
97
|
return false if sample.empty?
|
|
54
98
|
|
|
@@ -77,7 +121,7 @@ module Rufio
|
|
|
77
121
|
end
|
|
78
122
|
|
|
79
123
|
lines << line.chomp
|
|
80
|
-
rescue EncodingError, ArgumentError
|
|
124
|
+
rescue EncodingError, ArgumentError
|
|
81
125
|
# If encoding fails, add placeholder
|
|
82
126
|
lines << "[encoding error in line #{index + 1}]"
|
|
83
127
|
end
|
|
@@ -104,7 +148,7 @@ module Rufio
|
|
|
104
148
|
# Ensure line is properly encoded as UTF-8
|
|
105
149
|
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
106
150
|
lines << line.chomp
|
|
107
|
-
rescue EncodingError, ArgumentError
|
|
151
|
+
rescue EncodingError, ArgumentError
|
|
108
152
|
# If encoding fails, add placeholder
|
|
109
153
|
lines << "[encoding error in line #{index + 1}]"
|
|
110
154
|
end
|