Added: chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISBase64InputStream.m
URL: 
http://svn.apache.org/viewvc/chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISBase64InputStream.m?rev=1452344&view=auto
==============================================================================
--- chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISBase64InputStream.m 
(added)
+++ chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISBase64InputStream.m 
Mon Mar  4 15:14:24 2013
@@ -0,0 +1,430 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "CMISBase64InputStream.h"
+#import "CMISBase64Encoder.h"
+#import "CMISConstants.h"
+#import "CMISAtomEntryWriter.h"
+#import "CMISLog.h"
+
+NSString * HTTPSPACE = @" ";
+
+/**
+ * The class inherits from NSInputStream and implements the NSStreamDelegate.
+ * In order to work with NSURL loading system, 3 methods have to be 
implemented:
+    * - (void)_scheduleInCFRunLoop:(CFRunLoopRef)runLoop 
forMode:(CFStringRef)mode
+    * - (BOOL)_setCFClientFlags:(CFOptionFlags)flags 
callback:(CFReadStreamClientCallBack)callback 
context:(CFStreamClientContext*)context
+    * - (void)_unscheduleInCFRunLoop:(CFRunLoopRef)runLoop 
forMode:(CFStringRef)mode
+ * See the following blogs on this subject
+ * 
http://blog.octiplex.com/2011/06/how-to-implement-a-corefoundation-toll-free-bridged-nsinputstream-subclass/
+ * http://bjhomer.blogspot.co.uk/2011/04/subclassing-nsinputstream.html
+ *
+ * The 3 methods are Core Foundation methods. The underscore implies they are 
private API calls.
+ * The class does not call any of the 3 methods directly. Nevertheless, they 
MUST be provided in cases where the inputstream is being used in
+ * URL connections. E.g. when the HTTPBodyStream property on 
NSMutableURLRequest is set to the subclass of an NSInputStream.
+ * If the 3 methods are NOT provided, the app will crash with "[unrecognized 
selector...." errors.
+ * 
+ * For an alternative approach to use base64 encoding while streaming, take a 
look at the class
+ * CMISHttpUploadRequest
+ */
+
+
+@interface CMISBase64InputStream ()
+{
+       CFReadStreamClientCallBack copiedCallback;
+       CFStreamClientContext copiedContext;
+       CFOptionFlags requestedEvents;
+}
+@property (nonatomic, strong) NSInputStream * nonEncodedStream;
+@property (nonatomic, weak) id<NSStreamDelegate> delegate;
+@property (nonatomic, assign) NSStreamStatus streamStatus;
+@property (nonatomic, assign) NSUInteger nonEncodedBytes;
+@property (nonatomic, assign) BOOL encodedStreamHasBytesAvailable;
+@property (nonatomic, strong) CMISAtomEntryWriter *atomEntryWriter;
+@property (nonatomic, strong) NSData *xmlContentClosure;
+@property (nonatomic, assign, readwrite) NSUInteger encodedBytes;
+@property (nonatomic, strong) NSMutableData * residualDataBuffer;
++ (NSUInteger)base64EncodedLength:(NSUInteger)contentSize;
++ (NSInteger)base64BufferLength:(NSInteger)rawLength;
+- (void)prepareXML;
+- (void)storeResidualBytesFromBuffer:(NSData *)buffer 
fullSize:(NSInteger)expectedLength allowedSize:(NSInteger)actualLength;
+
+@end
+
+
+
+@implementation CMISBase64InputStream
+@synthesize delegate = _delegate;
+
+- (id)initWithInputStream:(NSInputStream *)nonEncodedStream
+           cmisProperties:(CMISProperties *)cmisProperties
+                 mimeType:(NSString *)mimeType
+          nonEncodedBytes:(NSUInteger)nonEncodedBytes
+{
+    self = [super init];
+    if (nil != self)
+    {
+        self.nonEncodedStream = nonEncodedStream;
+        [_nonEncodedStream setDelegate:self];
+        [self setDelegate:self];
+        _streamStatus = NSStreamStatusNotOpen;
+        self.nonEncodedBytes = nonEncodedBytes;
+        self.encodedBytes = 0;
+        self.atomEntryWriter = [[CMISAtomEntryWriter alloc] init];
+        self.atomEntryWriter.cmisProperties = cmisProperties;
+        self.atomEntryWriter.mimeType = mimeType;
+        [self prepareXML];
+    }
+    return self;
+}
+
+- (id<NSStreamDelegate>)delegate
+{
+    return _delegate;
+}
+
+- (void)setDelegate:(id<NSStreamDelegate>)delegate
+{
+    if (nil == delegate)
+    {
+        _delegate = self;
+    }
+    else
+    {
+        _delegate = delegate;
+    }
+}
+
+
+- (void)open
+{
+    _streamStatus = NSStreamStatusOpen;
+    self.encodedStreamHasBytesAvailable = YES;
+        
+    if (self.nonEncodedStream.streamStatus != NSStreamStatusOpen)
+    {
+        [self.nonEncodedStream open];
+    }
+    else
+    {
+        [self.nonEncodedStream setProperty:[NSNumber numberWithInt:0] 
forKey:NSStreamFileCurrentOffsetKey];
+    }
+}
+
+- (void)close
+{
+    _streamStatus = NSStreamStatusClosed;
+    if (self.nonEncodedStream.streamStatus != NSStreamStatusClosed)
+    {
+        [self.nonEncodedStream close];
+    }
+}
+
+- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
+{
+    // this doesn't seem to be called. But you never know
+    [self.nonEncodedStream scheduleInRunLoop:aRunLoop forMode:mode];
+}
+
+- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
+{
+    // this doesn't seem to be called. But you never know
+    [self.nonEncodedStream removeFromRunLoop:aRunLoop forMode:mode];
+}
+
+- (NSStreamStatus)streamStatus
+{
+    // we probably cannot simply pass through the original source status. In 
case the source stream closes before we
+    // have read in all encoded and XML data
+    NSStreamStatus status = self.nonEncodedStream.streamStatus;
+    return status;
+}
+
+- (NSError *)streamError
+{
+    NSError *error = [self.nonEncodedStream streamError];
+    CMISLogError(@"error in raw data stream: code = %d message = %@", [error 
code], [error localizedDescription]);
+    return error;
+}
+
+/**
+ there are 2 main operations in this read method. 
+ A.) read in the raw data and encode them
+ B.) copy the encoded bytes and any required encapsulating XML data into the 
read buffer passed in to the method
+ 
+ Because the amount of data we read in from the source is not equal to the 
amount of data we need to fill the overall inputstream with, we need to do some
+ byte jiggling.
+ 
+ Basically, we create 3 buffers
+ 1. writeBuffer - this contains data to be copied into the read buffer passed 
into this method. The size of this must NOT exceed maxLength
+ 2. encodedBuffer - this is the base64 encoded data set - after we read in 
chunk of data from the original source
+ 3. residualDataBuffer - this contains any bytes we couldn't store into 
writeBuffer as it would have exceeded the allowed buffer size
+ 
+ - At the beginning of each read call we clear out the residualDataBuffer as 
much as we can. If this means, that the maxLength is reached we return 
straightaway with a
+   complete read buffer
+ - We then read in from the source. This will be in multiples of 3 - to avoid 
any base64 padding at the end, which is only permitted at the end of a base64 
encoded
+   data set.
+ - All the time we keep track of any residual bytes we could not write out to 
the read buffer and store them until the next read.
+ 
+ We know the input stream has ended if the read from the source returns less 
bytes than requested (or 0). In this case we get the closing XML elements.
+ We have to be careful to tell the stream that it has still bytes available at 
that point.
+ 
+ Any read error from the source will be passed straight on - there is no point 
in continuing if that happens.
+ */
+- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
+{
+    NSMutableData *writeBuffer = [NSMutableData data];
+    if (self.residualDataBuffer.length > 0)
+    {
+        [writeBuffer appendData:self.residualDataBuffer];
+        [self.residualDataBuffer setLength:0];
+    }
+
+    NSInteger bytesOut = len - writeBuffer.length;
+    if (0 >= bytesOut)
+    {
+        [writeBuffer getBytes:buffer length:len];
+        [self storeResidualBytesFromBuffer:writeBuffer 
fullSize:writeBuffer.length allowedSize:len];
+        return len;
+    }
+    
+    NSUInteger rawMaxLength = [CMISBase64InputStream base64BufferLength:len];
+    uint8_t rawBuffer[rawMaxLength];
+    NSInteger rawDataReadIn = [self.nonEncodedStream read:rawBuffer 
maxLength:rawMaxLength];
+    
+    if ( 0 < rawDataReadIn )
+    {
+        NSMutableData *encodedBuffer = [NSMutableData 
dataWithData:[CMISBase64Encoder dataByEncodingText:[NSData 
dataWithBytes:rawBuffer
+                                                                               
                                         length:rawDataReadIn]]];
+        
+        //if the read data is less than requested we reached the end. Add 
closing XML elements
+        if (rawDataReadIn < rawMaxLength) {
+            [encodedBuffer appendData:self.xmlContentClosure];
+        }
+        NSInteger encodedTotalLength = encodedBuffer.length;
+        NSUInteger encodedOutLength = (bytesOut <= encodedTotalLength) ? 
bytesOut : encodedTotalLength;
+        
+        [self storeResidualBytesFromBuffer:encodedBuffer 
fullSize:encodedTotalLength allowedSize:encodedOutLength];
+        
+        [writeBuffer appendData:[encodedBuffer subdataWithRange:NSMakeRange(0, 
encodedOutLength)]];
+        
+        NSUInteger encodedBytes = writeBuffer.length;
+        [writeBuffer getBytes:buffer length:encodedBytes];
+        return encodedBytes;
+    }
+    else if( 0 == rawDataReadIn )
+    {
+        //at this stage we have reached the end of the source input stream. 
Read out any residual bytes
+        //be careful, as we may have more than 1 maxLength buffer left
+        NSUInteger bufferLength = writeBuffer.length;
+        bytesOut = (bufferLength <= len) ? bufferLength : len;
+        if (0 < bytesOut) {
+            [self storeResidualBytesFromBuffer:writeBuffer 
fullSize:bufferLength allowedSize:len];
+            [writeBuffer getBytes:buffer length:bytesOut];
+        }
+        else
+            self.encodedStreamHasBytesAvailable = NO;
+        return bytesOut;
+    }
+    
+    self.encodedStreamHasBytesAvailable = NO;
+    return -1;
+    
+//    return [self.nonEncodedStream read:buffer maxLength:len];
+}
+
+
+
+
+- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len
+{
+    ///hmmm - we never seem to enter this method
+    if (self.nonEncodedStream)
+    {
+        return [self.nonEncodedStream getBuffer:buffer length:len];
+    }
+       return NO;
+}
+
+- (BOOL)hasBytesAvailable
+{
+    BOOL rawDataAvailable = [self.nonEncodedStream hasBytesAvailable];
+    if (rawDataAvailable || self.encodedStreamHasBytesAvailable)
+    {
+        return YES;
+    }
+    return NO;
+//     return [self.nonEncodedStream hasBytesAvailable];
+}
+
+
+#pragma Private methods based on CFReadStream.
+/**
+ we must override the following 4 methods - otherwise subclassing 
NSInputStream will crash
+ */
+
+- (void)_scheduleInCFRunLoop:(CFRunLoopRef)runLoop forMode:(CFStringRef)mode
+{
+    if (_nonEncodedStream)
+    {
+        CFReadStreamScheduleWithRunLoop((CFReadStreamRef)_nonEncodedStream, 
runLoop, mode);
+    }
+}
+
+- (BOOL)_setCFClientFlags:(CFOptionFlags)flags
+                 callback:(CFReadStreamClientCallBack)callback
+                  context:(CFStreamClientContext*)context
+{
+    if (NULL != callback)
+    {
+        requestedEvents = flags;
+        copiedCallback = callback;
+        memcpy(&copiedContext, context, sizeof(CFStreamClientContext));
+        if (copiedContext.info && copiedContext.retain)
+        {
+            copiedContext.retain(copiedContext.info);
+        }
+    }
+    else
+    {
+        requestedEvents = kCFStreamEventNone;
+        copiedCallback = NULL;
+        if (copiedContext.info && copiedContext.retain)
+        {
+            copiedContext.retain(copiedContext.info);
+        }
+        memset(&copiedContext, 0, sizeof(CFStreamClientContext));
+    }
+    return YES;
+}
+
+/**
+ */
+- (void)_unscheduleInCFRunLoop:(CFRunLoopRef)runLoop forMode:(CFStringRef)mode
+{
+    if (_nonEncodedStream)
+    {
+        CFReadStreamUnscheduleFromRunLoop((CFReadStreamRef)_nonEncodedStream, 
runLoop, mode);
+    }
+}
+
+- (void)stream:(NSStream *)aStream
+   handleEvent:(NSStreamEvent)eventCode {
+       
+//    NSLog(@"**** we are in the NSStream delegate method stream:handleEvent");
+       assert(aStream == _nonEncodedStream);
+       
+       switch (eventCode) {
+               case NSStreamEventOpenCompleted:
+//            NSLog(@"**** we are in the NSStream delegate method 
stream:handleEvent NSStreamEventOpenCompleted");
+                       if (requestedEvents & kCFStreamEventOpenCompleted) {
+                               copiedCallback((__bridge CFReadStreamRef)self,
+                                                          
kCFStreamEventOpenCompleted,
+                                                          copiedContext.info);
+                       }
+                       break;
+                       
+               case NSStreamEventHasBytesAvailable:
+//            NSLog(@"**** we are in the NSStream delegate method 
stream:handleEvent NSStreamEventHasBytesAvailable");
+                       if (requestedEvents & kCFStreamEventHasBytesAvailable) {
+                               copiedCallback((__bridge CFReadStreamRef)self,
+                                                          
kCFStreamEventHasBytesAvailable,
+                                                          copiedContext.info);
+                       }
+                       break;
+                       
+               case NSStreamEventErrorOccurred:
+//            NSLog(@"**** we are in the NSStream delegate method 
stream:handleEvent NSStreamEventErrorOccurred");
+                       if (requestedEvents & kCFStreamEventErrorOccurred) {
+                               copiedCallback((__bridge CFReadStreamRef)self,
+                                                          
kCFStreamEventErrorOccurred,
+                                                          copiedContext.info);
+                       }
+                       break;
+                       
+               case NSStreamEventEndEncountered:
+//            NSLog(@"**** we are in the NSStream delegate method 
stream:handleEvent NSStreamEventEndEncountered");
+                       if (requestedEvents & kCFStreamEventEndEncountered) {
+                               copiedCallback((__bridge CFReadStreamRef)self,
+                                                          
kCFStreamEventEndEncountered,
+                                                          copiedContext.info);
+                       }
+                       break;
+                       
+               case NSStreamEventHasSpaceAvailable:
+//            not sure this makes sense in this case;
+                       break;
+                       
+               default:
+                       break;
+       }
+}
+
+#pragma private methods
+- (void)storeResidualBytesFromBuffer:(NSData *)buffer
+                            fullSize:(NSInteger)fullSize
+                         allowedSize:(NSInteger)allowedSize
+{
+    NSInteger restSize = fullSize - allowedSize;
+    if (0 < restSize) {
+        [self.residualDataBuffer appendData:[buffer 
subdataWithRange:NSMakeRange(allowedSize, restSize)]];
+        self.encodedStreamHasBytesAvailable = YES;
+    }
+}
+
++ (NSUInteger)base64EncodedLength:(NSUInteger)contentSize
+{
+    if (0 == contentSize)
+    {
+        return 0;
+    }
+    NSUInteger adjustedThirdPartOfSize = (contentSize / 3) + ( (0 == 
contentSize % 3 ) ? 0 : 1 );
+    
+    return 4 * adjustedThirdPartOfSize;
+}
+
+- (void)prepareXML
+{
+    self.encodedBytes = [CMISBase64InputStream 
base64EncodedLength:self.nonEncodedBytes];
+    NSMutableData *startData = [NSMutableData data];
+    [startData appendData:[[self.atomEntryWriter xmlStartElement] 
dataUsingEncoding:NSUTF8StringEncoding]];
+    [startData appendData:[[self.atomEntryWriter xmlContentStartElement] 
dataUsingEncoding:NSUTF8StringEncoding]];
+    
+    
+    NSMutableData *endData = [NSMutableData data];
+    [endData appendData:[[self.atomEntryWriter xmlContentEndElement] 
dataUsingEncoding:NSUTF8StringEncoding]];
+    [endData appendData:[[self.atomEntryWriter xmlPropertiesElements] 
dataUsingEncoding:NSUTF8StringEncoding]];
+    
+    self.xmlContentClosure = endData;
+    self.encodedBytes += startData.length;
+    self.encodedBytes += endData.length;
+    self.residualDataBuffer = [NSMutableData dataWithData:startData];
+}
+
++ (NSInteger)base64BufferLength:(NSInteger)rawLength
+{
+    NSInteger base64Length = (rawLength / 3) * 3;
+    if (0 == base64Length) {
+        base64Length = rawLength;
+    }
+    return base64Length;
+}
+
+
+@end

