I setup everything to run some code in the background when my app isn't active. I use BGProcessingTask.
When I Pause the program execution, and enter the command e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:#"com.myApp.processing"]
it tests the Process and runs my code, but it doesn't finish the job when it reaches the code to decrypt my data. I use RNCryptor to decrypt the data, but it seems like it doesn't want to run when my app is in the background.
This is the code I run:
- (void)nvmDoubleDecryptedAES256DataWithSecurityKey:(nonnull NSString*)theSecurityKey WithMasterPasswordKey:(nonnull NSString*)theMasterPassword WithCompletion:(void (^_Nullable)(NSData* _Nullable decryptedData, NSError* _Nullable error))completionBlock {
NSError *firstStatus, *secondStatus;
NSLog(#"AR - 111");
NSData *mpDecryptedData = [RNDecryptor decryptData:self withPassword:theMasterPassword error:&firstStatus];
NSLog(#"AR - 112");
if ((mpDecryptedData) && (!firstStatus)) {
NSData *skDecryptedData = [RNDecryptor decryptData:mpDecryptedData withPassword:theSecurityKey error:&secondStatus];
NSLog(#"AR - 113");
if ((skDecryptedData) && (!secondStatus)) {
if (completionBlock) completionBlock(skDecryptedData, nil);
}
else {
NSError *errorSecurityKeyIncorrect = [NSError errorWithDomain:[NVMFunctions getBundleIdentifier] code:PRLErrorCodeSecurityKeyIncorrect userInfo:#{NSLocalizedDescriptionKey:[NSString stringWithFormat:#"Security Key is incorrect | Decrypt error: %#", firstStatus]}]; //Keep this error code
if (completionBlock) completionBlock(nil, errorSecurityKeyIncorrect);
}
}
else {
NSError *errorMasterPasswordIncorrect = [NSError errorWithDomain:[NVMFunctions getBundleIdentifier] code:PRLErrorCodeMasterPasswordKeyIncorrect userInfo:#{NSLocalizedDescriptionKey:[NSString stringWithFormat:#"Master Password is incorrect | Decrypt error: %#", secondStatus]}]; //Keep this error code
if (completionBlock) completionBlock(nil, errorMasterPasswordIncorrect);
}
}
But in the logs I see i keeps hanging on AR - 111 . As soon as i reopen my app is continues with AR - 112. It seems like a big issue if I can't decrypt my data in the background...
Does anyone know a fix?
Full logs:
(lldb) e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:#"com.novem.Peral.processing"]
2020-06-03 21:38:06.120568+0200 Peral[14608:3145844] Simulating launch for task with identifier com.novem.Peral.processing
2020-06-03 21:38:14.539735+0200 Peral[14608:3146386] Starting simulated task: <BGProcessingTask: com.novem.Peral.processing>
2020-06-03 21:38:14.580935+0200 Peral[14608:3146383] AR - 111
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self registerBackgroundTaks];
return YES;
}
- (void)registerBackgroundTaks {
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:#"com.novem.Peral.processing" usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) {
[self handleAppRefresh:task];
}];
}
- (void)handleAppRefresh:(BGProcessingTask *)task {
[self scheduleAppRefresh];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock: ^{
NSString *theSecurityKey = #"key";
NSString *theMasterPassword = #"passord";
[self nvmDoubleDecryptedAES256DataWithSecurityKey:theSecurityKey WithMasterPasswordKey:theMasterPassword WithCompletion:^(NSData * _Nullable decryptedData, NSError * _Nullable error) {
[task setTaskCompletedWithSuccess:!queue.isSuspended];
}];
}];
[task setExpirationHandler:^{
[queue cancelAllOperations];
}];
}
- (void)scheduleAppRefresh {
BGProcessingTaskRequest *request = [[BGProcessingTaskRequest alloc] initWithIdentifier:#"com.novem.Peral.processing"];
[request setRequiresExternalPower:YES];
[request setRequiresNetworkConnectivity:YES];
//[request setEarliestBeginDate:[NSDate dateWithTimeIntervalSinceNow:2 * 60]]; //Start after 2 minutes
NSError *requestError;
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&requestError];
if (requestError) NSLog(#"Exception: %#", requestError);
}
EDIT
After looking at the RNCryptor, if found out that it freezes when reaching a certain line.
+ (NSData *)keyForPassword:(NSString *)password salt:(NSData *)salt settings:(RNCryptorKeyDerivationSettings)keySettings
{
NSLog(#"RN - 124831");
NSMutableData *derivedKey = [NSMutableData dataWithLength:keySettings.keySize];
NSLog(#"RN - 124832");
// See Issue #77. V2 incorrectly calculated key for multi-byte characters.
NSData *passwordData;
NSLog(#"RN - 124833");
if (keySettings.hasV2Password) {
NSLog(#"RN - 1248331");
passwordData = [NSData dataWithBytes:[password UTF8String] length:[password length]];
}
else {
NSLog(#"RN - 1248332");
passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
}
// Use the built-in PBKDF2 if it's available. Otherwise, we have our own. Hello crazy function pointer.
NSLog(#"RN - 124834");
int result;
NSLog(#"RN - 124835");
int (*PBKDF)(CCPBKDFAlgorithm algorithm, const char *password, size_t passwordLen,
const uint8_t *salt, size_t saltLen,
CCPseudoRandomAlgorithm prf, uint rounds,
uint8_t *derivedKey, size_t derivedKeyLen);
NSLog(#"RN - 124836");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
PBKDF = CCKeyDerivationPBKDF ?: RN_CCKeyDerivationPBKDF;
#pragma clang diagnostic pop
NSLog(#"RN - 124837");
result = PBKDF(keySettings.PBKDFAlgorithm, // algorithm
passwordData.bytes, // password
passwordData.length, // passwordLength
salt.bytes, // salt
salt.length, // saltLen
keySettings.PRF, // PRF
keySettings.rounds, // rounds
derivedKey.mutableBytes, // derivedKey
derivedKey.length); // derivedKeyLen
NSLog(#"RN - 124838");
// Do not log password here
NSAssert(result == kCCSuccess, #"Unable to create AES key for password: %d", result);
return derivedKey;
}
This is the log I get:
2020-06-04 21:37:32.238187+0200 Peral[16824:3659587] RN - 124831
2020-06-04 21:37:32.238507+0200 Peral[16824:3659587] RN - 124832
2020-06-04 21:37:32.238818+0200 Peral[16824:3659587] RN - 124833
2020-06-04 21:37:32.239046+0200 Peral[16824:3659587] RN - 1248332
2020-06-04 21:37:32.239419+0200 Peral[16824:3659587] RN - 124834
2020-06-04 21:37:32.239663+0200 Peral[16824:3659587] RN - 124835
2020-06-04 21:37:32.240287+0200 Peral[16824:3659587] RN - 124836
2020-06-04 21:37:32.240575+0200 Peral[16824:3659587] RN - 124837
so it freezes with result = PBKDF....
Related
i'm new to objective-c, please bear with me if i ask stupid questions :)
The following is part of code i have to start vpn tunnel, but keeps getting EXC_BAD_ACCESS error
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL * error))completionHandler {
vpnAdapter = [[OpenAdapter alloc] init];
vpnAdapter.delegate = self;
// get config
config = [[NSDictionary alloc] init];
NETunnelProviderProtocol *protocol = (NETunnelProviderProtocol *)self.protocolConfiguration;
config = protocol.providerConfiguration;
host = config[#"server"];
// Load config data
username = config[#"username"];
password = config[#"password"];
if(option != nil){
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
// return success;
completionHandler(&success); // Thread 2: EXC_BAD_ACCESS (code=1, address=0xbcc68f020)
}];
}else{
[vpnAdapter connect:host user:username pass:password add:NO completionHandler:^(BOOL success){
completionHandler(&success);
}];
}
}
here is connect method
- (void)connect: (NSString *) host user:(NSString *)username pass:(NSString *) password add:(Boolean) isAdd completionHandler:(void (^)(BOOL success)) completionHandler{
dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
dispatch_queue_t connectQueue = dispatch_queue_create("me.ss-abramchuk.open-adapter.connection", attributes);
dispatch_async(connectQueue, ^{
// Call connect
//int ret=1;
NSArray* options = [NSArray arrayWithObjects:
#"--user", username,
host,
nil];
if(isAdd){
options = [NSArray arrayWithObjects:
#"--user", username,
#"--protocol", #"ad",
host,
nil];
}
//NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
//NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:1+[options count]];
[arguments addObject:#"connect"];
[arguments addObjectsFromArray:options];
int argc = [arguments count];
char **argv = (char **)malloc(sizeof(char*) * (argc + 1));
[arguments enumerateObjectsUsingBlock:^(NSString *option, NSUInteger i, BOOL *stop) {
const char * c_string = [option UTF8String];
int length = (int)strlen(c_string);
char *c_string_copy = (char *) malloc(sizeof(char) * (length + 1));
strcpy(c_string_copy, c_string);
argv[i] = c_string_copy;
}];
argv[argc] = NULL;
const char *cfPass=[password UTF8String];
int ret = self.vpnClient->start2connect(argc, argv, cfPass);
BOOL result;
if (ret!=0){
result=false;
}
else {result = true;}
completionHandler(result);
});
}
all of these are from networkextension and while debugging, i found int ret = self.vpnClient->start2connect(argc, argv, cfPass);
seems not returning any value.
however, i confirmed that the start2connect method does return int value
so for now, anyone can help explain what's wrong?
thanks
The BOOL * is a pointer to a BOOL. We don’t use that pattern very often. We use it where the block needs to update a BOOL property somewhere, e.g. in enumerateMatchesinString, where you can update the boolean that stop points to in order to stop the enumeration.
But this is a completion handler, so there’s no point in passing a pointer to the boolean (one that presumably was on the stack, inviting problems). Just pass the boolean itself, not a pointer to it.
I would suggest that instead of:
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL * error))completionHandler {
...
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
completionHandler(&success);
}];
...
}
That you want:
- (void)startTunnelWithOptions:(NSDictionary *)options completionHandler:(void (^)(BOOL success))completionHandler {
...
[vpnAdapter connect:host user:username pass:password add:YES completionHandler:^(BOOL success){
completionHandler(success);
}];
...
}
Note the block parameter isn’t BOOL * error but rather BOOL success and when it calls the completionHandler, there’s not & before success.
If there’s some reason you needed to update the BOOL, then that’s a different matter, but it doesn’t make sense in the context of a completion handler.
I'm relatively new to objective-c and hardly know much of swift.
I've been trying to make an app which will implement simpleAuth in order to create a link to the ForsquareWeb API.
I'm using cocoapods and have imported the SimpleAuth related files into my product.
Every file seems to be fine except the SimpleAuth target, specifically the SimpleAuthForSquareWebProvider.m file. This is what the file itself looks like;
//
// SimpleAuthFoursquareWebProvider.m
// SimpleAuth
//
// Created by Julien Seren-Rosso on 23/01/2014.
// Copyright (c) 2014 Byliner, Inc. All rights reserved.
//
#import "SimpleAuthFoursquareWebProvider.h"
#import "SimpleAuthFoursquareWebLoginViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
#import "UIViewController+SimpleAuthAdditions.h"
#implementation SimpleAuthFoursquareWebProvider
#pragma mark - SimpleAuthProvider
+ (NSString *)type {
return #"foursquare-web";
}
+ (NSDictionary *)defaultOptions {
// Default present block
SimpleAuthInterfaceHandler presentBlock = ^(UIViewController *controller) {
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:controller];
navigation.modalPresentationStyle = UIModalPresentationFormSheet;
UIViewController *presented = [UIViewController SimpleAuth_presentedViewController];
[presented presentViewController:navigation animated:YES completion:nil];
};
// Default dismiss block
SimpleAuthInterfaceHandler dismissBlock = ^(id controller) {
[controller dismissViewControllerAnimated:YES completion:nil];
};
NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:[super defaultOptions]];
options[SimpleAuthPresentInterfaceBlockKey] = presentBlock;
options[SimpleAuthDismissInterfaceBlockKey] = dismissBlock;
return options;
}
- (void)authorizeWithCompletion:(SimpleAuthRequestHandler)completion {
[[[self accessToken]
flattenMap:^RACStream *(NSString *response) {
NSArray *signals = #[
[self accountWithAccessToken:response],
[RACSignal return:response]
];
return [self rac_liftSelector:#selector(dictionaryWithAccount:accessToken:) withSignalsFromArray:signals];
}]
subscribeNext:^(NSDictionary *response) {
completion(response, nil);
}
error:^(NSError *error) {
completion(nil, error);
}];
}
#pragma mark - Private
- (RACSignal *)accessToken {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
dispatch_async(dispatch_get_main_queue(), ^{
SimpleAuthFoursquareWebLoginViewController *login = [[SimpleAuthFoursquareWebLoginViewController alloc] initWithOptions:self.options];
login.completion = ^(UIViewController *login, NSURL *URL, NSError *error) {
SimpleAuthInterfaceHandler dismissBlock = self.options[SimpleAuthDismissInterfaceBlockKey];
dismissBlock(login);
// Parse URL
NSString *fragment = [URL fragment];
NSDictionary *dictionary = [CMDQueryStringSerialization dictionaryWithQueryString:fragment];
NSString *token = dictionary[#"access_token"];
// Check for error
if (![token length]) {
[subscriber sendError:error];
return;
}
// Send completion
[subscriber sendNext:token];
[subscriber sendCompleted];
};
SimpleAuthInterfaceHandler block = self.options[SimpleAuthPresentInterfaceBlockKey];
block(login);
});
return nil;
}];
}
- (RACSignal *)accountWithAccessToken:(NSString *)accessToken {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSDictionary *parameters = #{ #"oauth_token" : accessToken };
NSString *query = [CMDQueryStringSerialization queryStringWithDictionary:parameters];
NSString *URLString = [NSString stringWithFormat:#"https://api.foursquare.com/v2/users/self?v=20140210&%#", query];
NSURL *URL = [NSURL URLWithString:URLString];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request queue:self.operationQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 99)];
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if ([indexSet containsIndex:statusCode] && data) {
NSError *parseError = nil;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&parseError];
if (dictionary) {
[subscriber sendNext:dictionary];
[subscriber sendCompleted];
}
else {
[subscriber sendError:parseError];
}
}
else {
[subscriber sendError:connectionError];
}
}];
return nil;
}];
}
- (NSDictionary *)dictionaryWithAccount:(NSDictionary *)account accessToken:(NSString *)accessToken {
NSMutableDictionary *dictionary = [NSMutableDictionary new];
NSDictionary *userData = account[#"response"][#"user"];
// Provider
dictionary[#"provider"] = [[self class] type];
// Credentials
dictionary[#"credentials"] = #{
#"token" : accessToken
};
// User ID
dictionary[#"uid"] = userData[#"id"];
// Raw response
dictionary[#"extra"] = #{
#"raw_info" : userData
};
// User info
NSMutableDictionary *user = [NSMutableDictionary new];
if (userData[#"contact"][#"email"]) {
user[#"email"] = userData[#"contact"][#"email"];
}
if (userData[#"firstName"]) {
user[#"first_name"] = userData[#"firstName"];
}
if (userData[#"lastName"]) {
user[#"last_name"] = userData[#"lastName"];
}
user[#"name"] = [NSString stringWithFormat:#"%# %#", user[#"first_name"], user[#"last_name"]];
user[#"gender"] = userData[#"gender"];
if ([userData[#"photo"] isKindOfClass:NSDictionary.class]) {
user[#"image"] = [NSString stringWithFormat:#"%#500x500%#", userData[#"photo"][#"prefix"], userData[#"photo"][#"suffix"]];
} else if ([userData[#"photo"] isKindOfClass:NSString.class]) {
user[#"image"] = userData[#"photo"];
}
if (userData[#"photo"]) {
user[#"photo"] = userData[#"photo"];
}
if (userData[#"homeCity"]) {
NSString *homecity = [[userData[#"homeCity"] componentsSeparatedByString:#","] firstObject];
user[#"location"] = homecity;
}
user[#"urls"] = #{
#"Foursquare" : [NSString stringWithFormat:#"https://foursquare.com/user/%#", userData[#"id"]],
};
dictionary[#"info"] = user;
return dictionary;
}
#end
I think it would be easier to show you just a screen shot of the errors and where they're arising in the code itself;
I would really appreciate some insight into where the problem lies. I'm not sure why many of the errors say use of undeclared identifiers, are the imported files not correct?
After trying to re-install the pod file as it was suggested a class hadn't been installed I still have the same problem. Here are screen shots of my podfile and the terminals output when installing the pods;
I just used the Cocoapods application rather than terminal. I got additional information when clicking install.
"[!] The dPicDid [Debug] target overrides the ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES build setting defined in `Pods/Target Support Files/Pods-dPicDid/Pods-dPicDid.debug.xcconfig'. This can lead to problems with the CocoaPods installation
[!] The dPicDid [Release] target overrides the ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES build setting defined in Pods/Target Support Files/Pods-dPicDid/Pods-dPicDid.release.xcconfig'. This can lead to problems with the CocoaPods installation
- Use the$(inherited)flag, or
- Remove the build settings from the target.
- Use the$(inherited)` flag, or
- Remove the build settings from the target.
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!
I am writing an application that is supposed to control a projector from the desktop of a Mac Mini. I am writing the application in Objective-C using Xcode and termios.h to talk to the serial port. I am connecting from a USB-to-Serial adapter and I know the drivers are installed correctly because I can send HEX commands to the projector to control its functions.
I have code written that I believe should open the serial port, connect to the projector, send codes, and disconnect:
#implementation Projector
#synthesize helpUrl = _helpUrl;
#synthesize projectorConnected;
- (id)init {
//[self connectProjector];
if (TRUE == projectorConnected) {
// Success!
}
else {
// Error!
}
return self;
}
- (id)initWithHelpUrl:(NSString *)helpUrlString {
[self setHelpUrl:helpUrlString];
return [self init];
}
- (void)connectProjector {
[self setProjectorConnected:FALSE];
NSString *deviceNameString = #"/dev/tty.usbserial";
speed_t baudRate = B19200;
serialFileDescriptor = [self openSerialPort:[deviceNameString cStringUsingEncoding:NSASCIIStringEncoding] baud:baudRate];
if (serialFileDescriptor == -1) {
NSLog(#"Error opening serial port file!");
}
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor: serialFileDescriptor];
//[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveSerialMessage:) name:NSFileHandleReadCompletionNotification object:_fileHandle];
//[_fileHandle readInBackgroundAndNotify];
// [self closeSerialPort];
}
- (int)openSerialPort:(const char *)serialPortFile baud:(speed_t)baudRate {
struct termios toptions;
int fd;
fd = open(serialPortFile, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("init_serialport: Unable to open port ");
return -1;
}
if (tcgetattr(fd, &gOriginalTTYAttrs) < 0) {
perror("init_serialport: Couldn't get term attributes");
return -1;
}
toptions = gOriginalTTYAttrs;
cfsetispeed(&toptions, baudRate);
cfsetospeed(&toptions, baudRate);
toptions.c_cflag &= PARENB;
toptions.c_cflag &= CSTOPB;
toptions.c_cflag &= CSIZE;
toptions.c_cflag |= CS8;
toptions.c_cflag &= CRTSCTS;
toptions.c_cflag |= CREAD | CLOCAL;
toptions.c_iflag &= (IXON | IXOFF | IXANY);
toptions.c_lflag &= (ICANON | ECHO | ECHOE | ISIG);
toptions.c_oflag &= OPOST;
// see: http://unixwiz.net/techtips/termios-vmin-vtime.html
toptions.c_cc[VMIN] = 0;
toptions.c_cc[VTIME] = 20;
if( tcsetattr(fd, TCSANOW, &toptions) < 0) {
perror("init_serialport: Couldn't set term attributes");
return -1;
}
return fd;
}
- (void)sendSerialMessage:(NSString *)message {
NSString *deviceNameString = #"/dev/tty.usbserial";
speed_t baudRate = B19200;
serialFileDescriptor = [self openSerialPort:[deviceNameString cStringUsingEncoding:NSASCIIStringEncoding] baud:baudRate];
if (serialFileDescriptor == -1) {
NSLog(#"Error opening serial port file!");
}
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor: serialFileDescriptor];
//[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveSerialMessage:) name:NSFileHandleReadCompletionNotification object:_fileHandle];
//[_fileHandle readInBackgroundAndNotify];
// convert message string into NSData
NSString *dataString = [message stringByReplacingOccurrencesOfString:#" " withString:#""];
NSMutableData *dataToSend = [[NSMutableData alloc] init];
unsigned char whole_byte;
char byte_chars[3] = {'\0','\0','\0'};
int i;
for (i=0; i < 6; i++) {
byte_chars[0] = [dataString characterAtIndex:i*2];
byte_chars[1] = [dataString characterAtIndex:i*2+1];
whole_byte = strtol(byte_chars, NULL, 16);
[dataToSend appendBytes:&whole_byte length:1];
}
// write to the serial port file
[_fileHandle writeData:dataToSend];
sleep(10);
[self closeSerialPort];
}
- (void)receiveSerialMessage:(NSNotification *)notification {
NSData* messageData = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
if ( [messageData length] == 0 ) {
[_fileHandle readInBackgroundAndNotify];
return;
}
NSString* receivedMessage = [[NSString alloc] initWithData:messageData encoding:NSASCIIStringEncoding];
[[NSNotificationCenter defaultCenter] postNotificationName:#"serialMessageReceived" object:receivedMessage];
[_fileHandle readInBackgroundAndNotify];
}
// Given the file descriptor for a serial device, close that device.
- (void)closeSerialPort {
///*
// Block until all written output has been sent from the device.
// Note that this call is simply passed on to the serial device driver.
// See tcsendbreak(3) ("man 3 tcsendbreak") for details.
if (tcdrain(serialFileDescriptor) == -1) {
NSLog(#"Error waiting for drain - %s(%d).", strerror(errno), errno);
}
// Traditionally it is good practice to reset a serial port back to
// the state in which you found it. This is why the original termios struct
// was saved.
if (tcsetattr(serialFileDescriptor, TCSANOW, &gOriginalTTYAttrs) == -1) {
NSLog(#"Error resetting tty attributes - %s(%d).\n", strerror(errno), errno);
}
//*/
close(serialFileDescriptor);
serialFileDescriptor = -1;
_fileHandle = nil;
}
- (BOOL)powerOn {
[self sendSerialMessage:#"02 00 00 00 00 02"];
// TODO: change to success/failure
return TRUE;
}
- (BOOL)powerOff {
[self sendSerialMessage:#"02 01 00 00 00 03"];
// TODO: change to success/failure
return TRUE;
}
- (BOOL)blankScreen {
[self sendSerialMessage:#"02 10 00 00 00 12"];
// TODO: change to success/failure
return TRUE;
}
- (BOOL)showScreen {
[self sendSerialMessage:#"02 11 00 00 00 13"];
// TODO: change to success/failure
return TRUE;
}
- (BOOL)requestHelp {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:_helpUrl]];
// Perform request and get data back
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if (nil != responseData) {
// TODO change this to check for response code is 200?
return TRUE;
/*
// get String value of response
NSString *returnString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
// DEBUG
NSLog(#"Return string: %#", returnString);
// TODO: better way to do this?
NSString *successString = #"{\"status\":true}";
if ([returnString isEqualToString:successString]) {
// success
NSLog(#"Success!");
return TRUE;
}
else {
NSLog(#"Error bad response");
}
*/
}
else {
NSLog(#"Error: no response");
}
// return faliure!
return FALSE;
}
#end
I was wondering where I am going wrong though because when I run the program it hangs.
I've seen serious problems with Prolific's USB to serial adapter driver, particularly under Mac OS X Lion. You can try this alternative driver It's based on the osx-pl2303 open source driver code.
Personally, I would recommend getting a different USB to serial adapter. I like Keyspan's USA-19HS adapter and have never had a problem with their drivers on any version of Mac OS X. I've heard good things about FTDI too, but don't have personal experience with adapters based on that chipset.
See my earlier question for more info about this problem.
I'm trying to learn how to use the NSInputStream class on the iPhone using a unit test. I can get the NSStream to read data from a file using the polling method but for some reason the delegate/event method is not working.
I've posted the relevant code below. Please ignore memory leak errors and such since I'm just trying to ensure I know how to use the NSStream class in a sandboxed environment before rolling it into my larger project.
I'm wondering if maybe I'm missing something with regards to how the run loops work?
This is the logic test that creates a streamer class to read from a file.
#import "StreamingTests.h"
#import "Streamer.h"
#implementation StreamingTests
- (void) testStream {
NSLog(#"Starting stream test.");
Streamer * streamer = [[Streamer alloc] init];
streamer.usePolling = NO;
streamer.readingStream = YES;
NSThread * readThread = [[NSThread alloc] initWithTarget:streamer selector:#selector(startStreamRead:) object:nil];
[readThread start];
while(streamer.readingStream) {
[NSThread sleepForTimeInterval:0.5];
}
[readThread cancel];
}
#end
This is a simple test helper object that reads from an NSStream. When usePolling == YES it read data and outputs the appropriate NSLog messages. However, if usePolling == NO the delegate stream event function is never called.
#implementation Streamer
#synthesize readingStream, usePolling;
- (void) startStreamRead:(NSObject*) context {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(#"starting stream read.");
readingStream = YES;
/*
NSURL * url = [NSURL URLWithString:#"http://www.google.com"];
NSLog(#"Loading: %#",[url description]);
NSInputStream * inStream = [[NSInputStream alloc] initWithURL:url];
*/
NSInputStream * inStream = [[NSInputStream alloc] initWithFileAtPath:#"sample.ttc"];
if(!usePolling) {
[inStream setDelegate: self];
[inStream scheduleInRunLoop: [NSRunLoop currentRunLoop]
forMode: NSDefaultRunLoopMode];
}
[inStream open];
if(usePolling) {
while(1) {
if([inStream hasBytesAvailable]) {
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)inStream read:buf maxLength:1024];
NSLog(#"Read: %d",len);
}
NSStreamStatus status = [inStream streamStatus];
if(status != NSStreamStatusOpen && status != NSStreamStatusOpening) {
NSLog(#"Stream not open.");
break;
}
}
readingStream = NO;
NSStreamStatus status = [inStream streamStatus];
NSError * error = [inStream streamError];
NSLog(#"Status: %d Error Desc: %# Reason: %#",(int)status,[error localizedDescription], [error localizedFailureReason]);
[pool release];
}
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSMutableData * _data = nil;
NSNumber * bytesRead = nil;
NSLog(#"Event fired.");
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if(!_data) {
_data = [[NSMutableData data] retain];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[_data appendBytes:(const void *)buf length:len];
// bytesRead is an instance variable of type NSNumber.
//[bytesRead setIntValue:[bytesRead intValue]+len];
NSLog(#"Read %d bytes",len);
} else {
NSLog(#"no buffer!");
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[stream release];
stream = nil; // stream is ivar, so reinit it
readingStream = NO;
break;
}
default:
{
NSLog(#"Another event occurred.");
break;
}
// continued ...
}
}
#end
Thanks in advance,
b
The reason for it should be that the run loop is blocked since the unit test is executing. You could refer to the NSRunLoop documentation where the method
runUntilDate:
might help you to run the main run loop in the thread of execution of the unit test like this:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
This lets the run loop run for 1 second giving it time to process part of your file. It should be noted that this does not provide a reliable way for unit testing (since the time interval might differ depending on run loop size) and may then be unsuitable. By giving your unit an interface that could be used to check the status of the input stream read operation (with a reading finished state) such as
-(BOOL)hasFinishedReadingFile
the unit test could repeatedly execute the run loop until the above method returns TRUE and the file is read completely.
Addition: This question on stackoverflow also deals with the problem in a different way.