简述
对于获取目录或者给服务器建立目录等操作选择QFtp是最正确的方式,因为新的类QNetworkAccessManager并不提供有关文件列表等有关的操作。
网上很多人都说QFtp不支持在服务器一次性创建多级目录,这个可以说对也可以说不对,为什么呢?因为使用QFtp调用mkdir传一样的目录(如:/2019/08/06/20),结果是:在微软自带的IIS管理器创建的FTP服务器是创建不了目录的并且是第一级的2019都创建不了,只有把目录改为只有一级的时候(如:/2019)才会创建成功,但是使用别的软件去创建FTP服务器就不一样,如使用:FileZilla Server 去创建FTP服务器的话就可以把/2019/08/06/20这多级目录一次性的创建出来;同样的代码一个可以一个不可以只是创建服务器的方式不同,我想也许是创建服务器时给定的权限不够。
也许有人在想既然一次性创建多级目录不行,那来个for循环多次创建行不行,答案是当然行,但是还有个问题QFtp是异步处理方式,也就是你给它同时发送多条创建目录的命令(如:/2019,/2019/08,/2019/08/06,/2019/08/06/20),他不会立即返回而返回的只是你给的命令的ID;当你给的命令没有问题时他会一条一条命令的去执行创建目录,但是当你给的命令中有一条是它认为错误的话就会把整个命令列表清除,也就是只能执行到出错的那条命令而已因为后面的命令被清除掉了。
就拿创建目录举例,比如一开始是没有这些目录的,然后分别发送这四条命令去创建目录/2019,/2019/08,/2019/08/06以及/2019/08/06/20,因为目录都是空的所以这四条命令是完全可以被执行并创建目录的,因为QFtp没有接口调用去判断目录是否存在,所以在每次操作前你都需要for循环先发送命令去创建目录,当在下一个整点到来时你所要存放的东西的目录更改了即你所要把文件存放到了/2019/08/06/21下面,所以你又的发送命令去创建目录/2019,/2019/08,/2019/08/06以及/2019/08/06/21,但是这时你会发现在创建第一级目录/2019的时候系统会返回目录已存在创建失败等错误,然后往下的三条命令以及往后要上传文件的命令都被清除了,这就是很致命的问题,怎么办呢?其实我也不知道怎么办... ...
挣扎了好多天,网上也没找到资料,很无奈... ...最终我去看了源代码并且一步步跟踪调试,最后我发现了问题所在并把问题解决了。
修改源代码
在不断的调试跟踪下,我发现在QFtpPI类的readyRead()函数就是FTP服务器处理后的结果返回,返回形式是:代码+提示,如:"257 "2019/" directory created.\r\n","550 Cannot create a file when that file already exists. \r\n"。
如果创建已存在的目录会失败并返回550的代码,如果创建目录成功则会返回257的代码,如果想要在创建已存在目录失败的情况下继续操作,我们需要把返回的字符串代码前三个550改为257即可,因为目录是存在的即使系统认为是创建目录失败但是我们并不认为是失败的,我们的目的就是要创建目录,既然目录存在那就是成功了。这并不影响我们其他的操作如果是目录不存在又创建不成功的话这才是失败的,至于返回的代码就不贴出来了。
以下源码中start到end中就是要修改的部分
void QFtpPI::readyRead()
{
if (waitForDtpToClose)
return;
while (commandSocket.canReadLine()) {
// read line with respect to line continuation
QString line = QString::fromLatin1(commandSocket.readLine());
/*********start***********/
// 如果创建已存在的文件夹会失败并返回550的代码,如果创建文件夹成功则会返回257的代码
// 如果想要在创建已存在文件夹失败的情况下继续操作,则只需要把返回的字符串代码前三个550改为257
if (line.startsWith("550")) line.replace("550", "257");
/**********end**********/
if (replyText.isEmpty()) {
if (line.length() < 3) {
// protocol error
return;
}
const int lowerLimit[3] = {1,0,0};
const int upperLimit[3] = {5,5,9};
for (int i=0; i<3; i++) {
replyCode[i] = line[i].digitValue();
if (replyCode[i]<lowerLimit[i] || replyCode[i]>upperLimit[i]) {
// protocol error
return;
}
}
}
QString endOfMultiLine;
endOfMultiLine[0] = '0' + replyCode[0];
endOfMultiLine[1] = '0' + replyCode[1];
endOfMultiLine[2] = '0' + replyCode[2];
endOfMultiLine[3] = QLatin1Char(' ');
QString lineCont(endOfMultiLine);
lineCont[3] = QLatin1Char('-');
QString lineLeft4 = line.left(4);
while (lineLeft4 != endOfMultiLine) {
if (lineLeft4 == lineCont)
replyText += line.mid(4); // strip 'xyz-'
else
replyText += line;
if (!commandSocket.canReadLine())
return;
line = QString::fromLatin1(commandSocket.readLine());
lineLeft4 = line.left(4);
}
replyText += line.mid(4); // strip reply code 'xyz '
if (replyText.endsWith(QLatin1String("\r\n")))
replyText.chop(2);
if (processReply())
replyText = QLatin1String("");
}
}
创建多级目录接口
再给你们提供一个一次性创建多级目录的接口,其实也是一层层创建,只不过现在是创建存在的目录没有返回失败而清除命令列表而已。
int QFtp::mkpdir(const QString &dir)
{
QString path = dir;
// 如果存在文件名,则需要先去掉文件名
if (dir.indexOf(".") >= 0)
{
path = path.left(path.lastIndexOf("/"));
}
QStringList list = path.split("/");
for (int i = 0; i < list.count(); i++)
{
if (list[i].isEmpty()) continue;
path.clear();
for (int j = 0; j <= i; j++)
{
if (list[j].isEmpty()) continue;
path += QString("/%1").arg(list.at(j));
}
if (!path.isEmpty()) mkdir(path);
}
return 1;
}
怎么样,是不是很简单?看起来确实是很简单,可是就是这么简单的几行代码让人头疼了几天。如果QFtp有提供判断目录是否存在的接口的话也许就不用这么麻烦了。
对于搭建FTP服务不太了解的可以看看:https://blog.youkuaiyun.com/Ilson_/article/details/97818689
QetworkAccessManager实现FTP文件上传/下载功能:https://blog.youkuaiyun.com/Ilson_/article/details/97829233
QFtp实现文件上传、下载、新建文件夹、重命名、删除和刷新等功能:https://blog.youkuaiyun.com/Ilson_/article/details/98371848