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