ASIDataDecompressor.m 6.3 KB

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