rufio 0.32.0 → 0.33.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 +23 -0
- data/README.md +63 -7
- data/README_EN.md +25 -0
- data/docs/CHANGELOG_v0.33.0.md +444 -0
- data/docs/file-preview-optimization-analysis.md +759 -0
- data/docs/file-preview-performance-issue-FIXED.md +547 -0
- data/lib/rufio/application.rb +9 -1
- data/lib/rufio/background_command_executor.rb +98 -0
- data/lib/rufio/command_logger.rb +118 -0
- data/lib/rufio/command_mode.rb +17 -2
- data/lib/rufio/directory_listing.rb +60 -12
- data/lib/rufio/keybind_handler.rb +73 -2
- data/lib/rufio/native/rufio_native.bundle +0 -0
- data/lib/rufio/native/rufio_zig.bundle +0 -0
- data/lib/rufio/native_scanner.rb +306 -0
- data/lib/rufio/native_scanner_magnus.rb +194 -0
- data/lib/rufio/native_scanner_zig.rb +221 -0
- data/lib/rufio/terminal_ui.rb +66 -16
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +5 -0
- data/lib_rust/rufio_native/.cargo/config.toml +2 -0
- data/lib_rust/rufio_native/Cargo.lock +346 -0
- data/lib_rust/rufio_native/Cargo.toml +18 -0
- data/lib_rust/rufio_native/build.rs +46 -0
- data/lib_rust/rufio_native/src/lib.rs +197 -0
- data/lib_zig/rufio_native/Makefile +33 -0
- data/lib_zig/rufio_native/build.zig +45 -0
- data/lib_zig/rufio_native/src/main.zig +167 -0
- metadata +20 -2
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
module Rufio
|
|
7
|
+
# コマンド実行ログを保存・管理するクラス
|
|
8
|
+
class CommandLogger
|
|
9
|
+
attr_reader :log_dir
|
|
10
|
+
|
|
11
|
+
# 初期化
|
|
12
|
+
# @param log_dir [String] ログディレクトリのパス
|
|
13
|
+
def initialize(log_dir)
|
|
14
|
+
@log_dir = log_dir
|
|
15
|
+
FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# コマンド実行ログを保存
|
|
19
|
+
# @param command [String] 実行したコマンド
|
|
20
|
+
# @param output [String] コマンドの出力
|
|
21
|
+
# @param success [Boolean] 実行が成功したかどうか
|
|
22
|
+
# @param error [String, nil] エラーメッセージ(失敗時)
|
|
23
|
+
def log(command, output, success:, error: nil)
|
|
24
|
+
timestamp = Time.now
|
|
25
|
+
filename = generate_filename(command, timestamp)
|
|
26
|
+
filepath = File.join(@log_dir, filename)
|
|
27
|
+
|
|
28
|
+
content = format_log_content(command, output, timestamp, success, error)
|
|
29
|
+
|
|
30
|
+
File.write(filepath, content)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ログファイル一覧を取得(新しい順)
|
|
34
|
+
# @return [Array<String>] ログファイルのパス一覧
|
|
35
|
+
def list_logs
|
|
36
|
+
Dir.glob(File.join(@log_dir, "*.log")).sort.reverse
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# 古いログを削除
|
|
40
|
+
# @param max_logs [Integer] 保管する最大ログ数
|
|
41
|
+
def cleanup_old_logs(max_logs:)
|
|
42
|
+
logs = list_logs
|
|
43
|
+
return if logs.size <= max_logs
|
|
44
|
+
|
|
45
|
+
logs_to_delete = logs[max_logs..-1]
|
|
46
|
+
logs_to_delete.each do |log_file|
|
|
47
|
+
File.delete(log_file)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# ログファイル名を生成
|
|
54
|
+
# @param command [String] コマンド
|
|
55
|
+
# @param timestamp [Time] タイムスタンプ
|
|
56
|
+
# @return [String] ファイル名
|
|
57
|
+
def generate_filename(command, timestamp)
|
|
58
|
+
# ミリ秒を含めて一意性を確保
|
|
59
|
+
timestamp_str = timestamp.strftime("%Y%m%d%H%M%S") + sprintf("%03d", (timestamp.usec / 1000).to_i)
|
|
60
|
+
command_part = sanitize_command(command)
|
|
61
|
+
"#{timestamp_str}-#{command_part}.log"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# コマンド文字列をファイル名用にサニタイズ
|
|
65
|
+
# @param command [String] コマンド
|
|
66
|
+
# @return [String] サニタイズされたコマンド
|
|
67
|
+
def sanitize_command(command)
|
|
68
|
+
# Remove ! prefix if exists
|
|
69
|
+
cmd = command.start_with?('!') ? command[1..-1] : command
|
|
70
|
+
|
|
71
|
+
# Take first word (command name)
|
|
72
|
+
cmd = cmd.split.first || 'command'
|
|
73
|
+
|
|
74
|
+
# Remove unsafe characters
|
|
75
|
+
cmd = cmd.gsub(/[^\w\-]/, '_')
|
|
76
|
+
|
|
77
|
+
# Limit length
|
|
78
|
+
cmd[0...50]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# ログ内容をフォーマット
|
|
82
|
+
# @param command [String] コマンド
|
|
83
|
+
# @param output [String] 出力
|
|
84
|
+
# @param timestamp [Time] タイムスタンプ
|
|
85
|
+
# @param success [Boolean] 成功フラグ
|
|
86
|
+
# @param error [String, nil] エラーメッセージ
|
|
87
|
+
# @return [String] フォーマットされたログ内容
|
|
88
|
+
def format_log_content(command, output, timestamp, success, error)
|
|
89
|
+
lines = []
|
|
90
|
+
lines << "=" * 80
|
|
91
|
+
lines << "Command Execution Log"
|
|
92
|
+
lines << "=" * 80
|
|
93
|
+
lines << ""
|
|
94
|
+
lines << "Timestamp: #{timestamp.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
95
|
+
lines << "Command: #{command}"
|
|
96
|
+
lines << "Status: #{success ? 'Success' : 'Failed'}"
|
|
97
|
+
lines << ""
|
|
98
|
+
|
|
99
|
+
if error
|
|
100
|
+
lines << "Error:"
|
|
101
|
+
lines << error
|
|
102
|
+
lines << ""
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if output && !output.empty?
|
|
106
|
+
lines << "Output:"
|
|
107
|
+
lines << "-" * 80
|
|
108
|
+
lines << output
|
|
109
|
+
lines << "-" * 80
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
lines << ""
|
|
113
|
+
lines << "=" * 80
|
|
114
|
+
|
|
115
|
+
lines.join("\n")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/rufio/command_mode.rb
CHANGED
|
@@ -5,8 +5,11 @@ require 'open3'
|
|
|
5
5
|
module Rufio
|
|
6
6
|
# コマンドモード - プラグインコマンドを実行するためのインターフェース
|
|
7
7
|
class CommandMode
|
|
8
|
-
|
|
8
|
+
attr_accessor :background_executor
|
|
9
|
+
|
|
10
|
+
def initialize(background_executor = nil)
|
|
9
11
|
@commands = {}
|
|
12
|
+
@background_executor = background_executor
|
|
10
13
|
load_plugin_commands
|
|
11
14
|
end
|
|
12
15
|
|
|
@@ -17,7 +20,19 @@ module Rufio
|
|
|
17
20
|
|
|
18
21
|
# シェルコマンドの実行 (! で始まる場合)
|
|
19
22
|
if command_string.strip.start_with?('!')
|
|
20
|
-
|
|
23
|
+
shell_command = command_string.strip[1..-1]
|
|
24
|
+
|
|
25
|
+
# バックグラウンドエグゼキュータが利用可能な場合は非同期実行
|
|
26
|
+
if @background_executor
|
|
27
|
+
if @background_executor.execute_async(shell_command)
|
|
28
|
+
return "🔄 バックグラウンドで実行中: #{shell_command.split.first}"
|
|
29
|
+
else
|
|
30
|
+
return "⚠️ 既にコマンドが実行中です"
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
# バックグラウンドエグゼキュータがない場合は同期実行
|
|
34
|
+
return execute_shell_command(shell_command)
|
|
35
|
+
end
|
|
21
36
|
end
|
|
22
37
|
|
|
23
38
|
# コマンド名を取得 (前後の空白を削除)
|
|
@@ -22,18 +22,11 @@ module Rufio
|
|
|
22
22
|
|
|
23
23
|
@entries = []
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
name: name,
|
|
31
|
-
path: full_path,
|
|
32
|
-
type: determine_file_type(full_path),
|
|
33
|
-
size: safe_file_size(full_path),
|
|
34
|
-
modified: safe_file_mtime(full_path)
|
|
35
|
-
}
|
|
36
|
-
@entries << entry
|
|
25
|
+
# NativeScannerが利用可能な場合は使用
|
|
26
|
+
if use_native_scanner?
|
|
27
|
+
scan_with_native_scanner
|
|
28
|
+
else
|
|
29
|
+
scan_with_ruby
|
|
37
30
|
end
|
|
38
31
|
|
|
39
32
|
sort_entries!
|
|
@@ -84,6 +77,61 @@ module Rufio
|
|
|
84
77
|
|
|
85
78
|
private
|
|
86
79
|
|
|
80
|
+
# NativeScannerを使用するかどうか
|
|
81
|
+
def use_native_scanner?
|
|
82
|
+
defined?(Rufio::NativeScanner) && Rufio::NativeScanner.mode != 'ruby'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# NativeScannerでスキャン
|
|
86
|
+
def scan_with_native_scanner
|
|
87
|
+
entries = Rufio::NativeScanner.scan_directory(@current_path)
|
|
88
|
+
|
|
89
|
+
entries.each do |entry|
|
|
90
|
+
next if entry[:name] == '.'
|
|
91
|
+
|
|
92
|
+
full_path = File.join(@current_path, entry[:name])
|
|
93
|
+
|
|
94
|
+
# NativeScannerのエントリをDirectoryListingの形式に変換
|
|
95
|
+
converted_entry = {
|
|
96
|
+
name: entry[:name],
|
|
97
|
+
path: full_path,
|
|
98
|
+
type: convert_type(entry[:type], entry[:executable]),
|
|
99
|
+
size: entry[:size] || 0,
|
|
100
|
+
modified: entry[:mtime] ? Time.at(entry[:mtime]) : Time.now
|
|
101
|
+
}
|
|
102
|
+
@entries << converted_entry
|
|
103
|
+
end
|
|
104
|
+
rescue StandardError => e
|
|
105
|
+
# エラーが発生した場合はRubyでスキャン
|
|
106
|
+
Logger.debug "NativeScanner failed: #{e.message}, falling back to Ruby" if defined?(Logger)
|
|
107
|
+
scan_with_ruby
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Rubyの標準機能でスキャン
|
|
111
|
+
def scan_with_ruby
|
|
112
|
+
Dir.entries(@current_path).each do |name|
|
|
113
|
+
next if name == '.'
|
|
114
|
+
|
|
115
|
+
full_path = File.join(@current_path, name)
|
|
116
|
+
entry = {
|
|
117
|
+
name: name,
|
|
118
|
+
path: full_path,
|
|
119
|
+
type: determine_file_type(full_path),
|
|
120
|
+
size: safe_file_size(full_path),
|
|
121
|
+
modified: safe_file_mtime(full_path)
|
|
122
|
+
}
|
|
123
|
+
@entries << entry
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# NativeScannerのタイプをDirectoryListingのタイプに変換
|
|
128
|
+
def convert_type(type, executable)
|
|
129
|
+
return type if type == 'directory'
|
|
130
|
+
return 'executable' if executable
|
|
131
|
+
|
|
132
|
+
'file'
|
|
133
|
+
end
|
|
134
|
+
|
|
87
135
|
def determine_file_type(path)
|
|
88
136
|
return 'directory' if File.directory?(path)
|
|
89
137
|
return 'executable' if File.executable?(path) && !File.directory?(path)
|
|
@@ -56,6 +56,11 @@ module Rufio
|
|
|
56
56
|
@in_help_mode = false
|
|
57
57
|
@pre_help_directory = nil
|
|
58
58
|
|
|
59
|
+
# Log viewer mode
|
|
60
|
+
@in_log_viewer_mode = false
|
|
61
|
+
@pre_log_viewer_directory = nil
|
|
62
|
+
@log_dir = File.join(Dir.home, '.config', 'rufio', 'log')
|
|
63
|
+
|
|
59
64
|
# Preview pane focus and scroll
|
|
60
65
|
@preview_focused = false
|
|
61
66
|
@preview_scroll_offset = 0
|
|
@@ -103,6 +108,11 @@ module Rufio
|
|
|
103
108
|
return exit_help_mode
|
|
104
109
|
end
|
|
105
110
|
|
|
111
|
+
# ログビューワモード中のESCキー特別処理
|
|
112
|
+
if @in_log_viewer_mode && key == "\e"
|
|
113
|
+
return exit_log_viewer_mode
|
|
114
|
+
end
|
|
115
|
+
|
|
106
116
|
# フィルターモード中は他のキーバインドを無効化
|
|
107
117
|
return handle_filter_input(key) if @filter_manager.filter_mode
|
|
108
118
|
|
|
@@ -167,7 +177,7 @@ module Rufio
|
|
|
167
177
|
copy_selected_to_current
|
|
168
178
|
when 'x' # x - delete selected files
|
|
169
179
|
delete_selected_files
|
|
170
|
-
when '
|
|
180
|
+
when 'P' # P - project mode
|
|
171
181
|
enter_project_mode
|
|
172
182
|
when 'b' # b - add bookmark
|
|
173
183
|
add_bookmark
|
|
@@ -179,6 +189,8 @@ module Rufio
|
|
|
179
189
|
goto_bookmark(key.to_i)
|
|
180
190
|
when '?' # ? - enter help mode
|
|
181
191
|
enter_help_mode
|
|
192
|
+
when 'L' # L - enter log viewer mode
|
|
193
|
+
enter_log_viewer_mode
|
|
182
194
|
when ':' # : - command mode
|
|
183
195
|
activate_command_mode
|
|
184
196
|
else
|
|
@@ -255,6 +267,49 @@ module Rufio
|
|
|
255
267
|
true
|
|
256
268
|
end
|
|
257
269
|
|
|
270
|
+
# ログビューワモード関連メソッド
|
|
271
|
+
|
|
272
|
+
# ログビューワモード中かどうか
|
|
273
|
+
def log_viewer_mode?
|
|
274
|
+
@in_log_viewer_mode
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# ログビューワモードに入る
|
|
278
|
+
def enter_log_viewer_mode
|
|
279
|
+
return false unless @directory_listing
|
|
280
|
+
|
|
281
|
+
# 現在のディレクトリを保存
|
|
282
|
+
@pre_log_viewer_directory = @directory_listing.current_path
|
|
283
|
+
|
|
284
|
+
# log ディレクトリを作成(存在しない場合)
|
|
285
|
+
FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
|
|
286
|
+
|
|
287
|
+
# log ディレクトリに移動
|
|
288
|
+
navigate_to_directory(@log_dir)
|
|
289
|
+
|
|
290
|
+
# ログビューワモードを有効化
|
|
291
|
+
@in_log_viewer_mode = true
|
|
292
|
+
|
|
293
|
+
true
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# ログビューワモードを終了
|
|
297
|
+
def exit_log_viewer_mode
|
|
298
|
+
return false unless @in_log_viewer_mode
|
|
299
|
+
return false unless @pre_log_viewer_directory
|
|
300
|
+
|
|
301
|
+
# ログビューワモードを無効化
|
|
302
|
+
@in_log_viewer_mode = false
|
|
303
|
+
|
|
304
|
+
# 元のディレクトリに戻る
|
|
305
|
+
navigate_to_directory(@pre_log_viewer_directory)
|
|
306
|
+
|
|
307
|
+
# 保存したディレクトリをクリア
|
|
308
|
+
@pre_log_viewer_directory = nil
|
|
309
|
+
|
|
310
|
+
true
|
|
311
|
+
end
|
|
312
|
+
|
|
258
313
|
# ヘルプモード時の制限付き親ディレクトリナビゲーション
|
|
259
314
|
def navigate_parent_with_restriction
|
|
260
315
|
if @in_help_mode
|
|
@@ -276,8 +331,24 @@ module Rufio
|
|
|
276
331
|
|
|
277
332
|
# info ディレクトリ配下であれば、通常のナビゲーションを実行
|
|
278
333
|
navigate_parent
|
|
334
|
+
elsif @in_log_viewer_mode
|
|
335
|
+
# log ディレクトリより上には移動できない
|
|
336
|
+
current_path = @directory_listing.current_path
|
|
337
|
+
|
|
338
|
+
# 現在のパスが log ディレクトリ以下でない場合は移動を許可しない
|
|
339
|
+
unless current_path.start_with?(@log_dir)
|
|
340
|
+
return false
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# 現在のパスが log ディレクトリそのものの場合は移動を許可しない
|
|
344
|
+
if current_path == @log_dir
|
|
345
|
+
return false
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# log ディレクトリ配下であれば、通常のナビゲーションを実行
|
|
349
|
+
navigate_parent
|
|
279
350
|
else
|
|
280
|
-
#
|
|
351
|
+
# ヘルプモード・ログビューワモード外では通常のナビゲーション
|
|
281
352
|
navigate_parent
|
|
282
353
|
end
|
|
283
354
|
end
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Rufio
|
|
7
|
+
# NativeScanner - Rust/Goのネイティブライブラリを使った高速ディレクトリスキャナー
|
|
8
|
+
class NativeScanner
|
|
9
|
+
# ライブラリパス
|
|
10
|
+
LIB_DIR = File.expand_path('native', __dir__)
|
|
11
|
+
RUST_LIB = File.join(LIB_DIR, 'librufio_scanner.dylib')
|
|
12
|
+
GO_LIB = File.join(LIB_DIR, 'libscanner.dylib')
|
|
13
|
+
|
|
14
|
+
@mode = nil
|
|
15
|
+
@current_library = nil
|
|
16
|
+
|
|
17
|
+
# Rustライブラリ用のFFIモジュール
|
|
18
|
+
module RustLib
|
|
19
|
+
extend FFI::Library
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
ffi_lib RUST_LIB
|
|
23
|
+
attach_function :scan_directory, [:string], :pointer
|
|
24
|
+
attach_function :scan_directory_fast, [:string, :int], :pointer
|
|
25
|
+
attach_function :get_version, [], :pointer
|
|
26
|
+
@available = true
|
|
27
|
+
rescue LoadError, FFI::NotFoundError
|
|
28
|
+
@available = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.available?
|
|
32
|
+
@available
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Goライブラリ用のFFIモジュール
|
|
37
|
+
module GoLib
|
|
38
|
+
extend FFI::Library
|
|
39
|
+
|
|
40
|
+
begin
|
|
41
|
+
ffi_lib GO_LIB
|
|
42
|
+
attach_function :ScanDirectory, [:string], :pointer
|
|
43
|
+
attach_function :ScanDirectoryFast, [:string, :int], :pointer
|
|
44
|
+
attach_function :GetVersion, [], :pointer
|
|
45
|
+
attach_function :FreeCString, [:pointer], :void
|
|
46
|
+
@available = true
|
|
47
|
+
rescue LoadError, FFI::NotFoundError
|
|
48
|
+
@available = false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.available?
|
|
52
|
+
@available
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
# モード設定
|
|
58
|
+
def mode=(value)
|
|
59
|
+
case value
|
|
60
|
+
when 'rust'
|
|
61
|
+
if RustLib.available?
|
|
62
|
+
@mode = 'rust'
|
|
63
|
+
@current_library = RustLib
|
|
64
|
+
else
|
|
65
|
+
@mode = 'ruby'
|
|
66
|
+
@current_library = nil
|
|
67
|
+
end
|
|
68
|
+
when 'go'
|
|
69
|
+
if GoLib.available?
|
|
70
|
+
@mode = 'go'
|
|
71
|
+
@current_library = GoLib
|
|
72
|
+
else
|
|
73
|
+
@mode = 'ruby'
|
|
74
|
+
@current_library = nil
|
|
75
|
+
end
|
|
76
|
+
when 'auto'
|
|
77
|
+
# 優先順位: Rust > Go > Ruby
|
|
78
|
+
if RustLib.available?
|
|
79
|
+
@mode = 'rust'
|
|
80
|
+
@current_library = RustLib
|
|
81
|
+
elsif GoLib.available?
|
|
82
|
+
@mode = 'go'
|
|
83
|
+
@current_library = GoLib
|
|
84
|
+
else
|
|
85
|
+
@mode = 'ruby'
|
|
86
|
+
@current_library = nil
|
|
87
|
+
end
|
|
88
|
+
when 'ruby'
|
|
89
|
+
@mode = 'ruby'
|
|
90
|
+
@current_library = nil
|
|
91
|
+
else
|
|
92
|
+
# 無効なモードはrubyにフォールバック
|
|
93
|
+
@mode = 'ruby'
|
|
94
|
+
@current_library = nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 現在のモード取得
|
|
99
|
+
def mode
|
|
100
|
+
# 初回アクセス時はautoモードに設定
|
|
101
|
+
self.mode = 'auto' if @mode.nil?
|
|
102
|
+
@mode
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# 利用可能なライブラリをチェック
|
|
106
|
+
def available_libraries
|
|
107
|
+
{
|
|
108
|
+
rust: RustLib.available?,
|
|
109
|
+
go: GoLib.available?
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# ディレクトリをスキャン
|
|
114
|
+
def scan_directory(path)
|
|
115
|
+
# モードが未設定の場合は自動設定
|
|
116
|
+
mode if @mode.nil?
|
|
117
|
+
|
|
118
|
+
case @mode
|
|
119
|
+
when 'rust'
|
|
120
|
+
scan_with_rust(path)
|
|
121
|
+
when 'go'
|
|
122
|
+
scan_with_go(path)
|
|
123
|
+
else
|
|
124
|
+
scan_with_ruby(path)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# 高速スキャン(エントリ数制限付き)
|
|
129
|
+
def scan_directory_fast(path, max_entries = 1000)
|
|
130
|
+
# モードが未設定の場合は自動設定
|
|
131
|
+
mode if @mode.nil?
|
|
132
|
+
|
|
133
|
+
case @mode
|
|
134
|
+
when 'rust'
|
|
135
|
+
scan_fast_with_rust(path, max_entries)
|
|
136
|
+
when 'go'
|
|
137
|
+
scan_fast_with_go(path, max_entries)
|
|
138
|
+
else
|
|
139
|
+
scan_fast_with_ruby(path, max_entries)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# バージョン情報取得
|
|
144
|
+
def version
|
|
145
|
+
# モードが未設定の場合は自動設定
|
|
146
|
+
mode if @mode.nil?
|
|
147
|
+
|
|
148
|
+
case @mode
|
|
149
|
+
when 'rust'
|
|
150
|
+
ptr = RustLib.get_version
|
|
151
|
+
ptr.read_string
|
|
152
|
+
when 'go'
|
|
153
|
+
ptr = GoLib.GetVersion
|
|
154
|
+
result = ptr.read_string
|
|
155
|
+
GoLib.FreeCString(ptr)
|
|
156
|
+
result
|
|
157
|
+
else
|
|
158
|
+
"Ruby #{RUBY_VERSION}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# Rustライブラリでスキャン
|
|
165
|
+
def scan_with_rust(path)
|
|
166
|
+
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
167
|
+
|
|
168
|
+
ptr = RustLib.scan_directory(path)
|
|
169
|
+
json_str = ptr.read_string
|
|
170
|
+
parse_scan_result(json_str)
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
raise StandardError, "Rust scan failed: #{e.message}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Rustライブラリで高速スキャン
|
|
176
|
+
def scan_fast_with_rust(path, max_entries)
|
|
177
|
+
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
178
|
+
|
|
179
|
+
ptr = RustLib.scan_directory_fast(path, max_entries)
|
|
180
|
+
json_str = ptr.read_string
|
|
181
|
+
parse_scan_result(json_str)
|
|
182
|
+
rescue StandardError => e
|
|
183
|
+
raise StandardError, "Rust fast scan failed: #{e.message}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Goライブラリでスキャン
|
|
187
|
+
def scan_with_go(path)
|
|
188
|
+
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
189
|
+
|
|
190
|
+
ptr = GoLib.ScanDirectory(path)
|
|
191
|
+
json_str = ptr.read_string
|
|
192
|
+
GoLib.FreeCString(ptr)
|
|
193
|
+
parse_scan_result(json_str)
|
|
194
|
+
rescue StandardError => e
|
|
195
|
+
raise StandardError, "Go scan failed: #{e.message}"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Goライブラリで高速スキャン
|
|
199
|
+
def scan_fast_with_go(path, max_entries)
|
|
200
|
+
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
201
|
+
|
|
202
|
+
ptr = GoLib.ScanDirectoryFast(path, max_entries)
|
|
203
|
+
json_str = ptr.read_string
|
|
204
|
+
GoLib.FreeCString(ptr)
|
|
205
|
+
parse_scan_result(json_str)
|
|
206
|
+
rescue StandardError => e
|
|
207
|
+
raise StandardError, "Go fast scan failed: #{e.message}"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Rubyでスキャン(フォールバック実装)
|
|
211
|
+
def scan_with_ruby(path)
|
|
212
|
+
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
213
|
+
|
|
214
|
+
entries = []
|
|
215
|
+
Dir.foreach(path) do |entry|
|
|
216
|
+
next if entry == '.' || entry == '..'
|
|
217
|
+
|
|
218
|
+
full_path = File.join(path, entry)
|
|
219
|
+
stat = File.lstat(full_path)
|
|
220
|
+
|
|
221
|
+
entries << {
|
|
222
|
+
name: entry,
|
|
223
|
+
type: file_type(stat),
|
|
224
|
+
size: stat.size,
|
|
225
|
+
mtime: stat.mtime.to_i,
|
|
226
|
+
mode: stat.mode
|
|
227
|
+
}
|
|
228
|
+
end
|
|
229
|
+
entries
|
|
230
|
+
rescue StandardError => e
|
|
231
|
+
raise StandardError, "Ruby scan failed: #{e.message}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Ruby高速スキャン(エントリ数制限付き)
|
|
235
|
+
def scan_fast_with_ruby(path, max_entries)
|
|
236
|
+
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
237
|
+
|
|
238
|
+
entries = []
|
|
239
|
+
count = 0
|
|
240
|
+
|
|
241
|
+
Dir.foreach(path) do |entry|
|
|
242
|
+
next if entry == '.' || entry == '..'
|
|
243
|
+
break if count >= max_entries
|
|
244
|
+
|
|
245
|
+
full_path = File.join(path, entry)
|
|
246
|
+
stat = File.lstat(full_path)
|
|
247
|
+
|
|
248
|
+
entries << {
|
|
249
|
+
name: entry,
|
|
250
|
+
type: file_type(stat),
|
|
251
|
+
size: stat.size,
|
|
252
|
+
mtime: stat.mtime.to_i,
|
|
253
|
+
mode: stat.mode
|
|
254
|
+
}
|
|
255
|
+
count += 1
|
|
256
|
+
end
|
|
257
|
+
entries
|
|
258
|
+
rescue StandardError => e
|
|
259
|
+
raise StandardError, "Ruby fast scan failed: #{e.message}"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# ファイルタイプを判定
|
|
263
|
+
def file_type(stat)
|
|
264
|
+
if stat.directory?
|
|
265
|
+
'directory'
|
|
266
|
+
elsif stat.symlink?
|
|
267
|
+
'symlink'
|
|
268
|
+
elsif stat.file?
|
|
269
|
+
'file'
|
|
270
|
+
else
|
|
271
|
+
'other'
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# JSONレスポンスをパース
|
|
276
|
+
def parse_scan_result(json_str)
|
|
277
|
+
entries = JSON.parse(json_str, symbolize_names: true)
|
|
278
|
+
|
|
279
|
+
# エラーチェック(配列ではなくハッシュが返された場合)
|
|
280
|
+
if entries.is_a?(Hash) && entries[:error]
|
|
281
|
+
raise StandardError, entries[:error]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# 配列が返された場合は各エントリを変換
|
|
285
|
+
if entries.is_a?(Array)
|
|
286
|
+
return entries.map do |entry|
|
|
287
|
+
{
|
|
288
|
+
name: entry[:name],
|
|
289
|
+
type: entry[:is_dir] ? 'directory' : 'file',
|
|
290
|
+
size: entry[:size],
|
|
291
|
+
mtime: entry[:mtime],
|
|
292
|
+
mode: 0, # Rustライブラリはmodeを返さない
|
|
293
|
+
executable: entry[:executable],
|
|
294
|
+
hidden: entry[:hidden]
|
|
295
|
+
}
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# それ以外の場合は空配列
|
|
300
|
+
[]
|
|
301
|
+
rescue JSON::ParserError => e
|
|
302
|
+
raise StandardError, "Failed to parse scan result: #{e.message}"
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|