Working with FTP servers
@官方文档翻译-李冰
@译文
本章介绍如何使用CFFTP API的一些基本功能。管理FTP实事物是异步执行的,同时管理文件传输是同步执行的。
Downloading a File
使用CFFTP同CFHTTP非常相似,因为他们都基于CFStream。与使用CFStream的异步的任何其他的API一样,使用CFFTP下载文件需要你为文件创建读取流,并为该读取流创建回调函数。当读取流接收到数据,将运行回调函数并且你需要适当的下载字节。该过程通常需要使用两个函数执行:一个设置流,另一个作为回调函数。
Setting Up the FTP Streams
首先,使用CFReadStreamCreateWithFTPURL函数创建读取流,并传递要在远程服务器上下载的文件的URL字符串。URL字符串示例可能是ftp://ftp.example.com/file.txt。请注意这个字符串包含了服务器名,路径和文件。下一步,为将要下载的本地位置创建一个写入流。这一步使用CFWriteStreamCreateWithFile函数完成,传入文件将被下载的路径。
由于写入流和读取流需要保持同步,因此创建一个包含所有公共信息的结构体是一个好主意,例如代理字典,文件大小,写入的字节数, 剩余的字节和缓冲区。此结构体可能如表5-1所示。
表 5-1 A stream structure
typedef struct MyStreamInfo {
CFWriteStreamRef writeStream;
CFReadStreamRef readStream;
CFDictionaryRef proxyDict;
SInt64 fileSize;
UInt32 totalBytesWritten;
UInt32 leftOverByteCount;
UInt8 buffer[kMyBufferSize];
} MyStreamInfo;
使用刚创建的写入流和读取流初始化你的结构体。然后你可以定义流客户端上下文(CFStreamClientContext
)的info字段以指向你的结构体。
使用 CFWriteStreamOpen
函数打开你的写入流这样你就可以开始写入本地文件。为了确保流打开争取,调用函数CFWriteStreamGetStatus
并检查是否返回kCFStreamStatusOpen
或 kCFStreamStatusOpening
。
在写入流打开的情况下,将回调函数和读取流相关联、调用函数CFReadStreamSetClient
并传入读取流,你的回调函数将接受的网络事件,回调函数名和CFStreamClientContext
对象。通过之前设置流客户端上下文的info字段,每当它运行时,您的结构将被发送到回调函数。
一些FTP服务器也许需要一个用户名,有些可能还需要密码。如果你访问的服务器需要用户名用于认证,调用CFReadStreamSetProperty
函数并传入读取流,属性名kCFStreamPropertyFTPUserName
,和一个包含用户名的CFString
对象引用。另外如果需要设置密码,设置kCFStreamPropertyFTPPassword
属性。
某些网络配置也可以使用FTP代理。 您可以通过不同的方式获取代理信息,具体取决于您的代码是在OS X或iOS中运行。
- 在OS X中,你可以调用函数
SCDynamicStoreCopyProxies
并传入NULL来检索代理设置。 - 在iOS中,你可以调用
CFNetworkCopyProxiesForURL
检索代理设置。
这些函数返回动态存储引用。 您可以使用此值设置读取流的kCFStreamPropertyFTPProxy
属性。 这将设置代理服务器,指定端口,并返回一个布尔值,表示是否对FTP流强制执行被动模式。
-
kCFStreamPropertyFTPUserName
— 用于登录的用户名(可设置和可检索;不用为匿名FTP连接设置) -
kCFStreamPropertyFTPPassword
— 用于登录的密码 可设置和可检索;不用为匿名FTP连接设置) -
kCFStreamPropertyFTPUsePassiveMode
— 是否使用被动模式 (可设置和可检索) -
kCFStreamPropertyFTPResourceSize
— 正在下载的项目预期大小,如果可用(可检索;仅可用于FTP读取流) -
kCFStreamPropertyFTPFetchResourceInfo
— 下载前是否需要源信息,比如大小(可设置和检索);这只此属性可能会影响性能。 -
kCFStreamPropertyFTPFileTransferOffset
— 开始传输时的文件偏移量 (可设置和检索) -
kCFStreamPropertyFTPAttemptPersistentConnection
— 是否尝试重用连接(可设置和检索) -
kCFStreamPropertyFTPProxy
— 保存代理字典的键值对的 CFDictionary 类型(可设置和检索) -
kCFStreamPropertyFTPProxyHost
— FTP代理的主机名(可设置和检索) -
kCFStreamPropertyFTPProxyPort
— FTP代理的主机端口号(可设置和检索)
在将正确的属性分配给读取流后,使用CFReadStreamOpen
函数打开流。假设这些都没返回错误,所有的流都已正确的设置。
Implementing the Callback Function
你的回调函数将接收到三个参数:读取流,事件类型,以及你的MyStreamInfo
结构体。事件的类型决定将采取什么措施。
最常见的事件是kCFStreamEventHasBytesAvailable
,它是当读取流从服务器接收到字节时发送。首先,调用CFReadStreamRead
函数检查已读到的字节数。确保返回的值不小于0(错误),或等于0(下载完成)。如果返回值正确,然后你就可以开始将读取流的数据通过写入流写入磁盘。
调用CFWriteStreamWrite
函数将数据写入写入流。有时候 CFWriteStreamWrite
可以返回而不会从读取流写入所有数据。为此,设置一个循环运行,只要还有数据写入。这个循环代码在表5-2中,其中info是 指Setting up the Streams中的MyStreamInfo
结构体。这种写入流的方法使用阻塞流。你可以通过使写流事件驱动来实现更好的性能,但代码更复杂。
表 5-2 Writing data to a write stream from the read stream
bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize);
//...make sure bytesRead > 0 ...
bytesWritten = 0;
while (bytesWritten < bytesRead) {
CFIndex result;
result = CFWriteStreamWrite(info->writeStream, info->buffer + bytesWritten, bytesRead - bytesWritten);
if (result <= 0) {
fprintf(stderr, "CFWriteStreamWrite returned %ld\n", result);
goto exit;
}
bytesWritten += result;
}
info->totalBytesWritten += bytesWritten;
只有读取流中有可用字节,请重复整个过程。
你需要注意其他的两个事件是kCFStreamEventErrorOccurred
和 kCFStreamEventEndEncountered
。 如果出现错误,使用CFReadStreamGetError
检索错误然后退出。如果文件结尾出现,那么你的下载已经完成,你可以退出。
请确保一切完成后并且没有其他进程使用流的时候,删除所有流。
首先,关闭写入流并且设置客户端为NULL
。然后从run loop中取消调度流并释放。完成后从run loop中删除流。
Uploading a File
上传文件类似于下载文件。像下载文件一样,你需要一个读取流和写入流。但是,当你上传一个文件时,读取流将用于本地文件,写入流用于远程文件。
根据Setting up the Streams的指示,但是无论任何地方都要参考读取流去改写写入流代码,反之亦然。
在回调函数中,而不是查找kCFStreamEventHasBytesAvailable
事件,现在查找kCFStreamEventCanAcceptBytes
事件。首先,使用读取流从文件中读取字节并将数据放入MyStreamInfo
中的缓冲区中。然后,运行CFWriteStreamWrite
函数将字节从缓存区推入写入流。CFWriteStreamWrite
返回已写入的字节数。如果写入的字节数比从文件读取的字节数少,则计算剩余字节并把他们寸灰缓存区。在下一次写周期中,如果存在剩余字节,则先将它们写入写入流中而不是从读取流中加载新的数据。重复整个过程只要写流可以接收字节(CFWriteStreamCanAcceptBytes
)。这个循环见表5-3。
表 5-3 Writing data to the write stream
do {
// Check for leftover data
if (info->leftOverByteCount > 0) {
bytesRead = info->leftOverByteCount;
} else {
// Make sure there is no error reading from the file
bytesRead = CFReadStreamRead(info->readStream, info->buffer,
kMyBufferSize);
if (bytesRead < 0) {
fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
goto exit;
}
totalBytesRead += bytesRead;
}
// Write the data to the write stream
bytesWritten = CFWriteStreamWrite(info->writeStream, info->buffer, bytesRead);
if (bytesWritten > 0) {
info->totalBytesWritten += bytesWritten;
// Store leftover data until kCFStreamEventCanAcceptBytes event occurs again
if (bytesWritten < bytesRead) {
info->leftOverByteCount = bytesRead - bytesWritten;
memmove(info->buffer, info->buffer + bytesWritten,
info->leftOverByteCount);
} else {
info->leftOverByteCount = 0;
}
} else {
if (bytesWritten < 0)
fprintf(stderr, "CFWriteStreamWrite returned %ld\n", bytesWritten);
break;
}
} while (CFWriteStreamCanAcceptBytes(info->writeStream));
还要考虑下面的kCFStreamEventErrorOccurred
和kCFStreamEventEndEncoming
事件,就像下载文件一样。
Creating a Remote Directory
在一个远程服务器上创建目录,设置一个写入流,就像你要上传一个文件一样。但是,为传递给CFWriteStreamCreateWithFTPURL
函数的CFURL
对象提供一个目录路径而不是一个文件。已正斜杠结束路径。例如,一个正确的目录路径可能为ftp://ftp.example.com/newDirectory/而不是 ftp://ftp.example.com/newDirectory/newFile.txt。当回调函数被run loop执行的时候,它发送kCFStreamEventEndEncountered
事件,这意味着目录已经被创建(或kCFStreamEventErrorOccurred
如果出现一些错误)。
每次调用CFWriteStreamCreateWithFTPURL
只能创建一级目录。 此外,仅当您在服务器上具有正确的权限时,才会创建目录。
Downloading a Directory Listing
通过FTP下载目录列表同下载或上传文件有稍许不同。这是因为传入的数据必须被解析。首先,设置好一个读取流去获取目录列表。这应该像下载一个文件一样:创建流,注册回调函数,使用run loop调度(如果有必要,设置用户名,密码和代理信息),并且最后打开流。在以下示例中,检索目录列表时不需要读取流和写入流,因为传入数据将进入屏幕,而不是文件。
在回调函数中,监视kCFStreamEventHasBytesAvailable
事件。
在从读取流加载数据之前,请确保在上一次运行回调函数的流中没有剩余数据。从MyStreamInfo
结构的leftOverByteCount
字段加载偏移量。然后,从流中读取数据,同时考虑到你刚刚计算的偏移量。还应计算缓存区大小和已读取的字节数。这一些都在表5-4中完成。
表 5-4 Loading data for a directory listing
// If previous call had unloaded data
int offset = info->leftOverByteCount;
// Load data from the read stream, accounting for the offset
bytesRead = CFReadStreamRead(info->readStream, info->buffer + offset,
kMyBufferSize - offset);
if (bytesRead < 0) {
fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
break;
} else if (bytesRead == 0) {
break;
}
bufSize = bytesRead + offset;
totalBytesRead += bufSize;
当数据已经被读取到缓存区,设置好一个循环去解析数据。被解析的数据不一定是整个目录列表;它可以(并可能会)是列表块。创建循环以使用CFFTPCreateParsedResourceListing
函数解析数据,该函数应传入数据缓存区,缓存区大小和一个字典引用。它返回解析的字节数。只要这个值大于零,继续循环。 CFFTPCreateParsedResourceListing创建的字典包含所有目录列表信息; 有关keys的更多信息,请参阅Setting up the Streams。
CFFTPCreateParsedResourceListing
可能返回一个正值,但不能创建一个解析字典。例如,如果列表的结尾包含无法解析的信息,CFFTPCreateParsedResourceListing
将返回一个正值,告诉调用者数据已被消耗。 然而,CFFTPCreateParsedResourceListing
不会创建一个解析字典,因为它无法理解数据。
如果创建了解析字典,请重新计算读取的字节数和缓冲区大小,如列表5-5所示。
表 5-5 Loading the directory listing and parsing it
do
{
bufRemaining = info->buffer + totalBytesConsumed;
bytesConsumed = CFFTPCreateParsedResourceListing(NULL, bufRemaining,
bufSize, &parsedDict);
if (bytesConsumed > 0) {
// Make sure CFFTPCreateParsedResourceListing was able to properly
// parse the incoming data
if (parsedDict != NULL) {
// ...Print out data from parsedDict...
CFRelease(parsedDict);
}
totalBytesConsumed += bytesConsumed;
bufSize -= bytesConsumed;
info->leftOverByteCount = bufSize;
} else if (bytesConsumed == 0) {
// This is just in case. It should never happen due to the large buffer size
info->leftOverByteCount = bufSize;
totalBytesRead -= info->leftOverByteCount;
memmove(info->buffer, bufRemaining, info->leftOverByteCount);
} else if (bytesConsumed == -1) {
fprintf(stderr, "CFFTPCreateParsedResourceListing parse failure\n");
// ...Break loop and cleanup...
}
} while (bytesConsumed > 0);
当流没有可用字节,清理所有的流并从run loop删除。
Working with FTP servers
This chapter explains how to use some of the basic features of the CFFTP API. Managing the FTP transactions is performed asynchronously, while managing the file transfer is implemented synchronously.
Downloading a File
Using CFFTP is very similar to using CFHTTP because they are both based on CFStream. As with any other API that uses CFStream asynchronously, downloading a file with CFFTP requires that you create a read stream for the file, and a callback function for that read stream. When the read stream receives data, the callback function will be run and you will need to appropriately download the bytes. This procedure should normally be performed using two functions: one to set up the streams and one to act as the callback function.
Setting Up the FTP Streams
Begin by creating a read stream using the CFReadStreamCreateWithFTPURL function and passing it the URL string of the file to be downloaded on the remote server. An example of a URL string might be ftp://ftp.example.com/file.txt. Note that the string contains the server name, the path, and the file. Next, create a write stream for the local location where the file will be downloaded. This is accomplished using the CFWriteStreamCreateWithFile function, passing the path where the file will be downloaded.
Since the write stream and the read stream need to stay in sync, it is a good idea to create a structure that contains all of the common information, such as the proxy dictionary, the file size, the number of bytes written, the number of bytes left over, and a buffer. This structure might look like that in Listing 5-1.
Listing 5-1 A stream structure
typedef struct MyStreamInfo {
CFWriteStreamRef writeStream;
CFReadStreamRef readStream;
CFDictionaryRef proxyDict;
SInt64 fileSize;
UInt32 totalBytesWritten;
UInt32 leftOverByteCount;
UInt8 buffer[kMyBufferSize];
} MyStreamInfo;
Initialize your structure with the read stream and write stream you just created. You can then define the info field of your stream client context (CFStreamClientContext
) to point to your structure. This will become useful later.
Open your write stream with the CFWriteStreamOpen
function so you can begin writing to the local file. To make sure the stream opens properly, call the function CFWriteStreamGetStatus
and check whether it returns either kCFStreamStatusOpen
or kCFStreamStatusOpening
.
With the write stream open, associate a callback function with the read stream. Call the function CFReadStreamSetClient
and pass the read stream, the network events your callback function should receive, the callback function's name and the CFStreamClientContext
object. By having earlier set the info field of the stream client context, your structure will now be sent to your callback function whenever it is run.
Some FTP servers may require a user name, and some may also require a password. If the server you are accessing needs a user name for authentication, call the CFReadStreamSetProperty
function and pass the read stream, kCFStreamPropertyFTPUserName
for the property, and a reference to a CFString
object containing the user name. In addition, if you need to set a password, set the kCFStreamPropertyFTPPassword
property.
Some network configurations may also use FTP proxies. You obtain the proxy information in different ways depending on whether your code is running in OS X or iOS.
- In OS X, you can retrieve the proxy settings in a dictionary by calling the
SCDynamicStoreCopyProxies
function and passing it NULL. - In iOS, you can retrieve the proxy settings by calling
CFNetworkCopyProxiesForURL
.
These functions return a dynamic store reference. You can use this value to set the kCFStreamPropertyFTPProxy
property of the read stream. This sets the proxy server, specifies the port, and returns a Boolean value indicating whether passive mode is enforced for the FTP stream.
-
kCFStreamPropertyFTPUserName
— user name to use to log in (settable and retrievable; do not set for anonymous FTP connections) -
kCFStreamPropertyFTPPassword
— password to use to log in (settable and retrievable; do not set for anonymous FTP connections) -
kCFStreamPropertyFTPUsePassiveMode
— whether to use passive mode (settable and retrievable) -
kCFStreamPropertyFTPResourceSize
— the expected size of an item that is being downloaded, if available (retrievable; available only for FTP read streams) -
kCFStreamPropertyFTPFetchResourceInfo
— whether to require that resource information, such as size, be required before starting a download (settable and retrievable); setting this property may impact performance -
kCFStreamPropertyFTPFileTransferOffset
— file offset at which to start a transfer (settable and retrievable) -
kCFStreamPropertyFTPAttemptPersistentConnection
— whether to try to reuse connections (settable and retrievable) -
kCFStreamPropertyFTPProxy
— CFDictionary type that holds key-value pairs of proxy dictionary (settable and retrievable) -
kCFStreamPropertyFTPProxyHost
— name of an FTP proxy host (settable and retrievable) -
kCFStreamPropertyFTPProxyPort
— port number of an FTP proxy host (settable and retrievable)
After the correct properties have been assigned to the read stream, open the stream using the CFReadStreamOpen
function. Assuming that this does not return an error, all the streams have been properly set up.
Implementing the Callback Function
Your callback function will receive three parameters: the read stream, the type of event, and your MyStreamInfo
structure. The type of event determines what action must be taken.
The most common event is kCFStreamEventHasBytesAvailable
, which is sent when the read stream has received bytes from the server. First, check how many bytes have been read by calling the CFReadStreamRead
function. Make sure the return value is not less than zero (an error), or equal to zero (download has completed). If the return value is positive, then you can begin writing the data in the read stream to disk via the write stream.
Call the CFWriteStreamWrite
function to write the data to the write stream. Sometimes CFWriteStreamWrite
can return without writing all of the data from the read stream. For this reason, set up a loop to run as long as there is still data to be written. The code for this loop is in Listing 5-2, where info is the MyStreamInfo
structure from Setting up the Streams. This method of writing to the write stream uses blocking streams. You can achieve better performance by making the write stream event driven, but the code is more complex.
Listing 5-2 Writing data to a write stream from the read stream
bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize);
//...make sure bytesRead > 0 ...
bytesWritten = 0;
while (bytesWritten < bytesRead) {
CFIndex result;
result = CFWriteStreamWrite(info->writeStream, info->buffer + bytesWritten, bytesRead - bytesWritten);
if (result <= 0) {
fprintf(stderr, "CFWriteStreamWrite returned %ld\n", result);
goto exit;
}
bytesWritten += result;
}
info->totalBytesWritten += bytesWritten;
Repeat this entire procedure as long as there are available bytes in the read stream.
The other two events you need to watch out for are kCFStreamEventErrorOccurred
and kCFStreamEventEndEncountered
. If an error occurs, retrieve the error using CFReadStreamGetError
and then exit. If the end of the file occurs, then your download has completed and you can exit.
Make sure to remove all your streams after everything is completed and no other process is using the streams. First, close the write stream and set the client to NULL
. Then unschedule the stream from the run loop and release it. Remove the streams from the run loop when you are done.
Uploading a File
Uploading a file is similar to downloading a file. As with downloading a file, you need a read stream and a write stream. However, when uploading a file, the read stream will be for the local file and the write stream will be for the remote file. Follow the instructions in Setting up the Streams, but wherever it refers to the read stream, adapt the code for a write stream and visa versa.
In the callback function, rather than looking for the kCFStreamEventHasBytesAvailable
event, now look for the event kCFStreamEventCanAcceptBytes
. First, read bytes from the file using the read stream and place the data into the buffer in MyStreamInfo
. Then, run the CFWriteStreamWrite
function to push bytes from the buffer into the write stream. CFWriteStreamWrite
returns the number of bytes that have been written to the stream. If the number of bytes written to the stream is fewer than the number read from the file, calculate the leftover bytes and store them back into the buffer. During the next write cycle, if there are leftover bytes, write them to the write stream rather than loading new data from the read stream. Repeat this whole procedure as long as the write stream can accept bytes (CFWriteStreamCanAcceptBytes
). See this loop in code in Listing 5-3.
Listing 5-3 Writing data to the write stream
do {
// Check for leftover data
if (info->leftOverByteCount > 0) {
bytesRead = info->leftOverByteCount;
} else {
// Make sure there is no error reading from the file
bytesRead = CFReadStreamRead(info->readStream, info->buffer,
kMyBufferSize);
if (bytesRead < 0) {
fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
goto exit;
}
totalBytesRead += bytesRead;
}
// Write the data to the write stream
bytesWritten = CFWriteStreamWrite(info->writeStream, info->buffer, bytesRead);
if (bytesWritten > 0) {
info->totalBytesWritten += bytesWritten;
// Store leftover data until kCFStreamEventCanAcceptBytes event occurs again
if (bytesWritten < bytesRead) {
info->leftOverByteCount = bytesRead - bytesWritten;
memmove(info->buffer, info->buffer + bytesWritten,
info->leftOverByteCount);
} else {
info->leftOverByteCount = 0;
}
} else {
if (bytesWritten < 0)
fprintf(stderr, "CFWriteStreamWrite returned %ld\n", bytesWritten);
break;
}
} while (CFWriteStreamCanAcceptBytes(info->writeStream));
Also account for the kCFStreamEventErrorOccurred
and kCFStreamEventEndEncountered
events as you do when downloading a file.
Creating a Remote Directory
To create a directory on a remote server, set up a write stream as if you were going to be uploading a file. However, provide a directory path, not a file, for the CFURL
object that is passed to the CFWriteStreamCreateWithFTPURL
function. End the path with a forward slash. For example, a proper directory path would be ftp://ftp.example.com/newDirectory/, not ftp://ftp.example.com/newDirectory/newFile.txt. When the callback function is executed by the run loop, it sends the event kCFStreamEventEndEncountered
, which means the directory has been created (or kCFStreamEventErrorOccurred
if something went wrong).
Only one level of directories can be created with each call to CFWriteStreamCreateWithFTPURL
. Also, a directory is created only if you have the correct permissions on the server.
Downloading a Directory Listing
Downloading a directory listing via FTP is slightly different from downloading or uploading a file. This is because the incoming data has to be parsed. First, set up a read stream to get the directory listing. This should be done as it was for downloading a file: create the stream, register a callback function, schedule the stream with the run loop (if necessary, set up user name, password and proxy information), and finally open the stream. In the following example you do not need both a read and a write stream when retrieving the directory listing, because the incoming data is going to the screen rather than a file.
In the callback function, watch for the kCFStreamEventHasBytesAvailable
event. Prior to loading data from the read stream, make sure there is no leftover data in the stream from the previous time the callback function was run. Load the offset from the leftOverByteCount
field of your MyStreamInfo
structure. Then, read data from the stream, taking into account the offset you just calculated. The buffer size and number of bytes read should be calculated too. This is all accomplished in Listing 5-4.
Listing 5-4 Loading data for a directory listing
// If previous call had unloaded data
int offset = info->leftOverByteCount;
// Load data from the read stream, accounting for the offset
bytesRead = CFReadStreamRead(info->readStream, info->buffer + offset,
kMyBufferSize - offset);
if (bytesRead < 0) {
fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
break;
} else if (bytesRead == 0) {
break;
}
bufSize = bytesRead + offset;
totalBytesRead += bufSize;
After the data has been read to a buffer, set up a loop to parse the data. The data that is parsed is not necessarily the entire directory listing; it could (and probably will) be chunks of the listing. Create the loop to parse the data using the function CFFTPCreateParsedResourceListing
, which should be passed the buffer of data, the size of the buffer, and a dictionary reference. It returns the number of bytes parsed. As long as this value is greater than zero, continue to loop. The dictionary that CFFTPCreateParsedResourceListing
creates contains all the directory listing information; more information about the keys is available in Setting up the Streams.
It is possible for CFFTPCreateParsedResourceListing
to return a positive value, but not create a parse dictionary. For example, if the end of the listing contains information that cannot be parsed, CFFTPCreateParsedResourceListing
will return a positive value to tell the caller that data has been consumed. However, CFFTPCreateParsedResourceListing
will not create a parse dictionary since it could not understand the data.
If a parse dictionary is created, recalculate the number of bytes read and the buffer size as shown in Listing 5-5.
Listing 5-5 Loading the directory listing and parsing it
do
{
bufRemaining = info->buffer + totalBytesConsumed;
bytesConsumed = CFFTPCreateParsedResourceListing(NULL, bufRemaining,
bufSize, &parsedDict);
if (bytesConsumed > 0) {
// Make sure CFFTPCreateParsedResourceListing was able to properly
// parse the incoming data
if (parsedDict != NULL) {
// ...Print out data from parsedDict...
CFRelease(parsedDict);
}
totalBytesConsumed += bytesConsumed;
bufSize -= bytesConsumed;
info->leftOverByteCount = bufSize;
} else if (bytesConsumed == 0) {
// This is just in case. It should never happen due to the large buffer size
info->leftOverByteCount = bufSize;
totalBytesRead -= info->leftOverByteCount;
memmove(info->buffer, bufRemaining, info->leftOverByteCount);
} else if (bytesConsumed == -1) {
fprintf(stderr, "CFFTPCreateParsedResourceListing parse failure\n");
// ...Break loop and cleanup...
}
} while (bytesConsumed > 0);
When the stream has no more bytes available, clean up all the streams and remove them from the run loop.