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
data/lib/rufio/native_scanner.rb
CHANGED
|
@@ -17,69 +17,17 @@ module Rufio
|
|
|
17
17
|
|
|
18
18
|
# 非同期スキャン開始
|
|
19
19
|
def scan_async(path)
|
|
20
|
-
|
|
21
|
-
raise StandardError, "Scanner is already running" unless @state == :idle
|
|
22
|
-
|
|
23
|
-
@mutex.synchronize do
|
|
24
|
-
@state = :scanning
|
|
25
|
-
@results = []
|
|
26
|
-
@error = nil
|
|
27
|
-
@current_progress = 0
|
|
28
|
-
@total_progress = 0
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
@thread = Thread.new do
|
|
32
|
-
begin
|
|
33
|
-
# ディレクトリをスキャン
|
|
34
|
-
entries = []
|
|
35
|
-
Dir.foreach(path) do |entry|
|
|
36
|
-
next if entry == '.' || entry == '..'
|
|
37
|
-
|
|
38
|
-
# キャンセルチェック
|
|
39
|
-
break if @state == :cancelled
|
|
40
|
-
|
|
41
|
-
full_path = File.join(path, entry)
|
|
42
|
-
stat = File.lstat(full_path)
|
|
43
|
-
|
|
44
|
-
entries << {
|
|
45
|
-
name: entry,
|
|
46
|
-
type: file_type(stat),
|
|
47
|
-
size: stat.size,
|
|
48
|
-
mtime: stat.mtime.to_i,
|
|
49
|
-
mode: stat.mode,
|
|
50
|
-
executable: stat.executable?,
|
|
51
|
-
hidden: entry.start_with?('.')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# 進捗を更新
|
|
55
|
-
@mutex.synchronize do
|
|
56
|
-
@current_progress += 1
|
|
57
|
-
@total_progress = entries.length + 1
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# 結果を保存
|
|
62
|
-
@mutex.synchronize do
|
|
63
|
-
if @state == :cancelled
|
|
64
|
-
@state = :cancelled
|
|
65
|
-
else
|
|
66
|
-
@results = entries
|
|
67
|
-
@state = :done
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
rescue StandardError => e
|
|
71
|
-
@mutex.synchronize do
|
|
72
|
-
@error = e
|
|
73
|
-
@state = :failed
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
self
|
|
20
|
+
start_scan_thread(path, nil)
|
|
79
21
|
end
|
|
80
22
|
|
|
81
23
|
# 高速スキャン(エントリ数制限付き)
|
|
82
24
|
def scan_fast_async(path, max_entries)
|
|
25
|
+
start_scan_thread(path, max_entries)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def start_scan_thread(path, max_entries)
|
|
83
31
|
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
84
32
|
raise StandardError, "Scanner is already running" unless @state == :idle
|
|
85
33
|
|
|
@@ -88,7 +36,7 @@ module Rufio
|
|
|
88
36
|
@results = []
|
|
89
37
|
@error = nil
|
|
90
38
|
@current_progress = 0
|
|
91
|
-
@total_progress = max_entries
|
|
39
|
+
@total_progress = max_entries || 0
|
|
92
40
|
end
|
|
93
41
|
|
|
94
42
|
@thread = Thread.new do
|
|
@@ -98,9 +46,7 @@ module Rufio
|
|
|
98
46
|
|
|
99
47
|
Dir.foreach(path) do |entry|
|
|
100
48
|
next if entry == '.' || entry == '..'
|
|
101
|
-
break if count >= max_entries
|
|
102
|
-
|
|
103
|
-
# キャンセルチェック
|
|
49
|
+
break if max_entries && count >= max_entries
|
|
104
50
|
break if @state == :cancelled
|
|
105
51
|
|
|
106
52
|
full_path = File.join(path, entry)
|
|
@@ -117,17 +63,14 @@ module Rufio
|
|
|
117
63
|
}
|
|
118
64
|
count += 1
|
|
119
65
|
|
|
120
|
-
# 進捗を更新
|
|
121
66
|
@mutex.synchronize do
|
|
122
67
|
@current_progress = count
|
|
68
|
+
@total_progress = entries.length + 1 unless max_entries
|
|
123
69
|
end
|
|
124
70
|
end
|
|
125
71
|
|
|
126
|
-
# 結果を保存
|
|
127
72
|
@mutex.synchronize do
|
|
128
|
-
if @state
|
|
129
|
-
@state = :cancelled
|
|
130
|
-
else
|
|
73
|
+
if @state != :cancelled
|
|
131
74
|
@results = entries
|
|
132
75
|
@state = :done
|
|
133
76
|
end
|
|
@@ -143,6 +86,8 @@ module Rufio
|
|
|
143
86
|
self
|
|
144
87
|
end
|
|
145
88
|
|
|
89
|
+
public
|
|
90
|
+
|
|
146
91
|
# ポーリングして完了待ち
|
|
147
92
|
def wait(timeout: nil)
|
|
148
93
|
start_time = Time.now
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'fiddle'
|
|
4
4
|
require 'fiddle/import'
|
|
5
|
+
require 'rbconfig'
|
|
5
6
|
|
|
6
7
|
module Rufio
|
|
7
8
|
# Zig拡張の非同期FFI層
|
|
@@ -9,21 +10,58 @@ module Rufio
|
|
|
9
10
|
module NativeScannerZigFFI
|
|
10
11
|
extend Fiddle::Importer
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
NATIVE_DIR = File.expand_path('native', __dir__)
|
|
14
|
+
|
|
15
|
+
# Gem に同梱するプラットフォーム(lib/rufio/native/<platform>/<file>)
|
|
16
|
+
PLATFORMS = {
|
|
17
|
+
'aarch64-macos' => 'librufio_zig.dylib',
|
|
18
|
+
'x86_64-macos' => 'librufio_zig.dylib',
|
|
19
|
+
'x86_64-linux-gnu' => 'librufio_zig.so',
|
|
20
|
+
'aarch64-linux-gnu' => 'librufio_zig.so',
|
|
21
|
+
'x86_64-linux-musl' => 'librufio_zig.so',
|
|
22
|
+
'aarch64-linux-musl' => 'librufio_zig.so',
|
|
23
|
+
'x86_64-windows' => 'rufio_zig.dll',
|
|
24
|
+
'aarch64-windows' => 'rufio_zig.dll'
|
|
25
|
+
}.freeze
|
|
13
26
|
|
|
14
27
|
@loaded = false
|
|
15
28
|
@available = false
|
|
29
|
+
@loaded_path = nil
|
|
16
30
|
|
|
17
31
|
class << self
|
|
32
|
+
attr_reader :loaded_path
|
|
33
|
+
|
|
34
|
+
# RbConfig の host_os / host_cpu から同梱バイナリのディレクトリ名を決める(非対応なら nil)
|
|
35
|
+
def platform_dir(host_os = RbConfig::CONFIG['host_os'], host_cpu = RbConfig::CONFIG['host_cpu'])
|
|
36
|
+
cpu = case host_cpu.to_s
|
|
37
|
+
when /\A(arm64|aarch64)/i then 'aarch64'
|
|
38
|
+
when /\A(x86_64|x64|amd64)\z/i then 'x86_64'
|
|
39
|
+
end
|
|
40
|
+
return nil unless cpu
|
|
41
|
+
|
|
42
|
+
case host_os.to_s
|
|
43
|
+
when /darwin/ then "#{cpu}-macos"
|
|
44
|
+
when /linux/ then "#{cpu}-linux-#{host_os.to_s.include?('musl') ? 'musl' : 'gnu'}"
|
|
45
|
+
when /mingw|mswin/ then "#{cpu}-windows"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def library_path(host_os = RbConfig::CONFIG['host_os'], host_cpu = RbConfig::CONFIG['host_cpu'])
|
|
50
|
+
dir = platform_dir(host_os, host_cpu)
|
|
51
|
+
dir && File.join(NATIVE_DIR, dir, PLATFORMS.fetch(dir))
|
|
52
|
+
end
|
|
53
|
+
|
|
18
54
|
def load!
|
|
19
55
|
return @available if @loaded
|
|
20
56
|
|
|
21
57
|
@loaded = true
|
|
58
|
+
path = library_path
|
|
22
59
|
|
|
23
|
-
if File.exist?(
|
|
60
|
+
if path && File.exist?(path)
|
|
24
61
|
begin
|
|
25
62
|
# 動的ライブラリをロード
|
|
26
|
-
dlload
|
|
63
|
+
dlload path
|
|
64
|
+
@loaded_path = path
|
|
27
65
|
|
|
28
66
|
# ABI Boundary: Ruby ABI非依存のC関数(非同期版)
|
|
29
67
|
extern 'uint64_t core_async_create()'
|
|
@@ -39,6 +77,8 @@ module Rufio
|
|
|
39
77
|
extern 'char* core_async_version()'
|
|
40
78
|
|
|
41
79
|
@available = true
|
|
80
|
+
load_search_functions!
|
|
81
|
+
load_zip_functions!
|
|
42
82
|
rescue StandardError => e
|
|
43
83
|
warn "Failed to load zig extension: #{e.message}" if ENV['RUFIO_DEBUG']
|
|
44
84
|
@available = false
|
|
@@ -55,10 +95,55 @@ module Rufio
|
|
|
55
95
|
@available
|
|
56
96
|
end
|
|
57
97
|
|
|
98
|
+
# コンテンツ検索 API(古いライブラリには存在しない)
|
|
99
|
+
def search_available?
|
|
100
|
+
load! unless @loaded
|
|
101
|
+
@search_available == true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# ZIP データ処理 API(古いライブラリには存在しない)
|
|
105
|
+
def zip_available?
|
|
106
|
+
load! unless @loaded
|
|
107
|
+
@zip_available == true
|
|
108
|
+
end
|
|
109
|
+
|
|
58
110
|
# バージョン取得
|
|
59
111
|
def version
|
|
60
112
|
core_async_version.to_s
|
|
61
113
|
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def load_search_functions!
|
|
118
|
+
extern 'uint64_t core_search_create(const char*, size_t, const char*, size_t, const char*, size_t, const char*, size_t, uint32_t, uint64_t)'
|
|
119
|
+
extern 'uint8_t core_search_state(uint64_t)'
|
|
120
|
+
extern 'void* core_search_drain(uint64_t, void*)'
|
|
121
|
+
extern 'uint64_t core_search_skipped(uint64_t)'
|
|
122
|
+
extern 'void core_search_cancel(uint64_t)'
|
|
123
|
+
extern 'void core_search_destroy(uint64_t)'
|
|
124
|
+
extern 'uint64_t core_cache_create(uint64_t)'
|
|
125
|
+
extern 'uint64_t core_cache_bytes(uint64_t)'
|
|
126
|
+
extern 'void core_cache_destroy(uint64_t)'
|
|
127
|
+
@search_available = true
|
|
128
|
+
rescue Fiddle::DLError => e
|
|
129
|
+
warn "Zig content search unavailable: #{e.message}" if ENV['RUFIO_DEBUG']
|
|
130
|
+
@search_available = false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def load_zip_functions!
|
|
134
|
+
extern 'uint64_t core_zip_extract_create(const char*, size_t, const char*, size_t, uint32_t)'
|
|
135
|
+
extern 'uint64_t core_zip_deflate_create(const char*, size_t, int32_t, uint32_t)'
|
|
136
|
+
extern 'uint8_t core_zip_state(uint64_t)'
|
|
137
|
+
extern 'void core_zip_progress(uint64_t, void*, void*)'
|
|
138
|
+
extern 'uint8_t core_zip_status(uint64_t, size_t)'
|
|
139
|
+
extern 'uint8_t core_zip_deflate_result(uint64_t, size_t, void*, void*, void*)'
|
|
140
|
+
extern 'void core_zip_cancel(uint64_t)'
|
|
141
|
+
extern 'void core_zip_destroy(uint64_t)'
|
|
142
|
+
@zip_available = true
|
|
143
|
+
rescue Fiddle::DLError => e
|
|
144
|
+
warn "Zig ZIP engine unavailable: #{e.message}" if ENV['RUFIO_DEBUG']
|
|
145
|
+
@zip_available = false
|
|
146
|
+
end
|
|
62
147
|
end
|
|
63
148
|
end
|
|
64
149
|
|
|
@@ -200,12 +285,12 @@ module Rufio
|
|
|
200
285
|
if NativeScannerZigFFI.load!
|
|
201
286
|
class NativeScanner
|
|
202
287
|
class << self
|
|
203
|
-
# zig
|
|
288
|
+
# zigモードを追加(autoモードの優先順位: Zig > Ruby)
|
|
204
289
|
alias_method :original_mode=, :mode= unless method_defined?(:original_mode=)
|
|
205
290
|
|
|
206
291
|
def mode=(value)
|
|
207
292
|
case value
|
|
208
|
-
when 'zig'
|
|
293
|
+
when 'auto', 'zig'
|
|
209
294
|
if NativeScannerZigFFI.available?
|
|
210
295
|
@mode = 'zig'
|
|
211
296
|
@current_library = nil
|
|
@@ -214,7 +299,7 @@ module Rufio
|
|
|
214
299
|
@current_library = nil
|
|
215
300
|
end
|
|
216
301
|
else
|
|
217
|
-
original_mode
|
|
302
|
+
send(:original_mode=, value)
|
|
218
303
|
end
|
|
219
304
|
end
|
|
220
305
|
|
|
@@ -222,24 +307,10 @@ module Rufio
|
|
|
222
307
|
def scan_directory_with_zig(path)
|
|
223
308
|
raise StandardError, "Directory does not exist: #{path}" unless Dir.exist?(path)
|
|
224
309
|
|
|
225
|
-
# 非同期スキャナーを作成してスキャン
|
|
226
310
|
scanner = NativeScannerZigCore.new
|
|
227
311
|
begin
|
|
228
312
|
scanner.scan_async(path)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
# 結果の形式を統一(type フィールドを追加)
|
|
232
|
-
entries.map do |entry|
|
|
233
|
-
{
|
|
234
|
-
name: entry[:name],
|
|
235
|
-
type: entry[:is_dir] ? 'directory' : 'file',
|
|
236
|
-
size: entry[:size],
|
|
237
|
-
mtime: entry[:mtime],
|
|
238
|
-
mode: 0,
|
|
239
|
-
executable: entry[:executable],
|
|
240
|
-
hidden: entry[:hidden]
|
|
241
|
-
}
|
|
242
|
-
end
|
|
313
|
+
normalize_zig_entries(scanner.wait(timeout: 60))
|
|
243
314
|
ensure
|
|
244
315
|
scanner.close
|
|
245
316
|
end
|
|
@@ -254,19 +325,7 @@ module Rufio
|
|
|
254
325
|
scanner = NativeScannerZigCore.new
|
|
255
326
|
begin
|
|
256
327
|
scanner.scan_fast_async(path, max_entries)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
entries.map do |entry|
|
|
260
|
-
{
|
|
261
|
-
name: entry[:name],
|
|
262
|
-
type: entry[:is_dir] ? 'directory' : 'file',
|
|
263
|
-
size: entry[:size],
|
|
264
|
-
mtime: entry[:mtime],
|
|
265
|
-
mode: 0,
|
|
266
|
-
executable: entry[:executable],
|
|
267
|
-
hidden: entry[:hidden]
|
|
268
|
-
}
|
|
269
|
-
end
|
|
328
|
+
normalize_zig_entries(scanner.wait(timeout: 60))
|
|
270
329
|
ensure
|
|
271
330
|
scanner.close
|
|
272
331
|
end
|
|
@@ -324,28 +383,19 @@ module Rufio
|
|
|
324
383
|
original.merge(zig: NativeScannerZigFFI.available?)
|
|
325
384
|
end
|
|
326
385
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if NativeScannerZigFFI.available?
|
|
341
|
-
@mode = 'zig'
|
|
342
|
-
@current_library = nil
|
|
343
|
-
else
|
|
344
|
-
@mode = 'ruby'
|
|
345
|
-
@current_library = nil
|
|
346
|
-
end
|
|
347
|
-
else
|
|
348
|
-
send(:original_mode=, value)
|
|
386
|
+
private
|
|
387
|
+
|
|
388
|
+
def normalize_zig_entries(entries)
|
|
389
|
+
entries.map do |entry|
|
|
390
|
+
{
|
|
391
|
+
name: entry[:name],
|
|
392
|
+
type: entry[:is_dir] ? 'directory' : 'file',
|
|
393
|
+
size: entry[:size],
|
|
394
|
+
mtime: entry[:mtime],
|
|
395
|
+
mode: 0,
|
|
396
|
+
executable: entry[:executable],
|
|
397
|
+
hidden: entry[:hidden]
|
|
398
|
+
}
|
|
349
399
|
end
|
|
350
400
|
end
|
|
351
401
|
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fiddle'
|
|
4
|
+
require_relative 'native_scanner'
|
|
5
|
+
require_relative 'native_scanner_zig'
|
|
6
|
+
|
|
7
|
+
module Rufio
|
|
8
|
+
# Zig 実装の ZIP データ処理(並列・GVL 外)。
|
|
9
|
+
#
|
|
10
|
+
# 安全性の検証・パスの決定・ディレクトリ作成・rename・権限と日時の設定は Ruby 側
|
|
11
|
+
# (ZipExtractor / ZipWriter)が担い、ここではエントリ単位のデータ処理だけを行う。
|
|
12
|
+
# - 展開: ZIP 内の指定範囲を読み、伸長し、宣言サイズと CRC-32 を検証して一時ファイルに書く
|
|
13
|
+
# - 圧縮: ファイルを raw DEFLATE で一時ファイルに書き、CRC-32 とサイズを返す
|
|
14
|
+
module NativeZip
|
|
15
|
+
STATE_RUNNING = 1
|
|
16
|
+
STATE_DONE = 2
|
|
17
|
+
STATE_CANCELLED = 3
|
|
18
|
+
POLL_INTERVAL = 0.005
|
|
19
|
+
|
|
20
|
+
# タスクごとの結果コード(Zig 側 zip.zig の Status と一致させる)
|
|
21
|
+
STATUS_OK = 0
|
|
22
|
+
STATUS_CRC_MISMATCH = 1
|
|
23
|
+
STATUS_SIZE_MISMATCH = 2
|
|
24
|
+
STATUS_CORRUPT = 3
|
|
25
|
+
STATUS_IO_ERROR = 4
|
|
26
|
+
STATUS_CANCELLED = 5
|
|
27
|
+
STATUS_TOO_LARGE = 6
|
|
28
|
+
STATUS_PENDING = 255
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def available?
|
|
32
|
+
return false if ENV['RUFIO_NATIVE_ZIP'] == '0'
|
|
33
|
+
|
|
34
|
+
NativeScannerZigFFI.zip_available?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# 実行中のジョブ(展開・圧縮共通)
|
|
39
|
+
class Job
|
|
40
|
+
def initialize(handle, count)
|
|
41
|
+
raise StandardError, 'Failed to start native ZIP job' if handle.zero?
|
|
42
|
+
|
|
43
|
+
@handle = handle
|
|
44
|
+
@count = count
|
|
45
|
+
@done = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)
|
|
46
|
+
@total = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# 完了まで待つ。progress には (処理済みバイト数, 合計バイト数) を渡す。
|
|
50
|
+
# cancelled が true を返したらキャンセルを要求し、終了を待ってから戻る
|
|
51
|
+
def wait(progress: nil, cancelled: nil)
|
|
52
|
+
loop do
|
|
53
|
+
state = NativeScannerZigFFI.core_zip_state(@handle)
|
|
54
|
+
report(progress)
|
|
55
|
+
return state unless state == STATE_RUNNING
|
|
56
|
+
|
|
57
|
+
NativeScannerZigFFI.core_zip_cancel(@handle) if cancelled&.call
|
|
58
|
+
sleep POLL_INTERVAL
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def status(index)
|
|
63
|
+
NativeScannerZigFFI.core_zip_status(@handle, index)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def close
|
|
67
|
+
return if @handle.zero?
|
|
68
|
+
|
|
69
|
+
NativeScannerZigFFI.core_zip_destroy(@handle)
|
|
70
|
+
@handle = 0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
protected
|
|
74
|
+
|
|
75
|
+
attr_reader :handle
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def report(progress)
|
|
80
|
+
return unless progress
|
|
81
|
+
|
|
82
|
+
NativeScannerZigFFI.core_zip_progress(@handle, @done, @total)
|
|
83
|
+
progress.call(@done[0, 8].unpack1('Q<'), @total[0, 8].unpack1('Q<'))
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @param zip_path [String]
|
|
88
|
+
# @param tasks [Array<Hash>] data_offset:, compressed_size:, size:, crc32:, method:, output: (一時ファイルのパス)
|
|
89
|
+
def self.extract(zip_path, tasks, threads: 0)
|
|
90
|
+
buffer = tasks.map do |task|
|
|
91
|
+
output = task[:output].b
|
|
92
|
+
[task[:data_offset], task[:compressed_size], task[:size], task[:crc32], task[:method], output.bytesize]
|
|
93
|
+
.pack('Q<Q<Q<L<CL<') + output
|
|
94
|
+
end.join
|
|
95
|
+
path = zip_path.b
|
|
96
|
+
Job.new(NativeScannerZigFFI.core_zip_extract_create(path, path.bytesize, buffer, buffer.bytesize, threads), tasks.length)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# 圧縮ジョブ
|
|
100
|
+
class DeflateJob < Job
|
|
101
|
+
# @return [Array(Integer, Integer, Integer, Integer)] status, crc32, 圧縮後サイズ, 元サイズ
|
|
102
|
+
def result(index)
|
|
103
|
+
crc = Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE)
|
|
104
|
+
compressed = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)
|
|
105
|
+
size = Fiddle::Pointer.malloc(8, Fiddle::RUBY_FREE)
|
|
106
|
+
status = NativeScannerZigFFI.core_zip_deflate_result(handle, index, crc, compressed, size)
|
|
107
|
+
[status, crc[0, 4].unpack1('L<'), compressed[0, 8].unpack1('Q<'), size[0, 8].unpack1('Q<')]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @param tasks [Array<Hash>] source:, output: (一時ファイルのパス)
|
|
112
|
+
def self.deflate(tasks, level:, threads: 0)
|
|
113
|
+
buffer = tasks.map do |task|
|
|
114
|
+
source = task[:source].b
|
|
115
|
+
output = task[:output].b
|
|
116
|
+
[source.bytesize].pack('L<') + source + [output.bytesize].pack('L<') + output
|
|
117
|
+
end.join
|
|
118
|
+
DeflateJob.new(NativeScannerZigFFI.core_zip_deflate_create(buffer, buffer.bytesize, level, threads), tasks.length)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -67,31 +67,18 @@ module Rufio
|
|
|
67
67
|
# @param in_help_mode [Boolean] ヘルプモード中かどうか
|
|
68
68
|
# @param in_log_viewer_mode [Boolean] ログビューワモード中かどうか
|
|
69
69
|
def navigate_enter(entry, in_help_mode, in_log_viewer_mode)
|
|
70
|
-
return false unless entry
|
|
71
|
-
|
|
72
|
-
if entry[:type] == 'directory'
|
|
73
|
-
return false if in_help_mode || in_log_viewer_mode
|
|
70
|
+
return false unless entry && entry[:type] == 'directory'
|
|
71
|
+
return false if in_help_mode || in_log_viewer_mode
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
if result
|
|
77
|
-
@current_index = 0
|
|
78
|
-
clear_filter_mode
|
|
79
|
-
end
|
|
80
|
-
result
|
|
81
|
-
else
|
|
82
|
-
false
|
|
83
|
-
end
|
|
73
|
+
perform_navigation { @directory_listing.navigate_to(entry[:name]) }
|
|
84
74
|
end
|
|
85
75
|
|
|
86
76
|
def navigate_parent
|
|
87
|
-
|
|
77
|
+
perform_navigation { @directory_listing.navigate_to_parent }
|
|
78
|
+
end
|
|
88
79
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@current_index = 0
|
|
92
|
-
clear_filter_mode
|
|
93
|
-
end
|
|
94
|
-
result
|
|
80
|
+
def navigate_to_directory(path)
|
|
81
|
+
perform_navigation { @directory_listing.navigate_to_path(path) }
|
|
95
82
|
end
|
|
96
83
|
|
|
97
84
|
def refresh
|
|
@@ -249,18 +236,25 @@ module Rufio
|
|
|
249
236
|
end
|
|
250
237
|
end
|
|
251
238
|
|
|
252
|
-
# Enter
|
|
239
|
+
# Enterキーの処理:ディレクトリへ移動する
|
|
253
240
|
# @param entry [Hash] 現在選択中のエントリ
|
|
254
241
|
# @param in_help_mode [Boolean]
|
|
255
242
|
# @param in_log_viewer_mode [Boolean]
|
|
256
243
|
def handle_enter_key(entry, in_help_mode, in_log_viewer_mode)
|
|
257
244
|
return false unless entry
|
|
258
245
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
246
|
+
navigate_enter(entry, in_help_mode, in_log_viewer_mode)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
# 移動に成功したときだけ、移動後の選択・フィルタ状態を更新する。
|
|
252
|
+
def perform_navigation
|
|
253
|
+
return false unless @directory_listing
|
|
254
|
+
|
|
255
|
+
result = yield
|
|
256
|
+
clear_filter_mode if result
|
|
257
|
+
result
|
|
264
258
|
end
|
|
265
259
|
end
|
|
266
260
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module Rufio
|
|
6
|
+
# :cd に貼り付けられたパスを移動先に解決する。
|
|
7
|
+
# 引用符・末尾の改行・バックスラッシュエスケープされた空白・file:// URL を受け付け、
|
|
8
|
+
# ファイルを指す場合はその親ディレクトリとファイル自体を返す。
|
|
9
|
+
module PathJump
|
|
10
|
+
QUOTE_PAIRS = [%w[' '], %w[" "]].freeze
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# @param input [String] 貼り付けられたパス(空なら ホーム)
|
|
14
|
+
# @param base_dir [String] 相対パスの基準ディレクトリ
|
|
15
|
+
# @return [Hash] { directory:, file: } または { directory: nil, error: }
|
|
16
|
+
def resolve(input, base_dir)
|
|
17
|
+
path = normalize(input.to_s)
|
|
18
|
+
path = '~' if path.empty?
|
|
19
|
+
expanded = File.expand_path(path, base_dir)
|
|
20
|
+
|
|
21
|
+
if File.directory?(expanded)
|
|
22
|
+
{ directory: expanded, file: nil }
|
|
23
|
+
elsif File.file?(expanded)
|
|
24
|
+
{ directory: File.dirname(expanded), file: expanded }
|
|
25
|
+
else
|
|
26
|
+
{ directory: nil, error: "Path not found: #{path}" }
|
|
27
|
+
end
|
|
28
|
+
rescue ArgumentError => e
|
|
29
|
+
{ directory: nil, error: "Invalid path: #{e.message}" }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def normalize(text)
|
|
35
|
+
path = text.strip
|
|
36
|
+
QUOTE_PAIRS.each do |open, close|
|
|
37
|
+
if path.length >= 2 && path.start_with?(open) && path.end_with?(close)
|
|
38
|
+
path = path[1..-2]
|
|
39
|
+
break
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
path = from_file_url(path) if path.start_with?('file://')
|
|
43
|
+
path = path.gsub(/\\(.)/, '\1') unless Gem.win_platform?
|
|
44
|
+
path
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def from_file_url(url)
|
|
48
|
+
uri = URI.parse(url)
|
|
49
|
+
URI.decode_www_form_component(uri.path.to_s.gsub('+', '%2B'))
|
|
50
|
+
rescue URI::InvalidURIError
|
|
51
|
+
url.delete_prefix('file://')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/rufio/screen.rb
CHANGED
|
@@ -229,12 +229,7 @@ module Rufio
|
|
|
229
229
|
return if out_of_bounds?(x, y)
|
|
230
230
|
|
|
231
231
|
char_width = width || TextUtils.display_width(char)
|
|
232
|
-
@overlay_cells[y][x] =
|
|
233
|
-
char: char,
|
|
234
|
-
fg: fg,
|
|
235
|
-
bg: bg,
|
|
236
|
-
width: char_width
|
|
237
|
-
}
|
|
232
|
+
@overlay_cells[y][x] = Cell.new(char, fg, bg, char_width)
|
|
238
233
|
|
|
239
234
|
@dirty_rows.add(y)
|
|
240
235
|
|
|
@@ -243,12 +238,7 @@ module Rufio
|
|
|
243
238
|
(char_width - 1).times do |offset|
|
|
244
239
|
next_x = x + 1 + offset
|
|
245
240
|
break if next_x >= @width
|
|
246
|
-
@overlay_cells[y][next_x] =
|
|
247
|
-
char: '',
|
|
248
|
-
fg: nil,
|
|
249
|
-
bg: nil,
|
|
250
|
-
width: 0
|
|
251
|
-
}
|
|
241
|
+
@overlay_cells[y][next_x] = Cell.new('', nil, nil, 0)
|
|
252
242
|
end
|
|
253
243
|
end
|
|
254
244
|
end
|