Lompat ke konten
dokumentasi.id
Commit 0da916f
v1
Star0

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

10

SFTP Dual-Pane dan Editor Remote

Halaman ini membahas panel SFTP dua kolom di aplikasi dan editor teks yang membaca serta menulis berkas langsung ke server. Seluruh IO remote ditangani modul src-tauri/src/sftp/mod.rs lewat satu SftpSession dari russh-sftp per session_id, sementara komponen React di src/components/sftp/ hanya memanggil command Tauri.

Sesi SFTP per session_id

SftpManager menyimpan Mutex<HashMap<String, Arc<SftpConnection>>>, dan setiap SftpConnection memegang session_id, host_id, sftp, serta _handle yang menjaga Handle<SshClientHandler> tetap hidup agar koneksi tidak tertutup src-tauri/src/sftp/mod.rs:27-37. Helper connection meng-clone Arc keluar dari map sebelum IO dimulai; komentar di kode menyebut alasan eksplisitnya, yaitu agar transfer lambat tidak menahan lock manager dan menyerialkan semua panggilan SFTP lain src-tauri/src/sftp/mod.rs:46-55. Bila session_id tidak dikenal, hasilnya error "SFTP session not found. Please connect first." src-tauri/src/sftp/mod.rs:46-55.

connect membuka TcpStream, memanggil client::connect_stream, mengautentikasi password atau private key, membuka channel, lalu request_subsystem(true, "sftp") dan SftpSession::new(channel.into_stream()). Path awal diambil dari canonicalize(".") dan jatuh ke / bila gagal src-tauri/src/sftp/mod.rs:100-130. Di sisi command, sftp_connect mengambil Host dari database dan menyelesaikan auth lewat resolve_host_auth sebelum meneruskan ke SftpManager::connect src-tauri/src/commands/mod.rs:962-988.

graph LR
  SftpView["SftpView (dual pane)"] --> FilePane["FilePane"]
  SftpView --> FileEditorModal["FileEditorModal"]
  FilePane --> Api["api.ts invoke"]
  FileEditorModal --> Api
  Api --> Cmd["commands: sftp_*"]
  Cmd --> Mgr["SftpManager.connections"]
  Mgr --> Sess["SftpConnection + SftpSession"]
  Sess --> Remote["SFTP server"]
  style Mgr fill:#336791,color:#fff
  style Sess fill:#336791,color:#fff
  style Api fill:#f59e0b,color:#000
  style FilePane fill:#8b5cf6,color:#fff
  style FileEditorModal fill:#8b5cf6,color:#fff
  style Remote fill:#2d7d46,color:#fff
  style Cmd fill:#2d7d46,color:#fff
%% fig: Lapisan SFTP dari pane sampai server
%% desc: Komponen React memanggil command lewat api.ts, command meneruskan ke SftpManager, dan sesi russh-sftp menyelesaikan IO ke server.
Fig. 1: Lapisan SFTP dari pane sampai serverKomponen React memanggil command lewat api.ts, command meneruskan ke SftpManager, dan sesi russh-sftp menyelesaikan IO ke server.

Frontend membuat sendiri sessionId berformat sftp-<timestamp>-<random> pada connectRemote, memanggil api.connectSftp(host.id, sessionId), lalu mengambil listing direktori awal dengan api.listSftp src/stores/useSftpStore.ts:558-589. Pemetaan nama command ke binding invoke ada di api.ts, termasuk sftp_stat_many dan pasangan lokalnya local_stat_many src/lib/api.ts:291-315.

Daftar isi direktori dan operasi metadata

list membaca read_dir, melewati entri . dan .., lalu membentuk FileEntry berisi name, path, is_dir, size, modified, dan permissions. Hasilnya diurutkan dengan direktori lebih dulu, kemudian alfabetis tanpa membedakan huruf besar-kecil src-tauri/src/sftp/mod.rs:132-182. Operasi tulis metadata lainnya adalah mkdir, delete (memilih remove_dir atau remove_file berdasarkan is_dir), dan rename src-tauri/src/sftp/mod.rs:184-213. disconnect hanya menghapus entri dari map tanpa menutup channel secara eksplisit; Arc yang tersisa menentukan kapan koneksi dilepas src-tauri/src/sftp/mod.rs:430-434.

stat_many melakukan stat per path untuk pemeriksaan konflik transfer. Path yang tidak ada atau gagal di-stat dipetakan ke None, bukan menggagalkan seluruh batch, sehingga satu entri tidak terbaca tidak memblokir transfer src-tauri/src/sftp/mod.rs:218-240.

Upload dan download berkas

