rufio 0.32.0 → 0.34.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 +65 -0
- data/README.md +63 -7
- data/README_EN.md +25 -0
- data/docs/CHANGELOG_v0.34.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/async_scanner_fiber.rb +154 -0
- data/lib/rufio/async_scanner_promise.rb +66 -0
- data/lib/rufio/background_command_executor.rb +98 -0
- data/lib/rufio/command_logger.rb +121 -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_zig.bundle +0 -0
- data/lib/rufio/native_scanner.rb +325 -0
- data/lib/rufio/native_scanner_zig.rb +354 -0
- data/lib/rufio/parallel_scanner.rb +173 -0
- data/lib/rufio/terminal_ui.rb +66 -16
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +7 -0
- data/lib_zig/rufio_native/Makefile +34 -0
- data/lib_zig/rufio_native/build.zig +45 -0
- data/lib_zig/rufio_native/src/main.zig +378 -0
- data/lib_zig/rufio_native/src/main.zig.sync +205 -0
- metadata +17 -2
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'async'
|
|
5
|
+
ASYNC_GEM_AVAILABLE = true
|
|
6
|
+
rescue LoadError
|
|
7
|
+
ASYNC_GEM_AVAILABLE = false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Rufio
|
|
11
|
+
# Fiber(Asyncライブラリ)統合用ラッパークラス
|
|
12
|
+
#
|
|
13
|
+
# Asyncライブラリと統合し、ノンブロッキングで非同期スキャンを実行します。
|
|
14
|
+
#
|
|
15
|
+
# 使用例:
|
|
16
|
+
# Async do
|
|
17
|
+
# scanner = NativeScannerRubyCore.new
|
|
18
|
+
# wrapper = AsyncScannerFiberWrapper.new(scanner)
|
|
19
|
+
# entries = wrapper.scan_async('/path')
|
|
20
|
+
# puts "Found #{entries.length} entries"
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
class AsyncScannerFiberWrapper
|
|
24
|
+
def initialize(scanner)
|
|
25
|
+
@scanner = scanner
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# 非同期スキャンを開始し、Fiberで完了を待つ
|
|
29
|
+
#
|
|
30
|
+
# @param path [String] スキャンするディレクトリのパス
|
|
31
|
+
# @param timeout [Integer, nil] タイムアウト秒数(オプション)
|
|
32
|
+
# @return [Array<Hash>] スキャン結果
|
|
33
|
+
def scan_async(path, timeout: nil)
|
|
34
|
+
# スキャンを開始
|
|
35
|
+
@scanner.scan_async(path)
|
|
36
|
+
|
|
37
|
+
# Fiberでポーリング
|
|
38
|
+
poll_until_complete(timeout: timeout)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 高速スキャン(エントリ数制限付き)
|
|
42
|
+
#
|
|
43
|
+
# @param path [String] スキャンするディレクトリのパス
|
|
44
|
+
# @param max_entries [Integer] 最大エントリ数
|
|
45
|
+
# @param timeout [Integer, nil] タイムアウト秒数(オプション)
|
|
46
|
+
# @return [Array<Hash>] スキャン結果
|
|
47
|
+
def scan_fast_async(path, max_entries, timeout: nil)
|
|
48
|
+
# スキャンを開始
|
|
49
|
+
@scanner.scan_fast_async(path, max_entries)
|
|
50
|
+
|
|
51
|
+
# Fiberでポーリング
|
|
52
|
+
poll_until_complete(timeout: timeout)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# 進捗報告付きスキャン
|
|
56
|
+
#
|
|
57
|
+
# @param path [String] スキャンするディレクトリのパス
|
|
58
|
+
# @param timeout [Integer, nil] タイムアウト秒数(オプション)
|
|
59
|
+
# @yield [current, total] 進捗情報を受け取るブロック
|
|
60
|
+
# @return [Array<Hash>] スキャン結果
|
|
61
|
+
def scan_async_with_progress(path, timeout: nil, &block)
|
|
62
|
+
# スキャンを開始
|
|
63
|
+
@scanner.scan_async(path)
|
|
64
|
+
|
|
65
|
+
# 進捗付きでポーリング
|
|
66
|
+
poll_until_complete_with_progress(timeout: timeout, &block)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# スキャンをキャンセル
|
|
70
|
+
def cancel
|
|
71
|
+
@scanner.cancel
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 状態を取得
|
|
75
|
+
#
|
|
76
|
+
# @return [Symbol] 現在の状態
|
|
77
|
+
def get_state
|
|
78
|
+
@scanner.get_state
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 進捗を取得
|
|
82
|
+
#
|
|
83
|
+
# @return [Hash] 進捗情報 {current:, total:}
|
|
84
|
+
def get_progress
|
|
85
|
+
@scanner.get_progress
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# 完了までポーリング(Fiberでスリープ)
|
|
91
|
+
def poll_until_complete(timeout: nil)
|
|
92
|
+
start_time = Time.now
|
|
93
|
+
|
|
94
|
+
loop do
|
|
95
|
+
state = @scanner.get_state
|
|
96
|
+
|
|
97
|
+
case state
|
|
98
|
+
when :done
|
|
99
|
+
result = @scanner.get_results
|
|
100
|
+
@scanner.close
|
|
101
|
+
return result
|
|
102
|
+
when :failed
|
|
103
|
+
@scanner.close
|
|
104
|
+
raise StandardError, "Scan failed"
|
|
105
|
+
when :cancelled
|
|
106
|
+
@scanner.close
|
|
107
|
+
raise StandardError, "Scan cancelled"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if timeout && (Time.now - start_time) > timeout
|
|
111
|
+
@scanner.close
|
|
112
|
+
raise StandardError, "Timeout"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Fiberでスリープ(ノンブロッキング)
|
|
116
|
+
sleep 0.01
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# 進捗報告付きでポーリング
|
|
121
|
+
def poll_until_complete_with_progress(timeout: nil, &block)
|
|
122
|
+
start_time = Time.now
|
|
123
|
+
|
|
124
|
+
loop do
|
|
125
|
+
state = @scanner.get_state
|
|
126
|
+
progress = @scanner.get_progress
|
|
127
|
+
|
|
128
|
+
# 進捗コールバック実行
|
|
129
|
+
yield(progress[:current], progress[:total]) if block_given?
|
|
130
|
+
|
|
131
|
+
case state
|
|
132
|
+
when :done
|
|
133
|
+
result = @scanner.get_results
|
|
134
|
+
@scanner.close
|
|
135
|
+
return result
|
|
136
|
+
when :failed
|
|
137
|
+
@scanner.close
|
|
138
|
+
raise StandardError, "Scan failed"
|
|
139
|
+
when :cancelled
|
|
140
|
+
@scanner.close
|
|
141
|
+
raise StandardError, "Scan cancelled"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if timeout && (Time.now - start_time) > timeout
|
|
145
|
+
@scanner.close
|
|
146
|
+
raise StandardError, "Timeout"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Fiberでスリープ(ノンブロッキング)
|
|
150
|
+
sleep 0.01
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rufio
|
|
4
|
+
# Promise風インターフェースで非同期スキャンを扱うクラス
|
|
5
|
+
#
|
|
6
|
+
# 使用例:
|
|
7
|
+
# scanner = NativeScannerRubyCore.new
|
|
8
|
+
# AsyncScannerPromise.new(scanner)
|
|
9
|
+
# .scan_async('/path')
|
|
10
|
+
# .then { |entries| entries.select { |e| e[:type] == 'file' } }
|
|
11
|
+
# .then { |files| files.map { |f| f[:name] } }
|
|
12
|
+
# .wait
|
|
13
|
+
#
|
|
14
|
+
class AsyncScannerPromise
|
|
15
|
+
def initialize(scanner)
|
|
16
|
+
@scanner = scanner
|
|
17
|
+
@callbacks = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# 非同期スキャンを開始
|
|
21
|
+
#
|
|
22
|
+
# @param path [String] スキャンするディレクトリのパス
|
|
23
|
+
# @return [AsyncScannerPromise] self(メソッドチェーン用)
|
|
24
|
+
def scan_async(path)
|
|
25
|
+
@scanner.scan_async(path)
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# 高速スキャンを開始(エントリ数制限付き)
|
|
30
|
+
#
|
|
31
|
+
# @param path [String] スキャンするディレクトリのパス
|
|
32
|
+
# @param max_entries [Integer] 最大エントリ数
|
|
33
|
+
# @return [AsyncScannerPromise] self(メソッドチェーン用)
|
|
34
|
+
def scan_fast_async(path, max_entries)
|
|
35
|
+
@scanner.scan_fast_async(path, max_entries)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# コールバックを登録
|
|
40
|
+
#
|
|
41
|
+
# @yield [result] 前のステップの結果を受け取るブロック
|
|
42
|
+
# @return [AsyncScannerPromise] self(メソッドチェーン用)
|
|
43
|
+
def then(&block)
|
|
44
|
+
@callbacks << block if block_given?
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# スキャン完了を待ち、コールバックを順次実行
|
|
49
|
+
#
|
|
50
|
+
# @param timeout [Integer, nil] タイムアウト秒数(オプション)
|
|
51
|
+
# @return [Object] 最後のコールバックの戻り値、またはスキャン結果
|
|
52
|
+
def wait(timeout: nil)
|
|
53
|
+
result = @scanner.wait(timeout: timeout)
|
|
54
|
+
|
|
55
|
+
# 登録されたコールバックを順次実行
|
|
56
|
+
@callbacks.each do |callback|
|
|
57
|
+
result = callback.call(result)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
result
|
|
61
|
+
ensure
|
|
62
|
+
# 完了後はスキャナーを自動的にクローズ
|
|
63
|
+
@scanner.close
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Rufio
|
|
6
|
+
# バックグラウンドでシェルコマンドを実行するクラス
|
|
7
|
+
class BackgroundCommandExecutor
|
|
8
|
+
attr_reader :command_logger
|
|
9
|
+
|
|
10
|
+
# 初期化
|
|
11
|
+
# @param command_logger [CommandLogger] コマンドロガー
|
|
12
|
+
def initialize(command_logger)
|
|
13
|
+
@command_logger = command_logger
|
|
14
|
+
@thread = nil
|
|
15
|
+
@command = nil
|
|
16
|
+
@completed = false
|
|
17
|
+
@completion_message = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# コマンドを非同期で実行
|
|
21
|
+
# @param command [String] 実行するコマンド
|
|
22
|
+
# @return [Boolean] 実行を開始した場合はtrue、既に実行中の場合はfalse
|
|
23
|
+
def execute_async(command)
|
|
24
|
+
# 既に実行中の場合は新しいコマンドを開始しない
|
|
25
|
+
return false if running?
|
|
26
|
+
|
|
27
|
+
@command = command
|
|
28
|
+
@completed = false
|
|
29
|
+
@completion_message = nil
|
|
30
|
+
|
|
31
|
+
@thread = Thread.new do
|
|
32
|
+
begin
|
|
33
|
+
# コマンドを実行
|
|
34
|
+
stdout, stderr, status = Open3.capture3(command)
|
|
35
|
+
|
|
36
|
+
# 結果をログに保存
|
|
37
|
+
output = stdout + stderr
|
|
38
|
+
success = status.success?
|
|
39
|
+
|
|
40
|
+
error_message = success ? nil : stderr
|
|
41
|
+
|
|
42
|
+
@command_logger.log(
|
|
43
|
+
command,
|
|
44
|
+
output,
|
|
45
|
+
success: success,
|
|
46
|
+
error: error_message
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# 完了メッセージを生成
|
|
50
|
+
command_name = extract_command_name(command)
|
|
51
|
+
if success
|
|
52
|
+
@completion_message = "✓ #{command_name} 完了"
|
|
53
|
+
else
|
|
54
|
+
@completion_message = "✗ #{command_name} 失敗"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@completed = true
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
# エラーが発生した場合もログに記録
|
|
60
|
+
@command_logger.log(
|
|
61
|
+
command,
|
|
62
|
+
"",
|
|
63
|
+
success: false,
|
|
64
|
+
error: e.message
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
command_name = extract_command_name(command)
|
|
68
|
+
@completion_message = "✗ #{command_name} エラー"
|
|
69
|
+
@completed = true
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# コマンドが実行中かどうか
|
|
77
|
+
# @return [Boolean] 実行中の場合はtrue
|
|
78
|
+
def running?
|
|
79
|
+
@thread&.alive? || false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# 完了メッセージを取得
|
|
83
|
+
# @return [String, nil] 完了メッセージ(完了していない場合はnil)
|
|
84
|
+
def get_completion_message
|
|
85
|
+
@completion_message
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# コマンド文字列からコマンド名を抽出
|
|
91
|
+
# @param command [String] コマンド
|
|
92
|
+
# @return [String] コマンド名
|
|
93
|
+
def extract_command_name(command)
|
|
94
|
+
# 最初の単語を取得
|
|
95
|
+
command.strip.split.first || 'command'
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
# ディレクトリが存在しない場合は作成(バックグラウンドスレッドでの実行時の競合を防ぐ)
|
|
31
|
+
FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
|
|
32
|
+
|
|
33
|
+
File.write(filepath, content)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# ログファイル一覧を取得(新しい順)
|
|
37
|
+
# @return [Array<String>] ログファイルのパス一覧
|
|
38
|
+
def list_logs
|
|
39
|
+
Dir.glob(File.join(@log_dir, "*.log")).sort.reverse
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# 古いログを削除
|
|
43
|
+
# @param max_logs [Integer] 保管する最大ログ数
|
|
44
|
+
def cleanup_old_logs(max_logs:)
|
|
45
|
+
logs = list_logs
|
|
46
|
+
return if logs.size <= max_logs
|
|
47
|
+
|
|
48
|
+
logs_to_delete = logs[max_logs..-1]
|
|
49
|
+
logs_to_delete.each do |log_file|
|
|
50
|
+
File.delete(log_file)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# ログファイル名を生成
|
|
57
|
+
# @param command [String] コマンド
|
|
58
|
+
# @param timestamp [Time] タイムスタンプ
|
|
59
|
+
# @return [String] ファイル名
|
|
60
|
+
def generate_filename(command, timestamp)
|
|
61
|
+
# ミリ秒を含めて一意性を確保
|
|
62
|
+
timestamp_str = timestamp.strftime("%Y%m%d%H%M%S") + sprintf("%03d", (timestamp.usec / 1000).to_i)
|
|
63
|
+
command_part = sanitize_command(command)
|
|
64
|
+
"#{timestamp_str}-#{command_part}.log"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# コマンド文字列をファイル名用にサニタイズ
|
|
68
|
+
# @param command [String] コマンド
|
|
69
|
+
# @return [String] サニタイズされたコマンド
|
|
70
|
+
def sanitize_command(command)
|
|
71
|
+
# Remove ! prefix if exists
|
|
72
|
+
cmd = command.start_with?('!') ? command[1..-1] : command
|
|
73
|
+
|
|
74
|
+
# Take first word (command name)
|
|
75
|
+
cmd = cmd.split.first || 'command'
|
|
76
|
+
|
|
77
|
+
# Remove unsafe characters
|
|
78
|
+
cmd = cmd.gsub(/[^\w\-]/, '_')
|
|
79
|
+
|
|
80
|
+
# Limit length
|
|
81
|
+
cmd[0...50]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# ログ内容をフォーマット
|
|
85
|
+
# @param command [String] コマンド
|
|
86
|
+
# @param output [String] 出力
|
|
87
|
+
# @param timestamp [Time] タイムスタンプ
|
|
88
|
+
# @param success [Boolean] 成功フラグ
|
|
89
|
+
# @param error [String, nil] エラーメッセージ
|
|
90
|
+
# @return [String] フォーマットされたログ内容
|
|
91
|
+
def format_log_content(command, output, timestamp, success, error)
|
|
92
|
+
lines = []
|
|
93
|
+
lines << "=" * 80
|
|
94
|
+
lines << "Command Execution Log"
|
|
95
|
+
lines << "=" * 80
|
|
96
|
+
lines << ""
|
|
97
|
+
lines << "Timestamp: #{timestamp.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
98
|
+
lines << "Command: #{command}"
|
|
99
|
+
lines << "Status: #{success ? 'Success' : 'Failed'}"
|
|
100
|
+
lines << ""
|
|
101
|
+
|
|
102
|
+
if error
|
|
103
|
+
lines << "Error:"
|
|
104
|
+
lines << error
|
|
105
|
+
lines << ""
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if output && !output.empty?
|
|
109
|
+
lines << "Output:"
|
|
110
|
+
lines << "-" * 80
|
|
111
|
+
lines << output
|
|
112
|
+
lines << "-" * 80
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
lines << ""
|
|
116
|
+
lines << "=" * 80
|
|
117
|
+
|
|
118
|
+
lines.join("\n")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
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
|