rufio 0.33.0 → 0.40.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 +103 -7
- data/bin/rufio +17 -2
- data/docs/CHANGELOG_v0.33.0.md +2 -2
- data/docs/CHANGELOG_v0.40.0.md +416 -0
- data/lib/rufio/application.rb +2 -2
- data/lib/rufio/async_scanner_fiber.rb +154 -0
- data/lib/rufio/async_scanner_promise.rb +66 -0
- data/lib/rufio/color_helper.rb +59 -6
- data/lib/rufio/command_logger.rb +3 -0
- data/lib/rufio/command_mode_ui.rb +18 -0
- data/lib/rufio/dialog_renderer.rb +68 -0
- data/lib/rufio/keybind_handler.rb +53 -2
- data/lib/rufio/native/rufio_zig.bundle +0 -0
- data/lib/rufio/native_scanner.rb +252 -233
- data/lib/rufio/native_scanner_zig.rb +215 -82
- data/lib/rufio/parallel_scanner.rb +173 -0
- data/lib/rufio/plugins/stop.rb +32 -0
- data/lib/rufio/renderer.rb +64 -0
- data/lib/rufio/screen.rb +184 -0
- data/lib/rufio/terminal_ui.rb +557 -34
- data/lib/rufio/text_utils.rb +30 -18
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +5 -1
- data/lib_zig/rufio_native/Makefile +2 -1
- data/lib_zig/rufio_native/src/main.zig +328 -117
- data/lib_zig/rufio_native/src/main.zig.sync +205 -0
- metadata +10 -9
- data/lib/rufio/native/rufio_native.bundle +0 -0
- data/lib/rufio/native_scanner_magnus.rb +0 -194
- data/lib_rust/rufio_native/.cargo/config.toml +0 -2
- data/lib_rust/rufio_native/Cargo.lock +0 -346
- data/lib_rust/rufio_native/Cargo.toml +0 -18
- data/lib_rust/rufio_native/build.rs +0 -46
- data/lib_rust/rufio_native/src/lib.rs +0 -197
data/lib/rufio/text_utils.rb
CHANGED
|
@@ -18,10 +18,12 @@ module Rufio
|
|
|
18
18
|
# Line break constants
|
|
19
19
|
BREAK_POINT_THRESHOLD = 0.5 # Break after 50% of max_width
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
# Character width cache (memo化)
|
|
22
|
+
@char_width_cache = {}
|
|
23
|
+
|
|
24
|
+
# Calculate width for a single character with caching
|
|
25
|
+
def char_width(char)
|
|
26
|
+
@char_width_cache[char] ||= begin
|
|
25
27
|
case char
|
|
26
28
|
when /[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\uFF00-\uFFEF\u2500-\u257F\u2580-\u259F]/
|
|
27
29
|
FULLWIDTH_CHAR_WIDTH # Japanese characters (hiragana, katakana, kanji, full-width symbols, box drawing, block elements)
|
|
@@ -30,7 +32,17 @@ module Rufio
|
|
|
30
32
|
else
|
|
31
33
|
char.bytesize > MULTIBYTE_THRESHOLD ? FULLWIDTH_CHAR_WIDTH : HALFWIDTH_CHAR_WIDTH
|
|
32
34
|
end
|
|
33
|
-
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Calculate display width of a string
|
|
39
|
+
# Full-width characters (Japanese, etc.) count as 2, half-width as 1
|
|
40
|
+
def display_width(string)
|
|
41
|
+
width = 0
|
|
42
|
+
string.each_char do |char|
|
|
43
|
+
width += char_width(char)
|
|
44
|
+
end
|
|
45
|
+
width
|
|
34
46
|
end
|
|
35
47
|
|
|
36
48
|
# Truncate string to fit within max_width
|
|
@@ -44,11 +56,11 @@ module Rufio
|
|
|
44
56
|
target_width = max_width - ELLIPSIS_MIN_WIDTH
|
|
45
57
|
|
|
46
58
|
string.each_char do |char|
|
|
47
|
-
|
|
48
|
-
break if current_width +
|
|
59
|
+
cw = char_width(char)
|
|
60
|
+
break if current_width + cw > target_width
|
|
49
61
|
|
|
50
62
|
result += char
|
|
51
|
-
current_width +=
|
|
63
|
+
current_width += cw
|
|
52
64
|
end
|
|
53
65
|
|
|
54
66
|
result + ELLIPSIS
|
|
@@ -58,11 +70,11 @@ module Rufio
|
|
|
58
70
|
current_width = 0
|
|
59
71
|
|
|
60
72
|
string.each_char do |char|
|
|
61
|
-
|
|
62
|
-
break if current_width +
|
|
73
|
+
cw = char_width(char)
|
|
74
|
+
break if current_width + cw > max_width
|
|
63
75
|
|
|
64
76
|
result += char
|
|
65
|
-
current_width +=
|
|
77
|
+
current_width += cw
|
|
66
78
|
end
|
|
67
79
|
|
|
68
80
|
result
|
|
@@ -89,10 +101,10 @@ module Rufio
|
|
|
89
101
|
punct_break_point = nil
|
|
90
102
|
|
|
91
103
|
line.each_char.with_index do |char, index|
|
|
92
|
-
|
|
93
|
-
break if current_width +
|
|
104
|
+
cw = char_width(char)
|
|
105
|
+
break if current_width + cw > max_width
|
|
94
106
|
|
|
95
|
-
current_width +=
|
|
107
|
+
current_width += cw
|
|
96
108
|
best_break_point = index + 1
|
|
97
109
|
|
|
98
110
|
# Record break point at space
|
|
@@ -134,16 +146,16 @@ module Rufio
|
|
|
134
146
|
current_width = 0
|
|
135
147
|
|
|
136
148
|
line.each_char do |char|
|
|
137
|
-
|
|
149
|
+
cw = char_width(char)
|
|
138
150
|
|
|
139
|
-
if current_width +
|
|
151
|
+
if current_width + cw > max_width
|
|
140
152
|
# Start a new line
|
|
141
153
|
wrapped << current_line.join
|
|
142
154
|
current_line = [char]
|
|
143
|
-
current_width =
|
|
155
|
+
current_width = cw
|
|
144
156
|
else
|
|
145
157
|
current_line << char
|
|
146
|
-
current_width +=
|
|
158
|
+
current_width += cw
|
|
147
159
|
end
|
|
148
160
|
end
|
|
149
161
|
|
data/lib/rufio/version.rb
CHANGED
data/lib/rufio.rb
CHANGED
|
@@ -33,8 +33,12 @@ require_relative "rufio/shell_command_completion"
|
|
|
33
33
|
require_relative "rufio/command_logger"
|
|
34
34
|
require_relative "rufio/background_command_executor"
|
|
35
35
|
require_relative "rufio/native_scanner"
|
|
36
|
-
require_relative "rufio/native_scanner_magnus"
|
|
37
36
|
require_relative "rufio/native_scanner_zig"
|
|
37
|
+
require_relative "rufio/async_scanner_promise"
|
|
38
|
+
require_relative "rufio/async_scanner_fiber"
|
|
39
|
+
require_relative "rufio/parallel_scanner"
|
|
40
|
+
require_relative "rufio/screen"
|
|
41
|
+
require_relative "rufio/renderer"
|
|
38
42
|
|
|
39
43
|
# プロジェクトモード
|
|
40
44
|
require_relative "rufio/project_mode"
|
|
@@ -7,13 +7,14 @@ ZIG := zig
|
|
|
7
7
|
RUBY_INCLUDE := $(shell $(RUBY) -e "puts RbConfig::CONFIG['rubyhdrdir']")
|
|
8
8
|
RUBY_ARCH_INCLUDE := $(shell $(RUBY) -e "puts RbConfig::CONFIG['rubyarchhdrdir']")
|
|
9
9
|
RUBY_LIB_DIR := $(shell $(RUBY) -e "puts RbConfig::CONFIG['libdir']")
|
|
10
|
+
RUBY_VERSION := $(shell $(RUBY) -e "puts RbConfig::CONFIG['MAJOR'] + '.' + RbConfig::CONFIG['MINOR']")
|
|
10
11
|
|
|
11
12
|
# コンパイルフラグ
|
|
12
13
|
ZIG_FLAGS := -O ReleaseFast -dynamic -lc
|
|
13
14
|
ZIG_FLAGS += -I$(RUBY_INCLUDE)
|
|
14
15
|
ZIG_FLAGS += -I$(RUBY_ARCH_INCLUDE)
|
|
15
16
|
ZIG_FLAGS += -L$(RUBY_LIB_DIR)
|
|
16
|
-
ZIG_FLAGS += -lruby
|
|
17
|
+
ZIG_FLAGS += -lruby.$(RUBY_VERSION)
|
|
17
18
|
|
|
18
19
|
# ターゲット
|
|
19
20
|
TARGET := librufio_zig.dylib
|
|
@@ -1,167 +1,378 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
2
|
const c = @cImport({
|
|
3
|
-
@cInclude("ruby.h");
|
|
4
3
|
@cInclude("dirent.h");
|
|
5
4
|
@cInclude("sys/stat.h");
|
|
6
5
|
@cInclude("string.h");
|
|
7
|
-
@cInclude("
|
|
6
|
+
@cInclude("stdlib.h");
|
|
7
|
+
@cInclude("pthread.h");
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
///
|
|
10
|
+
/// スキャン状態
|
|
11
|
+
pub const ScanState = enum(u8) {
|
|
12
|
+
idle = 0,
|
|
13
|
+
scanning = 1,
|
|
14
|
+
done = 2,
|
|
15
|
+
cancelled = 3,
|
|
16
|
+
failed = 4,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/// ディレクトリエントリ情報(内部保持用)
|
|
11
20
|
const DirEntry = struct {
|
|
12
|
-
name: []
|
|
21
|
+
name: []u8,
|
|
13
22
|
is_dir: bool,
|
|
14
23
|
size: u64,
|
|
15
24
|
mtime: i64,
|
|
16
25
|
executable: bool,
|
|
17
26
|
hidden: bool,
|
|
27
|
+
|
|
28
|
+
fn deinit(self: *DirEntry) void {
|
|
29
|
+
c.free(self.name.ptr);
|
|
30
|
+
}
|
|
18
31
|
};
|
|
19
32
|
|
|
20
|
-
///
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
/// 非同期スキャナーコア(状態を保持)
|
|
34
|
+
const AsyncScanner = struct {
|
|
35
|
+
entries: []DirEntry,
|
|
36
|
+
capacity: usize,
|
|
37
|
+
count: usize,
|
|
38
|
+
state: ScanState,
|
|
39
|
+
progress: usize,
|
|
40
|
+
total_estimate: usize,
|
|
41
|
+
mutex: c.pthread_mutex_t,
|
|
42
|
+
path_copy: ?[*:0]u8,
|
|
43
|
+
max_entries: usize,
|
|
44
|
+
thread: c.pthread_t,
|
|
45
|
+
thread_started: bool,
|
|
46
|
+
|
|
47
|
+
fn init() !*AsyncScanner {
|
|
48
|
+
const self = @as(?*AsyncScanner, @ptrCast(@alignCast(c.malloc(@sizeOf(AsyncScanner))))) orelse return error.OutOfMemory;
|
|
49
|
+
|
|
50
|
+
self.*.capacity = 64;
|
|
51
|
+
const entries_size = self.*.capacity * @sizeOf(DirEntry);
|
|
52
|
+
const entries_ptr = c.malloc(entries_size) orelse {
|
|
53
|
+
c.free(self);
|
|
54
|
+
return error.OutOfMemory;
|
|
55
|
+
};
|
|
56
|
+
self.*.entries = @as([*]DirEntry, @ptrCast(@alignCast(entries_ptr)))[0..self.*.capacity];
|
|
57
|
+
self.*.count = 0;
|
|
58
|
+
self.*.state = .idle;
|
|
59
|
+
self.*.progress = 0;
|
|
60
|
+
self.*.total_estimate = 0;
|
|
61
|
+
self.*.path_copy = null;
|
|
62
|
+
self.*.max_entries = 0;
|
|
63
|
+
self.*.thread_started = false;
|
|
64
|
+
|
|
65
|
+
// mutex初期化
|
|
66
|
+
_ = c.pthread_mutex_init(&self.*.mutex, null);
|
|
67
|
+
|
|
68
|
+
return self;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn deinit(self: *AsyncScanner) void {
|
|
72
|
+
// スレッドが実行中なら待機
|
|
73
|
+
if (self.thread_started) {
|
|
74
|
+
_ = c.pthread_join(self.thread, null);
|
|
49
75
|
}
|
|
50
76
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
};
|
|
77
|
+
// 各エントリの名前を解放
|
|
78
|
+
for (self.entries[0..self.count]) |*entry| {
|
|
79
|
+
entry.deinit();
|
|
80
|
+
}
|
|
56
81
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
continue;
|
|
82
|
+
// パスのコピーを解放
|
|
83
|
+
if (self.path_copy) |path| {
|
|
84
|
+
c.free(path);
|
|
61
85
|
}
|
|
62
86
|
|
|
63
|
-
//
|
|
64
|
-
|
|
87
|
+
// エントリ配列を解放
|
|
88
|
+
c.free(self.entries.ptr);
|
|
65
89
|
|
|
66
|
-
//
|
|
67
|
-
|
|
90
|
+
// mutex破棄
|
|
91
|
+
_ = c.pthread_mutex_destroy(&self.mutex);
|
|
68
92
|
|
|
69
|
-
//
|
|
70
|
-
|
|
93
|
+
// 自身を解放
|
|
94
|
+
c.free(self);
|
|
95
|
+
}
|
|
71
96
|
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
fn clear(self: *AsyncScanner) void {
|
|
98
|
+
// 既存のエントリをクリア
|
|
99
|
+
for (self.entries[0..self.count]) |*entry| {
|
|
100
|
+
entry.deinit();
|
|
101
|
+
}
|
|
102
|
+
self.count = 0;
|
|
103
|
+
self.progress = 0;
|
|
104
|
+
self.total_estimate = 0;
|
|
105
|
+
}
|
|
74
106
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("is_dir")), if (is_dir) c.Qtrue else c.Qfalse);
|
|
78
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("size")), c.ULL2NUM(@as(c_ulonglong, @intCast(stat_buf.st_size))));
|
|
79
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("mtime")), c.LL2NUM(@as(c_longlong, @intCast(stat_buf.st_mtimespec.tv_sec))));
|
|
80
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("executable")), if (executable) c.Qtrue else c.Qfalse);
|
|
81
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("hidden")), if (hidden) c.Qtrue else c.Qfalse);
|
|
107
|
+
fn ensureCapacity(self: *AsyncScanner, needed: usize) !void {
|
|
108
|
+
if (needed <= self.capacity) return;
|
|
82
109
|
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
const new_capacity = @max(needed, self.capacity * 2);
|
|
111
|
+
const new_size = new_capacity * @sizeOf(DirEntry);
|
|
112
|
+
const new_ptr = c.realloc(self.entries.ptr, new_size) orelse return error.OutOfMemory;
|
|
113
|
+
self.entries = @as([*]DirEntry, @ptrCast(@alignCast(new_ptr)))[0..new_capacity];
|
|
114
|
+
self.capacity = new_capacity;
|
|
85
115
|
}
|
|
86
116
|
|
|
87
|
-
|
|
88
|
-
|
|
117
|
+
fn isCancelled(self: *AsyncScanner) bool {
|
|
118
|
+
_ = c.pthread_mutex_lock(&self.mutex);
|
|
119
|
+
defer _ = c.pthread_mutex_unlock(&self.mutex);
|
|
120
|
+
return self.state == .cancelled;
|
|
121
|
+
}
|
|
89
122
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
123
|
+
fn setState(self: *AsyncScanner, new_state: ScanState) void {
|
|
124
|
+
_ = c.pthread_mutex_lock(&self.mutex);
|
|
125
|
+
defer _ = c.pthread_mutex_unlock(&self.mutex);
|
|
126
|
+
self.state = new_state;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn scan(self: *AsyncScanner, path: [*:0]const u8, max_entries: usize) !void {
|
|
130
|
+
// クリア
|
|
131
|
+
self.clear();
|
|
132
|
+
|
|
133
|
+
const dir = c.opendir(path) orelse return error.CannotOpenDir;
|
|
134
|
+
defer _ = c.closedir(dir);
|
|
135
|
+
|
|
136
|
+
const path_len = c.strlen(path);
|
|
137
|
+
const path_slice = path[0..path_len];
|
|
138
|
+
|
|
139
|
+
while (c.readdir(dir)) |entry| {
|
|
140
|
+
// キャンセルチェック
|
|
141
|
+
if (self.isCancelled()) {
|
|
142
|
+
return error.Cancelled;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// エントリ数制限チェック
|
|
146
|
+
if (max_entries > 0 and self.count >= max_entries) break;
|
|
147
|
+
|
|
148
|
+
const name_ptr = @as([*:0]const u8, @ptrCast(&entry.*.d_name));
|
|
149
|
+
const name_len = c.strlen(name_ptr);
|
|
150
|
+
const name_slice = name_ptr[0..name_len];
|
|
151
|
+
|
|
152
|
+
// "." と ".." をスキップ
|
|
153
|
+
if (std.mem.eql(u8, name_slice, ".") or std.mem.eql(u8, name_slice, "..")) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 容量確保
|
|
158
|
+
try self.ensureCapacity(self.count + 1);
|
|
159
|
+
|
|
160
|
+
// フルパスを構築
|
|
161
|
+
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
162
|
+
const full_path = std.fmt.bufPrintZ(&path_buf, "{s}/{s}", .{ path_slice, name_slice }) catch continue;
|
|
163
|
+
|
|
164
|
+
// ファイル情報を取得
|
|
165
|
+
var stat_buf: c.struct_stat = undefined;
|
|
166
|
+
if (c.lstat(full_path.ptr, &stat_buf) != 0) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 名前をコピー
|
|
171
|
+
const name_copy_ptr = c.malloc(name_len + 1) orelse return error.OutOfMemory;
|
|
172
|
+
const name_copy_bytes = @as([*]u8, @ptrCast(@alignCast(name_copy_ptr)));
|
|
173
|
+
const name_copy = name_copy_bytes[0..name_len];
|
|
174
|
+
@memcpy(name_copy, name_slice);
|
|
175
|
+
name_copy_bytes[name_len] = 0;
|
|
176
|
+
|
|
177
|
+
// エントリを追加
|
|
178
|
+
self.entries[self.count] = DirEntry{
|
|
179
|
+
.name = name_copy,
|
|
180
|
+
.is_dir = (stat_buf.st_mode & c.S_IFMT) == c.S_IFDIR,
|
|
181
|
+
.size = @intCast(stat_buf.st_size),
|
|
182
|
+
.mtime = @intCast(stat_buf.st_mtimespec.tv_sec),
|
|
183
|
+
.executable = (stat_buf.st_mode & c.S_IXUSR) != 0,
|
|
184
|
+
.hidden = name_slice[0] == '.',
|
|
185
|
+
};
|
|
186
|
+
self.count += 1;
|
|
187
|
+
|
|
188
|
+
// 進捗更新
|
|
189
|
+
_ = c.pthread_mutex_lock(&self.mutex);
|
|
190
|
+
self.progress = self.count;
|
|
191
|
+
_ = c.pthread_mutex_unlock(&self.mutex);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
93
194
|
|
|
94
|
-
const max_entries
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const path_len = c.strlen(path_str);
|
|
98
|
-
const path = path_str[0..path_len];
|
|
195
|
+
fn scanAsync(self: *AsyncScanner, path: [*:0]const u8, max_entries: usize) !void {
|
|
196
|
+
_ = c.pthread_mutex_lock(&self.mutex);
|
|
197
|
+
defer _ = c.pthread_mutex_unlock(&self.mutex);
|
|
99
198
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
};
|
|
104
|
-
defer _ = c.closedir(dir);
|
|
199
|
+
if (self.state == .scanning) {
|
|
200
|
+
return error.AlreadyScanning;
|
|
201
|
+
}
|
|
105
202
|
|
|
106
|
-
|
|
107
|
-
|
|
203
|
+
// パスをコピー
|
|
204
|
+
const path_len = c.strlen(path);
|
|
205
|
+
const path_copy_ptr = c.malloc(path_len + 1) orelse return error.OutOfMemory;
|
|
206
|
+
const path_copy_bytes = @as([*:0]u8, @ptrCast(@alignCast(path_copy_ptr)));
|
|
207
|
+
@memcpy(path_copy_bytes[0..path_len], path[0..path_len]);
|
|
208
|
+
path_copy_bytes[path_len] = 0;
|
|
108
209
|
|
|
109
|
-
|
|
110
|
-
if (
|
|
210
|
+
// 古いパスを解放
|
|
211
|
+
if (self.path_copy) |old_path| {
|
|
212
|
+
c.free(old_path);
|
|
213
|
+
}
|
|
111
214
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
215
|
+
self.path_copy = path_copy_bytes;
|
|
216
|
+
self.max_entries = max_entries;
|
|
217
|
+
self.state = .scanning;
|
|
115
218
|
|
|
116
|
-
|
|
117
|
-
|
|
219
|
+
// スレッド開始
|
|
220
|
+
const result = c.pthread_create(&self.thread, null, scanWorkerEntry, self);
|
|
221
|
+
if (result != 0) {
|
|
222
|
+
self.state = .failed;
|
|
223
|
+
return error.ThreadCreateFailed;
|
|
118
224
|
}
|
|
225
|
+
self.thread_started = true;
|
|
226
|
+
}
|
|
119
227
|
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
228
|
+
fn scanWorker(self: *AsyncScanner) void {
|
|
229
|
+
const path = self.path_copy orelse {
|
|
230
|
+
self.setState(.failed);
|
|
231
|
+
return;
|
|
123
232
|
};
|
|
124
233
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
234
|
+
self.scan(path, self.max_entries) catch |err| {
|
|
235
|
+
// キャンセルエラーの場合は状態を変更しない(既にcancelledになっている)
|
|
236
|
+
if (err == error.Cancelled) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
self.setState(.failed);
|
|
240
|
+
return;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
self.setState(.done);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// スレッドエントリポイント
|
|
248
|
+
fn scanWorkerEntry(arg: ?*anyopaque) callconv(.c) ?*anyopaque {
|
|
249
|
+
const scanner = @as(*AsyncScanner, @ptrCast(@alignCast(arg)));
|
|
250
|
+
scanner.scanWorker();
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================
|
|
255
|
+
// ABI Boundary: Ruby ABI非依存・ハンドルベース
|
|
256
|
+
// ============================================================
|
|
129
257
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
258
|
+
/// 非同期スキャナーを作成(ハンドル返却)
|
|
259
|
+
export fn core_async_create() u64 {
|
|
260
|
+
const scanner = AsyncScanner.init() catch return 0;
|
|
261
|
+
return @intFromPtr(scanner);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// 非同期スキャン開始
|
|
265
|
+
export fn core_async_scan(handle: u64, path: [*:0]const u8) i32 {
|
|
266
|
+
if (handle == 0) return -1;
|
|
267
|
+
|
|
268
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
269
|
+
scanner.scanAsync(path, 0) catch return -1;
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// 高速スキャン(エントリ数制限付き)
|
|
274
|
+
export fn core_async_scan_fast(handle: u64, path: [*:0]const u8, max_entries: usize) i32 {
|
|
275
|
+
if (handle == 0) return -1;
|
|
276
|
+
|
|
277
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
278
|
+
scanner.scanAsync(path, max_entries) catch return -1;
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
133
281
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("size")), c.ULL2NUM(@as(c_ulonglong, @intCast(stat_buf.st_size))));
|
|
138
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("mtime")), c.LL2NUM(@as(c_longlong, @intCast(stat_buf.st_mtimespec.tv_sec))));
|
|
139
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("executable")), if (executable) c.Qtrue else c.Qfalse);
|
|
140
|
-
_ = c.rb_hash_aset(hash, c.ID2SYM(c.rb_intern("hidden")), if (hidden) c.Qtrue else c.Qfalse);
|
|
282
|
+
/// 状態取得
|
|
283
|
+
export fn core_async_get_state(handle: u64) u8 {
|
|
284
|
+
if (handle == 0) return @intFromEnum(ScanState.failed);
|
|
141
285
|
|
|
142
|
-
|
|
143
|
-
|
|
286
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
287
|
+
_ = c.pthread_mutex_lock(&scanner.mutex);
|
|
288
|
+
defer _ = c.pthread_mutex_unlock(&scanner.mutex);
|
|
289
|
+
return @intFromEnum(scanner.state);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// 進捗取得
|
|
293
|
+
export fn core_async_get_progress(handle: u64, current: *usize, total: *usize) void {
|
|
294
|
+
if (handle == 0) {
|
|
295
|
+
current.* = 0;
|
|
296
|
+
total.* = 0;
|
|
297
|
+
return;
|
|
144
298
|
}
|
|
145
299
|
|
|
146
|
-
|
|
300
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
301
|
+
_ = c.pthread_mutex_lock(&scanner.mutex);
|
|
302
|
+
defer _ = c.pthread_mutex_unlock(&scanner.mutex);
|
|
303
|
+
|
|
304
|
+
current.* = scanner.progress;
|
|
305
|
+
total.* = scanner.total_estimate;
|
|
147
306
|
}
|
|
148
307
|
|
|
149
|
-
///
|
|
150
|
-
fn
|
|
151
|
-
|
|
152
|
-
|
|
308
|
+
/// キャンセル
|
|
309
|
+
export fn core_async_cancel(handle: u64) void {
|
|
310
|
+
if (handle == 0) return;
|
|
311
|
+
|
|
312
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
313
|
+
_ = c.pthread_mutex_lock(&scanner.mutex);
|
|
314
|
+
defer _ = c.pthread_mutex_unlock(&scanner.mutex);
|
|
315
|
+
|
|
316
|
+
if (scanner.state == .scanning) {
|
|
317
|
+
scanner.state = .cancelled;
|
|
318
|
+
}
|
|
153
319
|
}
|
|
154
320
|
|
|
155
|
-
///
|
|
156
|
-
export fn
|
|
157
|
-
|
|
158
|
-
const rufio_module = c.rb_define_module("Rufio");
|
|
321
|
+
/// エントリ数を取得
|
|
322
|
+
export fn core_async_get_count(handle: u64) usize {
|
|
323
|
+
if (handle == 0) return 0;
|
|
159
324
|
|
|
160
|
-
|
|
161
|
-
|
|
325
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
326
|
+
_ = c.pthread_mutex_lock(&scanner.mutex);
|
|
327
|
+
defer _ = c.pthread_mutex_unlock(&scanner.mutex);
|
|
328
|
+
return scanner.count;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// 指定インデックスのエントリ名を取得
|
|
332
|
+
export fn core_async_get_name(handle: u64, index: usize, buf: [*]u8, buf_size: usize) usize {
|
|
333
|
+
if (handle == 0) return 0;
|
|
334
|
+
|
|
335
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
336
|
+
_ = c.pthread_mutex_lock(&scanner.mutex);
|
|
337
|
+
defer _ = c.pthread_mutex_unlock(&scanner.mutex);
|
|
338
|
+
|
|
339
|
+
if (index >= scanner.count) return 0;
|
|
340
|
+
|
|
341
|
+
const entry = &scanner.entries[index];
|
|
342
|
+
const copy_len = @min(entry.name.len, buf_size - 1);
|
|
343
|
+
@memcpy(buf[0..copy_len], entry.name[0..copy_len]);
|
|
344
|
+
buf[copy_len] = 0;
|
|
345
|
+
return copy_len;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/// 指定インデックスのエントリ属性を取得
|
|
349
|
+
export fn core_async_get_attrs(handle: u64, index: usize, is_dir: *u8, size: *u64, mtime: *i64, executable: *u8, hidden: *u8) i32 {
|
|
350
|
+
if (handle == 0) return -1;
|
|
351
|
+
|
|
352
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
353
|
+
_ = c.pthread_mutex_lock(&scanner.mutex);
|
|
354
|
+
defer _ = c.pthread_mutex_unlock(&scanner.mutex);
|
|
162
355
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
356
|
+
if (index >= scanner.count) return -1;
|
|
357
|
+
|
|
358
|
+
const entry = &scanner.entries[index];
|
|
359
|
+
is_dir.* = if (entry.is_dir) 1 else 0;
|
|
360
|
+
size.* = entry.size;
|
|
361
|
+
mtime.* = entry.mtime;
|
|
362
|
+
executable.* = if (entry.executable) 1 else 0;
|
|
363
|
+
hidden.* = if (entry.hidden) 1 else 0;
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/// スキャナーを破棄
|
|
368
|
+
export fn core_async_destroy(handle: u64) void {
|
|
369
|
+
if (handle == 0) return;
|
|
370
|
+
|
|
371
|
+
const scanner = @as(*AsyncScanner, @ptrFromInt(handle));
|
|
372
|
+
scanner.deinit();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/// バージョン情報
|
|
376
|
+
export fn core_async_version() [*:0]const u8 {
|
|
377
|
+
return "4.0.0-async";
|
|
167
378
|
}
|