22// SPDX-License-Identifier: GPL-3.0-or-later
33
44
5- using Renci . SshNet ;
6- using Renci . SshNet . Common ;
7- using Renci . SshNet . Sftp ;
8- using SnapX . Core . Upload . BaseUploaders ;
5+ using System . Runtime . InteropServices ;
96using SnapX . Core . Utils ;
7+ using Tmds . Ssh ;
108
119namespace SnapX . Core . Upload . File ;
1210
13- public sealed class SFTP : FileUploader , IDisposable
11+ public sealed class SFTP : FtpBase
1412{
1513 public FTPAccount Account { get ; private set ; }
1614
1715 public bool IsValidAccount => ( ! string . IsNullOrEmpty ( Account . Keypath ) && System . IO . File . Exists ( Account . Keypath ) ) || ! string . IsNullOrEmpty ( Account . Password ) ;
1816
19- public bool IsConnected => client != null && client . IsConnected ;
17+ public override bool IsConnected => client != null ;
2018
21- private SftpClient client ;
19+ private SftpClient ? client ;
2220
23- public SFTP ( FTPAccount account )
21+ public SFTP ( FTPAccount account ) : base ( account )
2422 {
2523 Account = account ;
2624 }
@@ -38,8 +36,8 @@ public override UploadResult Upload(Stream stream, string? fileName)
3836 try
3937 {
4038 IsUploading = true ;
41-
42- bool uploadResult = UploadStream ( stream , path , true ) ;
39+ if ( ! IsConnected ) Connect ( ) ;
40+ bool uploadResult = UploadStream ( stream , path , true ) . GetAwaiter ( ) . GetResult ( ) ;
4341
4442 if ( uploadResult && ! StopUploadRequested && ! IsError )
4543 {
@@ -72,55 +70,57 @@ public override void StopUpload()
7270 }
7371 }
7472 }
75-
76- public bool Connect ( )
73+ public string ConstructSshDestination ( string user , string host , int port = 22 )
74+ {
75+ return $ "{ user } @{ host } :{ port } ";
76+ }
77+ public override bool Connect ( )
7778 {
78- if ( client == null )
79+ var sshDestination = ConstructSshDestination ( Account . Username , Account . Host , Account . Port ) ;
80+
81+ var settings = new SshClientSettings ( sshDestination )
7982 {
80- if ( ! string . IsNullOrEmpty ( Account . Keypath ) )
81- {
82- if ( ! System . IO . File . Exists ( Account . Keypath ) )
83- {
84- throw new FileNotFoundException ( "Key file does not exist." , Account . Keypath ) ;
85- }
86-
87- PrivateKeyFile keyFile ;
88-
89- if ( string . IsNullOrEmpty ( Account . Passphrase ) )
90- {
91- keyFile = new PrivateKeyFile ( Account . Keypath ) ;
92- }
93- else
94- {
95- keyFile = new PrivateKeyFile ( Account . Keypath , Account . Passphrase ) ;
96- }
97-
98- client = new SftpClient ( Account . Host , Account . Port , Account . Username , keyFile ) ;
99- }
100- else if ( ! string . IsNullOrEmpty ( Account . Password ) )
83+ AutoConnect = true ,
84+ AutoReconnect = true ,
85+ } ;
86+ string ? keyPath = Account . Keypath ;
87+
88+ if ( string . IsNullOrWhiteSpace ( keyPath ) )
89+ {
90+ var home = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows )
91+ ? Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile )
92+ : Environment . GetEnvironmentVariable ( "HOME" ) ?? string . Empty ;
93+
94+ var defaultKeys = new [ ]
10195 {
102- client = new SftpClient ( Account . Host , Account . Port , Account . Username , Account . Password ) ;
103- }
96+ "id_ed25519" ,
97+ "id_rsa" ,
98+ "id_ecdsa" ,
99+ "id_dsa"
100+ } ;
104101
105- if ( client != null )
102+ foreach ( var key in defaultKeys )
106103 {
107- client . BufferSize = ( uint ) BufferSize ;
104+ var path = Path . Combine ( home , ".ssh" , key ) ;
105+ if ( ! System . IO . File . Exists ( path ) ) continue ;
106+ keyPath = path ;
107+ break ;
108108 }
109109 }
110+ if ( ! string . IsNullOrEmpty ( keyPath ) ) settings . Credentials . Add ( new PrivateKeyCredential ( keyPath , Account . Passphrase ) ) ;
111+ if ( ! string . IsNullOrEmpty ( Account . Password ) ) settings . Credentials . Add ( new PasswordCredential ( Account . Password ) ) ;
112+ settings . Credentials . Add ( new SshAgentCredentials ( ) ) ;
110113
111- if ( client != null && ! client . IsConnected )
112- {
113- client . Connect ( ) ;
114- }
115-
116- return IsConnected ;
114+ client = new SftpClient ( settings ) ;
115+ return client is not null ;
117116 }
118117
119- public void Disconnect ( )
118+ public override void Disconnect ( )
120119 {
121- if ( client != null && client . IsConnected )
120+ if ( client != null )
122121 {
123- client . Disconnect ( ) ;
122+ client . Dispose ( ) ;
123+ client = null ;
124124 }
125125 }
126126
@@ -130,9 +130,9 @@ public void ChangeDirectory(string? path, bool autoCreateDirectory = false)
130130 {
131131 try
132132 {
133- client . ChangeDirectory ( path ) ;
133+ client . GetDirectory ( path ) ;
134134 }
135- catch ( SftpPathNotFoundException ) when ( autoCreateDirectory )
135+ catch ( SftpException ) when ( autoCreateDirectory )
136136 {
137137 CreateDirectory ( path , true ) ;
138138 ChangeDirectory ( path ) ;
@@ -142,80 +142,66 @@ public void ChangeDirectory(string? path, bool autoCreateDirectory = false)
142142
143143 public bool DirectoryExists ( string ? path )
144144 {
145- if ( Connect ( ) )
145+ if ( string . IsNullOrEmpty ( path ) )
146+ return false ;
147+
148+ try
146149 {
147- return client . Exists ( path ) ;
150+ var attributes = client . GetAttributesAsync ( path , followLinks : true , filter : null ) . ConfigureAwait ( false ) . GetAwaiter ( ) . GetResult ( ) ;
151+ return attributes is { FileType : UnixFileType . Directory } ;
152+ }
153+ catch ( SftpException )
154+ {
155+ return false ;
156+ }
157+ catch ( Exception )
158+ {
159+ return false ;
148160 }
149-
150- return false ;
151161 }
152162
153- public void CreateDirectory ( string ? path , bool createMultiDirectory = false )
163+ public async void CreateDirectory ( string ? path , bool createMultiDirectory = false )
154164 {
155165 if ( Connect ( ) )
156166 {
157167 try
158168 {
159- client . CreateDirectory ( path ) ;
169+ await client . CreateDirectoryAsync ( path ) ;
160170
161171 DebugHelper . WriteLine ( $ "SFTP directory created: { path } ") ;
162172 }
163- catch ( SftpPathNotFoundException ) when ( createMultiDirectory )
164- {
165- CreateMultiDirectory ( path ) ;
166- }
167- catch ( SftpPermissionDeniedException )
173+ catch ( SftpException e ) when ( createMultiDirectory )
168174 {
175+ if ( e . Error == SftpError . PermissionDenied ) return ;
176+ await client . CreateDirectoryAsync ( path , true ) ;
177+
169178 }
170179 }
171180 }
172181
173- public List < string ? > CreateMultiDirectory ( string ? path )
182+ private async Task < bool > UploadStream ( Stream stream , string ? remotePath , bool autoCreateDirectory = false )
174183 {
175- List < string ? > directoryList = [ ] ;
176-
177- List < string ? > paths = URLHelpers . GetPaths ( path ) ;
178-
179- foreach ( string ? directory in paths )
184+ try
180185 {
181- if ( ! DirectoryExists ( directory ) )
182- {
183- CreateDirectory ( directory ) ;
184- directoryList . Add ( directory ) ;
185- }
186+ await using var remoteStream = await client . OpenOrCreateFileAsync ( remotePath , FileAccess . Write , null ) . ConfigureAwait ( false ) ;
187+ return await TransferDataAsync ( stream , remoteStream ) ;
186188 }
187-
188- return directoryList ;
189- }
190-
191- private bool UploadStream ( Stream stream , string ? remotePath , bool autoCreateDirectory = false )
192- {
193- if ( Connect ( ) )
189+ catch ( SftpException e ) when ( autoCreateDirectory )
194190 {
195- try
196- {
197- using ( SftpFileStream sftpStream = client . Create ( remotePath ) )
198- {
199- return TransferData ( stream , sftpStream ) ;
200- }
201- }
202- catch ( SftpPathNotFoundException ) when ( autoCreateDirectory )
203- {
204- // Happens when directory not exist, create directory and retry uploading
205-
206- CreateDirectory ( URLHelpers . GetDirectoryPath ( remotePath ) , true ) ;
207- return UploadStream ( stream , remotePath ) ;
208- }
209- catch ( NullReferenceException )
210- {
211- // Happens when disconnect while uploading
212- }
191+ var code = e . Error ;
192+ if ( code is not SftpError . NoSuchFile ) return false ;
193+ CreateDirectory ( URLHelpers . GetDirectoryPath ( remotePath ) , true ) ;
194+ return await UploadStream ( stream , remotePath ) . ConfigureAwait ( false ) ;
195+ }
196+ catch ( Exception ex )
197+ {
198+ DebugHelper . WriteException ( ex ) ;
199+ return false ;
213200 }
214-
215- return false ;
216201 }
217202
218- public void Dispose ( )
203+
204+ public override void Dispose ( )
219205 {
220206 if ( client != null )
221207 {
@@ -229,4 +215,17 @@ public void Dispose()
229215 }
230216 }
231217 }
218+
219+ public List < string ? > CreateMultiDirectory ( string ? remotePath )
220+ {
221+ List < string ? > paths = URLHelpers . GetPaths ( remotePath ) ;
222+
223+ foreach ( string ? path in paths )
224+ {
225+ CreateDirectory ( path ) ;
226+ DebugHelper . WriteLine ( $ "FTP directory created: { path } ") ;
227+ }
228+
229+ return paths ;
230+ }
232231}
0 commit comments