Modified: 
chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISDefaultNetworkProvider.m
URL: 
http://svn.apache.org/viewvc/chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISDefaultNetworkProvider.m?rev=1452344&r1=1452343&r2=1452344&view=diff
==============================================================================
--- 
chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISDefaultNetworkProvider.m 
(original)
+++ 
chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISDefaultNetworkProvider.m 
Mon Mar  4 15:14:24 2013
@@ -134,6 +134,45 @@ completionBlock:(void (^)(CMISHttpRespon
 - (void)invoke:(NSURL *)url
     httpMethod:(CMISHttpRequestMethod)httpRequestMethod
        session:(CMISBindingSession *)session
+   inputStream:(NSInputStream *)inputStream
+       headers:(NSDictionary *)additionalHeaders
+ bytesExpected:(unsigned long long)bytesExpected
+   cmisRequest:(CMISRequest *)cmisRequest
+cmisProperties:(CMISProperties *)cmisProperties
+      mimeType:(NSString *)mimeType
+completionBlock:(void (^)(CMISHttpResponse *httpResponse, NSError 
*error))completionBlock
+ progressBlock:(void (^)(unsigned long long bytesDownloaded, unsigned long 
long bytesTotal))progressBlock
+{
+    if (!cmisRequest.isCancelled) {
+        NSMutableURLRequest *urlRequest = [CMISDefaultNetworkProvider 
createRequestForUrl:url
+                                                                               
httpMethod:httpRequestMethod
+                                                                               
   session:session];
+        
+        CMISHttpUploadRequest* request = [CMISHttpUploadRequest 
startRequest:urlRequest
+                                                                  
httpMethod:httpRequestMethod
+                                                                 
inputStream:inputStream
+                                                                     
headers:additionalHeaders
+                                                               
bytesExpected:bytesExpected
+                                                      
authenticationProvider:session.authenticationProvider
+                                                              
cmisProperties:cmisProperties
+                                                                    
mimeType:mimeType
+                                                             
completionBlock:completionBlock
+                                                               
progressBlock:progressBlock];
+        if (request){
+            cmisRequest.httpRequest = request;
+        }
+    } else {
+        if (completionBlock) {
+            completionBlock(nil, [CMISErrors 
createCMISErrorWithCode:kCMISErrorCodeCancelled
+                                                 detailedDescription:@"Request 
was cancelled"]);
+        }
+    }
+}
+
+
+- (void)invoke:(NSURL *)url
+    httpMethod:(CMISHttpRequestMethod)httpRequestMethod
+       session:(CMISBindingSession *)session
   outputStream:(NSOutputStream *)outputStream
  bytesExpected:(unsigned long long)bytesExpected
    cmisRequest:(CMISRequest *)cmisRequest

Modified: 
chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.h
URL: 
http://svn.apache.org/viewvc/chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.h?rev=1452344&r1=1452343&r2=1452344&view=diff
==============================================================================
--- chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.h 
(original)
+++ chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.h 
Mon Mar  4 15:14:24 2013
@@ -18,16 +18,15 @@
  */
 
 #import "CMISHttpRequest.h"
-
-@interface CMISHttpUploadRequest : CMISHttpRequest 
+@interface CMISHttpUploadRequest : CMISHttpRequest <NSStreamDelegate>
 
 @property (nonatomic, strong) NSInputStream *inputStream;
 @property (nonatomic, assign) unsigned long long bytesExpected; // optional; 
if not set, expected content length from HTTP header is used
 @property (nonatomic, readonly) unsigned long long bytesUploaded;
 
 /**
- * starts a URL request with a provided input stream. The inputStream will be 
passed on to the NSMutableURLRequest by using its
- * setHTTPBodyStream method
+ * starts a URL request with a provided input stream. The input stream 
provided will be used directly to send the data upstrean.
+ * For this the class sets the HTTPBodyStream property (method) to this input 
stream. No base64 encoding will be done using this method.
  * completionBlock returns CMISHttpResponse instance or nil if unsuccessful
  */
 + (id)startRequest:(NSMutableURLRequest *)urlRequest
@@ -39,5 +38,22 @@
                        completionBlock:(void (^)(CMISHttpResponse 
*httpResponse, NSError *error))completionBlock
                          progressBlock:(void (^)(unsigned long long 
bytesUploaded, unsigned long long bytesTotal))progressBlock;
 
+/**
+ * starts a URL request with a provided input stream. The input stream has to 
point to the raw NON-encoded data set. This method will use the
+ * provided CMIS properties and mimeType to create the appropriate XML data. 
The base 64 encoding will be done while the data are being read in
+ * from the source input stream.
+ * In order to achieve this, the pairing an OutputStream (where we will write 
the XML and base64 data to) with a resulting fully base64 encoded
+ * input stream. This base64 encoded inputstream will be passed on to the 
NSMutableURLRequest via its HTTPBodyStream property/method.
+ */
++ (id)startRequest:(NSMutableURLRequest *)urlRequest
+        httpMethod:(CMISHttpRequestMethod)httpRequestMethod
+       inputStream:(NSInputStream*)sourceInputStream
+           headers:(NSDictionary*)addionalHeaders
+     bytesExpected:(unsigned long long)bytesExpected
+authenticationProvider:(id<CMISAuthenticationProvider>) authenticationProvider
+    cmisProperties:(CMISProperties *)cmisProperties
+          mimeType:(NSString *)mimeType
+   completionBlock:(void (^)(CMISHttpResponse *httpResponse, NSError 
*error))completionBlock
+     progressBlock:(void (^)(unsigned long long bytesUploaded, unsigned long 
long bytesTotal))progressBlock;
 
 @end

Modified: 
chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.m
URL: 
http://svn.apache.org/viewvc/chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.m?rev=1452344&r1=1452343&r2=1452344&view=diff
==============================================================================
--- chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.m 
(original)
+++ chemistry/objectivecmis/trunk/ObjectiveCMIS/Utils/CMISHttpUploadRequest.m 
Mon Mar  4 15:14:24 2013
@@ -17,12 +17,91 @@
   under the License.
  */
 
+/*
+ The base64 Encoding part of this class is based on the PostController.m class
+ of the sample app 'SimpleURLConnections' provided by Apple.
+ 
http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Introduction/Intro.html
+*/
+
 #import "CMISHttpUploadRequest.h"
+#import "CMISBase64Encoder.h"
+#import "CMISAtomEntryWriter.h"
+#import "CMISLog.h"
+/**
+ this is the buffer size for the input/output stream pair containing the 
base64 encoded data
+ */
+const NSUInteger kFullBufferSize = 32768;
+/**
+ this is the buffer size for the raw data. It must be an integer multiple of 
3. Base64 encoding uses
+ 4 bytes for each 3 bytes of raw data. Therefore, the amount of raw data we 
take is
+ kFullBufferSize/4 * 3.
+ */
+const NSUInteger kRawBufferSize = 24576;
+
+/**
+ A category that extends the NSStream class in order to pair an inputstream 
with an outputstream.
+ The input stream will be used by NSURLConnection via the HTTPBodyStream 
property of the URL request.
+ The paired output stream will buffer base64 encoded as well as XML data.
+ 
+ NOTE: the original sample code also provides a method for backward 
compatibility w.r.t  iOS versions below 5.0
+ However, since the CMIS library is only to be used with iOS version 5.1 and 
higher, this code is obsolete and has
+ been omitted here.
+ */
+
+@interface NSStream (StreamPair)
++ (void)createBoundInputStream:(NSInputStream **)inputStreamPtr
+                  outputStream:(NSOutputStream **)outputStreamPtr;
+@end
+
+@implementation NSStream (StreamPair)
++ (void)createBoundInputStream:(NSInputStream **)inputStreamPtr
+                  outputStream:(NSOutputStream **)outputStreamPtr
+{
+    CFReadStreamRef     readStream;
+    CFWriteStreamRef    writeStream;
+    
+    assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );
+    
+    readStream = NULL;
+    writeStream = NULL;
+    CFStreamCreateBoundPair(
+                            NULL,
+                            ((inputStreamPtr  != nil) ? &readStream : NULL),
+                            ((outputStreamPtr != nil) ? &writeStream : NULL),
+                            (CFIndex) kFullBufferSize
+                            );
+    
+    if (inputStreamPtr != NULL) {
+        *inputStreamPtr  = CFBridgingRelease(readStream);
+    }
+    if (outputStreamPtr != NULL) {
+        *outputStreamPtr = CFBridgingRelease(writeStream);
+    }
+}
+@end
+
 
 @interface CMISHttpUploadRequest ()
 
 @property (nonatomic, assign) unsigned long long bytesUploaded;
 @property (nonatomic, copy) void (^progressBlock)(unsigned long long 
bytesUploaded, unsigned long long bytesTotal);
+@property (nonatomic, assign) BOOL base64Encoding;
+@property (nonatomic, strong) NSInputStream * base64InputStream;
+@property (nonatomic, strong) NSOutputStream * encoderStream;
+@property (nonatomic, strong) NSData * streamStartData;
+@property (nonatomic, strong) NSData * streamEndData;
+@property (nonatomic, strong) NSMutableData * residualDataBuffer;
+@property (nonatomic, assign) unsigned long long encodedLength;
+@property (nonatomic, assign, readwrite) const uint8_t *    buffer;
+@property (nonatomic, assign, readwrite) uint8_t *          bufferOnHeap;
+@property (nonatomic, assign, readwrite) size_t             bufferOffset;
+@property (nonatomic, assign, readwrite) size_t             bufferLimit;
+
+- (void)stopSendWithStatus:(NSString *)statusString;
++ (NSUInteger)base64EncodedLength:(NSUInteger)contentSize;
+- (void)prepareXMLWithCMISProperties:(CMISProperties *)cmisProperties 
mimeType:(NSString *)mimeType;
+- (void)prepareStreams;
+
 - (id)initWithHttpMethod:(CMISHttpRequestMethod)httpRequestMethod
          completionBlock:(void (^)(CMISHttpResponse *httpResponse, NSError 
*error))completionBlock
            progressBlock:(void (^)(unsigned long long bytesUploaded, unsigned 
long long bytesTotal))progressBlock;
@@ -49,7 +128,40 @@
     httpRequest.additionalHeaders = additionalHeaders;
     httpRequest.bytesExpected = bytesExpected;
     httpRequest.authenticationProvider = authenticationProvider;
+    httpRequest.base64Encoding = NO;
+    httpRequest.base64InputStream = nil;
+    httpRequest.encoderStream = nil;
+    
+    if ([httpRequest startRequest:urlRequest] == NO) {
+        httpRequest = nil;
+    }
+    
+    return httpRequest;
+}
+
++ (id)startRequest:(NSMutableURLRequest *)urlRequest
+        httpMethod:(CMISHttpRequestMethod)httpRequestMethod
+       inputStream:(NSInputStream*)inputStream
+           headers:(NSDictionary*)addionalHeaders
+     bytesExpected:(unsigned long long)bytesExpected
+authenticationProvider:(id<CMISAuthenticationProvider>) authenticationProvider
+    cmisProperties:(CMISProperties *)cmisProperties
+          mimeType:(NSString *)mimeType
+   completionBlock:(void (^)(CMISHttpResponse *httpResponse, NSError 
*error))completionBlock
+     progressBlock:(void (^)(unsigned long long bytesUploaded, unsigned long 
long bytesTotal))progressBlock
+{
+    CMISHttpUploadRequest *httpRequest = [[self alloc] 
initWithHttpMethod:httpRequestMethod
+                                                          
completionBlock:completionBlock
+                                                            
progressBlock:progressBlock];
+    
+    httpRequest.inputStream = inputStream;
+    httpRequest.additionalHeaders = addionalHeaders;
+    httpRequest.bytesExpected = bytesExpected;
+    httpRequest.base64Encoding = YES;
+    httpRequest.authenticationProvider = authenticationProvider;
     
+    [httpRequest prepareStreams];
+    [httpRequest prepareXMLWithCMISProperties:cmisProperties 
mimeType:mimeType];
     if ([httpRequest startRequest:urlRequest] == NO) {
         httpRequest = nil;
     }
@@ -71,24 +183,45 @@
 }
 
 
+/**
+ if we are using on-the-go base64 encoding, we will use the base64InputStream 
in URL connections/request.
+ In this case a little extra work is required: i.e. we need to provide the 
length of the encoded data stream (including
+ the XML data).
+ */
 - (BOOL)startRequest:(NSMutableURLRequest*)urlRequest
 {
-    if (self.inputStream) {
-        urlRequest.HTTPBodyStream = self.inputStream;
+    if (self.base64Encoding)
+    {
+        if (self.base64InputStream) {
+            urlRequest.HTTPBodyStream = self.base64InputStream;
+            NSMutableDictionary *headers = [NSMutableDictionary 
dictionaryWithDictionary:self.additionalHeaders];
+            [headers setValue:[NSString stringWithFormat:@"%llu", 
self.encodedLength] forKey:@"Content-Length"];
+            self.additionalHeaders = [NSDictionary 
dictionaryWithDictionary:headers];
+        }
     }
+    else
+    {
+        if (self.inputStream) {
+            urlRequest.HTTPBodyStream = self.inputStream;
+        }
+    }
+    
 
     return [super startRequest:urlRequest];
 }
 
-
+#pragma CMISCancellableRequest method
 - (void)cancel
 {
     self.progressBlock = nil;
     
     [super cancel];
+    if (self.base64Encoding) {
+        [self stopSendWithStatus:@"connection has been cancelled."];
+    }
 }
 
-
+#pragma NSURLConnectionDataDelegate methods
 - (void)connection:(NSURLConnection *)connection 
didReceiveResponse:(NSURLResponse *)response
 {
     [super connection:connection didReceiveResponse:response];
@@ -111,20 +244,239 @@ totalBytesExpectedToWrite:(NSInteger)tot
 }
 
 
+/**
+ In addition to closing the connection we also have to close/reset all streams 
used in this class.
+ This is for base64 encoding only
+ */
 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError 
*)error
 {
     [super connection:connection didFailWithError:error];
     
+    if (self.base64Encoding) {
+        [self stopSendWithStatus:@"connection is being terminated with 
error."];
+    }
     self.progressBlock = nil;
 }
 
 
+/**
+ In addition to closing the connection we also have to close/reset all streams 
used in this class.
+ This is for base64 encoding only
+ */
 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
 {
     [super connectionDidFinishLoading:connection];
+    if (self.base64Encoding) {
+        [self stopSendWithStatus:@"Connection finished as expected."];
+    }
     
     self.progressBlock = nil;
 }
 
+#pragma NSStreamDelegate method
+/**
+ For encoding base64 data - this is the meat of this class.
+ The action is in the case where the eventCode == 
NSStreamEventHasSpaceAvailable
+ 
+ Note 1:
+ The output stream (encoderStream) is paired with the encoded input stream 
(base64InputStream) which is the one
+ the active URL connection uses to read from. Thereby any data made available 
to the outputstream will be available to this input stream as well.
+ Any action on the output stream (like close) will also affect this 
base64InputStream.
+ 
+ Note 2:
+ since we are encoding "on the fly" we are dealing with 2 different buffer 
sizes. The encoded buffer size kFullBufferSize, and the
+ buffer size of the raw/non-encoded data kRawBufferSize.
+ 
+ Note 3:
+ the reading from the source input stream, as well as the writing to the 
encoderStream is regulated via 2 variables: bufferLimit and bufferOffset
+ bufferLimit is the size of the XML data or the No of bytes read in from the 
raw data set (inputstream)
+ At each readIn, the bufferOffset will be reset to 0 to indicate a free buffer 
to write to.
+ When the data are finally written to the output stream, both bufferLimit and 
bufferOffset should be having the same value (unless we attempt to
+ write more bytes than are available in the buffer).
+ 
+ Once we reach the end of the raw data set, both bufferLimit and bufferOffset 
are set to 0. This indicates that the outputStream (and its paired
+ input stream) can be closed.
+ 
+ (Final Note:The Apple source code discourages removing the stream from the 
runloop in this method as it can cause random crashes.)
+ */
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
+{
+    switch (eventCode){
+        case NSStreamEventOpenCompleted:{
+            if (self.inputStream.streamStatus != NSStreamStatusOpen) {
+                [self.inputStream open];
+            }
+        }
+            break;
+        case NSStreamEventHasBytesAvailable: {
+        } break;
+        case NSStreamEventHasSpaceAvailable: {
+            /*
+             first we check if we can fill the output stream buffer with data
+             the criteria for that is that bufferOffset equals the buffer limit
+             */
+            if (self.bufferOffset == self.bufferLimit) {
+                if (self.streamStartData != nil) {
+                    self.streamStartData = nil;
+                    self.bufferOffset = 0;
+                    self.bufferLimit = 0;
+                    self.bufferOnHeap = malloc(kFullBufferSize);
+                    if (NULL != self.bufferOnHeap) {
+                        self.buffer = self.bufferOnHeap;
+                    }
+                }
+                if (self.inputStream != nil) {
+                    NSInteger rawBytesRead;
+                    uint8_t rawBuffer[kRawBufferSize];
+                    rawBytesRead = [self.inputStream read:rawBuffer 
maxLength:kRawBufferSize];
+                    if (-1 == rawBytesRead) {
+                        [self stopSendWithStatus:@"Error while reading from 
source input stream"];
+                    }
+                    else if (0 != rawBytesRead){
+                        NSData *encodedBuffer = [CMISBase64Encoder 
dataByEncodingText:[NSData dataWithBytes:rawBuffer length:rawBytesRead]];
+                        NSUInteger encodedLength = (encodedBuffer.length <= 
kFullBufferSize) ? encodedBuffer.length : kFullBufferSize;
+                        if (encodedBuffer.length > kFullBufferSize) {
+                            NSLog(@"encoded buffer length is %d but we should 
only have %d", encodedBuffer.length, kFullBufferSize);
+                        }
+                        [encodedBuffer getBytes:self.bufferOnHeap 
length:encodedLength];
+                        self.bufferOffset = 0;
+                        self.bufferLimit = encodedLength;
+                    }
+                    else{
+                        [self.inputStream close];
+                        self.inputStream = nil;
+                        if (self.bufferOnHeap != NULL) {
+                            free(self.bufferOnHeap);
+                        }
+                        self.bufferOnHeap = NULL;
+                        self.buffer = [self.streamEndData bytes];
+                        self.bufferOffset = 0;
+                        self.bufferLimit = self.streamEndData.length;
+                    }
+                    if ((self.bufferLimit == self.bufferOffset) && 
self.encoderStream != nil) {
+                        self.encoderStream.delegate = nil;
+                        [self.encoderStream close];
+                    }
+                }
+                
+                if ((self.bufferOffset == self.bufferLimit) && 
(self.encoderStream != nil)) {
+                    self.encoderStream.delegate = nil;
+                    [self.encoderStream close];
+                }
+                
+            }
+            if (self.bufferOffset != self.bufferLimit) {
+                NSInteger bytesWritten;
+                bytesWritten = [self.encoderStream 
write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - 
self.bufferOffset];
+                if (bytesWritten <= 0) {
+                    [self stopSendWithStatus:@"Network write error"];
+                }
+                else{
+                    self.bufferOffset += bytesWritten;
+                }
+            }
+            
+        }break;
+        case NSStreamEventErrorOccurred: {
+            [self stopSendWithStatus:@"Stream open error"];
+        }break;
+        case NSStreamEventEndEncountered: {
+        }break;
+        default:
+            break;
+    }
+}
+
+
+#pragma private methods
+- (void)prepareXMLWithCMISProperties:(CMISProperties *)cmisProperties 
mimeType:(NSString *)mimeType
+{
+    self.bufferOffset = 0;
+    CMISAtomEntryWriter *writer = [[CMISAtomEntryWriter alloc] init];
+    writer.cmisProperties = cmisProperties;
+    writer.mimeType = mimeType;
+    
+    NSString *xmlStart = [writer xmlStartElement];
+    NSString *xmlContentStart = [writer xmlContentStartElement];
+    
+    NSString *start = [NSString stringWithFormat:@"%@%@", xmlStart, 
xmlContentStart];
+    self.streamStartData = [NSMutableData dataWithData:[start 
dataUsingEncoding:NSUTF8StringEncoding]];
+    self.buffer = [self.streamStartData bytes];
+    self.bufferLimit = self.streamStartData.length;
+    self.bufferOnHeap = NULL;
+    
+    NSString *xmlContentEnd = [writer xmlContentEndElement];
+    NSString *xmlProperties = [writer xmlPropertiesElements];
+    NSString *end = [NSString stringWithFormat:@"%@%@", xmlContentEnd, 
xmlProperties];
+    self.streamEndData = [end dataUsingEncoding:NSUTF8StringEncoding];
+    
+    NSUInteger encodedLength = [CMISHttpUploadRequest 
base64EncodedLength:self.bytesExpected];
+    encodedLength += start.length;
+    encodedLength += end.length;
+    self.encodedLength = encodedLength;
+}
+
+- (void)prepareStreams
+{
+    /*
+     */
+    if (self.inputStream.streamStatus != NSStreamStatusOpen) {
+        [self.inputStream open];
+    }
+    
+    NSInputStream *requestInputStream;
+    NSOutputStream *outputStream;
+    [NSStream createBoundInputStream:&requestInputStream 
outputStream:&outputStream];
+    assert(requestInputStream != nil);
+    assert(outputStream != nil);
+    self.base64InputStream = requestInputStream;
+    self.encoderStream = outputStream;
+    self.encoderStream.delegate = self;
+    [self.encoderStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
forMode:NSDefaultRunLoopMode];
+    [self.encoderStream open];
+}
+
+
++ (NSUInteger)base64EncodedLength:(NSUInteger)contentSize
+{
+    if (0 == contentSize)
+    {
+        return 0;
+    }
+    NSUInteger adjustedThirdPartOfSize = (contentSize / 3) + ( (0 == 
contentSize % 3 ) ? 0 : 1 );
+    
+    return 4 * adjustedThirdPartOfSize;
+}
+
+- (void)stopSendWithStatus:(NSString *)statusString
+{
+    if(nil != statusString)
+        CMISLogDebug([NSString stringWithFormat:@"Upload request terminated: 
Message is %@", statusString]);
+    if (self.bufferOnHeap) {
+        free(self.bufferOnHeap);
+        self.bufferOnHeap = NULL;
+    }
+    self.buffer = NULL;
+    self.bufferOffset = 0;
+    self.bufferLimit  = 0;
+    if (self.connection != nil) {
+        [self.connection cancel];
+        self.connection = nil;
+    }
+    if (self.encoderStream != nil) {
+        self.encoderStream.delegate = nil;
+        [self.encoderStream removeFromRunLoop:[NSRunLoop currentRunLoop] 
forMode:NSDefaultRunLoopMode];
+        [self.encoderStream close];
+        self.encoderStream = nil;
+    }
+    self.base64InputStream = nil;
+    if(self.inputStream != nil){
+        [self.inputStream close];
+        self.inputStream = nil;
+    }
+    self.streamEndData = nil;
+    self.streamStartData = nil;
+}
+
 
 @end

Modified: chemistry/objectivecmis/trunk/ObjectiveCMISTests/ObjectiveCMISTests.m
URL: 
http://svn.apache.org/viewvc/chemistry/objectivecmis/trunk/ObjectiveCMISTests/ObjectiveCMISTests.m?rev=1452344&r1=1452343&r2=1452344&view=diff
==============================================================================
--- chemistry/objectivecmis/trunk/ObjectiveCMISTests/ObjectiveCMISTests.m 
(original)
+++ chemistry/objectivecmis/trunk/ObjectiveCMISTests/ObjectiveCMISTests.m Mon 
Mar  4 15:14:24 2013
@@ -502,6 +502,48 @@
     }];
 }
 
+- (void)testVerySmallDocument
+{
+    [self runTest:^{
+        NSString *fileToUploadPath = [[NSBundle bundleForClass:[self class]] 
pathForResource:@"small_test.txt" ofType:nil];
+        NSString *documentName = [NSString 
stringWithFormat:@"small_test_%@.txt", [self stringFromCurrentDate]];
+        
+        NSMutableDictionary *properties = [NSMutableDictionary dictionary];
+        [properties setObject:documentName forKey:kCMISPropertyName];
+        [properties setObject:kCMISPropertyObjectTypeIdValueDocument 
forKey:kCMISPropertyObjectTypeId];
+        __block NSString * smallObjectId;
+        [self.rootFolder createDocumentFromFilePath:fileToUploadPath 
mimeType:@"text/plain" properties:properties completionBlock:^(NSString 
*objectId, NSError *error){
+            if (objectId) {
+                CMISLogDebug(@"File upload completed");
+                STAssertNotNil(objectId, @"Object id received should be 
non-nil");
+                smallObjectId = objectId;
+                [self.session retrieveObject:smallObjectId 
completionBlock:^(CMISObject *object, NSError *objError){
+                    if (object) {
+                        CMISDocument *doc = (CMISDocument *)object;
+                        [doc deleteAllVersionsWithCompletionBlock:^(BOOL 
deleted, NSError *deleteError){
+                            STAssertTrue(deleted, @"should have successfully 
deleted file");
+                            if (deleteError) {
+                                CMISLogDebug(@"we have an error deleting the 
file %@ with message %@ and code %d", documentName, [deleteError 
localizedDescription], [deleteError code]);
+                            }
+                            self.testCompleted = YES;
+                        }];
+                    }
+                    else{
+                        STAssertNil(error, @"Got error while retrieving 
document: %@", [objError description]);
+                        self.testCompleted = YES;
+                        
+                    }
+                }];
+            }
+            else{
+                STAssertNil(error, @"Got error while creating document: %@", 
[error description]);
+                self.testCompleted = YES;
+            }
+        } progressBlock:^(unsigned long long bytesUploaded, unsigned long long 
total){}];
+        
+    }];
+}
+
 - (void)testCreateBigDocument
 {
     [self runTest:^ {
@@ -1019,10 +1061,13 @@
                                   [[NSFileManager defaultManager] 
removeItemAtPath:tempDownloadFilePath error:&fileError];
                                   STAssertNil(fileError, @"Error when deleting 
temporary downloaded file: %@", [fileError description]);
                                   
+//                                  self.testCompleted = YES;
                                   // Delete test document from server
+                                  
                                   [self 
deleteDocumentAndVerify:originalDocument completionBlock:^{
                                       self.testCompleted = YES;
                                   }];
+                                
                               } else {
                                   STAssertNil(error, @"Error while writing 
content: %@", [error description]);
                                   


Reply via email to