-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsftpmanager.cpp
More file actions
450 lines (405 loc) · 21.7 KB
/
sftpmanager.cpp
File metadata and controls
450 lines (405 loc) · 21.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
#include "sftpmanager.h"
#include <QDebug> // 包含这个头文件,方便我们进行调试输出
#include <QObject>
#include <QThread> //添加sleep()测试延迟
#include <QDir>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
// 辅助函数:将数字权限转换为 rwx 字符串格式
QString permsToString(unsigned int perms)
{
QString str;
str += (LIBSSH2_SFTP_S_ISDIR(perms)) ? 'd' : '-';
str += (perms & LIBSSH2_SFTP_S_IRUSR) ? 'r' : '-';
str += (perms & LIBSSH2_SFTP_S_IWUSR) ? 'w' : '-';
str += (perms & LIBSSH2_SFTP_S_IXUSR) ? 'x' : '-';
str += (perms & LIBSSH2_SFTP_S_IRGRP) ? 'r' : '-';
str += (perms & LIBSSH2_SFTP_S_IWGRP) ? 'w' : '-';
str += (perms & LIBSSH2_SFTP_S_IXGRP) ? 'x' : '-';
str += (perms & LIBSSH2_SFTP_S_IROTH) ? 'r' : '-';
str += (perms & LIBSSH2_SFTP_S_IWOTH) ? 'w' : '-';
str += (perms & LIBSSH2_SFTP_S_IXOTH) ? 'x' : '-';
return str;
}
SftpManager::SftpManager(QObject *parent)
: QObject{parent}, //冒号:后面的部分叫做"初始化列表"
m_socket(INVALID_SOCKET),
m_session(nullptr),
m_sftpSession(nullptr) //上述三行的意思是对象刚"出生时",立即将它的成员变量m_socket,m_session等设置为"无效"或"空"的初始状态
{
#ifdef WIN32
WSADATA wsadata;//声明一个WSADATA类型的名为wsadata的结构体变量,用来返回Windows Sockets事项的详细信息
WSAStartup(MAKEWORD(2, 2), &wsadata);//参数1:版本号(请求使用2.2版本的WInsock),参数2:将wsadata变量的内存地址传递给函数,以便函数将版本信息填写进去
#endif
libssh2_init(0); // 初始化 libssh2 库
//小结:创建SftpManager对象时,将所有的内部变量设为空,然后启动并准备好操作系统网络库和libssh2库,为后续的网络连接做好准备
}
SftpManager::~SftpManager()//在对象被销毁时自动调用,任务时逆向地释放掉构造函数申请的所有资源
{
if(m_sftpSession){
libssh2_sftp_shutdown(m_sftpSession);//关闭SFTP的子系统
}
if(m_session){
libssh2_session_disconnect(m_session,"Normal Shutdown");//通知服务器断开
libssh2_session_free(m_session);//释放会话内存
}
#ifdef WIN32
if(m_socket != INVALID_SOCKET){
closesocket(m_socket);//关闭套接字
}
WSACleanup();//清理Windows网络库
#endif
libssh2_exit(); // 释放 libssh2 库资源
qDebug() << "SftpManager destroyed";
//小结:析构函数里的每一步都与构造函数中的准备工作相对应,但是顺序相反
//1.关闭SFTP和SSH会话
//2.关闭Socket:关闭底层的TCP/IP网络连接
//3.清理库
}
// 为 connectToHost 提供一个函数体
void SftpManager::connectToHost(const QString &host, int port, const QString &user, const QString &pass)
{
qDebug() << "Connection attempt will start after a 10-second delay...";
// 人工添加10秒钟的延迟
QThread::sleep(10);
qDebug() << "Delay finished. Starting actual connection...";
//1.创建TCP连接
/*目标:向操作系统申请一个基础的网络通信工具。
比喻:在出发前,拥有一辆可以上路的"汽车"。这是出行的基础
关键函数socket():AF_INET:指定使用IPv4协议 SOCK_STREAM:指定使用TCP这种流式传输 0:系统自动选择协议(即TCP)
错误处理:如果操作系统因为资源耗尽原因,连一辆"汽车"无法供应(INVALID_SOCKET),整个任务开始就失败,直接退出*/
m_socket = socket(AF_INET,SOCK_STREAM,0);
if(m_socket == INVALID_SOCKET){
qDebug() << "Failed to create socket";
return;
}
//2.连接服务器
/*目标:驾驶"汽车",行驶到服务器的IP地址和端口,建立一条原始的,未加密的物理连接
比喻:驾驶"汽车"(m_socket),根据"地址名片"(sin)上的信息,行驶到大使馆的大门口,并成功地停在指定的停车位上。
关键函数 ::connect():
它接收我们创建的 socket 和包含目标地址信息的 sin 结构体作为参数。
这是一个阻塞式调用,程序会在这里等待,直到连接成功建立或因为网络不通、端口错误、防火墙拦截等原因而超时失败。
错误处理: 如果连接失败(返回非零值),我们必须“挂断电话”(closesocket),把“汽车”标记为报废(m_socket = INVALID_SOCKET),然后退出。*/
sockaddr_in sin;//sockaddr_in类型:专门用于存储IPv4网络地址信息的C语言结构体。
sin.sin_family = AF_INET;//设置地址族为AF_INET,即IPv4
sin.sin_port = htons(port);//htons()(主机字节序到网络字节序),因为网络传输规定了一个统一的"网络字节序"标准
QByteArray host_bytes = host.toLatin1();//准备步骤,后续inet_addr函数需要一个C语言风格的字符串(const char*),而host变量是QString,所以需要转换
sin.sin_addr.s_addr = inet_addr(host_bytes.constData());//设置目标的IP地址;inet_addr将十进制字符串转成网络协议中32为二进制整数形式
//.constData()负责从QByteArray中提取出C语言函数所需的const char*指针
//地址标签签写完毕,就可以执行"寄送快递"(即发起连接)的动作
if(::connect(m_socket,(sockaddr*)(&sin),sizeof(sockaddr_in))!=0){//参数1:本地套接字"发货渠道" 参数2:地址标签 参数3:地址标签有多大
qDebug()<<"Failed to connect to host";//输出打印日志
closesocket(m_socket);
m_socket = INVALID_SOCKET;//将套接字成员变量设为一个无效值
return;
}
//3.创建libssh22会话
/*目标:电话已经接通,现在我们要开始“保密会谈”。这一步是为会谈创建一个上下文环境。
比喻:您已经到达大使馆门口,现在您需要走进接待大厅,在前台登记,领取一个临时的**“访客证” (m_session)**。这个访客证将记录您接下来在大使馆内的一切活动。
关键函数: libssh2_session_init():
它不进行任何网络操作,只是在内存中创建一个 LIBSSH2_SESSION 结构体,用于保存会话的所有状态(如加密算法、密钥、认证状态等)。
错误处理: 如果因为内存不足等原因,连一个“访客证”都创建失败(返回 NULL),那么同样需要清理已建立的 TCP 连接并退出。*/
m_session = libssh2_session_init();//创建并初始化一个SSH会话对象
if (!m_session) {
emit error ("Failed to initialize libssh2 session");
closesocket(m_socket);
m_socket = INVALID_SOCKET;
return;
}
// 4. 执行 SSH 握手
/*目标:在原始的 TCP 连接之上,建立一条真正加密的安全通道。
比喻:拿着您的“访客证”,您现在要通过第一道、也是最重要的一道安检。您和安保主管(服务器)需要交换暗号、确认彼此的身份凭证(比如支持的SSH版本)、并商定一种双方都懂的加密语言(算法)。这个过程就是“握手”。
关键函数: libssh2_session_handshake():
这是第一个真正进行加密数据交换的步骤。它通过我们已经建立的 m_socket 通道,与服务器协商加密细节并交换密钥。
我们之前通过 libssh2_session_set_blocking(m_session, 1) 将会话设置为了阻塞模式,所以这个函数会一直等待,直到握手完成或失败。
错误处理: 如果握手失败(例如协议不兼容),说明安全通道无法建立,必须清理所有资源并退出*/
libssh2_session_set_blocking(m_session, 1);//设置会话为阻塞模式
if (libssh2_session_handshake(m_session, m_socket) != 0) {
//m_session:"会议记录本" m_socket:通话线路
//qDebug() << "SSH handshake failed";
emit error ("SSH handshake failed");
// ... (省略了清理代码,为了简洁)
return;
}
// 5. 使用密码进行身份验证
/*目标:向服务器证明“我是谁”以及“我有权限进入”。
比喻:通过安检后,您来到了一个具体的办事窗口。您需要向工作人员(服务器)出示您的最终身份证明——用户名和密码——来证明您有权在这里办理业务。
关键函数: libssh2_userauth_password():
它将用户名和密码通过已经建立好的加密通道安全地发送给服务器进行验证。
错误处理: 如果验证失败(返回非零值),这通常意味着用户名或密码错误。这是最常见的用户级错误。同样,需要清理所有资源并退出。*/
QByteArray user_bytes = user.toUtf8();
QByteArray pass_bytes = pass.toUtf8();
if (libssh2_userauth_password(m_session, user_bytes.constData(), pass_bytes.constData()) != 0) {
//qDebug() << "User authentication failed";
emit error ("User authentication failed");
// ... (省略了清理代码)
return;
}
//===================添加修复代码===========================
//6.初始化SFTP子系统
m_sftpSession = libssh2_sftp_init(m_session);
if(!m_sftpSession){
emit error("Failed to initialize SFTP session");
return;
}
//7. 如果所有步骤都成功了
//qDebug() << "SFTP Connection successful!";
emit connected();
}
//这段代码负责和服务器通信来获取文件列表
void SftpManager::listDirectory(const QModelIndex &parentIndex,const QString &path)
{
//1.前置检查(检查SFTP会话)
//逻辑:在尝试进行任何操作之前,必须先确认已经成功初始化了SFTP子系统会话。
if(!m_sftpSession){
//m_sftpSession 是文件收发室的钥匙
emit error("SFTP session not initialized.");
return;
}
//2.打开远程目录
//逻辑:管理员(SftpManager)拿着"文件收发室的钥匙"(m_sftpSession),走到了指定的"A仓库"(path)门口,然后打开了这个仓库的大门。
/*关键函数:libssh2_sftp_opendir():
用途:通过SFTP协议在远程服务器上打开一个目录。
参数:它需要SFTP会话(m_sftpSession)和打开的目录路径(path).注意path.toStdString().c_str()是将Qt的QString转换为libssh2这个C语言库能理解的const char*字符串
返回值:如果成功打开,回返回一个LIBSSH2_SFTP_HANDLE类型的目录句柄(sftp_handle).
*/
LIBSSH2_SFTP_HANDLE *sftp_handle = libssh2_sftp_opendir(m_sftpSession,path.toStdString().c_str());
//参数详解:m_sftpSession是LIBSSH2_SFTP会话指针,相当于文件收发室钥匙,有这把钥匙才有权利进入区域
//path.toStdString().c_str():要打开的远程目录的完整路径;libssh2是一个C语言库,需要通过.toStdString().c_str()将QString转换为C语言
//LIBSSH2_SFTP_HANDLE类型是由libssh2定义的一个句柄(Handle),相当于门把手,只有握住门把手,才可以执行后续操作,如(libssh2_sftp_readdir_ex)或关上门(libssh2_sftp_closedir)
//整行代码意思:声明一个名为sftp_handle的指针变量,这个变量专门存放"目录门把手"(LIBSSH2_SFTP_HANDLE类型)。然后调用libssh2_sftp_opendir函数去"开门",并将返回的"门把手"赋值给sftp_handle
if(!sftp_handle){
emit error("Failed to open remote directory:" + path);
return;
}
//3.循环读取目录内容
/*逻辑:管理员现在站在仓库门口,拿着一个清单板,开始一项一项地清点仓库里地货箱
关键函数:libssh2_sftp_readdir_ex():
用途: 这是 libssh2 中用来读取目录条目的核心函数。每调用一次,它就会读取目录中的下一项。
参数: sftp_handle (目录“门把手”),buffer (用于存放读取到的文件名),&attrs (一个指向 LIBSSH2_SFTP_ATTRIBUTES 结构体的指针,
函数会把读取到的文件所有属性,如大小、权限、时间等,都填写到这个结构体里)。
返回值: 如果成功读取到一项,它返回读取的字节数(一个正数)。如果已经没有更多项可以读取(到达目录末尾),它返回 0。如果发生错误,它返回一个负数。
while 循环: 因此,while (... > 0) 这个循环会一直执行,直到目录中的所有条目都被读取完毕。
*/
/*一张空白的"总清单"*/
QList<RemoteFile> fileList;//创建一个名为fileList的空列表,准备用来存放我们从服务器上读取的所有文件和文件夹的信息。
//QList<>:是Qt提供的一个动态 数组或列表容器
//RemoteFile是在sftpmanager.h中定义的结构体。像一张"物品信息卡",规定要记录远程文件(文件名,大小,是否是目录)。
/*一块临时的"草稿纸"*/
char buffer[1024];//字符数组,1024个字符的容量,足以存放绝大多数文件名,buffer:缓冲区
/*一张标准的"物品属性登记表"*/
LIBSSH2_SFTP_ATTRIBUTES attrs;
//LIBSSH2_SFTP_ATTRIBUTES:这是libssh2库预先定义好的一个结构体
//作用:作为标准化的容器或"表格",用来存放一个文件或文件夹的所有属性。当调用libssh2_sftp_readdir_ex时,不仅会把文件名写入buffer
//还会把这个文件的所有其他详细信息——如文件大小(filesize),权限(permissions),修改时间(mtime),所有者ID(uid),填写到attrs结构体变量中
/*小结:1.fileList:准备一个最终的,空的结果列表,用于汇总所有结果
2.buffer:准备一块临时的内存,用于在循环中接收C语言风格的文件名。
3.准备一个结构体,用于在循环中接收单个文件的各种详细属性。*/
while (libssh2_sftp_readdir_ex(sftp_handle, buffer, sizeof(buffer), nullptr, 0, &attrs) > 0)
//循环逻辑:关键在于libssh2_sftp_readdir_ex函数的返回值(返回一个正数,成功读取一项条目;返回0,表示目录中所有条目都已经读取完毕;返回一个负数,表示发生了错误)
//libssh2_sftp_readdir_ex函数参数详解
//参数1:sftp_handle:通过 libssh2_sftp_opendir 打开一个目录后获得的**“目录句柄”** 用途:告诉函数应该从哪个已经打开的目录里读取内容。
//参数2:buffer,指向字符数组的指针,也称缓冲区。 用途:输出参数。函数将读取到的文件名复制到这块内存中。
//参数3:buffer缓冲区的大小 防止内存溢出
//参数4:nullptr第二个可选的输出缓冲区,用于接收"长格式"的文件名,在本代码中,直接传入nullptr空指针,意在告诉函数忽略字段,不要填充
//参数5: 0(size_t) 第四个参数(长文件名缓冲区)的大小
//参数6:&attrs(LIBSSH2_SFTP_ATTRIBUTES*)指向LIBSSH2_SFTP_ATTRIBUTES结构体的指针。输出参数。指向预先声明的attrs结构体。
//函数会把当前读取到的文件条目的所有元数据属性都填写到这个结构体中。
//在执行完这个函数后,从attrs变量中读取到文件的大小(attrs.filesize),权限(attr.permissions),修改时间(attrs.mtime),所有者ID(attrs.uid)
{
//4.解析并存储每一项的信息
/*逻辑: 对于清点到的每一个货箱,管理员都在他的清单上记录下详细信息。
代码详解:
QString::fromUtf8(buffer): 将 C 语言风格的文件名 buffer 转换为 Qt 的 QString。
if (name == "." || name == "..") continue;: 过滤掉无意义的“当前目录”和“上级目录”条目。
RemoteFile file;: 创建一个我们自己定义的 RemoteFile 结构体实例,用来打包单个文件的所有信息。
file.isDirectory = LIBSSH2_SFTP_S_ISDIR(attrs.permissions);: 使用 libssh2 提供的宏,通过检查从 attrs 中得到的权限位,来判断这一项是文件夹还是文件。
file.size = attrs.filesize; 等: 从 attrs 结构体中直接获取文件大小和修改时间等属性。
fileList.append(file);: 将填好信息的 file 对象添加到我们的总清单 fileList 中。*/
//在while循环内部 listDirectory函数的"工作引擎"的核心部分,任务是处理单次读取到的文件/目录信息
QString name = QString::fromUtf8(buffer);
//buffer数组里存放的是服务器返回的文件名原始字节。默认是UTF-8编码,QString::fromUtf8静态函数的作用是转换成QString对象name
if (name == "." || name == "..") {
continue;}
RemoteFile file;//创建一个我们自己定义的 RemoteFile 结构体实例,用来打包单个文件的所有信息。
file.name = name;//将转换好的文件名,赋值给file结构体的name字段。
file.isDirectory = LIBSSH2_SFTP_S_ISDIR(attrs.permissions);//通过从attrs得到的权限位,判断这一项是文件夹还是文件
file.size = attrs.filesize;//从attrs结构体中直接获取文件大小(以字节为单位),并存入file结构体的size字段
file.modificationTime = QDateTime::fromSecsSinceEpoch(attrs.mtime);
//attrs.mtime:这是libssh2从服务器获取的"最后修改时间",是一个整数,代表从1970年1月1日0点0分0秒到现在的秒数。
//将服务器返回的Unix时间戳转换为Qt的日期时间对象
file.permissionsString = permsToString(attrs.permissions);//新增
file.ownerId = attrs.uid;
file.groupId = attrs.gid;
fileList.append(file);//将处理好的单条记录添加到最终列表中。
}
//5.收尾工作与结果汇报
libssh2_sftp_closedir(sftp_handle);//关闭一个已经打开的远程目录的句柄,释放相关资源(sftp_handle这个参数是之前打开目录时获得那个"门把手",告诉函数要关闭哪一个目录)
emit directoryListed(parentIndex,fileList);//将后台线程的工作成果(获取到的文件列表)通知给主界面线程
//主窗口在setupSFTP函数中已经通过connect语句监听了这个信号。一旦信号被发射,onSftpDirectoryListed槽函数被自动调用,并接收到这个fileList.
}
// 这是下载槽的具体实现
void SftpManager::downloadFile(const QString &remotePath, const QString &localPath)
{
// 前置检查,确保SFTP会话已就绪
if (!m_sftpSession) {
emit downloadFinished(remotePath, localPath, 0, false, "SFTP会话尚未初始化。");
return;
}
// 1. 打开远程服务器上的文件,准备读取
QByteArray remotePathBytes = remotePath.toUtf8();
LIBSSH2_SFTP_HANDLE *sftp_handle = libssh2_sftp_open(m_sftpSession, remotePathBytes.constData(), LIBSSH2_FXF_READ, 0);
if (!sftp_handle) {
emit downloadFinished(remotePath, localPath, 0, false, "无法打开远程文件。");
return;
}
// 2. 获取远程文件的大小,用于汇报进度
LIBSSH2_SFTP_ATTRIBUTES attrs;
if (libssh2_sftp_stat_ex(m_sftpSession, remotePathBytes.constData(), remotePathBytes.length(), LIBSSH2_SFTP_STAT, &attrs) != 0) {
libssh2_sftp_close_handle(sftp_handle); // 出错别忘了关闭句柄
emit downloadFinished(remotePath, localPath, 0, false, "无法获取远程文件属性。");
return;
}
qint64 totalBytes = attrs.filesize;
// 3. 在本地电脑上创建一个空文件,准备写入
QFile localFile(localPath);
if (!localFile.open(QIODevice::WriteOnly)) {
libssh2_sftp_close_handle(sftp_handle);
emit downloadFinished(remotePath, localPath, totalBytes, false, "无法创建本地文件用于写入。");
return;
}
// 4. 下载循环:持续从远程读取数据块,并写入本地文件
char buffer[1024 * 32]; // 创建一个32KB的缓冲区
qint64 bytesRead = 0;
ssize_t nread;
do {
// 从远程文件读取一块数据到缓冲区
nread = libssh2_sftp_read(sftp_handle, buffer, sizeof(buffer));
if (nread > 0) {
// 将刚刚读取到的数据块,写入到本地文件
if (localFile.write(buffer, nread) != nread) {
// 如果写入失败(比如磁盘满了),就跳出循环
break;
}
// 更新已下载的字节数,并发出进度信号
bytesRead += nread;
emit transferProgress(bytesRead, totalBytes);
}
} while (nread > 0); // 只要还能读到数据(nread > 0),就一直循环
// 5. 收尾工作:关闭文件句柄,并汇报最终结果
localFile.close();
libssh2_sftp_close_handle(sftp_handle);
if (nread < 0) {
// 如果 nread 是负数,说明读取过程中发生了错误
emit downloadFinished(remotePath, localPath, 0, false, "传输过程中读取远程文件出错。");
} else {
// 如果 nread 是0,说明文件已读取完毕,下载成功!
emit downloadFinished(remotePath, localPath, totalBytes, true, "");
}
/*上述downloadFile槽函数的逻辑:
1.打开远程文件:它使用 libssh2_sftp_open 来获取一个远程文件的“句柄”,就像打开本地文件一样。
2.获取文件大小:它获取文件的属性(stat)来得知总大小,这对于计算进度至关重要。
3.打开本地文件:它使用Qt的 QFile 类在你的电脑上创建一个空文件,准备接收数据。
4.下载循环:这是核心。它不断地:
从远程文件读取一小块数据(一次最多32KB)到临时的 buffer 中。
将 buffer 中的数据写入到本地文件中。
发出 transferProgress 信号来汇报当前进度。
循环往复,直到文件被完整读完。
5.清理与汇报:循环结束后,它会关闭远程和本地的文件句柄,并根据最终结果发出 downloadFinished 信号,报告成功或失败。
*/
}
// 这是上传槽的具体实现
void SftpManager::uploadFile(const QString &localPath, const QString &remotePath)
{
if (!m_sftpSession) {
emit uploadFinished(localPath, localPath, 0,false, "SFTP会话尚未初始化。");
return;
}
// 1. 打开本地文件,准备读取
QFile localFile(localPath);
if (!localFile.open(QIODevice::ReadOnly)) {
emit uploadFinished(localPath, remotePath, 0, false, "无法打开本地文件进行读取。");
return;
}
qint64 totalBytes = localFile.size(); // 获取文件总大小用于进度报告
// 2. 打开远程文件,准备写入
// 标志位说明: WRITE(写入), CREAT(如果文件不存在则创建), TRUNC(如果文件已存在则清空内容)
QByteArray remotePathBytes = remotePath.toUtf8();
LIBSSH2_SFTP_HANDLE *sftp_handle = libssh2_sftp_open(m_sftpSession, remotePathBytes.constData(),
LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
LIBSSH2_SFTP_S_IRUSR | LIBSSH2_SFTP_S_IWUSR | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH);
if (!sftp_handle) {
localFile.close();
emit uploadFinished(localPath, remotePath, totalBytes, false, "无法在远程服务器上创建或打开文件。");
return;
}
// 3. 上传循环:持续从本地文件读取数据块,并写入远程文件
char buffer[1024 * 32]; // 32KB的缓冲区
qint64 bytesWritten = 0;
qint64 nread; // 每次从本地读取的字节数
while ((nread = localFile.read(buffer, sizeof(buffer))) > 0) {
char *ptr = buffer;
qint64 nwritten;
// 持续写入,直到刚刚读取的数据块(nread)被完全写入远程文件
do {
nwritten = libssh2_sftp_write(sftp_handle, ptr, nread);
if (nwritten < 0) { // 如果发生写入错误
break;
}
ptr += nwritten;
nread -= nwritten;
bytesWritten += nwritten;
emit transferProgress(bytesWritten, totalBytes);
} while (nread > 0);
if (nwritten < 0) {
break; // 从外层循环跳出
}
}
// 4. 收尾工作:关闭文件句柄,并汇报最终结果
localFile.close();
libssh2_sftp_close_handle(sftp_handle);
if (nread < 0) {
// 如果 nread 是负数,说明从本地文件读取时发生了错误
emit uploadFinished(localPath, remotePath, totalBytes,false, "传输过程中读取本地文件出错。");
} else {
// 成功!
emit uploadFinished(localPath, remotePath, totalBytes, true, "");
}
/*
1.打开本地文件:这次我们使用 QFile 打开一个已存在的本地文件,准备读取它的内容。
2.打开远程文件:我们使用 libssh2_sftp_open 在远程服务器上创建一个新文件(或覆盖一个旧文件),准备写入数据。
3.上传循环:这是核心。它不断地:
从本地文件读取一小块数据到 buffer 中。
将 buffer 中的数据通过 libssh2_sftp_write 写入到远程文件中。
发出 transferProgress 信号来汇报进度。
循环往复,直到整个本地文件被读完。
4.清理与汇报:循环结束后,它会关闭所有文件句柄,并发出 uploadFinished 信号报告最终结果。
*/
}
void SftpManager::deleteRemoteFile(const QString &remotePath)
{
// 1. 前置安全检查
if (!m_sftpSession) {
emit remoteFileDeleted(remotePath, false, "SFTP会话尚未初始化。");
return;
}
// 2. 将QString转换为libssh2需要的C语言字符串格式
QByteArray remotePathBytes = remotePath.toUtf8();
// 3. 调用libssh2的删除文件函数
// 在类Unix系统中,“unlink”就是删除文件的标准系统调用
int rc = libssh2_sftp_unlink(m_sftpSession, remotePathBytes.constData());
// 4. 根据返回值,发出成功或失败的信号
if (rc == 0) {
// 返回值为0代表成功
emit remoteFileDeleted(remotePath, true, "");
} else {
// 如果返回非零值,代表失败,我们需要获取详细的错误信息
char *err_msg;
libssh2_session_last_error(m_session, &err_msg, nullptr, 0);
emit remoteFileDeleted(remotePath, false, QString::fromUtf8(err_msg));
}
/*libssh2_sftp_unlink(): 这是 libssh2 库中执行删除操作的核心函数。它接收 SFTP 会话和要删除的文件路径作为参数。
错误处理: 如果删除成功,函数返回 0。如果失败(比如文件不存在、权限不足等),它会返回一个负数。我们通过检查返回值 rc 来判断操作是否成功。
libssh2_session_last_error(): 当 libssh2 的函数调用失败时,我们可以用这个辅助函数来获取具体、详细的错误描述信息,这样就能在界面上给用户更明确的反馈。*/
}