Ben Copsey

* ASIDownloadCache now stores cached data using the appropriate file extension w…

…here available. This makes viewing cached local content in a web view work better
* Use xmlsave api to prevent libxml adding xml declaration to the generated html
* Add html5 audio tag, and video poster attribute to xpath query
* Prevent download of webm, ogg and ogv files
* Make accurate progress tracking work in iPhone sample
... ... @@ -144,16 +144,22 @@ static NSString *permanentCacheFolder = @"PermanentStore";
[[self accessLock] unlock];
return nil;
}
// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
NSString *extension = [[url path] pathExtension];
if (![extension length]) {
extension = @"html";
}
// Look in the session store
NSString *path = [[self storagePath] stringByAppendingPathComponent:sessionCacheFolder];
NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"html"]];
NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]];
if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
[[self accessLock] unlock];
return dataPath;
}
// Look in the permanent store
path = [[self storagePath] stringByAppendingPathComponent:permanentCacheFolder];
dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"html"]];
dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]];
if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
[[self accessLock] unlock];
return dataPath;
... ... @@ -196,7 +202,13 @@ static NSString *permanentCacheFolder = @"PermanentStore";
}
NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:@"html"]];
// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
NSString *extension = [[[request url] path] pathExtension];
if (![extension length]) {
extension = @"html";
}
path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]];
[[self accessLock] unlock];
return path;
}
... ...
... ... @@ -24,7 +24,7 @@
#import "ASIDataCompressor.h"
// Automatically set on build
NSString *ASIHTTPRequestVersion = @"v1.7-110 2010-10-10";
NSString *ASIHTTPRequestVersion = @"v1.7-111 2010-10-10";
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
... ...
... ... @@ -12,6 +12,7 @@
#import "ASIHTTPRequest.h"
#import <libxml/HTMLparser.h>
#import <libxml/xmlsave.h>
#import <libxml/xpath.h>
#import <libxml/xpathInternals.h>
... ...
... ... @@ -13,7 +13,7 @@
// An xPath query that controls the external resources ASIWebPageRequest will fetch
// By default, it will fetch stylesheets, javascript files, images, frames, iframes, and html 5 video / audio
static xmlChar *xpathExpr = (xmlChar *)"//link/@href|//a/@href|//script/@src|//img/@src|//frame/@src|//iframe/@src|//style|//*/@style|//source/@src";
static xmlChar *xpathExpr = (xmlChar *)"//link/@href|//a/@href|//script/@src|//img/@src|//frame/@src|//iframe/@src|//style|//*/@style|//source/@src|//video/@poster|//audio/@src";
static NSLock *xmlParsingLock = nil;
static NSMutableArray *requestsUsingXMLParser = nil;
... ... @@ -112,6 +112,7 @@ static NSMutableArray *requestsUsingXMLParser = nil;
[[self externalResourceQueue] cancelAllOperations];
[self setExternalResourceQueue:[ASINetworkQueue queue]];
[[self externalResourceQueue] setDelegate:self];
[[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
[[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
[[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
[[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
... ... @@ -132,7 +133,6 @@ static NSMutableArray *requestsUsingXMLParser = nil;
[externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
}
[[self externalResourceQueue] addOperation:externalResourceRequest];
[externalResourceRequest setShowAccurateProgress:YES];
}
[[self externalResourceQueue] go];
}
... ... @@ -180,6 +180,7 @@ static NSMutableArray *requestsUsingXMLParser = nil;
[[self externalResourceQueue] cancelAllOperations];
[self setExternalResourceQueue:[ASINetworkQueue queue]];
[[self externalResourceQueue] setDelegate:self];
[[self externalResourceQueue] setShowAccurateProgress:[self showAccurateProgress]];
[[self externalResourceQueue] setQueueDidFinishSelector:@selector(finishedFetchingExternalResources:)];
[[self externalResourceQueue] setRequestDidFinishSelector:@selector(externalResourceFetchSucceeded:)];
[[self externalResourceQueue] setRequestDidFailSelector:@selector(externalResourceFetchFailed:)];
... ... @@ -200,7 +201,6 @@ static NSMutableArray *requestsUsingXMLParser = nil;
[externalResourceRequest setDownloadDestinationPath:[self cachePathForRequest:externalResourceRequest]];
}
[[self externalResourceQueue] addOperation:externalResourceRequest];
[externalResourceRequest setShowAccurateProgress:YES];
}
[[self externalResourceQueue] go];
}
... ... @@ -264,24 +264,31 @@ static NSMutableArray *requestsUsingXMLParser = nil;
[xmlParsingLock lock];
[self updateResourceURLs];
xmlChar *bytes = nil;
int size = 0;
FILE *file = NULL;
// We'll use the xmlsave API so we can strip the xml declaration
xmlSaveCtxtPtr saveContext;
if ([self downloadDestinationPath]) {
file = fdopen([[NSFileHandle fileHandleForWritingAtPath:[self downloadDestinationPath]] fileDescriptor], "w");
xmlDocDump(file, doc);
saveContext = xmlSaveToFd([[NSFileHandle fileHandleForWritingAtPath:[self downloadDestinationPath]] fileDescriptor],NULL,XML_SAVE_NO_DECL);
xmlSaveDoc(saveContext, doc);
xmlSaveClose(saveContext);
} else {
xmlDocDumpMemory(doc,&bytes,&size);
[self setRawResponseData:[[[NSMutableData alloc] initWithBytes:bytes length:size] autorelease]];
xmlBufferPtr buffer = xmlBufferCreate();
saveContext = xmlSaveToBuffer(buffer,NULL,XML_SAVE_NO_DECL);
xmlSaveDoc(saveContext, doc);
xmlSaveClose(saveContext);
[self setRawResponseData:[[[NSMutableData alloc] initWithBytes:buffer->content length:buffer->use] autorelease]];
xmlBufferFree(buffer);
}
// libxml will generate UTF-8
[self setResponseEncoding:NSUTF8StringEncoding];
xmlFreeDoc(doc);
doc = nil;
if (file) {
fclose(file);
}
[requestsUsingXMLParser removeObject:self];
if (![requestsUsingXMLParser count]) {
xmlCleanupParser();
... ... @@ -299,7 +306,7 @@ static NSMutableArray *requestsUsingXMLParser = nil;
// Write the parsed content back to the cache
if ([self urlReplacementMode] != ASIDontModifyURLs) {
//[[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
[[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
}
[super requestFinished];
... ... @@ -342,12 +349,22 @@ static NSMutableArray *requestsUsingXMLParser = nil;
if ([[rel lowercaseString] isEqualToString:@"stylesheet"]) {
[self addURLToFetch:value];
}
// Parse the content of <style> tags and style attributes to find external image urls or external css files
} else if ([[nodeName lowercaseString] isEqualToString:@"style"]) {
NSArray *externalResources = [[self class] CSSURLsFromString:value];
for (NSString *theURL in externalResources) {
[self addURLToFetch:theURL];
}
// Parse the content of <source src=""> tags (HTML 5 audio + video)
// We explictly disable the download of files with .webm, .ogv and .ogg extensions, since it's highly likely they won't be useful to us
} else if ([[parentName lowercaseString] isEqualToString:@"source"] || [[parentName lowercaseString] isEqualToString:@"audio"]) {
NSString *fileExtension = [[value pathExtension] lowercaseString];
if (![fileExtension isEqualToString:@"ogg"] && ![fileExtension isEqualToString:@"ogv"] && ![fileExtension isEqualToString:@"webm"]) {
[self addURLToFetch:value];
}
// For all other elements matched by our xpath query (except hyperlinks), add the content as an external url to fetch
} else if (![[parentName lowercaseString] isEqualToString:@"a"]) {
[self addURLToFetch:value];
... ...
... ... @@ -41,12 +41,13 @@
[request setDidFinishSelector:@selector(webPageFetchSucceeded:)];
[request setDelegate:self];
[request setDownloadProgressDelegate:self];
[request setShowAccurateProgress:NO];
[request setUrlReplacementMode:([replaceURLsSwitch isOn] ? ASIReplaceExternalResourcesWithData : ASIReplaceExternalResourcesWithLocalURLs)];
// It is strongly recommended that you set both a downloadCache and a downloadDestinationPath for all ASIWebPageRequests
[request setDownloadCache:[ASIDownloadCache sharedCache]];
[request setCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
// This is actually the most efficient way to set a download path for ASIWebPageRequest, as it writes to the cache directly
[request setDownloadDestinationPath:[[ASIDownloadCache sharedCache] pathToStoreCachedResponseDataForRequest:request]];
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:NO];
... ... @@ -115,7 +116,9 @@
if (requestNumber != NSNotFound) {
RequestProgressCell *cell = (RequestProgressCell *)[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:requestNumber inSection:2]];
if ([theRequest contentLength]+[theRequest partialDownloadSize] > 0) {
[[cell progressView] setProgress:[theRequest totalBytesRead]/([theRequest contentLength]+[theRequest partialDownloadSize])];
unsigned long long totalSize = [theRequest contentLength]+[theRequest partialDownloadSize];
float progressAmount = ([theRequest totalBytesRead]*1.0f)/(([theRequest contentLength]+[theRequest partialDownloadSize])*1.0f);
[[cell progressView] setProgress:progressAmount];
}
}
}
... ... @@ -153,7 +156,7 @@
[responseField setText:@"HTML source will appear here"];
urlField = [[UITextField alloc] initWithFrame:CGRectZero];
[urlField setBorderStyle:UITextBorderStyleRoundedRect];
[urlField setText:@"http://allseeing-i.com/ASIHTTPRequest/tests/ASIWebPageRequest/index.html"];
[urlField setText:@"http://allseeing-i.com"];
}
static NSString *intro = @"ASIWebPageRequest lets you download complete webpages, including most of their external resources. ASIWebPageRequest can download stylesheets, javascript files, images (including those referenced in CSS), frames, iframes, and HTML 5 audio and video.\r\n\r\nYou can set ASIWebPageRequest to modify HTML and CSS content to point at locally cached external resources, or you can have it replace the urls of external files with their actual data. This lets you save a complete web page as a single file.\r\n\r\nASIWebPageRequest is NOT intended to be a drop-in replacement for UIWebView's regular loading mechanism. It is best used for getting more control over caching web pages you control, or for displaying web page content that requires more complex authentication (eg NTLM).\r\n\r\nIt is strongly recommended that you use ASIWebPageRequest in conjunction with a downloadCache, and you should always set a downloadDestinationPath for all ASIWebPageRequests. ASIWebPageRequests cannot be used with startSynchronous.\r\n\r\nTo use ASIWebPage request, you must link with libxml, and add '${SDK_DIR}/usr/include/libxml2' to your Header Search Paths.";
... ...