NSURLConnection authentication centralized controller - objective-c

I have several controller managing their own connection and data which are working perfectly. I simply would like to centralize the canAuthenticateAgainstProtectionSpace and didReceiveAuthenticationChallenge to avoid duplicating the code.
I tried to override the class without success.
The class and protocol :
#import <Foundation/Foundation.h>
#protocol SSLPiningDelegate;
#interface SSLPiningDelegate : NSURLConnection <NSURLConnectionDelegate>
{
id<SSLPiningDelegate> delegate;
}
#property (nonatomic,assign) id<SSLPiningDelegate> delegate;
#end
#protocol SSLPiningDelegate <NSURLConnectionDelegate>
#optional
-(id)initWithDelegate:(id)delegate;
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
#end
the code :
#import "SSLPiningDelegate.h"
#interface SSLPiningDelegate ()
{
CFArrayRef caChainArrayRef;
BOOL checkHostname;
}
#end
#implementation SSLPiningDelegate
-(id)initWithDelegate:(id)delegate {
//CGRect rect = [[UIScreen mainScreen] bounds];
if ((self = [super init])) {
_delegate = delegate;
}
return self;
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// TODO manage this correctly
checkHostname = YES;
NSString * derCaPath;
NSMutableArray * chain = [NSMutableArray array];
for(int i=0; i<= 3; i++)
{
if (i==0)
derCaPath = [[NSBundle mainBundle] pathForResource:#"xxx" ofType:#"cer"];
else if (i==1)
derCaPath = [[NSBundle mainBundle] pathForResource:#"comodorsacertificationauthority" ofType:#"cer"];
else if (i==2)
derCaPath = [[NSBundle mainBundle] pathForResource:#"xxx" ofType:#"cer"];
else
derCaPath = [[NSBundle mainBundle] pathForResource:#"xxx" ofType:#"cer"];
NSData *derCA = [NSData dataWithContentsOfFile:derCaPath];
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)derCA);
[chain addObject:(__bridge id)(caRef)];
}
caChainArrayRef = CFBridgingRetain(chain);
NSLog(#"Loading::didReceiveAuthenticationChallenge --- chain of trust length %lu", (unsigned long)[chain count]);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = nil;
SecTrustResultType result = kSecTrustResultInvalid;
OSStatus err = errSecSuccess;
{
NSLog(#"Chain received from the server (working 'up'):");
CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
for(int i = 0; i < certificateCount; i++) {
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
CFStringRef str = SecCertificateCopySubjectSummary(certRef);
NSLog(#" %02i: %#", 1+i, str);
CFRelease(str);
}
NSLog(#"Local Roots we trust:");
for(int i = 0; i < CFArrayGetCount(caChainArrayRef); i++) {
SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(caChainArrayRef, i);
CFStringRef str = SecCertificateCopySubjectSummary(certRef);
NSLog(#" %02i: %#", 1+i, str);
CFRelease(str);
}
}
if (checkHostname) {
// We use the standard Policy of SSL - which also checks hostnames.
// -- see SecPolicyCreateSSL() for details.
//
trust = challenge.protectionSpace.serverTrust;
//
#if DEBUG
NSLog(#"The certificate is expected to match '%#' as the hostname",
challenge.protectionSpace.host);
#endif
}
else {
// Create a new Policy - which goes easy on the hostname.
//
// Extract the chain of certificates provided by the server.
//
CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
NSMutableArray * chain = [NSMutableArray array];
for(int i = 0; i < certificateCount; i++) {
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
[chain addObject:(__bridge id)(certRef)];
}
for(int i = 0; i < CFArrayGetCount(caChainArrayRef); i++) {
SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(caChainArrayRef, i);
[chain addObject:(__bridge id)(certRef)];
}
// And create a bland policy which only checks signature paths.
//
if (err == errSecSuccess)
err = SecTrustCreateWithCertificates((__bridge CFArrayRef)(chain),
SecPolicyCreateBasicX509(), &trust);
#if DEBUG
NSLog(#"The certificate is NOT expected to match the hostname '%#' ",
challenge.protectionSpace.host);
#endif
}
// Explicity specify the list of certificates we actually trust (i.e. those I have hardcoded
// in the app - rather than those provided by some randon server on the internet).
//
if (err == errSecSuccess)
err = SecTrustSetAnchorCertificates(trust,caChainArrayRef);
// And only use above - i.e. do not check the system its global keychain or something
// else the user may have fiddled with.
//
if (err == errSecSuccess)
err = SecTrustSetAnchorCertificatesOnly(trust, YES);
if (err == errSecSuccess)
err = SecTrustEvaluate(trust, &result);
if (err == errSecSuccess) {
switch (result) {
case kSecTrustResultProceed:
// User gave explicit permission to trust this specific
// root at some point (in the past).
//
NSLog(#"GOOD. kSecTrustResultProceed - the user explicitly trusts this CA");
[challenge.sender useCredential:[NSURLCredential credentialForTrust:trust]
forAuthenticationChallenge:challenge];
goto done;
break;
case kSecTrustResultUnspecified:
// The chain is technically valid and matches up to the root
// we provided. The user has not had any say in this though,
// hence it is not a kSecTrustResultProceed.
//
NSLog(#"GOOD. kSecTrustResultUnspecified - So things are technically trusted. But the user was not involved.");
[challenge.sender useCredential:[NSURLCredential credentialForTrust:trust]
forAuthenticationChallenge:challenge];
goto done;
break;
case kSecTrustResultInvalid:
NSLog(#"FAIL. kSecTrustResultInvalid");
break;
case kSecTrustResultDeny:
NSLog(#"FAIL. kSecTrustResultDeny (i.e. user said no explicitly)");
break;
case kSecTrustResultFatalTrustFailure:
NSLog(#"FAIL. kSecTrustResultFatalTrustFailure");
break;
case kSecTrustResultOtherError:
NSLog(#"FAIL. kSecTrustResultOtherError");
break;
case kSecTrustResultRecoverableTrustFailure:
NSLog(#"FAIL. kSecTrustResultRecoverableTrustFailure (i.e. user could say OK, but has not been asked this)");
break;
default:
NSAssert(NO,#"Unexpected result: %d", result);
break;
}
// Reject.
[challenge.sender cancelAuthenticationChallenge:challenge];
goto done;
};
//CFStringRef str =SecCopyErrorMessageString(err,NULL);
//NSLog(#"Internal failure to validate: result %#", str);
//CFRelease(str);
[[challenge sender] cancelAuthenticationChallenge:challenge];
done:
if (!checkHostname)
CFRelease(trust);
return;
}
// In this example we can cancel at this point - as we only do
// canAuthenticateAgainstProtectionSpace against ServerTrust.
//
// But in other situations a more gentle continue may be appropriate.
//
// [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
NSLog(#"Not something we can handle - so we're canceling it.");
[challenge.sender cancelAuthenticationChallenge:challenge];
}
#end
I then declare the controller
#import <UIKit/UIKit.h>
#import "TagUIViewControllerLoading.h"
#import <Foundation/Foundation.h>
#import "SSLPiningDelegate.h"
#interface Loading : TagUIViewControllerLoading <SSLPiningDelegate>
But the delegate is never called.
Any help would be welcomed.

Solved in fact I was trying to delegate at a third level (a controller calling a controller build with a delegate). The code enclosed at the second controller level is working perfectly.

Related

Memory Leak Using IOKit (Adapted From USBPrivateDataSample)

Well. I'm back with another memory leak question. I recently asked a similar question here, but it turns out that code doesn't entirely do what I am trying to accomplish (which is to perform an action whenever any USB device connects/disconnects, and not just when an HID devices does).
I've started over from scratch, and have adapted Apple's USBPrivateDataSample and some of GitHub project ORSSerialPort into something that seems to work just like I want it to -- except --there are memory leaks. :(
I've tried applying the knowledge gained from my previous posts about dealing with memory leaks, but I'm stuck again. What am I missing? Thanks very much in advance.
Here's the code:
**AppDelegate.h:**
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (strong) NSMutableDictionary *deviceDictionary;
#end
**AppDelegate.m:**
#import "AppDelegate.h"
#include <IOKit/usb/IOUSBLib.h>
typedef struct DeviceData
{
io_object_t notification;
CFStringRef deviceName;
CFStringRef serialNum;
} DeviceData;
static IONotificationPortRef gNotifyPort;
static io_iterator_t gAddedIter;
static CFRunLoopRef gRunLoop;
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.deviceDictionary = [[NSMutableDictionary alloc] init];
CFMutableDictionaryRef matchingDict;
CFRunLoopSourceRef runLoopSource;
kern_return_t kr;
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
if (matchingDict == NULL)
{
fprintf(stderr, "IOServiceMatching returned NULL.\n");
}
// Create a notification port and add its run loop event source to our run loop
// This is how async notifications get set up.
gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault);
runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);
gRunLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode);
// Now set up a notification to be called when a device is first matched by I/O Kit.
kr = IOServiceAddMatchingNotification(gNotifyPort, // notifyPort
kIOFirstMatchNotification, // notificationType
matchingDict, // matching
DeviceAdded, // callback
NULL, // refCon
&gAddedIter // notification
);
// Iterate once to get already-present devices and arm the notification
DeviceAdded(NULL, gAddedIter);
// Start the run loop. Now we'll receive notifications.
CFRunLoopRun();
// can't do these, causes EXC_BAD_ACCESS crash
//CFRelease(matchingDict);
//CFRelease(gNotifyPort);
//CFRelease(kr);
CFRelease(runLoopSource);
CFRelease(gRunLoop);
}
void DeviceNotification(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
{
kern_return_t kr;
DeviceData *deviceDataRef = (DeviceData *) refCon;
if (messageType == kIOMessageServiceIsTerminated)
{
kr = IOObjectRelease(deviceDataRef->notification);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
if ((deviceDataRef->serialNum) && (deviceDataRef->deviceName))
{
// remove device from dict
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
NO];
}
free(deviceDataRef);
}
}
void DeviceAdded(void *refCon, io_iterator_t iterator)
{
kern_return_t kr;
io_service_t usbDevice;
while ((usbDevice = IOIteratorNext(iterator)))
{
io_name_t deviceName;
CFStringRef deviceNameCF;
DeviceData *deviceDataRef = NULL;
// Add some app-specific information about this device.
// Create a buffer to hold the data.
deviceDataRef = malloc(sizeof(DeviceData));
bzero(deviceDataRef, sizeof(DeviceData));
// Get the USB device's name.
kr = IORegistryEntryGetName(usbDevice, deviceName);
if (KERN_SUCCESS != kr)
{
deviceName[0] = '\0';
}
CFMutableDictionaryRef usbProperties = 0;
if (IORegistryEntryCreateCFProperties(usbDevice, &usbProperties, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS)
{
IOObjectRelease(usbDevice);
continue;
}
NSDictionary *properties = CFBridgingRelease(usbProperties);
NSString *serialNum = properties[(__bridge NSString *)CFSTR("USB Serial Number")];
NSNumber *builtIn = properties[(__bridge NSString *)CFSTR("Built-In")];
deviceNameCF = CFStringCreateWithCString(kCFAllocatorDefault, deviceName, kCFStringEncodingASCII);
if (builtIn.integerValue != 1)
{
// Save the device's name to our private data.
deviceDataRef->deviceName = deviceNameCF;
deviceDataRef->serialNum = (__bridge CFStringRef)(serialNum);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
YES];
}
// Register for an interest notification of this device being removed. Use a reference to our
// private data as the refCon which will be passed to the notification callback.
kr = IOServiceAddInterestNotification(gNotifyPort, // notifyPort
usbDevice, // service
kIOGeneralInterest, // interestType
DeviceNotification, // callback
deviceDataRef, // refCon
&(deviceDataRef->notification) // notification
);
// Done with this USB device; release the reference added by IOIteratorNext
kr = IOObjectRelease(usbDevice);
}
}
- (void) updateDeviceDict : (NSString *) deviceSerial : (NSString *) deviceName : (bool) keepDevice
{
if (deviceName)
{
if ((!deviceSerial) || ([deviceSerial isEqualToString:#""])) deviceSerial = deviceName;
#try
{
//#throw [[NSException alloc] initWithName:#"test" reason:#"test" userInfo:nil];
// if the device needs to be added
if ((keepDevice) && (deviceSerial))
{
if (![self.deviceDictionary objectForKey:deviceSerial])
{
[self.deviceDictionary setObject:[NSDictionary dictionaryWithObjectsAndKeys:deviceName, #"name", nil] forKey:deviceSerial];
NSLog(#"Device added: %#", deviceName);
}
}
else // if the device needs to be removed
{
[self.deviceDictionary removeObjectForKey:deviceSerial];
NSLog(#"Device removed: %#", deviceName);
}
}
#catch (NSException *exception)
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
#finally
{
NSLog(#"\n%#", self.deviceDictionary);
}
}
else // name is nil
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
}

Implementing SimpleFTPSample in swift

I am trying to convert an Objective-C app to swift.
I am reaching the part where I need to send a file through FTP to a distant server. I was able to to that in Objective-C but only because I had the SimpleFTPSample project that apple made to help the developers.
Right now I have almost complete the conversion between the two language but I am stuck.
I need to convert that code (from SimpleFTPSample+personal):
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
#pragma unused(aStream)
assert(aStream == self.networkStream);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:#"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
[self updateStatus:#"Sending"];
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:#"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:#"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
For now I managed to do that :
// An NSStream delegate callback that's called when events happen on our
// network stream.
func stream(theStream: NSStream!, handleEvent streamEvent: NSStreamEvent) {
switch (streamEvent) {
case NSStreamEvent.OpenCompleted:
println("Opened connection")
case NSStreamEvent.HasBytesAvailable:
println("Not suposed to happend")
case NSStreamEvent.HasSpaceAvailable:
println("Sending")
// If we don't have any data buffered, go read the next chunk of data.
if self.bufferOffset == self.bufferLimit {
var bytesRead: Int
bytesRead = self.fileStream.read(self.buffer, maxLength: 32768)
if bytesRead == -1 { self.stopSendWithStatus("File read error") }
else if bytesRead == 0 { self.stopSendWithStatus(nil) }
else {
self.bufferOffset = 0
self.bufferLimit = size_t(bytesRead)
}
}
// If we're not out of data completely, send the next chunk.
if self.bufferOffset != self.bufferLimit {
var bytesWritten: Int
bytesWritten = self.networkStream.write(&self.buffer[self.bufferOffset], maxLength: self.bufferLimit - self.bufferOffset)
if bytesWritten == -1 { self.stopSendWithStatus("Network write error") }
else { self.bufferOffset += size_t(bytesWritten) }
}
case NSStreamEvent.ErrorOccurred:
self.stopSendWithStatus("Stream open error")
case NSStreamEvent.EndEncountered:
println("ignore")
default:
println("default")
}
}
The kind of the used variables is :
var isSending = Bool()
var networkStream: NSOutputStream! = NSOutputStream()
var fileStream: NSInputStream! = NSInputStream()
var buffer: CConstPointer<UInt8>
var bufferOffset = size_t()
var bufferLimit = size_t()
I am stuck for the if self.bufferOffset != self.bufferLimit part.
Apparently &self.buffer[self.bufferOffset] is not correct (does not have a member name subscript), as well as self.bufferLimit - self.bufferOffset (could not find an overload '-' that accepts the supplied arguments)
To be honest I am not even sure to get what &self.buffer[self.bufferOffset] means. (not the variable but the notation.
So if someone has an idea on how to fixe that, he will be very welcomed !
You can find the SimpleFTPSample project here.
Sorry if my english is not perfect.
Can't you just compile SimpleFTP into a framework and use its methods directly from swift? No need to rewrite everything, Xcode does that for you. I use that for many custom libraries and it works like a charm. Just add target, and put in the required frameworks, include the framework and you're all set!
Here is my plagiarism of apple's SimpleFTPExample to solve my problem of uploading a csv file from an iOS app to a ftp server.
I took the PutController objective-c file, cut lots and lots (too much?) out of it an included it into my swift application.
I modified the parameters passed to the main PutController function, to pass in the data I wanted to send, the URL it has to go to and the username and password to get onto the server.
The cut down PutController.m
#import "PutController.h"
#include <CFNetwork/CFNetwork.h>
enum {
kSendBufferSize = 32768
};
#interface PutController () <NSStreamDelegate>
#property (nonatomic, strong, readwrite) NSOutputStream * networkStream;
#property (nonatomic, strong, readwrite) NSInputStream * fileStream;
#property (nonatomic, assign, readonly ) uint8_t * buffer;
#property (nonatomic, assign, readwrite) size_t bufferOffset;
#property (nonatomic, assign, readwrite) size_t bufferLimit;
#end
#implementation PutController
{
uint8_t _buffer[kSendBufferSize];
}
// Because buffer is declared as an array, you have to use a custom getter.
// A synthesised getter doesn't compile.
- (uint8_t *)buffer
{
return self->_buffer;
}
- (void)startSend:(NSData *)dataToUpload withURL:(NSURL *)toURL withUsername:(NSString *)username andPassword:(NSString *)password
{
printf(__FUNCTION__);
self.fileStream = [NSInputStream inputStreamWithData:dataToUpload];
[self.fileStream open];
// Open a CFFTPStream for the URL.
self.networkStream = CFBridgingRelease(
CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) toURL)
);
[self.networkStream setProperty:username forKey:(id)kCFStreamPropertyFTPUserName];
[self.networkStream setProperty:password forKey:(id)kCFStreamPropertyFTPPassword];
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
}
- (void)stopSendWithStatus:(NSString *)statusString
{
printf(__FUNCTION__);
if (self.networkStream != nil) {
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.networkStream.delegate = nil;
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
printf(__FUNCTION__);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
printf("Opened connection");
} break;
case NSStreamEventHasBytesAvailable: {
printf("should never happen for the output stream");
} break;
case NSStreamEventHasSpaceAvailable: {
printf("Sending");
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:#"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:#"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
#end
Then the PutController.h
#import <UIKit/UIKit.h>
#interface PutController : NSObject
- (void)startSend:(NSData *)dataToUpload withURL:(NSURL *)toURL withUsername:(NSString *)username andPassword:(NSString *)password;
#end
This is the bridging file, that is project wide, to allow the swift application to pick up the objective-C header files. The objective-C code detail can then be mostly ignored. It really is one line.
// PlantRoomChecks-Bridging-Header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "PutController.h"
This is my project file that call the PutController. I have cut it to show just the relevant parts.
// Showing that I am not including any networking libraries
import UIKit
import CoreGraphics
class GraphVC: UIViewController {
var sendFile : PutController = PutController()
let username = "arthur_dent"
let password = "six_times_seven"
func saveData(){
var filename = "ftp://www.domain.co.uk/subdirectory/datafile"
// Converting the messing filename into one that can be used as a URL
let convertedStringForURL = filename.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let uploadUrl = NSURL(string: convertedStringForURL!)
Let dataForFile : NSData = dataFromElseWhere
let dataToUpload = dataForFile.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
// USE THE OBJECTIVE-C VARIABLE
sendFile.startSend(dataToUpload, withURL: uploadUrl, withUsername: username, andPassword: password)
}

Close connection when NSOutputStream has finished

How can a connection be closed when the NSOutputStream has finished sending data?
After searching around i have found that the event NSStreamEventEndEncountered is only called if the server drops the connection. not if the OutputStream has finished the data to send.
StreamStatus is Always returning 0 (connection closed) or 2 (connection open) but never 4 (writing data).
since both methods mentioned above are not telling me enough about the write process i am not able to find a way do determine if the Stream is still writing or if it has finished and i can close the connection now.
After 5 days of googleling and trying i am totally out of ideas... Any help appreciated. Thanks
EDIT ADDED CODE AS REQUESTED:
- (void)startSend:(NSString *)filePath
{
BOOL success;
NSURL * url;
assert(filePath != nil);
assert([[NSFileManager defaultManager] fileExistsAtPath:filePath]);
assert( [filePath.pathExtension isEqual:#"png"] || [filePath.pathExtension isEqual:#"jpg"] );
assert(self.networkStream == nil); // don't tap send twice in a row!
assert(self.fileStream == nil); // ditto
// First get and check the URL.
...
....
.....
// If the URL is bogus, let the user know. Otherwise kick off the connection.
...
....
.....
if ( ! success) {
self.statusLabel.text = #"Invalid URL";
} else {
// Open a stream for the file we're going to send. We do not open this stream;
// NSURLConnection will do it for us.
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
assert(self.fileStream != nil);
[self.fileStream open];
// Open a CFFTPStream for the URL.
self.networkStream = CFBridgingRelease(
CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url)
);
assert(self.networkStream != nil);
if ([self.usernameText.text length] != 0) {
success = [self.networkStream setProperty:self.usernameText.text forKey:(id)kCFStreamPropertyFTPUserName];
assert(success);
success = [self.networkStream setProperty:self.passwordText.text forKey:(id)kCFStreamPropertyFTPPassword];
assert(success);
}
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
///////******** LINE ADDED BY ME TO DISONNECT FROM FTP AFTER CLOSING CONNECTION *********////////////
[self.networkStream setProperty:(id)kCFBooleanFalse forKey:(id)kCFStreamPropertyFTPAttemptPersistentConnection];
///////******** END LINE ADDED BY ME *********////////////
[self.networkStream open];
// Tell the UI we're sending.
[self sendDidStart];
}
}
- (void)stopSendWithStatus:(NSString *)statusString
{
if (self.networkStream != nil) {
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.networkStream.delegate = nil;
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
[self sendDidStopWithStatus:statusString];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
#pragma unused(aStream)
assert(aStream == self.networkStream);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:#"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
[self updateStatus:#"Sending"];
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:#"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:#"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// FOR WHATEVER REASON THIS IS NEVER CALLED!!!!
} break;
default: {
assert(NO);
} break;
}
}
There can be two interpretations to your question. If what you are asking is "I have a NSOutputStream and I'm finished writing to it how do I signal this?" then the answer is as simple as call the close method on it.
Alternately, If what you are really saying is "I have a NSInputStream and I want to know when I've reached the end-of-stream" then you can look at hasBytesAvailable or streamStatus == NSStreamStatusAtEnd.
For your information, to actually get the status NSStreamStatusWriting you would need to be calling the streamStatus method from another thread while this thread is calling write:maxLength:.
--- Edit: Code Suggestion
The reason you would never get notified is that an output stream is never finished (unless it's a fixed size stream, which an FTP stream is not). It's the input stream that gets "finished" at which point you can close your output stream. That's the answer to your original question.
As a further suggestion, I would skip run loop scheduling and the "event processing" except for handling errors on the output stream. Then I would put the read/write code into a NSOperation subclass and send it off into a NSOperationQueue. By keeping a reference to the NSOperations in that queue you would be able to cancel them easily and even show a progress bar by adding a percentComplete property. I've tested the code below and it works. Replace my memory output stream with your FTP output stream. You will notice that I have skipped the validations, which you should keep of course. They should probably be done outside the NSOperation to make it easier to query the user.
#interface NSSendFileOperation : NSOperation<NSStreamDelegate> {
NSInputStream *_inputStream;
NSOutputStream *_outputStream;
uint8_t *_buffer;
}
#property (copy) NSString* sourceFilePath;
#property (copy) NSString* targetFilePath;
#property (copy) NSString* username;
#property (copy) NSString* password;
#end
#implementation NSSendFileOperation
- (void) main
{
static int kBufferSize = 4096;
_inputStream = [NSInputStream inputStreamWithFileAtPath:self.sourceFilePath];
_outputStream = [NSOutputStream outputStreamToMemory];
_outputStream.delegate = self;
[_inputStream open];
[_outputStream open];
_buffer = calloc(1, kBufferSize);
while (_inputStream.hasBytesAvailable) {
NSInteger bytesRead = [_inputStream read:_buffer maxLength:kBufferSize];
if (bytesRead > 0) {
[_outputStream write:_buffer maxLength:bytesRead];
NSLog(#"Wrote %ld bytes to output stream",bytesRead);
}
}
NSData *outputData = [_outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
NSLog(#"Wrote a total of %lu bytes to output stream.", outputData.length);
free(_buffer);
_buffer = NULL;
[_outputStream close];
[_inputStream close];
}
- (void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
// Handle output stream errors such as disconnections here
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSOperationQueue *sendQueue = [[NSOperationQueue alloc] init];
NSSendFileOperation *sendOp = [[NSSendFileOperation alloc] init];
sendOp.username = #"test";
sendOp.password = #"test";
sendOp.sourceFilePath = #"/Users/eric/bin/data/english-words.txt";
sendOp.targetFilePath = #"/Users/eric/Desktop/english-words.txt";
[sendQueue addOperation:sendOp];
[sendQueue waitUntilAllOperationsAreFinished];
}
return 0;
}

Mac-to-bluetooth device file transfer, simple example?

I've spent two days googling and reading the Bluetooth programming guide while trying to piece together a small Mac app that will retrieve images from a drop folder and send any new files to a predetermined device over Bluetooth. There doesn't seem to be many good examples available.
I'm at the point where I'm able to spawn the Bluetooth Service Browser and select the device and its OBEX service, establishing a service and creating a connection, but then nothing more happens. Could anyone please point me in the direction of/show me a simple example that would work?
AppDelegate source code enclosed. Thanks for reading!
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
IOBluetoothServiceBrowserController *browser = [IOBluetoothServiceBrowserController serviceBrowserController:0];
[browser runModal];
//IOBluetoothSDPServiceRecord
IOBluetoothSDPServiceRecord *result = [[browser getResults] objectAtIndex:0];
[self describe:result];
if ([[result.device.name substringToIndex:8] isEqualToString:#"Polaroid"]) {
printer = result.device;
serviceRecord = result;
[self testPrint];
}
else {
NSLog(#"%# is not a valid device", result.device.name);
}
}
- (void) testPrint {
currentFilePath = #"/Users/oyvind/Desktop/_DSC8797.jpg";
[self sendFile:currentFilePath];
}
- (void) sendFile:(NSString *)filePath {
IOBluetoothOBEXSession *obexSession = [[IOBluetoothOBEXSession alloc] initWithSDPServiceRecord:serviceRecord];
if( obexSession != nil )
{
NSLog(#"OBEX Session Established");
OBEXFileTransferServices *fst = [OBEXFileTransferServices withOBEXSession:obexSession];
OBEXDelegate *obxd = [[OBEXDelegate alloc] init];
[obxd setFile:filePath];
[fst setDelegate:obxd];
OBEXError cnctResult = [fst connectToObjectPushService];
if( cnctResult != kIOReturnSuccess ) {
NSLog(#"Error creating connection");
return;
}
else {
NSLog(#"OBEX Session Created. Sending file: %#", filePath);
[fst sendFile:filePath];
[printer openConnection];
}
}
else {
NSLog(#"Error creating OBEX session");
NSLog(#"Error sending file");
}
}
#end
OK; here's what ultimately became the core parts of the functionality. The application I made was a sort of print server for Polaroid instant printers that would only accept images over Object Push.
First, ensure watched folder exists.
/*
Looks for a directory named PolaroidWatchFolder in the user's desktop directory
and creates it if it does not exist.
*/
- (void) ensureWatchedFolderExists {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *url = [NSURL URLWithString:#"PolaroidWatchFolder" relativeToURL:[[fileManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask] objectAtIndex:0]];
BOOL isDir;
if ([fileManager fileExistsAtPath:[url path] isDirectory:&isDir] && isDir) {
[self log:[NSString stringWithFormat:#"Watched folder exists at %#", [url absoluteURL]]];
watchFolderPath = url;
}
else {
NSError *theError = nil;
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:NO attributes:nil error:&theError]) {
[self log:[NSString stringWithFormat:#"Watched folder could not be created at %#", [url absoluteURL]]];
}
else {
watchFolderPath = url;
[self log:[NSString stringWithFormat:#"Watched folder created at %#", [url absoluteURL]]];
}
}
}
Then scan for available printers:
/*
Loops through all paired Bluetooth devices and retrieves OBEX Object Push service records
for each device who's name starts with "Polaroid".
*/
- (void) findPairedDevices {
NSArray *pairedDevices = [IOBluetoothDevice pairedDevices];
devicesTested = [NSMutableArray arrayWithCapacity:0];
for (IOBluetoothDevice *device in pairedDevices)
{
if ([self deviceQualifiesForAddOrRenew:device.name])
{
BluetoothPushDevice *pushDevice = [[BluetoothPushDevice new] initWithDevice:device];
if (pushDevice != nil)
{
[availableDevices addObject:pushDevice];
[pushDevice testConnection];
}
}
}
}
That last function call is to the BluetoothPushDevice's built-in method to test the connection. Here is the delegate handler for the response:
- (void) deviceStatusHandler: (NSNotification *)notification {
BluetoothPushDevice *device = [notification object];
NSString *status = [[notification userInfo] objectForKey:#"message"];
if ([devicesTested count] < [availableDevices count] && ![devicesTested containsObject:device.name]) {
[devicesTested addObject:device.name];
}
}
Upon server start, this method will run in response to a timer tick or manual scan:
- (void) checkWatchedFolder {
NSError *error = nil;
NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];
NSArray *files = [[NSFileManager defaultManager]
contentsOfDirectoryAtURL:watchFolderPath
includingPropertiesForKeys:properties
options:(NSDirectoryEnumerationSkipsHiddenFiles)
error:&error];
if (files == nil) {
[self log:#"Error reading watched folder"];
return;
}
if ([files count] > 0) {
int newFileCount = 0;
for (NSURL *url in files) {
if (![filesInTransit containsObject:[url path]]) {
NSLog(#"New file: %#", [url lastPathComponent]);
[self sendFile:[url path]];
newFileCount++;
}
}
}
}
When new files are found, ww first need to find a device that is not busy recieving a file of printing it:
/*
Loops through all discovered device service records and returns the a new OBEX session for
the first it finds that is not connected (meaning it is not currently in use, connections are
ad-hoc per print).
*/
- (BluetoothPushDevice*) getIdleDevice {
for (BluetoothPushDevice *device in availableDevices) {
if ([device.status isEqualToString:kBluetoothDeviceStatusReady]) {
return device;
}
}
return nil;
}
Then a file is sent with this method:
- (void) sendFile:(NSString *)filePath {
BluetoothPushDevice *device = [self getIdleDevice];
if( device != nil ) {
NSLog(#"%# is available", device.name);
if ([device sendFile:filePath]) {
[self log:[NSString stringWithFormat:#"Sending file: %#", filePath]];
[filesInTransit addObject:filePath];
}
else {
[self log:[NSString stringWithFormat:#"Error sending file: %#", filePath]];
}
}
else {
NSLog(#"No idle devices");
}
}
Upon transfer complete, this delegate method is called:
/*
Responds to BluetoothPushDevice's TransferComplete notification
*/
- (void) transferStatusHandler: (NSNotification *) notification {
NSString *status = [[notification userInfo] objectForKey:#"message"];
NSString *file = ((BluetoothPushDevice*)[notification object]).file;
if ([status isEqualToString:kBluetoothTransferStatusComplete]) {
if ([filesInTransit containsObject:file]) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
[fileManager removeItemAtPath:file error:&error];
if (error != nil) {
[self log:[NSString stringWithFormat:#"**ERROR** File %# could not be deleted (%#)", file, error.description]];
}
[self log:[NSString stringWithFormat:#"File deleted: %#", file]];
[filesInTransit removeObject:file];
}
else {
[self log:[NSString stringWithFormat:#"**ERROR** filesInTransit array does not contain file %#", file]];
}
}
[self updateDeviceStatusDisplay];
}
I hope this helps someone!

Pick Music from iOS Library and Send / Save

I have a query regarding Music Files.
I want to select 1/more Music files from the Music Library of iPhone/iTouch/iPad and Save in my Documents Folder or Send it to Server.
I roughly went through MPMediaPickerController and AddMusic(Sample Code).
But, I could only get details of selecting Songs and Playing it.
Is it possible to Save those selected Songs, which I can also use to Send to Server ?
Thanks
Yes, it is possible.
I think it will help you
// Have to add this framework
//CoreMedia.framework
//AudioToolbox.framework
//CoreAudio.framework
//MediaPlayer.framework
//AVFoundation.framework
//in UploadAudioViewController.h file
#import <UIKit/UIKit.h>
#import <CoreAudio/CoreAudioTypes.h>
#import <AudioToolbox/AudioToolbox.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
#interface UploadAudioViewController : UIViewController<UIActionSheetDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate,MPMediaPickerControllerDelegate,AVAudioRecorderDelegate, AVAudioPlayerDelegate,AVAudioSessionDelegate>{
MPMediaItem *song;
NSURL *exportURL;
}
#property (nonatomic, retain) NSData *audioData;
// in UploadAudioViewController.m file
#import "UploadAudioViewController.h"
//#import < AudioToolbox/AudioToolbox.h>
#interface UploadAudioViewController ()
#end
#implementation UploadAudioViewController
#synthesize musicPlayer,audioData;
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.title = #"Upload Audio";
self.musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
self.audioData=nil;
}
#pragma mark Browse Audio from Device
-(void)PickAudioForIndex_iPhone
{
if ([[[UIDevice currentDevice] model] isEqualToString:#"iPhone Simulator"]) {
//device is simulator
UIAlertView *alert1;
alert1 = [[UIAlertView alloc] initWithTitle:#"Alert" message:#"There is no Audio file in the Device" delegate:self cancelButtonTitle:nil otherButtonTitles:#"Ok",nil];
alert1.tag=2;
[alert1 show];
//[alert1 release],alert1=nil;
}else{
MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic];
mediaPicker.delegate = self;
mediaPicker.allowsPickingMultipleItems = NO; // this is the default
[self presentViewController:mediaPicker animated:YES completion:nil];
}
}
#pragma mark Media picker delegate methods
-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection {
// We need to dismiss the picker
[self dismissViewControllerAnimated:YES completion:nil];
// Assign the selected item(s) to the music player and start playback.
if ([mediaItemCollection count] < 1) {
return;
}
song = [[mediaItemCollection items] objectAtIndex:0];
[self handleExportTapped];
}
-(void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker {
// User did not select anything
// We need to dismiss the picker
[self dismissViewControllerAnimated:YES completion:nil ];
}
-(void)handleExportTapped{
// get the special URL
if (! song) {
return;
}
//[self startLoaderWithLabel:#"Preparing for upload..."];
NSURL *assetURL = [song valueForProperty:MPMediaItemPropertyAssetURL];
AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil];
NSLog (#"Core Audio %# directly open library URL %#",
coreAudioCanOpenURL (assetURL) ? #"can" : #"cannot",
assetURL);
NSLog (#"compatible presets for songAsset: %#",
[AVAssetExportSession exportPresetsCompatibleWithAsset:songAsset]);
/* approach 1: export just the song itself
*/
AVAssetExportSession *exporter = [[AVAssetExportSession alloc]
initWithAsset: songAsset
presetName: AVAssetExportPresetAppleM4A];
NSLog (#"created exporter. supportedFileTypes: %#", exporter.supportedFileTypes);
exporter.outputFileType = #"com.apple.m4a-audio";
NSString *exportFile = [myDocumentsDirectory() stringByAppendingPathComponent: #"exported.m4a"];
// end of approach 1
// set up export (hang on to exportURL so convert to PCM can find it)
myDeleteFile(exportFile);
//[exportURL release];
exportURL = [NSURL fileURLWithPath:exportFile];
exporter.outputURL = exportURL;
// do the export
[exporter exportAsynchronouslyWithCompletionHandler:^{
int exportStatus = exporter.status;
switch (exportStatus) {
case AVAssetExportSessionStatusFailed: {
// log error to text view
NSError *exportError = exporter.error;
NSLog (#"AVAssetExportSessionStatusFailed: %#", exportError);
//errorView.text = exportError ? [exportError description] : #"Unknown failure";
//errorView.hidden = NO;
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusCompleted: {
NSLog (#"AVAssetExportSessionStatusCompleted");
//fileNameLabel.text = [exporter.outputURL lastPathComponent];
// set up AVPlayer
//[self setUpAVPlayerForURL: exporter.outputURL];
///////////////// get audio data from url
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
NSURL *audioUrl = exportURL;
NSLog(#"Audio Url=%#",audioUrl);
self.audioData = [NSData dataWithContentsOfURL:audioUrl];
break;
}
case AVAssetExportSessionStatusUnknown: {
NSLog (#"AVAssetExportSessionStatusUnknown");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusExporting: {
NSLog (#"AVAssetExportSessionStatusExporting");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusCancelled: {
NSLog (#"AVAssetExportSessionStatusCancelled");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
case AVAssetExportSessionStatusWaiting: {
NSLog (#"AVAssetExportSessionStatusWaiting");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
default: {
NSLog (#"didn't get export status");
//[self stopLoader];
//[self showAlertWithMessage:#"There ia an error!"];
break;
}
}
}];
}
#pragma mark conveniences
NSString* myDocumentsDirectory(){
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];;
}
void myDeleteFile (NSString* path){
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSError *deleteErr = nil;
[[NSFileManager defaultManager] removeItemAtPath:path error:&deleteErr];
if (deleteErr) {
NSLog (#"Can't delete %#: %#", path, deleteErr);
}
}
}
// generic error handler from upcoming "Core Audio" book (thanks, Kevin!)
// if result is nonzero, prints error message and exits program.
static void CheckResult(OSStatus result, const char *operation)
{
if (result == noErr) return;
char errorString[20];
// see if it appears to be a 4-char-code
*(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(result);
if (isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4])) {
errorString[0] = errorString[5] = '\'';
errorString[6] = '\0';
} else
// no, format it as an integer
sprintf(errorString, "%d", (int)result);
fprintf(stderr, "Error: %s (%s)\n", operation, errorString);
exit(1);
}
#pragma mark core audio test
BOOL coreAudioCanOpenURL (NSURL* url){
OSStatus openErr = noErr;
AudioFileID audioFile = NULL;
openErr = AudioFileOpenURL((__bridge CFURLRef) url,
kAudioFileReadPermission ,
0,
&audioFile);
if (audioFile) {
AudioFileClose (audioFile);
}
return openErr ? NO : YES;
}
I have tried and succeed in exporting the ipod library song . Please find my code below:
extension AudioPostViewController: MPMediaPickerControllerDelegate {
func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
dismissViewControllerAnimated(true, completion: {
print("You selected \(mediaItemCollection)")
let item: MPMediaItem = mediaItemCollection.items[0]
let pathURL: NSURL? = item.valueForProperty(MPMediaItemPropertyAssetURL) as? NSURL
if pathURL == nil {
Alert.showPopupWithMessage("Unable to read DRM protected file.")
return
}
let title = item.valueForProperty(MPMediaItemPropertyTitle) as? String ?? "Now Playing..."
print("\(pathURL), title : \(title) ")
// get file extension andmime type
let str = pathURL!.absoluteString
let str2 = str.stringByReplacingOccurrencesOfString("ipod-library://item/item", withString: "")
let arr = str2.componentsSeparatedByString("?")
var mimeType = arr[0]
mimeType = mimeType.stringByReplacingOccurrencesOfString(".", withString: "")
// Export the ipod library as .m4a file to local directory for remote upload
let exportSession = AVAssetExportSession(asset: AVAsset(URL: pathURL!), presetName: AVAssetExportPresetAppleM4A)
exportSession?.shouldOptimizeForNetworkUse = true
exportSession?.outputFileType = AVFileTypeAppleM4A
FilePath.removeAudioFile() // Remove file if it exists
let fileUrl = FilePath.testFilePathURL()
exportSession?.outputURL = fileUrl
exportSession?.exportAsynchronouslyWithCompletionHandler({ () -> Void in
if exportSession!.status == AVAssetExportSessionStatus.Completed {
dispatch_async(dispatch_get_main_queue(), {
// Prepare audio to play
self.audioTitle = title
self.audioPath = fileUrl
self.mimeType = "audio/m4a"
self.audioFileName = "audio.m4a"
UIView.animateWithDuration(0.45, animations: {
if Platform.DeviceType.iPhone4 {
self.libraryBottomSpacing.constant = 5
self.view.layoutIfNeeded()
} else {
self.libraryBottomSpacing.constant = 25
self.view.layoutIfNeeded()
}
}, completion: {
(v: Bool) in
self.loadJukeBox(self.audioPath!, title: title)
})
})
} else {
dispatch_async(dispatch_get_main_queue(), {
Alert.showPopupWithMessage("Unable to read file as its DRM protected.")
})
}
})
})
}
func mediaPickerDidCancel(mediaPicker: MPMediaPickerController) {
dismissViewControllerAnimated(true, completion: nil)
}
}
Is it possible to save those selected songs, which I can also use to send to server?
No, you can only play songs from the iTunes library.
The raw audio files are not accessible, probably due to copyright protection.