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