ASIDataCompressor.m 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. //
  2. // ASIDataCompressor.m
  3. // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
  4. //
  5. // Created by Ben Copsey on 17/08/2010.
  6. // Copyright 2010 All-Seeing Interactive. All rights reserved.
  7. //
  8. #import "ASIDataCompressor.h"
  9. #import "ASIHTTPRequest.h"
  10. #define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
  11. #define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION
  12. @interface ASIDataCompressor ()
  13. + (NSError *)deflateErrorWithCode:(int)code;
  14. @end
  15. @implementation ASIDataCompressor
  16. + (id)compressor
  17. {
  18. ASIDataCompressor *compressor = [[[self alloc] init] autorelease];
  19. [compressor setupStream];
  20. return compressor;
  21. }
  22. - (void)dealloc
  23. {
  24. if (streamReady) {
  25. [self closeStream];
  26. }
  27. [super dealloc];
  28. }
  29. - (NSError *)setupStream
  30. {
  31. if (streamReady) {
  32. return nil;
  33. }
  34. // Setup the inflate stream
  35. zStream.zalloc = Z_NULL;
  36. zStream.zfree = Z_NULL;
  37. zStream.opaque = Z_NULL;
  38. zStream.avail_in = 0;
  39. zStream.next_in = 0;
  40. int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
  41. if (status != Z_OK) {
  42. return [[self class] deflateErrorWithCode:status];
  43. }
  44. streamReady = YES;
  45. return nil;
  46. }
  47. - (NSError *)closeStream
  48. {
  49. if (!streamReady) {
  50. return nil;
  51. }
  52. // Close the deflate stream
  53. streamReady = NO;
  54. int status = deflateEnd(&zStream);
  55. if (status != Z_OK) {
  56. return [[self class] deflateErrorWithCode:status];
  57. }
  58. return nil;
  59. }
  60. - (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish
  61. {
  62. if (length == 0) return nil;
  63. NSUInteger halfLength = length/2;
  64. // We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below
  65. NSMutableData *outputData = [NSMutableData dataWithLength:length/2];
  66. int status;
  67. zStream.next_in = bytes;
  68. zStream.avail_in = (unsigned int)length;
  69. zStream.avail_out = 0;
  70. NSUInteger bytesProcessedAlready = zStream.total_out;
  71. while (zStream.avail_out == 0) {
  72. if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
  73. [outputData increaseLengthBy:halfLength];
  74. }
  75. zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
  76. zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
  77. status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH);
  78. if (status == Z_STREAM_END) {
  79. break;
  80. } else if (status != Z_OK) {
  81. if (err) {
  82. *err = [[self class] deflateErrorWithCode:status];
  83. }
  84. return NO;
  85. }
  86. }
  87. // Set real length
  88. [outputData setLength: zStream.total_out-bytesProcessedAlready];
  89. return outputData;
  90. }
  91. + (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err
  92. {
  93. NSError *theError = nil;
  94. NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES];
  95. if (theError) {
  96. if (err) {
  97. *err = theError;
  98. }
  99. return nil;
  100. }
  101. return outputData;
  102. }
  103. + (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
  104. {
  105. NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
  106. // Create an empty file at the destination path
  107. if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
  108. if (err) {
  109. *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
  110. }
  111. return NO;
  112. }
  113. // Ensure the source file exists
  114. if (![fileManager fileExistsAtPath:sourcePath]) {
  115. if (err) {
  116. *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
  117. }
  118. return NO;
  119. }
  120. UInt8 inputData[DATA_CHUNK_SIZE];
  121. NSData *outputData;
  122. NSInteger readLength;
  123. NSError *theError = nil;
  124. ASIDataCompressor *compressor = [ASIDataCompressor compressor];
  125. NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
  126. [inputStream open];
  127. NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
  128. [outputStream open];
  129. while ([compressor streamReady]) {
  130. // Read some data from the file
  131. readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
  132. // Make sure nothing went wrong
  133. if ([inputStream streamStatus] == NSStreamStatusError) {
  134. if (err) {
  135. *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
  136. }
  137. [compressor closeStream];
  138. return NO;
  139. }
  140. // Have we reached the end of the input data?
  141. if (!readLength) {
  142. break;
  143. }
  144. // Attempt to deflate the chunk of data
  145. outputData = [compressor compressBytes:inputData length:(NSUInteger)readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE];
  146. if (theError) {
  147. if (err) {
  148. *err = theError;
  149. }
  150. [compressor closeStream];
  151. return NO;
  152. }
  153. // Write the deflated data out to the destination file
  154. [outputStream write:(const uint8_t *)[outputData bytes] maxLength:[outputData length]];
  155. // Make sure nothing went wrong
  156. if ([inputStream streamStatus] == NSStreamStatusError) {
  157. if (err) {
  158. *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
  159. }
  160. [compressor closeStream];
  161. return NO;
  162. }
  163. }
  164. [inputStream close];
  165. [outputStream close];
  166. NSError *error = [compressor closeStream];
  167. if (error) {
  168. if (err) {
  169. *err = error;
  170. }
  171. return NO;
  172. }
  173. return YES;
  174. }
  175. + (NSError *)deflateErrorWithCode:(int)code
  176. {
  177. return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %d",code],NSLocalizedDescriptionKey,nil]];
  178. }
  179. @synthesize streamReady;
  180. @end