upload_file membaca ukuran berkas lokal, membuka berkas remote dengan OpenFlags::CREATE | TRUNCATE | WRITE, lalu menyalin per chunk 64 * 1024 byte (64 KB). Setiap chunk menaikkan bytes_done, dan on_progress hanya dipanggil bila sudah lewat Duration::from_millis(100); komentar di kode menyebut target sekitar 10 pembaruan per detik agar channel IPC tidak banjir. Di akhir ada flush, shutdown, dan satu panggilan progres penutup on_progress(total_bytes, total_bytes) src-tauri/src/sftp/mod.rs:242-304.

download_file adalah cerminannya: ukuran total diambil dari metadata remote (jatuh ke 0 bila tidak tersedia), berkas remote dibuka dengan OpenFlags::READ, berkas lokal dibuat lewat tokio::fs::File::create, dan chunk 64 KB ditulis dengan ambang emisi 100 ms yang sama src-tauri/src/sftp/mod.rs:306-362.

⚠️ upload_file membuka berkas tujuan dengan TRUNCATE. Berkas remote dengan nama sama akan dipotong lebih dulu, jadi pemeriksaan konflik di frontend adalah satu-satunya pengaman sebelum isi lama hilang src-tauri/src/sftp/mod.rs:260-267.

Editor teks remote

read_text_file menolak berkas di atas 5 * 1024 * 1024 byte dengan pesan "File is too large to open in the inline editor (max 5 MB)", lalu membaca seluruh isi dan mengubahnya ke String. Isi biner ditolak dengan pesan "File contains binary data and cannot be displayed as plain text" src-tauri/src/sftp/mod.rs:364-396. write_text_file membuka berkas dengan CREATE | TRUNCATE | WRITE, menulis seluruh content, lalu flush dan shutdown src-tauri/src/sftp/mod.rs:398-428.

FileEditorModal memuat isi lewat api.readSftpFile(sessionId, file.path) untuk berkas remote atau api.readLocalFile untuk berkas lokal, dan menyimpan salinan awal di originalContent src/components/sftp/FileEditorModal.tsx:31-66. Status isDirty dihitung dari content !== originalContent; handleSave memanggil api.writeSftpFile atau api.writeLocalFile, mengubah originalContent, menyalakan saveSuccess selama 2500 ms, dan memicu onSaved src/components/sftp/FileEditorModal.tsx:68-90. Menutup editor saat isDirty memunculkan confirm("You have unsaved changes. Are you sure you want to close?") src/components/sftp/FileEditorModal.tsx:117-124.

stateDiagram-v2
  [*] --> loading
  loading --> ready: readSftpFile / readLocalFile
  loading --> error: String(e)
  ready --> dirty: content != originalContent
  dirty --> saving: handleSave (Ctrl+S)
  saving --> saved: writeSftpFile sukses
  saving --> error: String(e)
  saved --> ready: setTimeout 2500 ms
  error --> saving: percobaan ulang
  style loading fill:#f59e0b,color:#000
  style ready fill:#2d7d46,color:#fff
  style dirty fill:#8b5cf6,color:#fff
  style saved fill:#2d7d46,color:#fff
  style error fill:#dc2626,color:#fff
  style saving fill:#336791,color:#fff
%% fig: Siklus keadaan FileEditorModal
%% desc: Modal memuat isi berkas, menandai perubahan yang belum disimpan, lalu berpindah ke saved atau error setelah handleSave.
Fig. 2: Siklus keadaan FileEditorModalModal memuat isi berkas, menandai perubahan yang belum disimpan, lalu berpindah ke saved atau error setelah handleSave.

Sisi lokal dan batas angka

Pane kiri memakai helper lokal di modul yang sama: list_local_directory menyusun FileEntry dengan permissions: None dan urutan direktori-dulu yang identik dengan versi remote src-tauri/src/sftp/mod.rs:438-476, sedangkan read_local_text_file memakai batas 5 MB yang sama dan write_local_text_file menulis lewat fs::write src-tauri/src/sftp/mod.rs:510-516. Command local_stat_many, local_home_dir, local_list, local_read_file, dan local_write_file membungkus helper itu; yang menyentuh disk dijalankan lewat run_blocking agar tidak menahan thread utama src-tauri/src/commands/mod.rs:1126-1130 dan src-tauri/src/commands/mod.rs:1146-1168.

NilaiLokasiArti
64 * 1024upload_file, download_fileukuran buffer chunk 64 KB
Duration::from_millis(100)upload_file, download_filejeda minimum antar event progres
5 * 1024 * 1024read_text_file, read_local_text_filebatas berkas untuk editor inline
MAX_TRAVERSAL_DEPTH = 32useSftpStorekedalaman maksimum penelusuran plan transfer
ROW_HEIGHT = 30, OVERSCAN_ROWS = 8FilePaneukuran baris dan overscan virtual window
Math.min(lineCount, 1000)FileEditorModaljumlah nomor baris yang dirender

