/MapView/Map/RMWebTileImage.m

http://github.com/route-me/route-me · Objective C · 287 lines · 190 code · 64 blank · 33 comment · 29 complexity · b9f38c92e98ecfcaaffdbbc6aaddce1b MD5 · raw file

  1. //
  2. // RMWebTileImage.m
  3. //
  4. // Copyright (c) 2008-2009, Route-Me Contributors
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // * Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // * Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. // POSSIBILITY OF SUCH DAMAGE.
  27. #import "RMWebTileImage.h"
  28. #import <QuartzCore/CALayer.h>
  29. #import "RMMapContents.h"
  30. #import "RMTileLoader.h"
  31. NSString *RMWebTileImageErrorDomain = @"RMWebTileImageErrorDomain";
  32. NSString *RMWebTileImageHTTPResponseCodeKey = @"RMWebTileImageHTTPResponseCodeKey";
  33. NSString *RMWebTileImageNotificationErrorKey = @"RMWebTileImageNotificationErrorKey";
  34. static NSOperationQueue *_queue = nil;
  35. @interface RMWebTileImage () <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
  36. - (void)requestTile;
  37. - (void)startLoading:(NSTimer *)timer;
  38. @end
  39. @implementation RMWebTileImage
  40. + (void) initialize
  41. {
  42. _queue = [[NSOperationQueue alloc] init];
  43. [_queue setMaxConcurrentOperationCount:kMaxConcurrentConnections];
  44. }
  45. - (id) initWithTile: (RMTile)_tile FromURL:(NSString*)urlStr
  46. {
  47. if (![super initWithTile:_tile])
  48. return nil;
  49. [super displayProxy:[RMTileProxy loadingTile]];
  50. url = [[NSURL alloc] initWithString:urlStr];
  51. connectionOp = nil;
  52. data =[[NSMutableData alloc] initWithCapacity:0];
  53. retries = kWebTileRetries;
  54. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileRequested object:self];
  55. [self requestTile];
  56. return self;
  57. }
  58. - (void) dealloc
  59. {
  60. [self cancelLoading];
  61. if ( lastError ) [lastError release]; lastError = nil;
  62. [data release];
  63. data = nil;
  64. [url release];
  65. url = nil;
  66. [super dealloc];
  67. }
  68. - (void) requestTile
  69. {
  70. //RMLog(@"fetching: %@", url);
  71. if (connectionOp) // re-request
  72. {
  73. //RMLog(@"Refetching: %@: %d", url, retries);
  74. [connectionOp cancel];
  75. [connectionOp release];
  76. connectionOp = nil;
  77. if(retries == 0) // No more retries
  78. {
  79. [super displayProxy:[RMTileProxy errorTile]];
  80. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
  81. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:lastError forKey:RMWebTileImageNotificationErrorKey]];
  82. [lastError autorelease]; lastError = nil;
  83. return;
  84. }
  85. retries--;
  86. [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(startLoading:) userInfo:nil repeats:NO];
  87. }
  88. else
  89. {
  90. [self startLoading:nil];
  91. }
  92. }
  93. - (void)startLoading:(NSTimer *)timer
  94. {
  95. NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
  96. connectionOp = [[RMURLConnectionOperation alloc] initWithRequest:request delegate:self];
  97. if (!connectionOp)
  98. {
  99. [super displayProxy:[RMTileProxy errorTile]];
  100. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
  101. } else {
  102. [_queue addOperation:connectionOp];
  103. }
  104. }
  105. - (void) cancelLoading
  106. {
  107. if (!connectionOp)
  108. return;
  109. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
  110. [connectionOp cancel];
  111. [connectionOp release];
  112. connectionOp = nil;
  113. if (lastError) [lastError release]; lastError = nil;
  114. [super cancelLoading];
  115. }
  116. #pragma mark - NSURLConnectionDelegate
  117. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
  118. {
  119. int statusCode = NSURLErrorUnknown; // unknown
  120. if ([response isKindOfClass:[NSHTTPURLResponse class]])
  121. statusCode = [(NSHTTPURLResponse*)response statusCode];
  122. [data setLength:0];
  123. /// \bug magic number
  124. if(statusCode < 400) { // Success
  125. }
  126. /// \bug magic number
  127. else if (statusCode == 404) { // Not Found
  128. [super displayProxy:[RMTileProxy missingTile]];
  129. NSError *error = [NSError errorWithDomain:RMWebTileImageErrorDomain
  130. code:RMWebTileImageErrorNotFoundResponse
  131. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  132. NSLocalizedString(@"The requested tile was not found on the server", @""), NSLocalizedDescriptionKey, nil]];
  133. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:RMWebTileImageNotificationErrorKey]];
  134. [self cancelLoading];
  135. }
  136. else { // Other Error
  137. //RMLog(@"didReceiveResponse %@ %d", _connection, statusCode);
  138. BOOL retry = FALSE;
  139. switch(statusCode) {
  140. /// \bug magic number
  141. case 500: retry = TRUE; break;
  142. case 503: retry = TRUE; break;
  143. }
  144. NSError *error = [NSError errorWithDomain:RMWebTileImageErrorDomain
  145. code:RMWebTileImageErrorUnexpectedHTTPResponse
  146. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  147. [NSNumber numberWithInt:statusCode], RMWebTileImageHTTPResponseCodeKey,
  148. [NSString stringWithFormat:NSLocalizedString(@"The server returned error code %d", @""), statusCode], NSLocalizedDescriptionKey, nil]];
  149. if (retry) {
  150. if ( lastError ) [lastError release];
  151. lastError = [error retain];
  152. [self requestTile];
  153. } else {
  154. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:RMWebTileImageNotificationErrorKey]];
  155. [self cancelLoading];
  156. }
  157. }
  158. }
  159. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)newData
  160. {
  161. [data appendData:newData];
  162. }
  163. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
  164. {
  165. BOOL retry = FALSE;
  166. switch([error code])
  167. {
  168. case NSURLErrorBadURL: // -1000
  169. case NSURLErrorTimedOut: // -1001
  170. case NSURLErrorUnsupportedURL: // -1002
  171. case NSURLErrorCannotFindHost: // -1003
  172. case NSURLErrorCannotConnectToHost: // -1004
  173. case NSURLErrorNetworkConnectionLost: // -1005
  174. case NSURLErrorDNSLookupFailed: // -1006
  175. case NSURLErrorResourceUnavailable: // -1008
  176. case NSURLErrorNotConnectedToInternet: // -1009
  177. retry = TRUE;
  178. break;
  179. }
  180. if(retry)
  181. {
  182. if (lastError) [lastError release];
  183. lastError = [error retain];
  184. [self requestTile];
  185. }
  186. else
  187. {
  188. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:RMWebTileImageNotificationErrorKey]];
  189. [self cancelLoading];
  190. }
  191. }
  192. - (void)connectionDidFinishLoading:(NSURLConnection *)connection
  193. {
  194. if ([data length] == 0) {
  195. //RMLog(@"connectionDidFinishLoading %@ data size %d", _connection, [data length]);
  196. if ( lastError ) [lastError release];
  197. lastError = [[NSError errorWithDomain:RMWebTileImageErrorDomain
  198. code:RMWebTileImageErrorZeroLengthResponse
  199. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  200. NSLocalizedString(@"The server returned a zero-length response", @""), NSLocalizedDescriptionKey, nil]] retain];
  201. [self requestTile];
  202. } else {
  203. if ( ![self updateImageUsingData:data] ) {
  204. if ( lastError ) [lastError release];
  205. lastError = [[NSError errorWithDomain:RMWebTileImageErrorDomain
  206. code:RMWebTileImageErrorUnexpectedHTTPResponse
  207. userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  208. NSLocalizedString(@"The server returned an invalid response", @""), NSLocalizedDescriptionKey, nil]] retain];
  209. [self requestTile];
  210. return;
  211. }
  212. [data release];
  213. data = nil;
  214. [url release];
  215. url = nil;
  216. [connectionOp cancel];
  217. [connectionOp release];
  218. connectionOp = nil;
  219. if (lastError) [lastError release]; lastError = nil;
  220. [[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
  221. }
  222. }
  223. @end