<commit_before>/*
 * Copyright (c) 2012-2013, <NAME>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "BluetoothWorker.h"
#import "BluetoothDeviceResources.h"
#import <Foundation/NSObject.h>
#import <IOBluetooth/objc/IOBluetoothDevice.h>
#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
#import <IOBluetooth/objc/IOBluetoothSDPUUID.h>
#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h>

#include <v8.h>
#include <node.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <node_object_wrap.h>
#include "pipe.h"

#ifndef RFCOMM_UUID
#define RFCOMM_UUID 0x0003
#endif

using namespace node;
using namespace v8;

/** Private class for wrapping a pipe */
@interface Pipe : NSObject {
	pipe_t *pipe;
}
@property (nonatomic, assign) pipe_t *pipe;
@end

/** Implementation of the pipe class */
@implementation Pipe
@synthesize pipe;
@end

/** Private class for wrapping data */
@interface BTData : NSObject {
	NSData *data;
	NSString *address;
}
@property (nonatomic, assign) NSData *data;
@property (nonatomic, assign) NSString *address;
@end

/** Implementation of bt data class */
@implementation BTData
@synthesize data;
@synthesize address;
@end

/** Class that is handling all the Bluetooth work */
@implementation BluetoothWorker

/** The BluetoothWorker class is a singleton. An instance can be obtained using this method */
+ (id)getInstance: (NSString *) address
{
    if (!address) {
        address = @"inquiry";
    }

    static NSMutableDictionary *instances = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        instances = [[NSMutableDictionary alloc] init];
    });

    BluetoothWorker *worker = nil;
    @synchronized (instances) {
        worker = [instances objectForKey:address];
        if (worker == nil) {
            worker = [[BluetoothWorker alloc] init];
            [instances setObject: worker forKey: address];
        }
    }

    return worker;
}

/** Initializes a BluetoothWorker object */
- (id) init
{
    self = [super init];
    sdpLock = [[NSLock alloc] init];
    res = nil;
    deviceLock = [[NSLock alloc] init];
    connectLock = [[NSLock alloc] init];
    writeLock = [[NSLock alloc] init];

    // creates a worker thread that handles all the asynchronous stuff
    worker = [[NSThread alloc]initWithTarget: self selector: @selector(startBluetoothThread:) object: nil];
    [worker start];
    return self;
}

/** Creates a run loop and sets a timer to keep the run loop alive */
- (void) startBluetoothThread: (id) arg
{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    //schedule a timer so runMode won't stop immediately
    keepAliveTimer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture]
        interval:1 target:nil selector:nil userInfo:nil repeats:YES];
    [runLoop addTimer:keepAliveTimer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

/** Disconnect from a Bluetooth device */
- (void) disconnectFromDevice:(NSString *)address
{
	// this function is called synchronous from javascript so it waits on the worker task to complete.
	[self performSelector:@selector(disconnectFromDeviceTask:) onThread:worker withObject: address waitUntilDone:true];
}

/** Task on the worker to disconnect from a Bluetooth device */
- (void) disconnectFromDeviceTask: (NSString *) address
{
	// make it safe
	[deviceLock lock];

	if (res != nil) {
		if (res.producer != NULL) {
			pipe_producer_free(res.producer);
			res.producer = NULL;
		}

		if (res.channel != NULL) {
			[res.channel closeChannel];
			res.channel = NULL;
		}

		if (res.device != NULL) {
			[res.device closeConnection];
			res.device = NULL;
		}

        res = nil;
	}

	[deviceLock unlock];
}

/** Connect to a Bluetooth device on a specific channel using a pipe to communicate with the main thread */
- (IOReturn)connectDevice: (NSString *) address onChannel: (int) channel withPipe: (pipe_t *)pipe
{
	[connectLock lock];

	Pipe *pipeObj = [[Pipe alloc] init];
	pipeObj.pipe = pipe;

	NSDictionary *parameters = [[NSDictionary alloc] initWithObjectsAndKeys:
		address, @"address", [NSNumber numberWithInt: channel], @"channel", pipeObj, @"pipe", nil];

	// connect to a device and wait for the result
	[self performSelector:@selector(connectDeviceTask:) onThread:worker withObject:parameters waitUntilDone:true];
	IOReturn result = connectResult;
	[connectLock unlock];

	return result;
}

/** Task to connect to a specific device */
- (void)connectDeviceTask: (NSDictionary *)parameters
{
	NSString *address = [parameters objectForKey:@"address"];
	NSNumber *channelID = [parameters objectForKey:@"channel"];
	pipe_t *pipe = ((Pipe *)[parameters objectForKey:@"pipe"]).pipe;

	connectResult = kIOReturnError;

	[deviceLock lock];

	if (res == nil) {
		IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:address];

		if (device != nil) {
			IOBluetoothRFCOMMChannel *channel = [[IOBluetoothRFCOMMChannel alloc] init];
			if ([device openRFCOMMChannelSync: &channel withChannelID:[channelID intValue] delegate: self] == kIOReturnSuccess) {
				connectResult = kIOReturnSuccess;
			   	pipe_producer_t *producer = pipe_producer_new(pipe);
                res = [[BluetoothDeviceResources alloc] init];
			   	res.device = device;
			   	res.producer = producer;
			   	res.channel = channel;
			}
		}
	}

	[deviceLock unlock];
}

