RMWebTileImage.m 7.61 KB
//
//  RMWebTileImage.m
//
// Copyright (c) 2008-2009, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#import "RMWebTileImage.h"
#import <QuartzCore/CALayer.h>

#import "RMMapContents.h"
#import "RMTileLoader.h"

NSString *RMWebTileImageErrorDomain = @"RMWebTileImageErrorDomain";
NSString *RMWebTileImageHTTPResponseCodeKey = @"RMWebTileImageHTTPResponseCodeKey";
NSString *RMWebTileImageNotificationErrorKey = @"RMWebTileImageNotificationErrorKey";

@implementation RMWebTileImage

- (id)initWithTile:(RMTile)_tile fromURL:(NSString *)urlStr
{
	if (!(self = [super initWithTile:_tile]))
		return nil;

    connection = nil;

	url = [[NSURL alloc] initWithString:urlStr];
	data =[[NSMutableData alloc] initWithCapacity:0];

	retries = kWebTileRetries;

	[self requestTile];

	return self;
}

- (void)dealloc
{
	[self cancelLoading];

	[data release];
	data = nil;

	[url release];
	url = nil;

	[super dealloc];
}

- (void)requestTile
{
	//RMLog(@"fetching: %@", url);
	if (connection) // re-request
	{
		//RMLog(@"Refetching: %@: %d", url, retries);
		[connection release];
		connection = nil;

		if (retries == 0) // No more retries
		{
			[super displayProxy:[RMTileImage errorTile]];
            [lastError autorelease]; lastError = nil;

			return;
		}
		retries--;		

		[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(startLoading:) userInfo:nil repeats:NO];
	}
	else {
		[self startLoading:nil];
	}
}

- (void)startLoading:(NSTimer *)timer
{
	NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];

	connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];

	if (!connection) {
		[super displayProxy:[RMTileImage errorTile]];
	}
}

- (void)cancelLoading
{
	if (!connection)
		return;

	[connection cancel];

	[connection release];
	connection = nil;

    if (lastError) {
        [lastError release]; lastError = nil;
    }

	[super cancelLoading];
}

#pragma mark URL loading functions
// Delegate methods for loading the image

//– connection:didCancelAuthenticationChallenge:  delegate method  
//– connection:didReceiveAuthenticationChallenge:  delegate method  
//Connection Data and Responses
//– connection:willCacheResponse:  delegate method  
//– connection:didReceiveResponse:  delegate method  
//– connection:didReceiveData:  delegate method  
//– connection:willSendRequest:redirectResponse:  delegate method  
//Connection Completion
//– connection:didFailWithError:  delegate method
//– connectionDidFinishLoading:  delegate method 

- (void)connection:(NSURLConnection *)_connection didReceiveResponse:(NSURLResponse *)response
{
	int statusCode = NSURLErrorUnknown; // unknown

	if ([response isKindOfClass:[NSHTTPURLResponse class]])
	  statusCode = [(NSHTTPURLResponse *)response statusCode];

	[data setLength:0];

	if (statusCode < 400) { // Success
	}
    else if (statusCode == 404) { // Not Found
        [super displayProxy:[RMTileImage missingTile]];

        NSError *error = [NSError errorWithDomain:RMWebTileImageErrorDomain 
                                             code:RMWebTileImageErrorNotFoundResponse
                                         userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
                                                   NSLocalizedString(@"The requested tile was not found on the server", @""), NSLocalizedDescriptionKey, nil]];

		[self cancelLoading];
	}
	else { // Other Error
           //RMLog(@"didReceiveResponse %@ %d", _connection, statusCode);
		BOOL retry = FALSE;
		
		switch(statusCode)
		{
			case 500: retry = TRUE; break;
			case 503: retry = TRUE; break;
		}
    
        NSError *error = [NSError errorWithDomain:RMWebTileImageErrorDomain 
                                             code:RMWebTileImageErrorUnexpectedHTTPResponse 
                                         userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
                                                   [NSNumber numberWithInt:statusCode], RMWebTileImageHTTPResponseCodeKey,
                                                   [NSString stringWithFormat:NSLocalizedString(@"The server returned error code %d", @""), statusCode], NSLocalizedDescriptionKey, nil]];

		if (retry) {
            if ( lastError ) [lastError release];
            lastError = [error retain];
			[self requestTile];
		}
		else {
			[self cancelLoading];
		}
	}
}

- (void)connection:(NSURLConnection *)_connection didReceiveData:(NSData *)newData
{
	[data appendData:newData];
}

- (void)connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error
{
	//RMLog(@"didFailWithError %@ %d %@", _connection, [error code], [error localizedDescription]);
	BOOL retry = FALSE;

	switch ([error code])
	{
          case NSURLErrorBadURL:                      // -1000
          case NSURLErrorTimedOut:                    // -1001
          case NSURLErrorUnsupportedURL:              // -1002
          case NSURLErrorCannotFindHost:              // -1003
          case NSURLErrorCannotConnectToHost:         // -1004
          case NSURLErrorNetworkConnectionLost:       // -1005
          case NSURLErrorDNSLookupFailed:             // -1006
          case NSURLErrorResourceUnavailable:         // -1008
          case NSURLErrorNotConnectedToInternet:      // -1009
            retry = TRUE; 
            break;
	}
	
	if (retry) {
        if ( lastError ) [lastError release];
        lastError = [error retain];
		[self requestTile];
	}
	else {
		[self cancelLoading];
	}
}

- (void)connectionDidFinishLoading:(NSURLConnection *)_connection
{
	if ([data length] == 0) {
		//RMLog(@"connectionDidFinishLoading %@ data size %d", _connection, [data length]);
        
        if ( lastError ) [lastError release];
        lastError = [[NSError errorWithDomain:RMWebTileImageErrorDomain 
                                         code:RMWebTileImageErrorZeroLengthResponse
                                     userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
                                               NSLocalizedString(@"The server returned a zero-length response", @""), NSLocalizedDescriptionKey, nil]] retain];
		[self requestTile];
	}
	else {
		[self updateImageUsingData:data];
		[data release];	data = nil;
		[url release]; url = nil;
		[connection release]; connection = nil;
		if ( lastError ) {
            [lastError release]; lastError = nil;
        }
	}
}

@end