@@ -143,7 +143,32 @@ const createEmptyPane = (id?: string): SftpPane => ({
143143 filter : "" ,
144144} ) ;
145145
146- export const useSftpState = ( hosts : Host [ ] , keys : SSHKey [ ] , identities : Identity [ ] ) => {
146+ // File watch event types
147+ export interface FileWatchSyncedEvent {
148+ watchId : string ;
149+ localPath : string ;
150+ remotePath : string ;
151+ bytesWritten : number ;
152+ }
153+
154+ export interface FileWatchErrorEvent {
155+ watchId : string ;
156+ localPath : string ;
157+ remotePath : string ;
158+ error : string ;
159+ }
160+
161+ export interface SftpStateOptions {
162+ onFileWatchSynced ?: ( event : FileWatchSyncedEvent ) => void ;
163+ onFileWatchError ?: ( event : FileWatchErrorEvent ) => void ;
164+ }
165+
166+ export const useSftpState = (
167+ hosts : Host [ ] ,
168+ keys : SSHKey [ ] ,
169+ identities : Identity [ ] ,
170+ options ?: SftpStateOptions
171+ ) => {
147172 // Multi-tab state: left and right sides each have multiple tabs
148173 const [ leftTabs , setLeftTabs ] = useState < SftpSideTabs > ( {
149174 tabs : [ ] ,
@@ -540,6 +565,29 @@ export const useSftpState = (hosts: Host[], keys: SSHKey[], identities: Identity
540565 } ;
541566 } , [ ] ) ;
542567
568+ // Listen for file watch events (auto-sync feature)
569+ useEffect ( ( ) => {
570+ const bridge = netcattyBridge . get ( ) ;
571+ if ( ! bridge ?. onFileWatchSynced || ! bridge ?. onFileWatchError ) return ;
572+
573+ const unsubscribeSynced = bridge . onFileWatchSynced ( ( payload : FileWatchSyncedEvent ) => {
574+ options ?. onFileWatchSynced ?.( payload ) ;
575+ } ) ;
576+
577+ const unsubscribeError = bridge . onFileWatchError ( ( payload : FileWatchErrorEvent ) => {
578+ options ?. onFileWatchError ?.( payload ) ;
579+ } ) ;
580+
581+ return ( ) => {
582+ try {
583+ unsubscribeSynced ?.( ) ;
584+ unsubscribeError ?.( ) ;
585+ } catch {
586+ // ignore cleanup errors
587+ }
588+ } ;
589+ } , [ options ] ) ;
590+
543591 // Track if initial auto-connect has been done
544592 const initialConnectDoneRef = useRef ( false ) ;
545593
@@ -2604,8 +2652,16 @@ export const useSftpState = (hosts: Host[], keys: SSHKey[], identities: Identity
26042652 ) ;
26052653
26062654 // Download file to temp directory and open with external application
2655+ // If enableWatch is true and the file is remote, starts watching the temp file for changes
2656+ // Returns { localTempPath, watchId } if watch was started, otherwise just { localTempPath }
26072657 const downloadToTempAndOpen = useCallback (
2608- async ( side : "left" | "right" , remotePath : string , fileName : string , appPath : string ) : Promise < void > => {
2658+ async (
2659+ side : "left" | "right" ,
2660+ remotePath : string ,
2661+ fileName : string ,
2662+ appPath : string ,
2663+ options ?: { enableWatch ?: boolean }
2664+ ) : Promise < { localTempPath : string ; watchId ?: string } > => {
26092665 const pane = getActivePane ( side ) ;
26102666 if ( ! pane ?. connection ) {
26112667 throw new Error ( "No connection available" ) ;
@@ -2617,9 +2673,9 @@ export const useSftpState = (hosts: Host[], keys: SSHKey[], identities: Identity
26172673 }
26182674
26192675 if ( pane . connection . isLocal ) {
2620- // For local files, just open directly
2676+ // For local files, just open directly (no watching needed)
26212677 await bridge . openWithApplication ( remotePath , appPath ) ;
2622- return ;
2678+ return { localTempPath : remotePath } ;
26232679 }
26242680
26252681 const sftpId = sftpSessionsRef . current . get ( pane . connection . id ) ;
@@ -2628,10 +2684,42 @@ export const useSftpState = (hosts: Host[], keys: SSHKey[], identities: Identity
26282684 }
26292685
26302686 // Download to temp directory
2687+ console . log ( "[SFTP] Downloading file to temp" , { sftpId, remotePath, fileName } ) ;
26312688 const localTempPath = await bridge . downloadSftpToTemp ( sftpId , remotePath , fileName ) ;
2689+ console . log ( "[SFTP] File downloaded to temp" , { localTempPath } ) ;
2690+
2691+ // Register temp file for cleanup when SFTP session closes (regardless of auto-sync setting)
2692+ if ( bridge . registerTempFile ) {
2693+ try {
2694+ await bridge . registerTempFile ( sftpId , localTempPath ) ;
2695+ } catch ( err ) {
2696+ console . warn ( "[SFTP] Failed to register temp file for cleanup:" , err ) ;
2697+ }
2698+ }
26322699
26332700 // Open with the selected application
2701+ console . log ( "[SFTP] Opening with application" , { localTempPath, appPath } ) ;
26342702 await bridge . openWithApplication ( localTempPath , appPath ) ;
2703+ console . log ( "[SFTP] Application launched" ) ;
2704+
2705+ // Start file watching if enabled
2706+ let watchId : string | undefined ;
2707+ console . log ( "[SFTP] Auto-sync enabled check" , { enableWatch : options ?. enableWatch , hasStartFileWatch : ! ! bridge . startFileWatch } ) ;
2708+ if ( options ?. enableWatch && bridge . startFileWatch ) {
2709+ try {
2710+ console . log ( "[SFTP] Starting file watch" , { localTempPath, remotePath, sftpId } ) ;
2711+ const result = await bridge . startFileWatch ( localTempPath , remotePath , sftpId ) ;
2712+ watchId = result . watchId ;
2713+ console . log ( "[SFTP] File watch started successfully" , { watchId, localTempPath, remotePath } ) ;
2714+ } catch ( err ) {
2715+ console . warn ( "[SFTP] Failed to start file watch:" , err ) ;
2716+ // Don't fail the operation if watching fails
2717+ }
2718+ } else {
2719+ console . log ( "[SFTP] File watching not enabled or not available" ) ;
2720+ }
2721+
2722+ return { localTempPath, watchId } ;
26352723 } ,
26362724 [ getActivePane ] ,
26372725 ) ;
0 commit comments