/** Write synchronized to a connected Bluetooth device */
- (IOReturn)writeAsync:(void *)data length:(UInt16)length toDevice: (NSString *)address
{
	[writeLock lock];

	BTData *writeData = [[BTData alloc] init];
	writeData.data = [NSData dataWithBytes: data length: length];
	writeData.address = address;

	// wait for the write to be performed on the worker thread
	[self performSelector:@selector(writeAsyncTask:) onThread:worker withObject:writeData waitUntilDone:true];

	IOReturn result = writeResult;
	[writeLock unlock];

	return result;
}

/** Task to do the writing */
- (void)writeAsyncTask:(BTData *)writeData<commit_msg>Global lock around channel open.<commit_after>/*
 * Copyright (c) 2012-2013, <NAME>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "BluetoothWorker.h"
#import "BluetoothDeviceResources.h"
#import <Foundation/NSObject.h>
#import <IOBluetooth/objc/IOBluetoothDevice.h>
#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
#import <IOBluetooth/objc/IOBluetoothSDPUUID.h>
#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h>

#include <v8.h>
#include <node.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <node_object_wrap.h>
#include "pipe.h"

#ifndef RFCOMM_UUID
#define RFCOMM_UUID 0x0003
#endif

using namespace node;
using namespace v8;

/** Private class for wrapping a pipe */
@interface Pipe : NSObject {
	pipe_t *pipe;
}
@property (nonatomic, assign) pipe_t *pipe;
@end

/** Implementation of the pipe class */
@implementation Pipe
@synthesize pipe;
@end

/** Private class for wrapping data */
@interface BTData : NSObject {
	NSData *data;
	NSString *address;
}
@property (nonatomic, assign) NSData *data;
@property (nonatomic, assign) NSString *address;
@end

/** Implementation of bt data class */
@implementation BTData
@synthesize data;
@synthesize address;
@end

<<<<<<< HEAD
=======
static NSMutableDictionary *instanceWorkers = nil;
static NSLock *globalConnectLock = nil;

>>>>>>> 0f08ed3... Global lock around channel open.
/** Class that is handling all the Bluetooth work */
@implementation BluetoothWorker

/** The BluetoothWorker class is a singleton. An instance can be obtained using this method */
+ (id)getInstance: (NSString *) address
{
    if (!address) {
        address = @"inquiry";
    }

    static NSMutableDictionary *instances = nil;
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
<<<<<<< HEAD
        instances = [[NSMutableDictionary alloc] init];
=======
        BTSPLog("Creating worker dictionary.");
        globalConnectLock = [[NSLock alloc] init];
        instanceWorkers = [[NSMutableDictionary alloc] init];
>>>>>>> 0f08ed3... Global lock around channel open.
    });

    BluetoothWorker *worker = nil;
    @synchronized (instances) {
        worker = [instances objectForKey:address];
        if (worker == nil) {
            worker = [[BluetoothWorker alloc] init];
            [instances setObject: worker forKey: address];
        }
    }

    return worker;
}

/** Initializes a BluetoothWorker object */
- (id) init
{
    self = [super init];
    sdpLock = [[NSLock alloc] init];
    res = nil;
    deviceLock = [[NSLock alloc] init];
    connectLock = [[NSLock alloc] init];
    writeLock = [[NSLock alloc] init];

    // creates a worker thread that handles all the asynchronous stuff
    worker = [[NSThread alloc]initWithTarget: self selector: @selector(startBluetoothThread:) object: nil];
    [worker start];
    return self;
}

