rufio 1.1.0 → 1.3.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 +34 -0
- data/README.md +74 -3
- data/README_ja.md +71 -2
- data/Rakefile +14 -1
- data/bin/rufio +31 -2
- data/docs/fast_zip_design_ja.md +167 -0
- data/info/help.md +2 -0
- data/info/keybindings.md +3 -1
- data/info/welcome.md +1 -1
- 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 +6 -1
- data/lib/rufio/command_history.rb +5 -2
- data/lib/rufio/command_mode.rb +40 -134
- data/lib/rufio/command_mode_ui.rb +4 -42
- data/lib/rufio/config.rb +88 -136
- data/lib/rufio/config_loader.rb +3 -11
- data/lib/rufio/dialog_renderer.rb +64 -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_preview.rb +46 -2
- data/lib/rufio/global_search.rb +121 -9
- data/lib/rufio/help_menu.rb +8 -2
- 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 +81 -115
- 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 +18 -20
- 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 +55 -294
- data/lib/rufio/text_utils.rb +26 -3
- data/lib/rufio/ui_renderer.rb +63 -40
- 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 +5 -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 +125 -3
- data/lib/rufio/native/rufio_zig.bundle +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61f470cce894bacf7a88eaffbca98212689c7a74fe615c41cc90f75af4c6c8da
|
|
4
|
+
data.tar.gz: eb507f8b6ccfc365f1276512fd13d7959cffac7ae9b8208d8e3ae6a9716ab2d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43483f6db196732505620756a314556d7a2c07c709df960995e87e56b68d7aa348271344c215307859b92bcc4f90c5c0afd349cabf999eaf7b64845698c4da11
|
|
7
|
+
data.tar.gz: 9e0998e7819be5f14f4ad25a6beb8ddcd2de7da78858168e32b884d0b604347395f0b812b0fb3555e1773687d7cdbeaaf02893e09e1de9d2997e3a37b150f718
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ All notable changes to rufio will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.3.0] - 2026-11-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Preview ZIP archives as an entry list (size, method, modified time, name), including ZIP64, UTF-8 / CP932 file names, and the Info-ZIP Unicode Path extra field. The list is read from the central directory without extracting files
|
|
14
|
+
- Press `z` with items selected in Files mode to extract them when every selected item is a `.zip` file (each into `<name>/`, numbered if the name is taken) or otherwise compress them into one `.zip` in the current directory. The work runs as a background job with progress in Jobs mode, can be cancelled, removes a partially extracted folder on failure, and refreshes the file list when done. Encrypted entries are skipped. Without a selection, `z` still opens zoxide history
|
|
15
|
+
- The bundled Zig library adds a ZIP engine that decompresses entries and compresses files of 1 MiB or more in parallel outside the GVL, rebuilt for all eight bundled platforms. Ruby still validates archives, chooses paths, and renames files, so the safety checks are shared. On 8 × 64 MB text files with 4 CPUs it compressed about 4.6x and extracted about 3.2x faster than the Ruby implementation. `RUFIO_NATIVE_ZIP=0` disables it, and older libraries without the ZIP functions fall back to Ruby
|
|
16
|
+
- `Rufio::ZipExtractor` extracts Stored and Deflate entries after validating the whole archive first: it rejects path traversal, absolute paths, overlapping entries, and archives over size, entry count, or compression ratio limits, skips symlink and encrypted entries, never follows existing symlinks, verifies CRC-32, refuses to overwrite files unless asked, and leaves no partial files
|
|
17
|
+
- `Rufio::ZipWriter` creates ZIP archives with UTF-8 names, falls back to Stored for incompressible files, uses ZIP64 for large files and more than 65,535 entries, skips symlinks, and writes through a temporary file
|
|
18
|
+
|
|
19
|
+
## [1.2.0] - 2026-10-31
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- G mode content search reads PDF (`pdf-reader`), Excel `.xlsx` (`roo`), and PowerPoint `.pptx` (Rubyzip and XML) directly from Ruby, without Python or external conversion commands
|
|
23
|
+
- Document results show the PDF page, Excel sheet and cell, or PowerPoint slide alongside matching text; Enter still locates the source file in Files mode
|
|
24
|
+
- A bounded in-memory document text cache avoids re-extracting unchanged documents when editing the query; file changes invalidate cached text, and corrupt or encrypted documents report errors without stopping other searches
|
|
25
|
+
|
|
26
|
+
- G mode content search runs on a bundled Zig engine: parallel directory walking, SIMD literal matching, and a revalidated in-memory file cache make the first search about 1.8x and repeated searches about 10x faster than `rg` on a 31k-file tree; queries with non-ASCII cased letters still use the Ruby search, and `RUFIO_NATIVE_SEARCH=0` disables the engine
|
|
27
|
+
- G mode highlights the matching text (case-insensitive, every occurrence) in file name results and content snippets
|
|
28
|
+
- `T` quits rufio and moves the shell to the current directory; `rufio --shell-init [bash|zsh|fish|powershell]` prints the `rf` shell function that does the move (PowerShell on Windows, the login shell elsewhere), and `--choosedir FILE` receives the directory
|
|
29
|
+
- The gem bundles the Zig library for macOS (arm64/x86_64), Linux (x86_64/aarch64, glibc 2.17+ and musl), and Windows (x64/arm64); unsupported platforms fall back to Ruby automatically
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Files mode shows the current path on its own top row above the bookmark list (full path with `~` for home, shortened from the left when too long) instead of on the bottom tab line; Jobs mode keeps the path on the tab line
|
|
33
|
+
- Rebuild the Zig library with Zig 0.17 (`make -C lib_zig/rufio_native dist`); bundled binaries live in `lib/rufio/native/<platform>/`
|
|
34
|
+
- Consolidate UI rendering, dialog drawing, file operations, bookmark flows, native scanner helpers, shell execution, and localized message definitions, removing dead code and runtime warnings
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- Windows: dialogs no longer compete with the input thread for keys, so bookmark names and other dialog input no longer need every key pressed twice
|
|
38
|
+
- Windows: wait longer for arrow key escape sequences in G mode so arrow keys no longer close G mode as a lone ESC
|
|
39
|
+
- Windows: DSL shell command timeouts stop the process with KILL instead of failing with `Invalid argument` on TERM and leaving it running
|
|
40
|
+
- Restore the scanner mode after native scanner tests so test order no longer affects normal directory listings
|
|
41
|
+
|
|
8
42
|
## [1.1.0] - 2026-10-25
|
|
9
43
|
|
|
10
44
|
### Added
|
data/README.md
CHANGED
|
@@ -87,6 +87,7 @@ rufio /path/to # Launch in specified directory
|
|
|
87
87
|
| `g/}` | Top/bottom |
|
|
88
88
|
| `Enter` | Enter directory/open file |
|
|
89
89
|
| `q` | Quit |
|
|
90
|
+
| `T` | Quit and cd to the current directory ([Shell Integration](#shell-integration)) |
|
|
90
91
|
|
|
91
92
|
### File Operations
|
|
92
93
|
|
|
@@ -98,6 +99,7 @@ rufio /path/to # Launch in specified directory
|
|
|
98
99
|
| `r` | Rename |
|
|
99
100
|
| `d` | Delete |
|
|
100
101
|
| `m/c/x` | Move/copy/delete (selected) |
|
|
102
|
+
| `z` | Extract selected `.zip` files / compress selected items (selected) |
|
|
101
103
|
|
|
102
104
|
### Search & Filter
|
|
103
105
|
|
|
@@ -116,7 +118,7 @@ rufio /path/to # Launch in specified directory
|
|
|
116
118
|
| `0` | Return to startup directory |
|
|
117
119
|
| `1-9` | Jump to bookmark |
|
|
118
120
|
| `Tab` | Cycle to next bookmark (Files mode only) |
|
|
119
|
-
| `z` | zoxide history |
|
|
121
|
+
| `z` | zoxide history (when nothing is selected) |
|
|
120
122
|
|
|
121
123
|
### Tool Runtime
|
|
122
124
|
|
|
@@ -135,8 +137,8 @@ rufio /path/to # Launch in specified directory
|
|
|
135
137
|
Press `G` to search recursively across all bookmarked directories. `Tab`
|
|
136
138
|
switches between **File names** and **Content**. Type a literal string to
|
|
137
139
|
search (case insensitive); content results include the file, line number,
|
|
138
|
-
and matching text.
|
|
139
|
-
first 8 KB are skipped as binary. Use Up/Down or Ctrl+N/P to select a
|
|
140
|
+
and matching text. Plain text is read as UTF-8; other unsupported files containing
|
|
141
|
+
NUL bytes in the first 8 KB are skipped as binary. Use Up/Down or Ctrl+N/P to select a
|
|
140
142
|
result, and press Enter to jump to that file in Files mode. `Ctrl+U` clears
|
|
141
143
|
the query; `Esc` cancels and returns to your previous view. Scanning runs in
|
|
142
144
|
the background and skips inaccessible directories and directory symlinks.
|
|
@@ -154,12 +156,62 @@ GLOBAL_SEARCH_EXCLUDED_EXTENSIONS = %w[.png .jpg .zip .tar.gz .log].freeze
|
|
|
154
156
|
Extensions are case insensitive and the leading dot is optional. The default
|
|
155
157
|
is `[]` (no extension exclusions). Directory names are unaffected.
|
|
156
158
|
|
|
159
|
+
#### PDF, Excel and PowerPoint content search (v1.2.0)
|
|
160
|
+
|
|
161
|
+
Content mode also extracts text from documents using Ruby gems; Python,
|
|
162
|
+
Microsoft Office and external conversion tools are not required.
|
|
163
|
+
|
|
164
|
+
| File | Searchable content | Result location |
|
|
165
|
+
|------|--------------------|-----------------|
|
|
166
|
+
| `.pdf` | Extractable text | Page number |
|
|
167
|
+
| `.xlsx` | Cell text and values | Sheet name and cell address |
|
|
168
|
+
| `.pptx` | Text in slide shapes and tables | Slide number |
|
|
169
|
+
|
|
170
|
+
Search remains literal and case insensitive. Split text runs in a PowerPoint
|
|
171
|
+
paragraph are joined, and slides follow the presentation's display order.
|
|
172
|
+
Excel formulas use the values saved in the workbook; Rufio does not recalculate them.
|
|
173
|
+
Enter locates the original document in Files mode; it does not open Office at a cell or slide.
|
|
174
|
+
|
|
175
|
+
Extracted text is cached in memory across searches within the running app.
|
|
176
|
+
The cache keeps up to 64 documents and approximately 16 MiB of text and metadata;
|
|
177
|
+
larger extracted documents remain searchable without being cached. Updated or
|
|
178
|
+
replaced files are re-extracted. No document text cache is written to disk.
|
|
179
|
+
|
|
180
|
+
Extension exclusions apply to documents too. Damaged, unsupported or encrypted
|
|
181
|
+
documents may report errors while other files continue to be searched.
|
|
182
|
+
Scanned images need OCR, which is not included. Legacy `.xls`/`.ppt`, slide notes,
|
|
183
|
+
and text embedded only in images or charts are not supported by this implementation.
|
|
184
|
+
PDF text extraction depends on the document's font and character mappings.
|
|
185
|
+
|
|
157
186
|
### Config Mode (C)
|
|
158
187
|
|
|
159
188
|
Press `C` to open `~/.config/rufio/`, creating it if needed. Navigation, previews,
|
|
160
189
|
and file operations work as in Files mode. Browse settings and scripts directly;
|
|
161
190
|
leaving the config directory returns the mode display to Files.
|
|
162
191
|
|
|
192
|
+
### Shell Integration
|
|
193
|
+
|
|
194
|
+
`rufio --shell-init` prints a shell function named `rf` for your OS: PowerShell on
|
|
195
|
+
Windows, and your login shell (`bash`, `zsh` or `fish`) elsewhere. Pass the shell name to
|
|
196
|
+
choose explicitly. Launch rufio with `rf` and press `T` to quit and move the shell to the
|
|
197
|
+
directory you were viewing; `q` quits without moving.
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# bash / zsh (~/.bashrc or ~/.zshrc)
|
|
201
|
+
eval "$(rufio --shell-init)"
|
|
202
|
+
|
|
203
|
+
# fish (~/.config/fish/config.fish)
|
|
204
|
+
rufio --shell-init fish | source
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```powershell
|
|
208
|
+
# PowerShell ($PROFILE)
|
|
209
|
+
Invoke-Expression (& rufio --shell-init powershell | Out-String)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
To avoid starting Ruby with every shell, append the output once instead, for example
|
|
213
|
+
`rufio --shell-init >> ~/.bashrc`.
|
|
214
|
+
|
|
163
215
|
## Command Mode
|
|
164
216
|
|
|
165
217
|
Press `:` to enter command mode and execute various commands.
|
|
@@ -354,6 +406,25 @@ apt install fzf bat zoxide
|
|
|
354
406
|
|
|
355
407
|
## Advanced Features
|
|
356
408
|
|
|
409
|
+
### Native Content Search
|
|
410
|
+
|
|
411
|
+
G mode content search uses a bundled Zig engine on macOS (arm64/x86_64), Linux (x86_64/aarch64, glibc 2.17+ or musl), and Windows (x64/arm64). It searches bookmarks in parallel and caches file contents between queries, so retyping a query is about 10x faster than `rg`. Other platforms, and queries containing non-ASCII letters with case, use the Ruby search automatically. Set `RUFIO_NATIVE_SEARCH=0` to disable it.
|
|
412
|
+
|
|
413
|
+
### ZIP Archives
|
|
414
|
+
|
|
415
|
+
Select items in Files mode with `Space` and press `z`: when every selected item is a `.zip` file, each is extracted into `<name>/` in the current directory; otherwise the selection is compressed into one `.zip`. The work runs as a job (progress and cancel in Jobs mode). `.zip` files also show their entry list in the preview.
|
|
416
|
+
|
|
417
|
+
Extraction validates the whole archive before writing anything: it rejects path traversal and absolute paths, overlapping entries, and archives over 16 GiB in total, 100,000 entries, or a 1000:1 compression ratio (for entries over 1 MiB); it skips symlink and encrypted entries, verifies CRC-32, and never overwrites existing files. Only Stored and Deflate entries are supported.
|
|
418
|
+
|
|
419
|
+
On the same platforms as content search, the bundled Zig engine decompresses entries and compresses files of 1 MiB or more in parallel (in our measurements on 8 × 64 MB text files with 4 CPUs, about 4.6x faster to compress and 3.2x faster to extract than the Ruby implementation). Validation stays in Ruby either way. Set `RUFIO_NATIVE_ZIP=0` to use the Ruby implementation only.
|
|
420
|
+
|
|
421
|
+
To rebuild the bundled binaries (requires Zig 0.17):
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
make -C lib_zig/rufio_native dist # all platforms
|
|
425
|
+
bundle exec rake native:check # verify bundled binaries
|
|
426
|
+
```
|
|
427
|
+
|
|
357
428
|
### Native Scanner (Experimental)
|
|
358
429
|
|
|
359
430
|
Support for native implementation for fast directory scanning:
|
data/README_ja.md
CHANGED
|
@@ -87,6 +87,7 @@ rufio /path/to # 指定したディレクトリで起動
|
|
|
87
87
|
| `g/}` | 先頭/末尾 |
|
|
88
88
|
| `Enter` | ディレクトリに入る/ファイルを開く |
|
|
89
89
|
| `q` | 終了 |
|
|
90
|
+
| `T` | 終了して現在のディレクトリへ移動([シェル連携](#シェル連携)) |
|
|
90
91
|
|
|
91
92
|
### ファイル操作
|
|
92
93
|
|
|
@@ -98,6 +99,7 @@ rufio /path/to # 指定したディレクトリで起動
|
|
|
98
99
|
| `r` | リネーム |
|
|
99
100
|
| `d` | 削除 |
|
|
100
101
|
| `m/c/x` | 移動/コピー/削除(選択済み) |
|
|
102
|
+
| `z` | 選択した `.zip` を展開 / 選択したアイテムを `.zip` に圧縮(選択済み) |
|
|
101
103
|
|
|
102
104
|
### 検索・フィルター
|
|
103
105
|
|
|
@@ -116,7 +118,7 @@ rufio /path/to # 指定したディレクトリで起動
|
|
|
116
118
|
| `0` | 起動ディレクトリに戻る |
|
|
117
119
|
| `1-9` | ブックマークにジャンプ |
|
|
118
120
|
| `Tab` | 次のブックマークへ循環移動(Filesモード限定) |
|
|
119
|
-
| `z` | zoxide
|
|
121
|
+
| `z` | zoxide履歴(未選択時) |
|
|
120
122
|
|
|
121
123
|
### ツールランタイム
|
|
122
124
|
|
|
@@ -135,7 +137,7 @@ rufio /path/to # 指定したディレクトリで起動
|
|
|
135
137
|
`G` を押すと、ブックマークした全ディレクトリの配下を再帰的に検索します。
|
|
136
138
|
Tabで **File names(ファイル名)** と **Content(ファイル内容)** を切り替えます。
|
|
137
139
|
大文字・小文字を区別しない文字列の部分一致で検索し、内容検索ではファイル名・行番号・一致した行を表示します。
|
|
138
|
-
|
|
140
|
+
通常のテキストはUTF-8として読み込み、文書抽出に対応していないファイルは先頭8 KBにNULバイトがあればバイナリとしてスキップします。
|
|
139
141
|
上下矢印または `Ctrl+N/P` で選択、
|
|
140
142
|
Enterで選んだファイルの場所へ移動します。`Ctrl+U` で検索語をクリアし、
|
|
141
143
|
Escで元の表示に戻ります。検索はバックグラウンドで進みます。
|
|
@@ -154,12 +156,60 @@ GLOBAL_SEARCH_EXCLUDED_EXTENSIONS = %w[.png .jpg .zip .tar.gz .log].freeze
|
|
|
154
156
|
大文字・小文字は区別せず、先頭のドットは省略できます。デフォルトは `[]`(拡張子の除外なし)です。
|
|
155
157
|
ディレクトリ名には適用しません。
|
|
156
158
|
|
|
159
|
+
#### PDF・Excel・PowerPointの内容検索(v1.2.0)
|
|
160
|
+
|
|
161
|
+
Contentモードで文書内の文字列も検索できます。Rubyのgemで抽出するため、
|
|
162
|
+
Python・Microsoft Office・外部変換コマンドは不要です。
|
|
163
|
+
|
|
164
|
+
| ファイル | 検索する内容 | 結果の位置表示 |
|
|
165
|
+
|------------|--------------|----------------|
|
|
166
|
+
| `.pdf` | 抽出可能な文字列 | ページ番号 |
|
|
167
|
+
| `.xlsx` | セルの文字列・値 | シート名・セル番地 |
|
|
168
|
+
| `.pptx` | スライドの図形・表内の文字列 | スライド番号 |
|
|
169
|
+
|
|
170
|
+
大文字・小文字を区別しない部分一致検索です。PowerPointの段落内で分割された文字列は連結し、
|
|
171
|
+
スライド番号はプレゼンテーションの表示順に対応します。
|
|
172
|
+
Excelの数式はファイルに保存済みの計算結果を検索し、再計算は行いません。
|
|
173
|
+
Enterでは元の文書の場所へ移動します。Officeでセルやスライドを直接開く機能ではありません。
|
|
174
|
+
|
|
175
|
+
抽出した文字列は起動中のメモリにキャッシュし、検索語を変更しても再利用します。
|
|
176
|
+
最大64文書、文字列とメタデータの概算で16 MiBまで保持し、超える文書もキャッシュせずに検索します。
|
|
177
|
+
ファイルを更新・置換した場合は再抽出します。キャッシュをディスクには保存しません。
|
|
178
|
+
|
|
179
|
+
拡張子除外設定は文書にも適用します。破損・非対応・暗号化された文書でエラーが発生しても、他のファイルの検索は続行します。
|
|
180
|
+
画像だけのPDFにはOCRが必要ですが、今回の実装には含みません。旧形式の`.xls`・`.ppt`、
|
|
181
|
+
スライドノート、画像やグラフだけに含まれる文字列も対象外です。
|
|
182
|
+
PDFの文字抽出精度は、文書のフォントや文字コードの対応情報に依存します。
|
|
183
|
+
|
|
157
184
|
### Configモード(C)
|
|
158
185
|
|
|
159
186
|
`C` で `~/.config/rufio/` を開きます。ディレクトリがなければ作成します。
|
|
160
187
|
移動・プレビュー・ファイル操作はFilesモードと共通で、設定やスクリプトを直接編集できます。
|
|
161
188
|
設定ディレクトリの外へ移動すると、モード表示がFilesに戻ります。
|
|
162
189
|
|
|
190
|
+
### シェル連携
|
|
191
|
+
|
|
192
|
+
`rufio --shell-init` は OS に合わせた `rf` というシェル関数を出力します。Windows では
|
|
193
|
+
PowerShell 用、それ以外ではログインシェル(`bash`・`zsh`・`fish`)用です。シェル名を
|
|
194
|
+
指定して切り替えることもできます。`rf` で rufio を起動し `T` を押すと、終了してシェルが
|
|
195
|
+
表示中のディレクトリへ移動します。`q` で終了した場合は移動しません。
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# bash / zsh(~/.bashrc または ~/.zshrc)
|
|
199
|
+
eval "$(rufio --shell-init)"
|
|
200
|
+
|
|
201
|
+
# fish(~/.config/fish/config.fish)
|
|
202
|
+
rufio --shell-init fish | source
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```powershell
|
|
206
|
+
# PowerShell($PROFILE)
|
|
207
|
+
Invoke-Expression (& rufio --shell-init powershell | Out-String)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
シェル起動のたびに Ruby を起動したくない場合は、`rufio --shell-init >> ~/.bashrc` の
|
|
211
|
+
ように一度だけ出力を追記してください。
|
|
212
|
+
|
|
163
213
|
## コマンドモード
|
|
164
214
|
|
|
165
215
|
`:` でコマンドモードを起動し、様々なコマンドを実行できます。
|
|
@@ -320,6 +370,25 @@ apt install fzf bat zoxide
|
|
|
320
370
|
|
|
321
371
|
## 高度な機能
|
|
322
372
|
|
|
373
|
+
### ネイティブコンテンツ検索
|
|
374
|
+
|
|
375
|
+
G mode のコンテンツ検索は、macOS(arm64/x86_64)、Linux(x86_64/aarch64、glibc 2.17 以降または musl)、Windows(x64/arm64)では同梱の Zig エンジンで動作します。ブックマークを並列に検索し、検索の間でファイル内容をキャッシュするため、検索語を打ち直したときの再検索は `rg` の約 10 倍速くなります。その他のプラットフォームや、大文字小文字のある非 ASCII 文字を含む検索語では自動的に Ruby の検索を使います。`RUFIO_NATIVE_SEARCH=0` で無効化できます。
|
|
376
|
+
|
|
377
|
+
### ZIP の圧縮・展開
|
|
378
|
+
|
|
379
|
+
Files モードで `Space` で選択して `z` を押すと、選択がすべて `.zip` ならそれぞれを現在のディレクトリの `<名前>/` に展開し、それ以外は 1 つの `.zip` に圧縮します。処理はジョブとして実行されます(Jobs モードで進捗表示・キャンセル)。`.zip` はプレビューでエントリ一覧も表示します。
|
|
380
|
+
|
|
381
|
+
展開時は書き込み前にアーカイブ全体を検証します。パストラバーサル・絶対パス・データ範囲が重なるエントリ、合計 16GiB・10 万エントリ・圧縮率 1000:1(1MiB 超のエントリ)を超えるアーカイブを拒否し、シンボリックリンクと暗号化エントリはスキップし、CRC-32 を検証し、既存ファイルは上書きしません。対応する圧縮方式は Stored と Deflate です。
|
|
382
|
+
|
|
383
|
+
コンテンツ検索と同じプラットフォームでは、同梱の Zig エンジンがエントリの伸長と 1MiB 以上のファイルの圧縮を並列に行います(4 CPU で 64MB のテキスト 8 ファイルを計測したところ、Ruby 実装より圧縮が約 4.6 倍、展開が約 3.2 倍速くなりました)。検証はどちらの場合も Ruby 側で行います。`RUFIO_NATIVE_ZIP=0` で Ruby 実装のみを使います。
|
|
384
|
+
|
|
385
|
+
同梱バイナリの再ビルド(Zig 0.17 が必要):
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
make -C lib_zig/rufio_native dist # 全プラットフォーム
|
|
389
|
+
bundle exec rake native:check # 同梱バイナリの検証
|
|
390
|
+
```
|
|
391
|
+
|
|
323
392
|
### ネイティブスキャナー(実験的)
|
|
324
393
|
|
|
325
394
|
高速なディレクトリスキャンのためのネイティブ実装をサポート:
|
data/Rakefile
CHANGED
|
@@ -14,10 +14,23 @@ end
|
|
|
14
14
|
# Default task
|
|
15
15
|
task default: :test
|
|
16
16
|
|
|
17
|
+
# Zig native library tasks
|
|
18
|
+
namespace :native do
|
|
19
|
+
desc 'Cross-compile the Zig library for every bundled platform (requires zig)'
|
|
20
|
+
task :build do
|
|
21
|
+
sh 'make -C lib_zig/rufio_native dist'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc 'Verify bundled Zig binaries for macOS, Linux and Windows'
|
|
25
|
+
task :check do
|
|
26
|
+
ruby '-I test -I lib test/test_native_platform_binaries.rb'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
17
30
|
# Gem build and push tasks
|
|
18
31
|
namespace :gem do
|
|
19
32
|
desc 'Build the gem file'
|
|
20
|
-
task :build do
|
|
33
|
+
task :build => 'native:check' do
|
|
21
34
|
puts "Building rufio v#{Rufio::VERSION}..."
|
|
22
35
|
|
|
23
36
|
# Clean old gem files
|
data/bin/rufio
CHANGED
|
@@ -19,6 +19,8 @@ start_directory = nil
|
|
|
19
19
|
test_mode = false
|
|
20
20
|
yjit_mode = false
|
|
21
21
|
zjit_mode = false
|
|
22
|
+
choosedir = nil
|
|
23
|
+
shell_init = nil
|
|
22
24
|
skip_next = false
|
|
23
25
|
|
|
24
26
|
ARGV.each_with_index do |arg, idx|
|
|
@@ -49,6 +51,21 @@ ARGV.each_with_index do |arg, idx|
|
|
|
49
51
|
end
|
|
50
52
|
when /^--native=(rust|go|auto|ruby)$/
|
|
51
53
|
native_mode = $1
|
|
54
|
+
when '--choosedir'
|
|
55
|
+
choosedir = ARGV[idx + 1]
|
|
56
|
+
skip_next = true
|
|
57
|
+
when /^--choosedir=(.+)$/
|
|
58
|
+
choosedir = $1
|
|
59
|
+
when '--shell-init'
|
|
60
|
+
# 次の引数がシェル名なら使い、なければ OS から判定する
|
|
61
|
+
if Rufio::ShellInit::SHELLS.include?(ARGV[idx + 1]) || ARGV[idx + 1] == 'pwsh'
|
|
62
|
+
shell_init = ARGV[idx + 1]
|
|
63
|
+
skip_next = true
|
|
64
|
+
else
|
|
65
|
+
shell_init = Rufio::ShellInit.detect
|
|
66
|
+
end
|
|
67
|
+
when /^--shell-init=(.+)$/
|
|
68
|
+
shell_init = $1
|
|
52
69
|
when '-c', '--check-health', '--help', '-h', '--yjit', '--zjit'
|
|
53
70
|
# これらは後で処理
|
|
54
71
|
when /^--/
|
|
@@ -70,7 +87,15 @@ if ENV['RUFIO_NATIVE'] && defined?(Rufio::NativeScanner)
|
|
|
70
87
|
end
|
|
71
88
|
|
|
72
89
|
# コマンド処理
|
|
73
|
-
if
|
|
90
|
+
if shell_init
|
|
91
|
+
begin
|
|
92
|
+
print Rufio::ShellInit.script(shell_init)
|
|
93
|
+
exit(0)
|
|
94
|
+
rescue ArgumentError => e
|
|
95
|
+
warn e.message
|
|
96
|
+
exit(1)
|
|
97
|
+
end
|
|
98
|
+
elsif ARGV.include?('--check-health') || ARGV.include?('-c')
|
|
74
99
|
# ヘルスチェック実行
|
|
75
100
|
health_checker = Rufio::HealthChecker.new
|
|
76
101
|
success = health_checker.run_check
|
|
@@ -87,6 +112,9 @@ elsif ARGV.include?('--help') || ARGV.include?('-h')
|
|
|
87
112
|
puts " --test Show FPS counter in footer (for performance testing)"
|
|
88
113
|
puts " --yjit Enable YJIT JIT compiler (Ruby 3.1+)"
|
|
89
114
|
puts " --zjit Enable ZJIT JIT compiler (Ruby 3.4+)"
|
|
115
|
+
puts " --shell-init [SHELL] Print the rf shell function (cd on T quit)"
|
|
116
|
+
puts " SHELL: bash|zsh|fish|powershell (default: by OS)"
|
|
117
|
+
puts " --choosedir FILE Write the directory to FILE when quitting with T"
|
|
90
118
|
if defined?(Rufio::NativeScanner)
|
|
91
119
|
puts " --native[=MODE] Enable native scanner (experimental)"
|
|
92
120
|
puts " MODE: auto|rust|go (default: auto)"
|
|
@@ -117,11 +145,12 @@ elsif ARGV.include?('--help') || ARGV.include?('-h')
|
|
|
117
145
|
puts " RUFIO_NATIVE=rust rufio # Use Rust via environment variable"
|
|
118
146
|
end
|
|
119
147
|
puts " rufio -c # Check dependencies"
|
|
148
|
+
puts " eval \"$(rufio --shell-init)\" # Add rf to bash/zsh (T quits and cd)"
|
|
120
149
|
exit(0)
|
|
121
150
|
else
|
|
122
151
|
start_directory ||= Dir.pwd
|
|
123
152
|
|
|
124
153
|
# アプリケーション開始
|
|
125
|
-
app = Rufio::Application.new(start_directory)
|
|
154
|
+
app = Rufio::Application.new(start_directory, choosedir: choosedir)
|
|
126
155
|
app.run(test_mode: test_mode)
|
|
127
156
|
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# FastZip 実装検討(rufio 1.3)
|
|
2
|
+
|
|
3
|
+
ブランチ: `v1.3.0`
|
|
4
|
+
ステータス: Phase 1〜5 実装済み(Pure Ruby の一覧・展開・作成、`z` キー、Zig エンジンの並列展開・圧縮、8 プラットフォームのバイナリ同梱)
|
|
5
|
+
|
|
6
|
+
「FastZip 開発構想メモ(Ruby × Zig ネイティブ拡張アーカイバ)」を rufio 1.3 の機能として取り込む前提で、既存コードとの整合と実装方針を整理する。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. 結論
|
|
11
|
+
|
|
12
|
+
| 項目 | 構想メモ | 本検討での方針 |
|
|
13
|
+
|------|----------|----------------|
|
|
14
|
+
| 置き場所 | 独立Gem `fast_zip` | まず rufio 内の機能として実装。ABIが Ruby 非依存なので、後から Gem に切り出せる |
|
|
15
|
+
| Ruby との接続 | C拡張(`Init_fast_zip` + `rb_thread_call_without_gvl`) | **既存の流儀に合わせて Fiddle + Ruby ABI 非依存の C 関数** |
|
|
16
|
+
| GVL 解放 | 自前で `rb_thread_call_without_gvl` | Fiddle が既定で解放する(下記 2.3)。長時間処理は既存スキャナと同じ「ハンドル+ポーリング」型にしてキャンセル可能にする |
|
|
17
|
+
| ネイティブ非搭載環境 | 記載なし | **Pure Ruby 実装を必須のフォールバック兼リファレンス**とする(Phase 1 で実装済み) |
|
|
18
|
+
| 最初のユーザー価値 | — | `.zip` をプレビューペインで一覧表示(`unzip -l` 相当)※実装済み |
|
|
19
|
+
|
|
20
|
+
### 1.1 決定事項(2026-10-31)
|
|
21
|
+
- C 拡張にはしない。既存の Zig 連携(Fiddle + Ruby ABI 非依存の C 関数)で実装する
|
|
22
|
+
- Pure Ruby 版は残す(ネイティブ非搭載環境のフォールバック兼、互換性の基準)
|
|
23
|
+
- 展開時の安全対策を入れる(4.2)
|
|
24
|
+
- ネイティブバイナリはビルド済みのものを Gem に同梱して配る(1.2 の `make dist` の 8 プラットフォーム)
|
|
25
|
+
- 操作は Files モードで選択後に `z`(すべて .zip なら展開、それ以外は圧縮)。暗号化エントリはスキップのまま
|
|
26
|
+
- 展開上限は現在の値(合計 16GiB / 10 万エントリ / 1MiB 超で 1000:1)で確定。対応方式は Stored と Deflate のみ
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. 現状調査(事実)
|
|
31
|
+
|
|
32
|
+
### 2.1 rufio のアーカイブ対応
|
|
33
|
+
- `FilePreview#determine_file_type` は `.zip` 等を `archive` と判定するが、実際はバイナリ判定が先に走り「Binary file / Cannot preview」と表示されていた。
|
|
34
|
+
- 圧縮・展開機能はない。
|
|
35
|
+
|
|
36
|
+
### 2.2 既存の Zig ネイティブ拡張(1.2.0 時点)
|
|
37
|
+
- ソース: `lib_zig/rufio_native/src/`(`main.zig` がエクスポート、`search.zig` がコンテンツ検索、`os.zig` が OS 差分の吸収)。Ruby ヘッダは include せず、`core_async_*` / `core_search_*` / `core_cache_*` という **Ruby ABI 非依存の C 関数**をエクスポートしている。
|
|
38
|
+
- Ruby 側: `lib/rufio/native_scanner_zig.rb` が `Fiddle::Importer` でロード。関数グループごとに `search_available?` のような判定があり、古いバイナリでも Ruby 実装に切り替わる。
|
|
39
|
+
- ビルド: Zig 0.17。`make -C lib_zig/rufio_native dist` で macOS(arm64/x86_64)・Linux(x86_64/aarch64、glibc 2.17+ と musl)・Windows(x64/arm64)の 8 種類をクロスコンパイルし、`lib/rufio/native/<platform>/` に同梱する。
|
|
40
|
+
- `test/test_native_platform_binaries.rb` が各バイナリの形式・CPU・必須エクスポート(`REQUIRED_EXPORTS`)を検証している。
|
|
41
|
+
- 1.2.0 で `rubyzip`(`>= 2.4, < 3.0`)が G モードの `.pptx` 読み取り用に依存関係に入った。ただし rubyzip 2.4 はファイル名を ASCII-8BIT で保持し、UTF-8 文字列で引けない(このブランチのテストで確認)。プレビュー・展開・作成は本実装を使い、rubyzip は相互運用テストの相手として使う。
|
|
42
|
+
|
|
43
|
+
### 2.3 Fiddle と GVL
|
|
44
|
+
- 本環境の fiddle 1.1.8 の `ext/fiddle/function.c` では、`need_gvl` の既定値が `Qfalse` で、その場合 `rb_thread_call_without_gvl` 経由で関数を呼ぶ。`Fiddle::Importer#extern` は `need_gvl` を指定しないので、**既存の Zig 関数呼び出しは GVL を解放して実行されている**。
|
|
45
|
+
- したがって構想メモの「GVL 解放のための C 拡張化」は、Fiddle 経由でも同じ効果が得られる。C 拡張にすると Ruby のマイナーバージョンごとにバイナリが必要になるデメリットの方が大きい。
|
|
46
|
+
- ただし Fiddle の GVL 解放ではキャンセル(unblock 関数)が指定できない。TUI から中断できるようにするため、展開・圧縮は既存スキャナと同じ非同期ハンドル方式を採る。
|
|
47
|
+
|
|
48
|
+
### 2.4 Ruby 標準 zlib でできること
|
|
49
|
+
- 構想メモの「zlib は ZIP 構造を扱えない」は正しいが、ZIP のコンテナ構造(EOCD / Central Directory / Local Header)は数百行の Ruby で扱える(Phase 1 で実装)。
|
|
50
|
+
- `Zlib::Inflate.new(-Zlib::MAX_WBITS)` / `Zlib::Deflate.new(level, -Zlib::MAX_WBITS)` で raw DEFLATE を扱え、`Zlib.crc32` もある。つまり **Pure Ruby + zlib だけで Store / Deflate の展開・作成まで実装できる**。ネイティブ実装は「速度のための加速器」という位置づけになる。
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 3. 実装内容(このブランチ)
|
|
55
|
+
|
|
56
|
+
### 3.1 `Rufio::ZipArchive`(`lib/rufio/zip_archive.rb`)
|
|
57
|
+
読み取り専用。ファイル本体は読まず、末尾と Central Directory だけを読む。
|
|
58
|
+
|
|
59
|
+
- EOCD の後方探索(末尾 22 + 65535 バイト)。コメント長が整合し、かつ指す先に Central Directory シグネチャがある候補のみ採用(コメント内の偽シグネチャ対策)
|
|
60
|
+
- ZIP64(ZIP64 EOCD Locator / ZIP64 EOCD / ZIP64 拡張フィールド 0x0001)
|
|
61
|
+
- ファイル名のエンコーディング: 汎用フラグ bit 11 → UTF-8、Info-ZIP Unicode Path 拡張(0x7075、CRC 一致時のみ)、UTF-8 として不正なら CP932(Windows 日本語環境で作られた ZIP)として解釈、それも失敗したら置換文字
|
|
62
|
+
- DOS 日時の変換、ディレクトリ・暗号化フラグの判定
|
|
63
|
+
- `limit:` で先頭 N 件だけ読む(プレビュー用。総数は EOCD から得る)
|
|
64
|
+
- 範囲外オフセット・切り詰められたファイルは `ZipArchive::FormatError`
|
|
65
|
+
- マルチディスク(分割)ZIP は非対応
|
|
66
|
+
|
|
67
|
+
### 3.2 プレビュー統合
|
|
68
|
+
- `FilePreview#preview_file` で拡張子 `.zip` または先頭が `PK\x03\x04` / `PK\x05\x06` のファイルを `type: "archive"` として一覧表示(最大 200 件、超過分は件数のみ)
|
|
69
|
+
- 壊れた ZIP は `Invalid ZIP archive: <理由>` をエラー表示
|
|
70
|
+
- `UIRenderer#extract_preview_lines` が `archive` を表示対象に追加
|
|
71
|
+
|
|
72
|
+
### 3.3 テスト(`test/test_zip_archive.rb`)
|
|
73
|
+
フィクスチャはテスト内の最小 ZIP ライタで生成し、外部コマンドに依存しない。加えて `zip` / `unzip` がある環境では `unzip -Z1` の一覧と完全一致することを確認する(ない環境では skip)。
|
|
74
|
+
|
|
75
|
+
### 3.4 計測(参考値)
|
|
76
|
+
環境: このクラウドコンテナ(Linux x86_64, Ruby 3.3.6)、`zip -qr` で作成した 20,001 エントリ / 3.2MB の ZIP。各 5 回平均。
|
|
77
|
+
|
|
78
|
+
| 処理 | 時間 |
|
|
79
|
+
|------|------|
|
|
80
|
+
| `ZipArchive.list`(全 20,001 件) | 176 ms |
|
|
81
|
+
| `ZipArchive.list(limit: 200)` | 1.9 ms |
|
|
82
|
+
| `FilePreview#preview_file`(200 件表示) | 1.9 ms |
|
|
83
|
+
| 参考: `unzip -l`(全件を標準出力へ、`time` の real) | 55 ms |
|
|
84
|
+
|
|
85
|
+
プレビュー用途では Pure Ruby で十分速い。ネイティブ化の効果が出るのは「全件走査」と「展開・圧縮(データ本体の処理)」である。
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### 3.5 Phase 2: 展開・作成(Pure Ruby)
|
|
90
|
+
- `Rufio::ZipExtractor`(`lib/rufio/zip_extractor.rb`): Stored / Deflate の展開。安全対策は 4.2 をすべて実装。書き込み前に全エントリを検証し、危険なエントリが 1 件でもあれば何も書かない。進捗コールバックとキャンセルに対応。
|
|
91
|
+
- `Rufio::ZipWriter`(`lib/rufio/zip_writer.rb`): ファイル・ディレクトリから ZIP を作成。UTF-8 名、縮まないファイルは Stored、ZIP64(4GiB 超・65535 件超)、シンボリックリンクと出力先自身はスキップ、一時ファイル経由で失敗時に残骸を残さない。
|
|
92
|
+
- テスト: `test/test_zip_extractor.rb` / `test/test_zip_writer.rb`。`zip` コマンド製(データディスクリプタ付きを含む)の展開、作成物の `unzip -t` 通過、rubyzip での読み取り、70,000 件での ZIP64 を確認。
|
|
93
|
+
|
|
94
|
+
## 4. 計画
|
|
95
|
+
|
|
96
|
+
| Phase | 内容 | 実装 | 備考 |
|
|
97
|
+
|-------|------|------|------|
|
|
98
|
+
| 2 | Store / Deflate の展開・作成 | Pure Ruby + zlib | **実装済み**。ネイティブ版のテストオラクルにする |
|
|
99
|
+
| 3 | Zig コア(展開・圧縮のデータ処理) | Zig + Fiddle | **実装済み**。同じテストを両実装で回す |
|
|
100
|
+
| 4 | 並列展開・並列圧縮 | Zig(ワーカースレッド) | **実装済み**。最大 16 スレッド、既定は CPU 数 |
|
|
101
|
+
| 5 | 配布・ベンチマーク | — | **実装済み**。`make dist` で 8 プラットフォーム分を同梱、`REQUIRED_EXPORTS` に `core_zip_*` を追加 |
|
|
102
|
+
|
|
103
|
+
### 4.1 TUI への組み込み(実装済み)
|
|
104
|
+
- Files モードで `Space` で選択してから `z`。選択がすべて `.zip` なら展開、それ以外は圧縮(`lib/rufio/archive_controller.rb`)
|
|
105
|
+
- 展開: 確認ダイアログの後、各 ZIP を現在のディレクトリの `<名前>/` に展開(既にあれば `<名前> 2/` …)。失敗・キャンセル時は作った展開先を削除する
|
|
106
|
+
- 圧縮: アーカイブ名を入力(空 Enter で 1 件なら `<名前>.zip`、複数なら `Archive.zip`)。同名ファイルがあればエラー通知
|
|
107
|
+
- `..` は対象外
|
|
108
|
+
- 選択がなければ `z` は従来どおり zoxide(キー設定 `zoxide` を共用)
|
|
109
|
+
- 処理は `JobManager` のジョブとしてバックグラウンドスレッドで実行。ジョブ名に 5% 刻みで進捗を表示し、Jobs モードのキャンセルで中断できる。完了時に通知し、表示中のディレクトリなら一覧を再読み込みする
|
|
110
|
+
- 暗号化エントリはスキップ(決定事項)
|
|
111
|
+
|
|
112
|
+
### 4.2 安全性要件(実装済み。Ruby と Zig で共通)
|
|
113
|
+
- **Zip Slip**: `..`、絶対パス、Windows のドライブレター・`\` 区切りを含むエントリを拒否し、展開先ディレクトリ外に書かない
|
|
114
|
+
- **シンボリックリンク**: 外部属性で symlink を示すエントリは展開しない。展開先に既にあるシンボリックリンクも辿らない
|
|
115
|
+
- **ZIP 爆弾**: 展開後合計サイズ・圧縮率・エントリ数の上限。Central Directory 上のサイズを信用せず、実際に書いたバイト数で打ち切る。Local Header の範囲が重なるエントリ(重複参照型の爆弾)を拒否
|
|
116
|
+
- **CRC-32 検証**: 不一致なら展開したファイルを削除してエラー
|
|
117
|
+
- **既存ファイルの上書き**: 既定は拒否(`overwrite: true` で上書き)。UI では確認ダイアログを出す想定
|
|
118
|
+
- **ファイルモード**: 実行権限だけを引き継ぎ、setuid / setgid / sticky と他者書き込み権限は付けない
|
|
119
|
+
- 暗号化エントリ(ZipCrypto / AES)は初期は非対応としてスキップ
|
|
120
|
+
|
|
121
|
+
### 4.3 Zig エンジン(実装済み)
|
|
122
|
+
`lib_zig/rufio_native/src/zip.zig`。役割分担は次のとおりで、安全性の検証は Ruby 実装と共通にした。
|
|
123
|
+
|
|
124
|
+
| 処理 | 担当 |
|
|
125
|
+
|------|------|
|
|
126
|
+
| Central Directory の解析、パス正規化、上限・重複範囲・シンボリックリンクの検証、ディレクトリ作成 | Ruby |
|
|
127
|
+
| ZIP 内のデータ範囲を読み、伸長し、宣言サイズ超過で打ち切り、CRC-32 を検証して一時ファイルに書く | Zig(エントリ単位で並列) |
|
|
128
|
+
| 一時ファイルの rename、権限・日時の設定、失敗時の一時ファイル削除 | Ruby |
|
|
129
|
+
| ファイルを raw DEFLATE で一時ファイルへ圧縮し、CRC-32 とサイズを返す(1MiB 以上のファイルのみ) | Zig(ファイル単位で並列) |
|
|
130
|
+
| ローカルヘッダ・Central Directory・ZIP64 を書いてアーカイブを組み立てる | Ruby |
|
|
131
|
+
|
|
132
|
+
- エクスポート: `core_zip_extract_create` / `core_zip_deflate_create` / `core_zip_state` / `core_zip_progress` / `core_zip_status` / `core_zip_deflate_result` / `core_zip_cancel` / `core_zip_destroy`
|
|
133
|
+
- Ruby 側は `NativeScannerZigFFI.zip_available?` で関数グループの有無を判定し、ない場合(古いバイナリ)や `RUFIO_NATIVE_ZIP=0` では Pure Ruby を使う
|
|
134
|
+
- DEFLATE は Zig 標準ライブラリの `std.compress.flate`、CRC-32 は `std.hash.Crc32`。libdeflate は同梱していない
|
|
135
|
+
- ファイル操作は `os.zig` に追加(Linux は直接システムコール、macOS は libc、Windows は Win32。位置指定読み込み・排他作成・書き込み)
|
|
136
|
+
- `flate.Compress` は約 230KB あり初期化時にスタックへ載るため、ワーカースレッドのスタックは 4MB
|
|
137
|
+
- 1MiB 未満のファイルの圧縮は Ruby の zlib で行う。小さいファイルを Zig に渡すと、一時ファイルの作成・コピー・削除の分だけ遅くなった(2 万ファイルで 0.98 秒 → 5.26 秒)ため
|
|
138
|
+
- テスト: `test/test_zip_extractor.rb` / `test/test_zip_writer.rb` の全テストを `TestZipExtractorNative` / `TestZipWriterNative` で Zig 実装でも実行。Debug ビルド(安全チェック有効)でも全件通過を確認
|
|
139
|
+
|
|
140
|
+
### 4.4 計測(このクラウドコンテナ、4 CPU、Linux x86_64、ReleaseFast、各 3 回の最小値)
|
|
141
|
+
|
|
142
|
+
| データ | 処理 | Ruby | Zig | zip / unzip |
|
|
143
|
+
|--------|------|------|-----|-------------|
|
|
144
|
+
| 8 ファイル × 64MB のテキスト | 圧縮 | 15.36 秒 | 3.34 秒 | 20.20 秒 |
|
|
145
|
+
| 同上 | 展開 | 3.03 秒 | 0.94 秒 | 4.11 秒 |
|
|
146
|
+
| 2 万ファイル・計 79MB | 圧縮 | 0.93 秒 | 0.90 秒 | 0.30 秒 |
|
|
147
|
+
| 同上 | 展開 | 5.82 秒 | 5.00 秒 | 4.62 秒 |
|
|
148
|
+
|
|
149
|
+
- 圧縮後サイズは Zig の方がやや大きい(512MB → Ruby/zlib 67MB、Zig 71MB)。同じレベル指定でも実装が異なるため
|
|
150
|
+
- 小さいファイル大量の展開はファイル作成が律速で、計測ごとのばらつきが大きい(`sync` を挟まない計測では unzip 1.4〜1.8 秒、Ruby/Zig 3〜6 秒)。ファイルごとの Ruby 側処理(rename・chmod・utime)が残るため、unzip との差は縮め切れていない
|
|
151
|
+
|
|
152
|
+
## 5. 技術的な注意点(構想メモへのコメント)
|
|
153
|
+
|
|
154
|
+
- **libdeflate はストリーミング API を持たない**(バッファ全体を一度に処理する設計)。巨大エントリを扱うにはエントリ全体をメモリに載せるか mmap が必要。メモリ上限の設計とセットで検討する。ライセンスは MIT。
|
|
155
|
+
- **CRC-32**: 自前の SIMD 実装より、libdeflate の `libdeflate_crc32`(CPU 機能に応じた高速版を内部で選択)を使う方が保守コストが低い。
|
|
156
|
+
- **mmap**: POSIX と Windows で API が異なる。Windows 対応を続けるなら抽象化が必要(rufio は Windows 対応の修正を継続的に入れている)。
|
|
157
|
+
- **Zig のバージョン固定**: 既存 `build.zig` は `addExecutable(.{ .root_source_file = ... })` の書式。Zig はリリースごとにビルド API・標準ライブラリ(`std.compress.flate` 含む)が変わっているため、採用前に対象バージョンを決めて動作確認する必要がある(本検討では未確認)。
|
|
158
|
+
- **圧縮率**: 構想メモの通り DEFLATE は 32KB 窓・エントリ単位圧縮なので 7z には勝てない。比較軸は速度と OS 標準互換性に絞る。
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 6. 要判断事項
|
|
163
|
+
1. **独立 Gem 化のタイミング**: rufio 内で安定させてからか、`fast_zip` として切り出すか
|
|
164
|
+
|
|
165
|
+
## 7. 未確認事項
|
|
166
|
+
- Zig 版は Linux x86_64(このコンテナ)でのみ実行・計測した。macOS / Windows / aarch64 向けはクロスコンパイルとバイナリ形式・エクスポートの検証(`rake native:check`)までで、実機では動かしていない。特に Windows のファイル操作(`CreateFileW` / 位置指定 `ReadFile`)は未実行
|
|
167
|
+
- 計測は 1 環境・1 種類のデータのみ。CPU 数やストレージによって倍率は変わる
|
data/info/help.md
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
| `m` | Move selected items to current directory |
|
|
49
49
|
| `c` | Copy selected items to current directory |
|
|
50
50
|
| `x` | Delete selected items |
|
|
51
|
+
| `z` | With selected items: extract when all are `.zip` files (each into `<name>/`), otherwise compress them into one `.zip` (runs as a job). Without a selection, `z` opens zoxide history |
|
|
51
52
|
|
|
52
53
|
### Filtering & Search
|
|
53
54
|
|
|
@@ -132,6 +133,7 @@ Press `B` then select script path management to:
|
|
|
132
133
|
| `?` | Enter help mode (this mode) |
|
|
133
134
|
| `ESC` | Exit current mode |
|
|
134
135
|
| `q` | Quit rufio |
|
|
136
|
+
| `T` | Quit rufio and cd the shell to the current directory (requires `rufio --shell-init`) |
|
|
135
137
|
|
|
136
138
|
### Help Mode
|
|
137
139
|
|
data/info/keybindings.md
CHANGED
|
@@ -33,6 +33,7 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
33
33
|
| `m` | Move selected items to current directory |
|
|
34
34
|
| `c` | Copy selected items to current directory |
|
|
35
35
|
| `x` | Delete selected items |
|
|
36
|
+
| `z` | Extract selected `.zip` files / compress selected items into a `.zip` |
|
|
36
37
|
|
|
37
38
|
## Filtering & Search
|
|
38
39
|
|
|
@@ -50,7 +51,7 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
50
51
|
| `B` | Bookmark menu (view/add/remove/script paths) |
|
|
51
52
|
| `0` | Jump to startup directory |
|
|
52
53
|
| `1-9` | Jump to bookmark 1-9 |
|
|
53
|
-
| `z` | Navigate using zoxide history |
|
|
54
|
+
| `z` | Navigate using zoxide history (when nothing is selected) |
|
|
54
55
|
|
|
55
56
|
## Command Mode & Tool Runtime
|
|
56
57
|
|
|
@@ -69,6 +70,7 @@ Complete reference of all keyboard shortcuts in **rufio**.
|
|
|
69
70
|
| `?` | Enter help mode |
|
|
70
71
|
| `ESC` | Exit current mode (help/filter/command) |
|
|
71
72
|
| `q` | Quit rufio |
|
|
73
|
+
| `T` | Quit rufio and cd the shell to the current directory (requires `rufio --shell-init`) |
|
|
72
74
|
|
|
73
75
|
---
|
|
74
76
|
|
data/info/welcome.md
CHANGED
|
@@ -24,7 +24,7 @@ A TUI file manager that executes and coordinates tools and scripts from files.
|
|
|
24
24
|
| `s` | Search with fzf |
|
|
25
25
|
| `b` | Add bookmark |
|
|
26
26
|
| `B` | Bookmark menu |
|
|
27
|
-
| `z` | zoxide history |
|
|
27
|
+
| `z` | zoxide history / extract or compress selected items |
|
|
28
28
|
| `:` | Command mode |
|
|
29
29
|
| `J` | Job mode |
|
|
30
30
|
| `?` | Help mode |
|