Sumber baris konstanta transfer ada di src-tauri/src/sftp/mod.rs:271-274 dan src-tauri/src/sftp/mod.rs:335-336, sedangkan kedalaman penelusuran didefinisikan di src/stores/useSftpStore.ts:85-150 dan geometri baris di src/components/sftp/FilePane.tsx:364-372.

Pelaporan progres sftp-transfer-progress

Command sftp_upload dan sftp_download membungkus upload_file/download_file dengan closure yang memancarkan event sftp-transfer-progress berisi TransferProgressEvent src-tauri/src/commands/mod.rs:1028-1037 dan src-tauri/src/commands/mod.rs:1039-1085. Struct itu memakai #[serde(rename_all = "camelCase")], sehingga frontend menerima sessionId, path, bytesDone, dan bytesTotal src-tauri/src/commands/mod.rs:1028-1037.

Field eventTipeIsi
sessionIdstringsession_id sesi SFTP yang memancarkan progres
pathstringlocal_path untuk upload, remote_path untuk download
bytesDonenumberbyte yang sudah dipindahkan
bytesTotalnumberukuran total berkas yang sedang dipindah

Di frontend, listener didaftarkan sekali di level modul store dan meneruskan payload ke applyTransferBytes src/stores/useSftpStore.ts:742-746. Fungsi itu mengabaikan event dari sesi lain, mengabaikan event untuk berkas yang bukan currentFile, lalu memakai Math.max(prog.currentFile.size, event.bytesTotal) karena stat langsung dianggap lebih baru daripada ukuran dari listing src/stores/useSftpStore.ts:719-738.

Plan transfer, konflik, dan dual pane

buildTransferPlan menelusuri pohon yang dipilih secara rekursif untuk arah upload (memakai api.listLocal) atau download (memakai api.listSftp), mencatat direktori dan berkas ke daftar datar, dan menyimpan path yang sudah dikunjungi agar dua root yang tumpang tindih tidak menulis tujuan yang sama dua kali src/stores/useSftpStore.ts:85-150. runTransfer lalu memanggil resolveTransferConflicts sebelum menghitung total byte, sehingga bilah progres hanya menghitung berkas yang benar-benar akan ditulis src/stores/useSftpStore.ts:329-342. Berkas dipindahkan berurutan; setiap berkas mengatur currentFile di store, memanggil api.uploadSftp atau api.downloadSftp, dan menambah doneCount serta completedBytes src/stores/useSftpStore.ts:378-402.

Urutan lengkap satu upload, termasuk pelaporan progres per chunk, tampak pada diagram berikut.

sequenceDiagram
  participant U as User
  participant P as FilePane
  participant St as useSftpStore
  participant C as commands::sftp_upload
  participant M as SftpManager
  participant S as SFTP server
  participant O as TransferProgressOverlay
  U->>P: klik ArrowRight (transferLocal)
  P->>St: uploadSelected(entries)
  St->>St: buildTransferPlan + resolveTransferConflicts
  loop setiap berkas (berurutan)
    St->>C: invoke sftp_upload
    C->>M: upload_file(local_path, remote_path)
    M->>S: open_with_flags CREATE|TRUNCATE|WRITE
    loop chunk 64 KB
      M->>S: write_all
      M-->>C: on_progress bila sudah 100 ms
      C-->>St: emit sftp-transfer-progress
      St-->>O: applyTransferBytes
    end
    M->>S: flush lalu shutdown
  end
  St->>P: navigateRemote(remotePath)
%% fig: Alur upload beserta pelaporan progres byte
%% desc: Store menyusun plan lalu command sftp_upload meneruskan ke SftpManager; setiap chunk memicu event sftp-transfer-progress yang memperbarui overlay.
Fig. 3: Alur upload beserta pelaporan progres byteStore menyusun plan lalu command sftp_upload meneruskan ke SftpManager; setiap chunk memicu event sftp-transfer-progress yang memperbarui overlay.

Tampilan dua kolom dirakit SftpView: FilePane kiri berjudul Local Machine, dua tombol tengah untuk upload dan download, dan FilePane kanan berlabel host remote. Tombol upload dinonaktifkan bila tidak ada berkas lokal terpilih, remoteSessionId kosong, atau transferring bernilai benar src/components/sftp/SftpView.tsx:222-293. Klik ganda pada baris memanggil onNavigate untuk direktori dan onOpenFile untuk berkas, sehingga berkas langsung dibuka di FileEditorModal src/components/sftp/FilePane.tsx:337-346. Modal editor dipasang di akhir SftpView dengan sessionId sesi remote dan onSaved yang menyegarkan pane asal src/components/sftp/SftpView.tsx:298-313.

⚠️ local_delete untuk folder memakai std::fs::remove_dir_all, jadi satu konfirmasi yang salah bisa menghapus seluruh isi direktori lokal tanpa jalan kembali src-tauri/src/commands/mod.rs:1178-1188.