/** Creates a run loop and sets a timer to keep the run loop alive */
- (void) startBluetoothThread: (id) arg
{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    //schedule a timer so runMode won't stop immediately
    keepAliveTimer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture]
        interval:1 target:nil selector:nil userInfo:nil repeats:YES];
    [runLoop addTimer:keepAliveTimer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

/** Disconnect from a Bluetooth device */
- (void) disconnectFromDevice:(NSString *)address
{
	// this function is called synchronous from javascript so it waits on the worker task to complete.
	[self performSelector:@selector(disconnectFromDeviceTask:) onThread:worker withObject: address waitUntilDone:true];
}

/** Task on the worker to disconnect from a Bluetooth device */
- (void) disconnectFromDeviceTask: (NSString *) address
{
	// make it safe
	[deviceLock lock];

	if (res != nil) {
		if (res.producer != NULL) {
			pipe_producer_free(res.producer);
			res.producer = NULL;
		}

		if (res.channel != NULL) {
			[res.channel closeChannel];
			res.channel = NULL;
		}

		if (res.device != NULL) {
			[res.device closeConnection];
			res.device = NULL;
		}

        res = nil;
	}

	[deviceLock unlock];
}

/** Connect to a Bluetooth device on a specific channel using a pipe to communicate with the main thread */
- (IOReturn)connectDevice: (NSString *) address onChannel: (int) channel withPipe: (pipe_t *)pipe
{
	[connectLock lock];

	Pipe *pipeObj = [[Pipe alloc] init];
	pipeObj.pipe = pipe;

	NSDictionary *parameters = [[NSDictionary alloc] initWithObjectsAndKeys:
		address, @"address", [NSNumber numberWithInt: channel], @"channel", pipeObj, @"pipe", nil];

	// connect to a device and wait for the result
	[self performSelector:@selector(connectDeviceTask:) onThread:worker withObject:parameters waitUntilDone:true];
	IOReturn result = connectResult;
	[connectLock unlock];

	return result;
}

/** Task to connect to a specific device */
- (void)connectDeviceTask: (NSDictionary *)parameters
{
	NSString *address = [parameters objectForKey:@"address"];
	NSNumber *channelID = [parameters objectForKey:@"channel"];
	pipe_t *pipe = ((Pipe *)[parameters objectForKey:@"pipe"]).pipe;

	connectResult = kIOReturnError;

	[deviceLock lock];

	if (res == nil) {
		IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:address];

		if (device != nil) {
			IOBluetoothRFCOMMChannel *channel = [[IOBluetoothRFCOMMChannel alloc] init];
            [globalConnectLock lock];
			if ([device openRFCOMMChannelSync: &channel withChannelID:[channelID intValue] delegate: self] == kIOReturnSuccess) {
                [globalConnectLock unlock];
				connectResult = kIOReturnSuccess;
			   	pipe_producer_t *producer = pipe_producer_new(pipe);
                res = [[BluetoothDeviceResources alloc] init];
			   	res.device = device;
			   	res.producer = producer;
			   	res.channel = channel;
<<<<<<< HEAD
			}
=======

			   	[devices setObject:res forKey:address];
            } else {
                [globalConnectLock unlock];
            }
>>>>>>> 0f08ed3... Global lock around channel open.
		}
	}

	[deviceLock unlock];
}

/** Write synchronized to a connected Bluetooth device */
- (IOReturn)writeAsync:(void *)data length:(UInt16)length toDevice: (NSString *)address
{
	[writeLock lock];

	BTData *writeData = [[BTData alloc] init];
	writeData.data = [NSData dataWithBytes: data length: length];
	writeData.address = address;

	// wait for the write to be performed on the worker thread
	[self performSelector:@selector(writeAsyncTask:) onThread:worker withObject:writeData waitUntilDone:true];

	IOReturn result = writeResult;
	[writeLock unlock];

	return result;
}

/** Task to do the writing */
- (void)writeAsyncTask:(BTData *)writeData
{'fraction_non_alphanumeric': 0.10554455445544554, 'fraction_numerical': 0.0031683168316831685, 'mean_word_length': 4.327355836849508, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2198231', 'n_tokens_mistral': 4661, 'n_tokens_neox': 4259, 'n_words': 1505}