| Paste number 73272: | problem using CFNetwork in attempt to upload a second file |
| Pasted by: | JonBad |
| When: | 5 months, 3 weeks ago |
| Share: | Tweet this! | http://paste.lisp.org/+1KJC |
| Channel: | #macdev |
| Paste contents: |
//
// FCFileUploaderController.m
// FastCast Audition Manager
//
// Created by Jonathan Badeen on 12/10/08.
// Copyright 2008 Casting Networks. All rights reserved.
//
#import "FCFileUploaderController.h"
#import "FC.h"
#include <CoreServices/CoreServices.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <sys/dirent.h>
#include <sys/stat.h>
#include <unistd.h> // getopt
#include <string.h> // strmode
#include <stdlib.h>
#include <inttypes.h>
#define LOCAL_LOGGING 1
id uploaderController = nil;
#pragma mark ***** Common Code and Data Structures
/* When using file streams, the 32KB buffer is probably not enough.
A good way to establish a buffer size is to increase it over time.
If every read consumes the entire buffer, start increasing the buffer
size, and at some point you would then cap it. 32KB is fine for network
sockets, although using the technique described above is still a good idea.
This sample avoids the technique because of the added complexity it
would introduce. */
#define kMyBufferSize 32768
/* MyStreamInfo holds the state of a particular operation (download, upload, or
directory listing. Some fields are only valid for some operations, as explained
by their comments. */
typedef struct MyStreamInfo {
CFWriteStreamRef writeStream; // download (destination file stream) and upload (FTP stream) only
CFReadStreamRef readStream; // download (FTP stream), upload (source file stream), directory list (FTP stream)
//CFDictionaryRef proxyDict; // necessary to workaround <rdar://problem/3745574>, per discussion below
SInt64 fileSize; // download only, 0 indicates unknown
UInt32 totalBytesWritten; // download and upload only
UInt32 leftOverByteCount; // upload and directory list only, number of valid bytes at start of buffer
UInt8 buffer[kMyBufferSize]; // buffer to hold left over bytes
} MyStreamInfo;
static const CFOptionFlags kNetworkEvents =
kCFStreamEventOpenCompleted
| kCFStreamEventHasBytesAvailable
| kCFStreamEventEndEncountered
| kCFStreamEventCanAcceptBytes
| kCFStreamEventErrorOccurred;
/* MyStreamInfoCreate creates a MyStreamInfo 'object' with the specified read and write stream. */
static void MyStreamInfoCreate(MyStreamInfo ** info, CFReadStreamRef readStream, CFWriteStreamRef writeStream)
{
MyStreamInfo * streamInfo;
assert(info != NULL);
assert(readStream != NULL);
// writeStream may be NULL (this is the case for the directory list operation)
streamInfo = malloc(sizeof(MyStreamInfo));
assert(streamInfo != NULL);
streamInfo->readStream = readStream;
streamInfo->writeStream = writeStream;
//streamInfo->proxyDict = NULL; // see discussion of <rdar://problem/3745574> below
streamInfo->fileSize = 0;
streamInfo->totalBytesWritten = 0;
streamInfo->leftOverByteCount = 0;
*info = streamInfo;
}
/* MyStreamInfoDestroy destroys a MyStreamInfo 'object', cleaning up any resources that it owns. */
static void MyStreamInfoDestroy(MyStreamInfo * info)
{
assert(info != NULL);
if (info->readStream) {
CFReadStreamUnscheduleFromRunLoop(info->readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
(void) CFReadStreamSetClient(info->readStream, kCFStreamEventNone, NULL, NULL);
/* CFReadStreamClose terminates the stream. */
CFReadStreamClose(info->readStream);
CFRelease(info->readStream);
}
if (info->writeStream) {
CFWriteStreamUnscheduleFromRunLoop(info->writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
(void) CFWriteStreamSetClient(info->writeStream, kCFStreamEventNone, NULL, NULL);
/* CFWriteStreamClose terminates the stream. */
NSLog(@"FTP Connection will close.");
CFWriteStreamClose(info->writeStream);
CFRelease(info->writeStream);
}
//if (info->proxyDict) {
// CFRelease(info->proxyDict); // see discussion of <rdar://problem/3745574> below
//}
free(info);
}
/* MyCFStreamSetUsernamePassword applies the specified user name and password to the stream. */
static void MyCFStreamSetUsernamePassword(CFTypeRef stream, CFStringRef username, CFStringRef password)
{
Boolean success;
assert(stream != NULL);
assert( (username != NULL) || (password == NULL) );
if (username && CFStringGetLength(username) > 0) {
if (CFGetTypeID(stream) == CFReadStreamGetTypeID()) {
success = CFReadStreamSetProperty((CFReadStreamRef)stream, kCFStreamPropertyFTPUserName, username);
assert(success);
if (password) {
success = CFReadStreamSetProperty((CFReadStreamRef)stream, kCFStreamPropertyFTPPassword, password);
assert(success);
}
} else if (CFGetTypeID(stream) == CFWriteStreamGetTypeID()) {
success = CFWriteStreamSetProperty((CFWriteStreamRef)stream, kCFStreamPropertyFTPUserName, username);
assert(success);
if (password) {
success = CFWriteStreamSetProperty((CFWriteStreamRef)stream, kCFStreamPropertyFTPPassword, password);
assert(success);
}
} else {
assert(false);
}
}
}
/* MyCFStreamSetFTPProxy applies the current proxy settings to the specified stream. This returns a
reference to the proxy dictionary that we used because of <rdar://problem/3745574>, discussed below. */
static void MyCFStreamSetFTPProxy(CFTypeRef stream, CFDictionaryRef * proxyDictPtr)
{
CFDictionaryRef proxyDict;
CFNumberRef passiveMode;
CFBooleanRef isPassive;
Boolean success;
assert(stream != NULL);
assert(proxyDictPtr != NULL);
/* SCDynamicStoreCopyProxies gets the current Internet proxy settings. Then we call
CFReadStreamSetProperty, with property name kCFStreamPropertyFTPProxy, to apply the
settings to the FTP read stream. */
proxyDict = SCDynamicStoreCopyProxies(NULL);
assert(proxyDict != NULL);
/* Get the FTP passive mode setting from the proxy dictionary. Because of a bug <rdar://problem/3625438>
setting the kCFStreamPropertyFTPProxy property using the SCDynamicStore proxy dictionary does not
currently set the FTP passive mode setting on the stream, so we need to do it ourselves.
Also, <rdar://problem/4526438> indicates that out in the real world some people are setting
kSCPropNetProxiesFTPPassive to a Boolean, as opposed to a number. That's just incorrect,
but I've hardened the code against it. */
passiveMode = CFDictionaryGetValue(proxyDict, kSCPropNetProxiesFTPPassive);
if ( (passiveMode != NULL) && (CFGetTypeID(passiveMode) == CFNumberGetTypeID()) ) {
int value;
success = CFNumberGetValue(passiveMode, kCFNumberIntType, &value);
assert(success);
if (value) isPassive = kCFBooleanTrue;
else isPassive = kCFBooleanFalse;
} else {
assert(false);
isPassive = kCFBooleanTrue; // if prefs malformed, we just assume true
}
if (CFGetTypeID(stream) == CFReadStreamGetTypeID()) {
success = CFReadStreamSetProperty((CFReadStreamRef)stream, kCFStreamPropertyFTPProxy, proxyDict);
assert(success);
success = CFReadStreamSetProperty((CFReadStreamRef)stream, kCFStreamPropertyFTPUsePassiveMode, isPassive);
assert(success);
} else if (CFGetTypeID(stream) == CFWriteStreamGetTypeID()) {
success = CFWriteStreamSetProperty((CFWriteStreamRef)stream, kCFStreamPropertyFTPProxy, proxyDict);
assert(success);
success = CFWriteStreamSetProperty((CFWriteStreamRef)stream, kCFStreamPropertyFTPUsePassiveMode, isPassive);
assert(success);
} else {
fprintf(stderr, "This is not a CFStream\n");
}
/* Prior to Mac OS X 10.4, CFFTPStream has a bug <rdar://problem/3745574> that causes it to reference the
proxy dictionary that you applied /after/ it has released its last reference to that dictionary. This causes
a crash. We work around this bug by holding on to our own reference to the proxy dictionary until we're
done with the stream. Thus, our reference prevents the dictionary from being disposed, and thus CFFTPStream
can access it safely. So, rather than release our reference to the proxy dictionary, we pass it back to
our caller and require it to release it. */
// CFRelease(proxyDict); After bug #3745574 is fixed, we'll be able to release the proxyDict here.
*proxyDictPtr = proxyDict;
}
#pragma mark ***** Download Command
/* MyDownloadCallBack is the stream callback for the CFFTPStream during a download operation.
Its main purpose is to read bytes off the FTP stream for the file being downloaded and write
them to the file stream of the destination file. */
static void MyDownloadCallBack(CFReadStreamRef readStream, CFStreamEventType type, void * clientCallBackInfo)
{
MyStreamInfo *info = (MyStreamInfo *)clientCallBackInfo;
CFIndex bytesRead = 0, bytesWritten = 0;
CFStreamError error;
CFNumberRef cfSize;
SInt64 size;
float progress;
assert(readStream != NULL);
assert(info != NULL);
assert(info->readStream == readStream);
switch (type) {
case kCFStreamEventOpenCompleted:
/* Retrieve the file size from the CFReadStream. */
cfSize = CFReadStreamCopyProperty(info->readStream, kCFStreamPropertyFTPResourceSize);
fprintf(stderr, "Open complete\n");
if (cfSize) {
if (CFNumberGetValue(cfSize, kCFNumberSInt64Type, &size)) {
fprintf(stderr, "File size is %" PRId64 "\n", size);
info->fileSize = size;
}
CFRelease(cfSize);
} else {
fprintf(stderr, "File size is unknown\n");
assert(info->fileSize == 0); // It was set up this way by MyStreamInfoCreate.
}
break;
case kCFStreamEventHasBytesAvailable:
/* CFReadStreamRead will return the number of bytes read, or -1 if an error occurs
preventing any bytes from being read, or 0 if the stream's end was encountered. */
bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize);
if (bytesRead > 0) {
/* Just in case we call CFWriteStreamWrite and it returns without writing all
the data, we loop until all the data is written successfully. Since we're writing
to the "local" file system, it's unlikely that CFWriteStreamWrite will return before
writing all the data, but it would be bad if we simply exited the callback because
we'd be losing some of the data that we downloaded. */
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;
} else {
/* If bytesRead < 0, we've hit an error. If bytesRead == 0, we've hit the end of the file.
In either case, we do nothing, and rely on CF to call us with kCFStreamEventErrorOccurred
or kCFStreamEventEndEncountered in order for us to do our clean up. */
}
if (info->fileSize > 0) {
progress = 100*((float)info->totalBytesWritten/(float)info->fileSize);
fprintf(stderr, "\r%.0f%%", progress);
}
break;
case kCFStreamEventErrorOccurred:
error = CFReadStreamGetError(info->readStream);
fprintf(stderr, "CFReadStreamGetError returned (%d, %ld)\n", error.domain, error.error);
goto exit;
case kCFStreamEventEndEncountered:
fprintf(stderr, "\nDownload complete\n");
goto exit;
default:
fprintf(stderr, "Received unexpected CFStream event (%d)", type);
break;
}
return;
exit:
MyStreamInfoDestroy(info);
CFRunLoopStop(CFRunLoopGetCurrent());
return;
}
/* MySimpleDownload implements the download command. It sets up a MyStreamInfo 'object'
with the read stream being an FTP stream of the file to download and the write stream being
a file stream of the destination file. It then returns, and the real work happens
asynchronously in the runloop. The function returns true if the stream setup succeeded,
and false if it failed. */
static Boolean MySimpleDownload(CFStringRef urlString, CFURLRef destinationFolder, CFStringRef username, CFStringRef password)
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamClientContext context = { 0, NULL, NULL, NULL, NULL };
CFURLRef downloadPath, downloadURL;
CFStringRef fileName;
Boolean dirPath, success = true;
MyStreamInfo *streamInfo;
assert(urlString != NULL);
assert(destinationFolder != NULL);
assert( (username != NULL) || (password == NULL) );
/* Returns true if the CFURL path represents a directory. */
dirPath = CFURLHasDirectoryPath(destinationFolder);
if (!dirPath) {
fprintf(stderr, "Download destination must be a directory.\n");
return false;
}
/* Create a CFURL from the urlString. */
downloadURL = CFURLCreateWithString(kCFAllocatorDefault, urlString, NULL);
assert(downloadURL != NULL);
/* Copy the end of the file path and use it as the file name. */
fileName = CFURLCopyLastPathComponent(downloadURL);
assert(fileName != NULL);
/* Create the downloadPath by taking the destination folder and appending the file name. */
downloadPath = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, destinationFolder, fileName, false);
assert(downloadPath != NULL);
CFRelease(fileName);
/* Create a CFWriteStream for the file being downloaded. */
writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, downloadPath);
assert(writeStream != NULL);
CFRelease(downloadPath);
/* CFReadStreamCreateWithFTPURL creates an FTP read stream for downloading from an FTP URL. */
readStream = CFReadStreamCreateWithFTPURL(kCFAllocatorDefault, downloadURL);
assert(readStream != NULL);
CFRelease(downloadURL);
/* Initialize our MyStreamInfo structure, which we use to store some information about the stream. */
MyStreamInfoCreate(&streamInfo, readStream, writeStream);
context.info = (void *)streamInfo;
/* CFWriteStreamOpen will return success/failure. Opening a stream causes it to reserve all the
system resources it requires. If the stream can open non-blocking, this will always return TRUE;
listen to the run loop source to find out when the open completes and whether it was successful. */
success = CFWriteStreamOpen(writeStream);
if (success) {
/* CFReadStreamSetClient registers a callback to hear about interesting events that occur on a stream. */
success = CFReadStreamSetClient(readStream, kNetworkEvents, MyDownloadCallBack, &context);
if (success) {
/* Schedule a run loop on which the client can be notified about stream events. The client
callback will be triggered via the run loop. It's the caller's responsibility to ensure that
the run loop is running. */
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
MyCFStreamSetUsernamePassword(readStream, username, password);
//MyCFStreamSetFTPProxy(readStream, &streamInfo->proxyDict);
/* Setting the kCFStreamPropertyFTPFetchResourceInfo property will instruct the FTP stream
to fetch the file size before downloading the file. Note that fetching the file size adds
some time to the length of the download. Fetching the file size allows you to potentially
provide a progress dialog during the download operation. You will retrieve the actual file
size after your CFReadStream Callback gets called with a kCFStreamEventOpenCompleted event. */
CFReadStreamSetProperty(readStream, kCFStreamPropertyFTPFetchResourceInfo, kCFBooleanTrue);
/* CFReadStreamOpen will return success/failure. Opening a stream causes it to reserve all the
system resources it requires. If the stream can open non-blocking, this will always return TRUE;
listen to the run loop source to find out when the open completes and whether it was successful. */
success = CFReadStreamOpen(readStream);
if (success == false) {
fprintf(stderr, "CFReadStreamOpen failed\n");
MyStreamInfoDestroy(streamInfo);
}
} else {
fprintf(stderr, "CFReadStreamSetClient failed\n");
MyStreamInfoDestroy(streamInfo);
}
} else {
fprintf(stderr, "CFWriteStreamOpen failed\n");
MyStreamInfoDestroy(streamInfo);
}
return success;
}
#pragma mark ***** Upload Command
/* MyUploadCallBack is the stream callback for the CFFTPStream during an upload operation.
Its main purpose is to wait for space to become available in the FTP stream (the write stream),
and then read bytes from the file stream (the read stream) and write them to the FTP stream. */
static void MyUploadCallBack(CFWriteStreamRef writeStream, CFStreamEventType type, void * clientCallBackInfo)
{
MyStreamInfo *info = (MyStreamInfo *)clientCallBackInfo;
CFIndex bytesRead;
CFIndex bytesAvailable;
CFIndex bytesWritten;
CFStreamError error;
BOOL success = NO;
NSString *description = nil;
assert(writeStream != NULL);
assert(info != NULL);
assert(info->writeStream == writeStream);
switch (type) {
case kCFStreamEventOpenCompleted:
fprintf(stderr, "Open complete\n");
break;
case kCFStreamEventCanAcceptBytes:
/* The first thing we do is check to see if there's some leftover data that we read
in a previous callback, which we were unable to upload for whatever reason. */
if (info->leftOverByteCount > 0) {
bytesRead = 0;
bytesAvailable = info->leftOverByteCount;
} else {
/* If not, we try to read some more data from the file. CFReadStreamRead will
return the number of bytes read, or -1 if an error occurs preventing
any bytes from being read, or 0 if the stream's end was encountered. */
bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize);
if (bytesRead < 0) {
fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
success = YES;
description = [NSString stringWithFormat:@"CFReadStreamRead returned %ld\n", bytesRead];
goto exit;
}
bytesAvailable = bytesRead;
}
bytesWritten = 0;
if (bytesAvailable == 0) {
/* We've hit the end of the file being uploaded. Shut everything down.
Previous versions of this sample would terminate the upload stream
by writing zero bytes to the stream. After discussions with CF engineering,
we've decided that it's better to terminate the upload stream by just
closing the stream. */
fprintf(stderr, "\nEnd up uploaded file; closing down\n");
success = YES;
description = @"Successfully uploaded file.";
goto exit;
} else {
/* CFWriteStreamWrite returns the number of bytes successfully written, -1 if an error has
occurred, or 0 if the stream has been filled to capacity (for fixed-length streams).
If the stream is not full, this call will block until at least one byte is written.
However, as we're in the kCFStreamEventCanAcceptBytes callback, we know that at least
one byte can be written, so we won't block. */
bytesWritten = CFWriteStreamWrite(info->writeStream, info->buffer, bytesAvailable);
if (bytesWritten > 0) {
info->totalBytesWritten += bytesWritten;
/* If we couldn't upload all the data that we read, we temporarily store the data in our MyStreamInfo
context until our CFWriteStream callback is called again with a kCFStreamEventCanAcceptBytes event.
Copying the data down inside the buffer is not the most efficient approach, but it makes the code
significantly easier. */
if (bytesWritten < bytesAvailable) {
info->leftOverByteCount = bytesAvailable - bytesWritten;
memmove(info->buffer, info->buffer + bytesWritten, info->leftOverByteCount);
} else {
info->leftOverByteCount = 0;
}
} else if (bytesWritten < 0) {
fprintf(stderr, "CFWriteStreamWrite returned %ld\n", bytesWritten);
/* If CFWriteStreamWrite failed, the write stream is dead. We will clean up
when we get kCFStreamEventErrorOccurred. */
}
}
/* Print a status update if we made any forward progress. */
if ( (bytesRead > 0) || (bytesWritten > 0) ) {
fprintf(stderr, "\rRead %7ld bytes; Wrote %8ld bytes", bytesRead, info->totalBytesWritten);
}
break;
case kCFStreamEventErrorOccurred:
error = CFWriteStreamGetError(info->writeStream);
fprintf(stderr, "CFReadStreamGetError returned (%d, %ld)\n", error.domain, error.error);
success = NO;
description = [NSString stringWithFormat:@"FTPError: %ld", error.error];
goto exit;
case kCFStreamEventEndEncountered:
fprintf(stderr, "\nUpload complete\n");
success = YES;
description = @"Upload Complete";
goto exit;
default:
fprintf(stderr, "Received unexpected CFStream event (%d)", type);
success = YES;
description = [NSString stringWithFormat:@"Received unexpected CFStream event (%d)", type];
break;
}
return;
exit:
MyStreamInfoDestroy(info);
CFRunLoopStop(CFRunLoopGetCurrent());
[uploaderController performSelector:@selector(uploadDidFinish:description:)
withObject:[NSNumber numberWithBool:success]
withObject:description];
return;
}
/* MySimpleUpload implements the upload command. It sets up a MyStreamInfo 'object'
with the read stream being a file stream of the file to upload and the write stream being
an FTP stream of the destination file. It then returns, and the real work happens
asynchronously in the runloop. The function returns true if the stream setup succeeded,
and false if it failed. */
static Boolean MySimpleUpload(CFStringRef uploadDirectory, CFURLRef fileURL, CFStringRef username, CFStringRef password)
{
NSLog(@"MySimpleUpload began");
CFWriteStreamRef writeStream;
CFReadStreamRef readStream;
CFStreamClientContext context = { 0, NULL, NULL, NULL, NULL };
CFURLRef uploadURL, destinationURL;
CFStringRef fileName;
Boolean success = true;
MyStreamInfo *streamInfo;
assert(uploadDirectory != NULL);
assert(fileURL != NULL);
assert( (username != NULL) || (password == NULL) );
/* Create a CFURL from the upload directory string */
destinationURL = CFURLCreateWithString(kCFAllocatorDefault, uploadDirectory, NULL);
assert(destinationURL != NULL);
NSLog(@"created a uploadDirectory string");
/* Copy the end of the file path and use it as the file name. */
fileName = CFURLCopyLastPathComponent(fileURL);
assert(fileName != NULL);
NSLog(@"copied fileURL string");
/* Create the destination URL by taking the upload directory and appending the file name. */
uploadURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, destinationURL, fileName, false);
assert(uploadURL != NULL);
CFRelease(destinationURL);
CFRelease(fileName);
NSLog(@"released some stuff");
/* Create a CFReadStream from the local file being uploaded. */
readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL);
assert(readStream != NULL);
NSLog(@"created a read stream");
/* Create an FTP write stream for uploading operation to a FTP URL. If the URL specifies a
directory, the open will be followed by a close event/state and the directory will have been
created. Intermediary directory structure is not created. */
writeStream = CFWriteStreamCreateWithFTPURL(kCFAllocatorDefault, uploadURL);
assert(writeStream != NULL);
NSLog(@"created a write stream");
CFRelease(uploadURL);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyFTPUsePassiveMode, kCFBooleanFalse);
NSLog(@"set passive mode to false");
/* Initialize our MyStreamInfo structure, which we use to store some information about the stream. */
MyStreamInfoCreate(&streamInfo, readStream, writeStream);
context.info = (void *)streamInfo;
NSLog(@"created stream info");
/* CFReadStreamOpen will return success/failure. Opening a stream causes it to reserve all the
system resources it requires. If the stream can open non-blocking, this will always return TRUE;
listen to the run loop source to find out when the open completes and whether it was successful. */
success = CFReadStreamOpen(readStream);
if (success) {
NSLog(@"successfully opened read stream");
/* CFWriteStreamSetClient registers a callback to hear about interesting events that occur on a stream. */
success = CFWriteStreamSetClient(writeStream, kNetworkEvents, MyUploadCallBack, &context);
if (success) {
NSLog(@"successfully set client for write stream");
/* Schedule a run loop on which the client can be notified about stream events. The client
callback will be triggered via the run loop. It's the caller's responsibility to ensure that
the run loop is running. */
CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
NSLog(@"scheduled callback for run loop");
MyCFStreamSetUsernamePassword(writeStream, username, password);
//MyCFStreamSetFTPProxy(writeStream, &streamInfo->proxyDict);
/* CFWriteStreamOpen will return success/failure. Opening a stream causes it to reserve all the
system resources it requires. If the stream can open non-blocking, this will always return TRUE;
listen to the run loop source to find out when the open completes and whether it was successful. */
success = CFWriteStreamOpen(writeStream);
if (success == false) {
fprintf(stderr, "CFWriteStreamOpen failed\n");
MyStreamInfoDestroy(streamInfo);
}
NSLog(@"successfully opened the write stream");
} else {
fprintf(stderr, "CFWriteStreamSetClient failed\n");
MyStreamInfoDestroy(streamInfo);
}
} else {
fprintf(stderr, "CFReadStreamOpen failed\n");
MyStreamInfoDestroy(streamInfo);
}
return success;
}
#pragma mark ***** List Command
/* MyPrintDirectoryListing prints a FTP directory entry, represented by a CFDictionary
as returned by CFFTPCreateParsedResourceListing, as a single line of text, much like
you'd get from "ls -l". */
static void MyPrintDirectoryListing(CFDictionaryRef dictionary)
{
CFDateRef cfModDate;
CFNumberRef cfType, cfMode, cfSize;
CFStringRef cfOwner, cfName, cfLink, cfGroup;
char owner[256], group[256], name[256];
char permString[12], link[1024];
SInt64 size;
SInt32 mode, type;
assert(dictionary != NULL);
/* You should not assume that the directory entry dictionary will contain all the possible keys.
Most of the time it will, however, depending on the FTP server, some of the keys may be missing. */
cfType = CFDictionaryGetValue(dictionary, kCFFTPResourceType);
if (cfType) {
assert(CFGetTypeID(cfType) == CFNumberGetTypeID());
CFNumberGetValue(cfType, kCFNumberSInt32Type, &type);
cfMode = CFDictionaryGetValue(dictionary, kCFFTPResourceMode);
if (cfMode) {
assert(CFGetTypeID(cfMode) == CFNumberGetTypeID());
CFNumberGetValue(cfMode, kCFNumberSInt32Type, &mode);
/* Converts inode status information into a symbolic string */
strmode(mode + DTTOIF(type), permString);
fprintf(stderr, "%s ", permString);
}
}
cfOwner = CFDictionaryGetValue(dictionary, kCFFTPResourceOwner);
if (cfOwner) {
assert(CFGetTypeID(cfOwner) == CFStringGetTypeID());
CFStringGetCString(cfOwner, owner, sizeof(owner), kCFStringEncodingASCII);
fprintf(stderr, "%9s", owner);
}
cfGroup = CFDictionaryGetValue(dictionary, kCFFTPResourceGroup);
if (cfGroup) {
assert(CFGetTypeID(cfGroup) == CFStringGetTypeID());
CFStringGetCString(cfGroup, group, sizeof(group), kCFStringEncodingASCII);
fprintf(stderr, "%9s", group);
}
cfSize = CFDictionaryGetValue(dictionary, kCFFTPResourceSize);
if (cfSize) {
assert(CFGetTypeID(cfSize) == CFNumberGetTypeID());
CFNumberGetValue(cfSize, kCFNumberSInt64Type, &size);
fprintf(stderr, "%9lld ", size);
}
cfModDate = CFDictionaryGetValue(dictionary, kCFFTPResourceModDate);
if (cfModDate) {
CFLocaleRef locale;
CFDateFormatterRef formatDate;
CFDateFormatterRef formatTime;
CFStringRef cfDate;
CFStringRef cfTime;
char date[256];
char time[256];
assert(CFGetTypeID(cfModDate) == CFDateGetTypeID());
locale = CFLocaleCopyCurrent();
assert(locale != NULL);
formatDate = CFDateFormatterCreate(kCFAllocatorDefault, locale, kCFDateFormatterShortStyle, kCFDateFormatterNoStyle );
assert(formatDate != NULL);
formatTime = CFDateFormatterCreate(kCFAllocatorDefault, locale, kCFDateFormatterNoStyle, kCFDateFormatterShortStyle);
assert(formatTime != NULL);
cfDate = CFDateFormatterCreateStringWithDate(kCFAllocatorDefault, formatDate, cfModDate);
assert(cfDate != NULL);
cfTime = CFDateFormatterCreateStringWithDate(kCFAllocatorDefault, formatTime, cfModDate);
assert(cfTime != NULL);
CFStringGetCString(cfDate, date, sizeof(date), kCFStringEncodingUTF8);
CFStringGetCString(cfTime, time, sizeof(time), kCFStringEncodingUTF8);
fprintf(stderr, "%10s %5s ", date, time);
CFRelease(cfTime);
CFRelease(cfDate);
CFRelease(formatTime);
CFRelease(formatDate);
CFRelease(locale);
}
/* Note that this sample assumes UTF-8 since that's what the Mac OS X
FTP server returns, however, some servers may use a different encoding. */
cfName = CFDictionaryGetValue(dictionary, kCFFTPResourceName);
if (cfName) {
assert(CFGetTypeID(cfName) == CFStringGetTypeID());
CFStringGetCString(cfName, name, sizeof(name), kCFStringEncodingUTF8);
fprintf(stderr, "%s", name);
cfLink = CFDictionaryGetValue(dictionary, kCFFTPResourceLink);
if (cfLink) {
assert(CFGetTypeID(cfLink) == CFStringGetTypeID());
CFStringGetCString(cfLink, link, sizeof(link), kCFStringEncodingUTF8);
if (strlen(link) > 0) fprintf(stderr, " -> %s", link);
}
}
fprintf(stderr, "\n");
}
/* MyDirectoryListingCallBack is the stream callback for the CFFTPStream during a directory
list operation. Its main purpose is to read bytes off the FTP stream, which is returning bytes
of the directory listing, parse them, and 'pretty' print the resulting directory entries. */
static void MyDirectoryListingCallBack(CFReadStreamRef readStream, CFStreamEventType type, void * clientCallBackInfo)
{
MyStreamInfo *info = (MyStreamInfo *)clientCallBackInfo;
CFIndex bytesRead;
CFStreamError error;
CFDictionaryRef parsedDict;
assert(readStream != NULL);
assert(info != NULL);
assert(info->readStream == readStream);
switch (type) {
case kCFStreamEventOpenCompleted:
fprintf(stderr, "Open complete\n");
break;
case kCFStreamEventHasBytesAvailable:
/* When we get here, there are bytes to be read from the stream. There are two cases:
either info->leftOverByteCount is zero, in which case we complete processed the last
buffer full of data (or we're at the beginning of the listing), or
info->leftOverByteCount is non-zero, in which case there are that many bytes at the
start of info->buffer that were left over from the last time that we were called.
By definition, any left over bytes were insufficient to form a complete directory
entry.
In both cases, we just read the next chunk of data from the directory listing stream
and append it to our buffer. We then process the buffer to see if it now contains
any complete directory entries. */
/* CFReadStreamRead will return the number of bytes read, or -1 if an error occurs
preventing any bytes from being read, or 0 if the stream's end was encountered. */
bytesRead = CFReadStreamRead(info->readStream, info->buffer + info->leftOverByteCount, kMyBufferSize - info->leftOverByteCount);
if (bytesRead > 0) {
const UInt8 * nextByte;
CFIndex bytesRemaining;
CFIndex bytesConsumedThisTime;
/* Parse directory entries from the buffer until we either run out of bytes
or we stop making forward progress (indicating that the buffer does not have
enough bytes of valid data to make a complete directory entry). */
nextByte = info->buffer;
bytesRemaining = bytesRead + info->leftOverByteCount;
do
{
/* CFFTPCreateParsedResourceListing parses a line of file or folder listing
of Unix format, and stores the extracted result in a CFDictionary. */
bytesConsumedThisTime = CFFTPCreateParsedResourceListing(NULL, nextByte, bytesRemaining, &parsedDict);
if (bytesConsumedThisTime > 0) {
/* It is possible for CFFTPCreateParsedResourceListing to return a positive number
but not create a parse dictionary. For example, if the end of the listing text
contains stuff that can't be parsed, CFFTPCreateParsedResourceListing returns
a positive number (to tell the calle that it's consumed the data), but doesn't
create a parse dictionary (because it couldn't make sens of the data).
So, it's important that we only try to print parseDict if it's not NULL. */
if (parsedDict != NULL) {
MyPrintDirectoryListing(parsedDict);
CFRelease(parsedDict);
}
nextByte += bytesConsumedThisTime;
bytesRemaining -= bytesConsumedThisTime;
} else if (bytesConsumedThisTime == 0) {
/* This should never happen because we supply a pretty large buffer.
Still, we handle it by leaving the loop, which leaves the remaining
bytes in the buffer. */
} else if (bytesConsumedThisTime == -1) {
fprintf(stderr, "CFFTPCreateParsedResourceListing parse failure\n");
goto exit;
}
} while ( (bytesRemaining > 0) && (bytesConsumedThisTime > 0) );
/* If any bytes were left over, leave them in the buffer for next time. */
if (bytesRemaining > 0) {
memmove(info->buffer, nextByte, bytesRemaining);
}
info->leftOverByteCount = bytesRemaining;
} else {
/* If bytesRead < 0, we've hit an error. If bytesRead == 0, we've hit the end of the
directory listing. In either case, we do nothing, and rely on CF to call us with
kCFStreamEventErrorOccurred or kCFStreamEventEndEncountered in order for us to do our
clean up. */
}
break;
case kCFStreamEventErrorOccurred:
error = CFReadStreamGetError(info->readStream);
fprintf(stderr, "CFReadStreamGetError returned (%d, %ld)\n", error.domain, error.error);
goto exit;
case kCFStreamEventEndEncountered:
fprintf(stderr, "Listing complete\n");
goto exit;
default:
fprintf(stderr, "Received unexpected CFStream event (%d)", type);
break;
}
return;
exit:
MyStreamInfoDestroy(info);
CFRunLoopStop(CFRunLoopGetCurrent());
return;
}
/* MySimpleDirectoryListing implements the directory list command. It sets up a MyStreamInfo
'object' with the read stream being an FTP stream of the directory to list and with no
write stream. It then returns, and the real work happens asynchronously in the runloop.
The function returns true if the stream setup succeeded, and false if it failed. */
static Boolean MySimpleDirectoryListing(CFStringRef urlString, CFStringRef username, CFStringRef password)
{
CFReadStreamRef readStream;
CFStreamClientContext context = { 0, NULL, NULL, NULL, NULL };
CFURLRef downloadURL;
Boolean success = true;
MyStreamInfo *streamInfo;
assert(urlString != NULL);
downloadURL = CFURLCreateWithString(kCFAllocatorDefault, urlString, NULL);
assert(downloadURL != NULL);
/* Create an FTP read stream for downloading operation from an FTP URL. */
readStream = CFReadStreamCreateWithFTPURL(kCFAllocatorDefault, downloadURL);
assert(readStream != NULL);
CFRelease(downloadURL);
/* Initialize our MyStreamInfo structure, which we use to store some information about the stream. */
MyStreamInfoCreate(&streamInfo, readStream, NULL);
context.info = (void *)streamInfo;
/* CFReadStreamSetClient registers a callback to hear about interesting events that occur on a stream. */
success = CFReadStreamSetClient(readStream, kNetworkEvents, MyDirectoryListingCallBack, &context);
if (success) {
/* Schedule a run loop on which the client can be notified about stream events. The client
callback will be triggered via the run loop. It's the caller's responsibility to ensure that
the run loop is running. */
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
MyCFStreamSetUsernamePassword(readStream, username, password);
//MyCFStreamSetFTPProxy(readStream, &streamInfo->proxyDict);
/* CFReadStreamOpen will return success/failure. Opening a stream causes it to reserve all the
system resources it requires. If the stream can open non-blocking, this will always return TRUE;
listen to the run loop source to find out when the open completes and whether it was successful. */
success = CFReadStreamOpen(readStream);
if (success == false) {
fprintf(stderr, "CFReadStreamOpen failed\n");
MyStreamInfoDestroy(streamInfo);
}
} else {
fprintf(stderr, "CFReadStreamSetClient failed\n");
MyStreamInfoDestroy(streamInfo);
}
return success;
}
@implementation FCFileUploaderController
- (id)init
{
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"FCFileUploaderController::init");
#endif
self = [super init];
if (self != nil) {
uploaderController = self;
managedObjectURL = nil;
documentURL = nil;
[[NSRunLoop mainRunLoop] run];
}
return self;
}
- (id)initWithServer:(NSString *)serverName
{
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"FCFileUploaderController::initWithName:server:");
#endif
self = [super init];
uploaderController = self;
// Connect to server
distantObject = [NSConnection rootProxyForConnectionWithRegisteredName:serverName host:nil];
if (distantObject == nil)
{
NSLog(@"Error 1006: Could not connect to FastCast Audition Manager");
}
else
{
[distantObject setProtocolForProxy:@protocol(FCDistributedObjectProtocol)];
connection = [distantObject connectionForProxy];
[distantObject retain];
[self checkForTasks:nil];
[[NSRunLoop mainRunLoop] run];
}
return self;
}
- (void)checkForTasks:(id)userInfo
{
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"FCFileUploaderController::checkForTasks:");
#endif
if ([connection isValid] == NO)
{
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"Connection is not valid.");
#endif
[timer invalidate];
[timer release];
timer = nil;
[self release];
}
request = nil;
request = [[distantObject uploadingTask] copy];
if (request != nil)
{
if (timer != nil)
{
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"Disabling timer.");
#endif
[timer invalidate];
[timer release];
timer = nil;
}
NSLog(@"about to set documentURL and managedObjectURL");
documentURL = [request valueForKey:kFCDocumentURLKey];
managedObjectURL = [request valueForKey:kFCManagedObjectURIKey];
Boolean status;
CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
(CFStringRef)[request valueForKey:kFCFileNameKey],
kCFURLPOSIXPathStyle,
false);
NSLog(@"creating a fileURL");
CFStringRef urlString = (CFStringRef)[NSString stringWithFormat:
@"ftp://%@/%@/",
[request valueForKey:kFCFTPAddressKey],
[request valueForKey:kFCSessionIDKey]];
NSLog(@"creating urlString");
CFStringRef username = (CFStringRef)[request valueForKey:kFCFTPUsernameKey];
CFStringRef password = (CFStringRef)[request valueForKey:kFCFTPPasswordKey];
NSLog(@"got username and password");
NSLog((NSString *)urlString);
/* Start uploading a file to the specified URL destination. */
status = MySimpleUpload(urlString, fileURL, username, password);
NSLog(@"upload should have started");
if (!status)
NSLog(@"Uploading Failed");
CFRelease(fileURL);
NSLog(@"releasing fileURL");
CFRelease(urlString);
NSLog(@"releasing urlString");
if (username) CFRelease(username);
if (password) CFRelease(password);
NSLog(@"released username and password");
if (status == NO)
{
double waitTime = 5.00;
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"Trying again in %i seconds...", [[NSNumber numberWithDouble:waitTime] integerValue]);
#endif
timer = [[NSTimer scheduledTimerWithTimeInterval:waitTime
target:self
selector:@selector(checkForTasks:)
userInfo:nil
repeats:NO] retain];
}
else
{
NSLog(@"about to run loop");
CFRunLoopRun();
[self checkForTasks:nil];
}
}
else
{
double waitTime = 5.00;
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"No tasks available. Checking again in %i seconds...", [[NSNumber numberWithDouble:waitTime] integerValue]);
#endif
timer = [[NSTimer scheduledTimerWithTimeInterval:waitTime
target:self
selector:@selector(checkForTasks:)
userInfo:nil
repeats:NO] retain];
}
}
- (void)uploadDidFinish:(NSNumber *)success
description:(NSString *)description
{
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"FCFileUploaderController::uploadDidFinish:description:");
#endif
[distantObject finishedUploadingForDocument:documentURL
usingFile:managedObjectURL
wasSuccessful:[success boolValue]];
documentURL = nil;
managedObjectURL = nil;
}
- (void)dealloc
{
#if GLOBAL_LOGGING || LOCAL_LOGGING
NSLog(@"FCFileUploaderController::dealloc");
#endif
if (distantObject)
[distantObject release];
if (timer)
[timer release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
@endThis paste has no annotations.