├── .gitignore ├── AsyncTable.xcodeproj └── project.pbxproj ├── AsyncTable_Prefix.pch ├── Classes ├── AppDelegate │ ├── AsyncTableAppDelegate.h │ └── AsyncTableAppDelegate.m ├── Controllers │ ├── FlickrCell.h │ ├── FlickrCell.m │ ├── FlickrCellDelegate.h │ ├── FlickrController.h │ ├── FlickrController.m │ ├── FlickrItemController.h │ └── FlickrItemController.m ├── Helpers │ ├── RSS.h │ ├── RSS.m │ └── RSSDelegate.h └── Models │ ├── FlickrItem.h │ ├── FlickrItem.m │ └── FlickrItemDelegate.h ├── Definitions.h ├── Entitlements.plist ├── Externals ├── ASIHTTPRequest │ ├── ASIFormDataRequest.h │ ├── ASIFormDataRequest.m │ ├── ASIHTTPRequest.h │ ├── ASIHTTPRequest.m │ ├── ASINetworkQueue.h │ ├── ASINetworkQueue.m │ ├── NSHTTPCookieAdditions.h │ └── NSHTTPCookieAdditions.m └── Reachability │ ├── Reachability.h │ └── Reachability.m ├── Info.plist ├── README.md ├── Resources ├── Default.png ├── FlickrItem.xib ├── Icon.png └── MainWindow.xib └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | Documentation 2 | build 3 | *.pbxuser 4 | *.mode2v3 5 | *.mode1v3 6 | .DS_Store 7 | 8 | *.perspectivev3 9 | xcuserdata 10 | project.xcworkspace 11 | 12 | -------------------------------------------------------------------------------- /AsyncTable.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; 11 | 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 12 | 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; 13 | 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765A40DF7441C002DB57D /* CoreGraphics.framework */; }; 14 | 3A0DC39E1501198200E24823 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A0DC39D1501198200E24823 /* libz.dylib */; }; 15 | 3A18BE2F0F643D0C007D5CB9 /* AsyncTableAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE1D0F643D0C007D5CB9 /* AsyncTableAppDelegate.m */; }; 16 | 3A18BE310F643D0C007D5CB9 /* FlickrCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE220F643D0C007D5CB9 /* FlickrCell.m */; }; 17 | 3A18BE320F643D0C007D5CB9 /* FlickrController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE240F643D0C007D5CB9 /* FlickrController.m */; }; 18 | 3A18BE340F643D0C007D5CB9 /* RSS.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE290F643D0C007D5CB9 /* RSS.m */; }; 19 | 3A18BE350F643D0C007D5CB9 /* FlickrItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE2D0F643D0C007D5CB9 /* FlickrItem.m */; }; 20 | 3A18BE400F643D3C007D5CB9 /* ASIFormDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE390F643D3C007D5CB9 /* ASIFormDataRequest.m */; }; 21 | 3A18BE410F643D3C007D5CB9 /* ASIHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE3B0F643D3C007D5CB9 /* ASIHTTPRequest.m */; }; 22 | 3A18BE420F643D3C007D5CB9 /* ASINetworkQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE3D0F643D3C007D5CB9 /* ASINetworkQueue.m */; }; 23 | 3A18BE430F643D3C007D5CB9 /* NSHTTPCookieAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE3F0F643D3C007D5CB9 /* NSHTTPCookieAdditions.m */; }; 24 | 3A18BE7B0F644130007D5CB9 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A18BE7A0F644130007D5CB9 /* Reachability.m */; }; 25 | 3A18BE8E0F644258007D5CB9 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A18BE8D0F644258007D5CB9 /* SystemConfiguration.framework */; }; 26 | 3A18BE900F644263007D5CB9 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A18BE8F0F644263007D5CB9 /* CFNetwork.framework */; }; 27 | 3A4686AE0F645A91001B5E56 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 3A4686A90F645A91001B5E56 /* Default.png */; }; 28 | 3A4686B00F645A91001B5E56 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 3A4686AB0F645A91001B5E56 /* Icon.png */; }; 29 | 3A4686B10F645A91001B5E56 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3A4686AC0F645A91001B5E56 /* MainWindow.xib */; }; 30 | 3A4686B40F645AB6001B5E56 /* Entitlements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3A4686B30F645AB6001B5E56 /* Entitlements.plist */; }; 31 | 3ABCE8A50F645D2C003CE490 /* FlickrItemController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3ABCE8A40F645D2C003CE490 /* FlickrItemController.m */; }; 32 | 3ABCE8A70F645D9C003CE490 /* FlickrItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3ABCE8A60F645D9C003CE490 /* FlickrItem.xib */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 37 | 1D6058910D05DD3D006BFB54 /* AsyncTable.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AsyncTable.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 39 | 288765A40DF7441C002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 40 | 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 41 | 32CA4F630368D1EE00C91783 /* AsyncTable_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncTable_Prefix.pch; sourceTree = ""; }; 42 | 3A0DC39D1501198200E24823 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 43 | 3A18BE1C0F643D0C007D5CB9 /* AsyncTableAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncTableAppDelegate.h; sourceTree = ""; }; 44 | 3A18BE1D0F643D0C007D5CB9 /* AsyncTableAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncTableAppDelegate.m; sourceTree = ""; }; 45 | 3A18BE210F643D0C007D5CB9 /* FlickrCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlickrCell.h; sourceTree = ""; }; 46 | 3A18BE220F643D0C007D5CB9 /* FlickrCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlickrCell.m; sourceTree = ""; }; 47 | 3A18BE230F643D0C007D5CB9 /* FlickrController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlickrController.h; sourceTree = ""; }; 48 | 3A18BE240F643D0C007D5CB9 /* FlickrController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlickrController.m; sourceTree = ""; }; 49 | 3A18BE280F643D0C007D5CB9 /* RSS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSS.h; sourceTree = ""; }; 50 | 3A18BE290F643D0C007D5CB9 /* RSS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSS.m; sourceTree = ""; }; 51 | 3A18BE2A0F643D0C007D5CB9 /* RSSDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSSDelegate.h; sourceTree = ""; }; 52 | 3A18BE2C0F643D0C007D5CB9 /* FlickrItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlickrItem.h; sourceTree = ""; }; 53 | 3A18BE2D0F643D0C007D5CB9 /* FlickrItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlickrItem.m; sourceTree = ""; }; 54 | 3A18BE2E0F643D0C007D5CB9 /* FlickrItemDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlickrItemDelegate.h; sourceTree = ""; }; 55 | 3A18BE380F643D3C007D5CB9 /* ASIFormDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIFormDataRequest.h; sourceTree = ""; }; 56 | 3A18BE390F643D3C007D5CB9 /* ASIFormDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIFormDataRequest.m; sourceTree = ""; }; 57 | 3A18BE3A0F643D3C007D5CB9 /* ASIHTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIHTTPRequest.h; sourceTree = ""; }; 58 | 3A18BE3B0F643D3C007D5CB9 /* ASIHTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIHTTPRequest.m; sourceTree = ""; }; 59 | 3A18BE3C0F643D3C007D5CB9 /* ASINetworkQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASINetworkQueue.h; sourceTree = ""; }; 60 | 3A18BE3D0F643D3C007D5CB9 /* ASINetworkQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASINetworkQueue.m; sourceTree = ""; }; 61 | 3A18BE3E0F643D3C007D5CB9 /* NSHTTPCookieAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSHTTPCookieAdditions.h; sourceTree = ""; }; 62 | 3A18BE3F0F643D3C007D5CB9 /* NSHTTPCookieAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSHTTPCookieAdditions.m; sourceTree = ""; }; 63 | 3A18BE6C0F644076007D5CB9 /* Definitions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Definitions.h; sourceTree = ""; }; 64 | 3A18BE790F644130007D5CB9 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 65 | 3A18BE7A0F644130007D5CB9 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; 66 | 3A18BE8D0F644258007D5CB9 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 67 | 3A18BE8F0F644263007D5CB9 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 68 | 3A4686A90F645A91001B5E56 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 69 | 3A4686AB0F645A91001B5E56 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; 70 | 3A4686AC0F645A91001B5E56 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; 71 | 3A4686B30F645AB6001B5E56 /* Entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Entitlements.plist; sourceTree = SOURCE_ROOT; }; 72 | 3ABCE8A30F645D2C003CE490 /* FlickrItemController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlickrItemController.h; sourceTree = ""; }; 73 | 3ABCE8A40F645D2C003CE490 /* FlickrItemController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlickrItemController.m; sourceTree = ""; }; 74 | 3ABCE8A60F645D9C003CE490 /* FlickrItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FlickrItem.xib; sourceTree = ""; }; 75 | 3ABCE8EA0F646481003CE490 /* FlickrCellDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlickrCellDelegate.h; sourceTree = ""; }; 76 | 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Info.plist; sourceTree = ""; }; 77 | /* End PBXFileReference section */ 78 | 79 | /* Begin PBXFrameworksBuildPhase section */ 80 | 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | 3A0DC39E1501198200E24823 /* libz.dylib in Frameworks */, 85 | 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, 86 | 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, 87 | 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */, 88 | 3A18BE8E0F644258007D5CB9 /* SystemConfiguration.framework in Frameworks */, 89 | 3A18BE900F644263007D5CB9 /* CFNetwork.framework in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 1D6058910D05DD3D006BFB54 /* AsyncTable.app */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 3A18BE1A0F643D0C007D5CB9 /* Classes */, 108 | 3A18BE360F643D3C007D5CB9 /* Externals */, 109 | 29B97315FDCFA39411CA2CEA /* Other Sources */, 110 | 3A4686A70F645A91001B5E56 /* Resources */, 111 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 112 | 19C28FACFE9D520D11CA2CBB /* Products */, 113 | ); 114 | name = CustomTemplate; 115 | sourceTree = ""; 116 | }; 117 | 29B97315FDCFA39411CA2CEA /* Other Sources */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 3A18BE6C0F644076007D5CB9 /* Definitions.h */, 121 | 32CA4F630368D1EE00C91783 /* AsyncTable_Prefix.pch */, 122 | 29B97316FDCFA39411CA2CEA /* main.m */, 123 | ); 124 | name = "Other Sources"; 125 | sourceTree = ""; 126 | }; 127 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 3A0DC39D1501198200E24823 /* libz.dylib */, 131 | 3A18BE8F0F644263007D5CB9 /* CFNetwork.framework */, 132 | 3A18BE8D0F644258007D5CB9 /* SystemConfiguration.framework */, 133 | 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, 134 | 1D30AB110D05D00D00671497 /* Foundation.framework */, 135 | 288765A40DF7441C002DB57D /* CoreGraphics.framework */, 136 | ); 137 | name = Frameworks; 138 | sourceTree = ""; 139 | }; 140 | 3A18BE1A0F643D0C007D5CB9 /* Classes */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 3A18BE1B0F643D0C007D5CB9 /* AppDelegate */, 144 | 3A18BE1E0F643D0C007D5CB9 /* Controllers */, 145 | 3A18BE270F643D0C007D5CB9 /* Helpers */, 146 | 3A18BE2B0F643D0C007D5CB9 /* Models */, 147 | ); 148 | path = Classes; 149 | sourceTree = ""; 150 | }; 151 | 3A18BE1B0F643D0C007D5CB9 /* AppDelegate */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 3A18BE1C0F643D0C007D5CB9 /* AsyncTableAppDelegate.h */, 155 | 3A18BE1D0F643D0C007D5CB9 /* AsyncTableAppDelegate.m */, 156 | ); 157 | path = AppDelegate; 158 | sourceTree = ""; 159 | }; 160 | 3A18BE1E0F643D0C007D5CB9 /* Controllers */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 3A18BE210F643D0C007D5CB9 /* FlickrCell.h */, 164 | 3A18BE220F643D0C007D5CB9 /* FlickrCell.m */, 165 | 3ABCE8EA0F646481003CE490 /* FlickrCellDelegate.h */, 166 | 3A18BE230F643D0C007D5CB9 /* FlickrController.h */, 167 | 3A18BE240F643D0C007D5CB9 /* FlickrController.m */, 168 | 3ABCE8A30F645D2C003CE490 /* FlickrItemController.h */, 169 | 3ABCE8A40F645D2C003CE490 /* FlickrItemController.m */, 170 | ); 171 | path = Controllers; 172 | sourceTree = ""; 173 | }; 174 | 3A18BE270F643D0C007D5CB9 /* Helpers */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 3A18BE280F643D0C007D5CB9 /* RSS.h */, 178 | 3A18BE290F643D0C007D5CB9 /* RSS.m */, 179 | 3A18BE2A0F643D0C007D5CB9 /* RSSDelegate.h */, 180 | ); 181 | path = Helpers; 182 | sourceTree = ""; 183 | }; 184 | 3A18BE2B0F643D0C007D5CB9 /* Models */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | 3A18BE2C0F643D0C007D5CB9 /* FlickrItem.h */, 188 | 3A18BE2D0F643D0C007D5CB9 /* FlickrItem.m */, 189 | 3A18BE2E0F643D0C007D5CB9 /* FlickrItemDelegate.h */, 190 | ); 191 | path = Models; 192 | sourceTree = ""; 193 | }; 194 | 3A18BE360F643D3C007D5CB9 /* Externals */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 3A18BE780F644130007D5CB9 /* Reachability */, 198 | 3A18BE370F643D3C007D5CB9 /* ASIHTTPRequest */, 199 | ); 200 | path = Externals; 201 | sourceTree = ""; 202 | }; 203 | 3A18BE370F643D3C007D5CB9 /* ASIHTTPRequest */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 3A18BE380F643D3C007D5CB9 /* ASIFormDataRequest.h */, 207 | 3A18BE390F643D3C007D5CB9 /* ASIFormDataRequest.m */, 208 | 3A18BE3A0F643D3C007D5CB9 /* ASIHTTPRequest.h */, 209 | 3A18BE3B0F643D3C007D5CB9 /* ASIHTTPRequest.m */, 210 | 3A18BE3C0F643D3C007D5CB9 /* ASINetworkQueue.h */, 211 | 3A18BE3D0F643D3C007D5CB9 /* ASINetworkQueue.m */, 212 | 3A18BE3E0F643D3C007D5CB9 /* NSHTTPCookieAdditions.h */, 213 | 3A18BE3F0F643D3C007D5CB9 /* NSHTTPCookieAdditions.m */, 214 | ); 215 | path = ASIHTTPRequest; 216 | sourceTree = ""; 217 | }; 218 | 3A18BE780F644130007D5CB9 /* Reachability */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 3A18BE790F644130007D5CB9 /* Reachability.h */, 222 | 3A18BE7A0F644130007D5CB9 /* Reachability.m */, 223 | ); 224 | path = Reachability; 225 | sourceTree = ""; 226 | }; 227 | 3A4686A70F645A91001B5E56 /* Resources */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | 3A4686B30F645AB6001B5E56 /* Entitlements.plist */, 231 | 8D1107310486CEB800E47090 /* Info.plist */, 232 | 3A4686A90F645A91001B5E56 /* Default.png */, 233 | 3A4686AB0F645A91001B5E56 /* Icon.png */, 234 | 3A4686AC0F645A91001B5E56 /* MainWindow.xib */, 235 | 3ABCE8A60F645D9C003CE490 /* FlickrItem.xib */, 236 | ); 237 | path = Resources; 238 | sourceTree = ""; 239 | }; 240 | /* End PBXGroup section */ 241 | 242 | /* Begin PBXNativeTarget section */ 243 | 1D6058900D05DD3D006BFB54 /* AsyncTable */ = { 244 | isa = PBXNativeTarget; 245 | buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "AsyncTable" */; 246 | buildPhases = ( 247 | 1D60588D0D05DD3D006BFB54 /* Resources */, 248 | 1D60588E0D05DD3D006BFB54 /* Sources */, 249 | 1D60588F0D05DD3D006BFB54 /* Frameworks */, 250 | ); 251 | buildRules = ( 252 | ); 253 | dependencies = ( 254 | ); 255 | name = AsyncTable; 256 | productName = AsyncTable; 257 | productReference = 1D6058910D05DD3D006BFB54 /* AsyncTable.app */; 258 | productType = "com.apple.product-type.application"; 259 | }; 260 | /* End PBXNativeTarget section */ 261 | 262 | /* Begin PBXProject section */ 263 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 264 | isa = PBXProject; 265 | attributes = { 266 | LastUpgradeCheck = 0420; 267 | }; 268 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AsyncTable" */; 269 | compatibilityVersion = "Xcode 3.2"; 270 | developmentRegion = English; 271 | hasScannedForEncodings = 1; 272 | knownRegions = ( 273 | en, 274 | ); 275 | mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; 276 | projectDirPath = ""; 277 | projectRoot = ""; 278 | targets = ( 279 | 1D6058900D05DD3D006BFB54 /* AsyncTable */, 280 | ); 281 | }; 282 | /* End PBXProject section */ 283 | 284 | /* Begin PBXResourcesBuildPhase section */ 285 | 1D60588D0D05DD3D006BFB54 /* Resources */ = { 286 | isa = PBXResourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 3A4686AE0F645A91001B5E56 /* Default.png in Resources */, 290 | 3A4686B00F645A91001B5E56 /* Icon.png in Resources */, 291 | 3A4686B10F645A91001B5E56 /* MainWindow.xib in Resources */, 292 | 3A4686B40F645AB6001B5E56 /* Entitlements.plist in Resources */, 293 | 3ABCE8A70F645D9C003CE490 /* FlickrItem.xib in Resources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | /* End PBXResourcesBuildPhase section */ 298 | 299 | /* Begin PBXSourcesBuildPhase section */ 300 | 1D60588E0D05DD3D006BFB54 /* Sources */ = { 301 | isa = PBXSourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | 1D60589B0D05DD56006BFB54 /* main.m in Sources */, 305 | 3A18BE2F0F643D0C007D5CB9 /* AsyncTableAppDelegate.m in Sources */, 306 | 3A18BE310F643D0C007D5CB9 /* FlickrCell.m in Sources */, 307 | 3A18BE320F643D0C007D5CB9 /* FlickrController.m in Sources */, 308 | 3A18BE340F643D0C007D5CB9 /* RSS.m in Sources */, 309 | 3A18BE350F643D0C007D5CB9 /* FlickrItem.m in Sources */, 310 | 3A18BE400F643D3C007D5CB9 /* ASIFormDataRequest.m in Sources */, 311 | 3A18BE410F643D3C007D5CB9 /* ASIHTTPRequest.m in Sources */, 312 | 3A18BE420F643D3C007D5CB9 /* ASINetworkQueue.m in Sources */, 313 | 3A18BE430F643D3C007D5CB9 /* NSHTTPCookieAdditions.m in Sources */, 314 | 3A18BE7B0F644130007D5CB9 /* Reachability.m in Sources */, 315 | 3ABCE8A50F645D2C003CE490 /* FlickrItemController.m in Sources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXSourcesBuildPhase section */ 320 | 321 | /* Begin XCBuildConfiguration section */ 322 | 1D6058940D05DD3E006BFB54 /* Debug */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ALWAYS_SEARCH_USER_PATHS = NO; 326 | COPY_PHASE_STRIP = NO; 327 | GCC_DYNAMIC_NO_PIC = NO; 328 | GCC_OPTIMIZATION_LEVEL = 0; 329 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 330 | GCC_PREFIX_HEADER = AsyncTable_Prefix.pch; 331 | INFOPLIST_FILE = Info.plist; 332 | PRODUCT_NAME = AsyncTable; 333 | }; 334 | name = Debug; 335 | }; 336 | 1D6058950D05DD3E006BFB54 /* Release */ = { 337 | isa = XCBuildConfiguration; 338 | buildSettings = { 339 | ALWAYS_SEARCH_USER_PATHS = NO; 340 | COPY_PHASE_STRIP = YES; 341 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 342 | GCC_PREFIX_HEADER = AsyncTable_Prefix.pch; 343 | INFOPLIST_FILE = Info.plist; 344 | PRODUCT_NAME = AsyncTable; 345 | }; 346 | name = Release; 347 | }; 348 | C01FCF4F08A954540054247B /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 352 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 353 | GCC_C_LANGUAGE_STANDARD = c99; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | ONLY_ACTIVE_ARCH = YES; 357 | SDKROOT = iphoneos; 358 | }; 359 | name = Debug; 360 | }; 361 | C01FCF5008A954540054247B /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 366 | GCC_C_LANGUAGE_STANDARD = c99; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | SDKROOT = iphoneos; 370 | }; 371 | name = Release; 372 | }; 373 | /* End XCBuildConfiguration section */ 374 | 375 | /* Begin XCConfigurationList section */ 376 | 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "AsyncTable" */ = { 377 | isa = XCConfigurationList; 378 | buildConfigurations = ( 379 | 1D6058940D05DD3E006BFB54 /* Debug */, 380 | 1D6058950D05DD3E006BFB54 /* Release */, 381 | ); 382 | defaultConfigurationIsVisible = 0; 383 | defaultConfigurationName = Release; 384 | }; 385 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "AsyncTable" */ = { 386 | isa = XCConfigurationList; 387 | buildConfigurations = ( 388 | C01FCF4F08A954540054247B /* Debug */, 389 | C01FCF5008A954540054247B /* Release */, 390 | ); 391 | defaultConfigurationIsVisible = 0; 392 | defaultConfigurationName = Release; 393 | }; 394 | /* End XCConfigurationList section */ 395 | }; 396 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 397 | } 398 | -------------------------------------------------------------------------------- /AsyncTable_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'AsyncTable' target in the 'AsyncTable' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /Classes/AppDelegate/AsyncTableAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncTableAppDelegate.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright akosma software 2009. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FlickrController; 12 | 13 | @interface AsyncTableAppDelegate : NSObject 14 | { 15 | @private 16 | IBOutlet UIWindow *window; 17 | FlickrController *controller; 18 | NSOperationQueue *downloadQueue; 19 | UIView *loadingView; 20 | } 21 | 22 | @property (nonatomic, retain) NSOperationQueue *downloadQueue; 23 | 24 | + (AsyncTableAppDelegate *)sharedAppDelegate; 25 | 26 | - (void)showLoadingView; 27 | - (void)hideLoadingView; 28 | 29 | @end 30 | 31 | -------------------------------------------------------------------------------- /Classes/AppDelegate/AsyncTableAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncTableAppDelegate.m 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright akosma software 2009. All rights reserved. 7 | // 8 | 9 | #import "AsyncTableAppDelegate.h" 10 | #import "FlickrController.h" 11 | 12 | @implementation AsyncTableAppDelegate 13 | 14 | @synthesize downloadQueue; 15 | 16 | + (AsyncTableAppDelegate *)sharedAppDelegate 17 | { 18 | return (AsyncTableAppDelegate *)[UIApplication sharedApplication].delegate; 19 | } 20 | 21 | - (void)dealloc 22 | { 23 | [controller release]; 24 | [downloadQueue release]; 25 | [super dealloc]; 26 | } 27 | 28 | #pragma mark - 29 | #pragma mark UIApplicationDelegate methods 30 | 31 | - (void)applicationDidFinishLaunching:(UIApplication *)application 32 | { 33 | downloadQueue = [[NSOperationQueue alloc] init]; 34 | controller = [[FlickrController alloc] init]; 35 | [window addSubview:controller.navigationController.view]; 36 | [window makeKeyAndVisible]; 37 | [controller reloadFeed]; 38 | } 39 | 40 | #pragma mark - 41 | #pragma mark Public methods 42 | 43 | - (void)showLoadingView 44 | { 45 | if (loadingView == nil) 46 | { 47 | loadingView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 480.0)]; 48 | loadingView.opaque = NO; 49 | loadingView.backgroundColor = [UIColor darkGrayColor]; 50 | loadingView.alpha = 0.5; 51 | 52 | UIActivityIndicatorView *spinningWheel = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(142.0, 222.0, 37.0, 37.0)]; 53 | [spinningWheel startAnimating]; 54 | spinningWheel.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; 55 | [loadingView addSubview:spinningWheel]; 56 | [spinningWheel release]; 57 | } 58 | 59 | [window addSubview:loadingView]; 60 | } 61 | 62 | - (void)hideLoadingView 63 | { 64 | [loadingView removeFromSuperview]; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Classes/Controllers/FlickrCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrCell.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlickrItemDelegate.h" 11 | #import "FlickrCellDelegate.h" 12 | 13 | @class FlickrItem; 14 | 15 | @interface FlickrCell : UITableViewCell 16 | { 17 | @private 18 | UILabel *textLabel; 19 | FlickrItem *item; 20 | UIImageView *photo; 21 | UIActivityIndicatorView *scrollingWheel; 22 | NSObject *delegate; 23 | } 24 | 25 | @property (nonatomic, retain) FlickrItem *item; 26 | @property (nonatomic, assign) NSObject *delegate; 27 | 28 | - (void)loadImage; 29 | - (void)toggleImage; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Classes/Controllers/FlickrCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrCell.m 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import "FlickrCell.h" 10 | #import "FlickrItem.h" 11 | 12 | @implementation FlickrCell 13 | 14 | @synthesize item; 15 | @synthesize delegate; 16 | 17 | #pragma mark - 18 | #pragma mark Constructor and destructor 19 | 20 | - (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier 21 | { 22 | if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) 23 | { 24 | self.backgroundColor = [UIColor blackColor]; 25 | 26 | textLabel = [[UILabel alloc] initWithFrame:CGRectMake(80.0, 5.0, 290.0, 70.0)]; 27 | textLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:18.0]; 28 | textLabel.contentMode = UIViewContentModeScaleToFill; 29 | [self.contentView addSubview:textLabel]; 30 | 31 | photo = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 75.0, 75.0)]; 32 | photo.contentMode = UIViewContentModeScaleAspectFill; 33 | photo.clipsToBounds = YES; 34 | [self.contentView addSubview:photo]; 35 | 36 | scrollingWheel = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(27.0, 27.0, 20.0, 20.0)]; 37 | scrollingWheel.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 38 | scrollingWheel.hidesWhenStopped = YES; 39 | [scrollingWheel stopAnimating]; 40 | [self.contentView addSubview:scrollingWheel]; 41 | 42 | self.accessoryType = UITableViewCellAccessoryNone; 43 | } 44 | return self; 45 | } 46 | 47 | - (void)dealloc 48 | { 49 | delegate = nil; 50 | [photo release]; 51 | [textLabel release]; 52 | [item setDelegate:nil]; 53 | [item release]; 54 | [super dealloc]; 55 | } 56 | 57 | #pragma mark - 58 | #pragma mark Public methods 59 | 60 | - (void)setItem:(FlickrItem *)newItem 61 | { 62 | if (newItem != item) 63 | { 64 | item.delegate = nil; 65 | [item release]; 66 | item = nil; 67 | 68 | item = [newItem retain]; 69 | [item setDelegate:self]; 70 | 71 | if (item != nil) 72 | { 73 | textLabel.text = item.title; 74 | 75 | // This is to avoid the item loading the image 76 | // when this setter is called; we only want that 77 | // to happen depending on the scrolling of the table 78 | if ([item hasLoadedThumbnail]) 79 | { 80 | photo.image = item.thumbnail; 81 | [scrollingWheel stopAnimating]; 82 | } 83 | else 84 | { 85 | photo.image = nil; 86 | } 87 | } 88 | } 89 | } 90 | 91 | - (void)toggleImage 92 | { 93 | [UIView beginAnimations:nil context:NULL]; 94 | [UIView setAnimationDuration:0.5]; 95 | [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:photo cache:YES]; 96 | [UIView setAnimationDelegate:self]; 97 | [UIView setAnimationDidStopSelector:@selector(animationFinished)]; 98 | 99 | photo.image = item.thumbnail; 100 | 101 | [UIView commitAnimations]; 102 | } 103 | 104 | - (void)loadImage 105 | { 106 | // The getter in the FlickrItem class is overloaded...! 107 | // If the image is not yet downloaded, it returns nil and 108 | // begins the asynchronous downloading of the image. 109 | UIImage *image = item.thumbnail; 110 | if (image == nil) 111 | { 112 | [scrollingWheel startAnimating]; 113 | } 114 | photo.image = image; 115 | } 116 | 117 | #pragma mark - 118 | #pragma mark FlickrItemDelegate methods 119 | 120 | - (void)flickrItem:(FlickrItem *)item didLoadThumbnail:(UIImage *)image 121 | { 122 | photo.image = image; 123 | [scrollingWheel stopAnimating]; 124 | } 125 | 126 | - (void)flickrItem:(FlickrItem *)item couldNotLoadImageError:(NSError *)error 127 | { 128 | // Here we could show a "default" or "placeholder" image... 129 | [scrollingWheel stopAnimating]; 130 | } 131 | 132 | #pragma mark - 133 | #pragma mark UIView animation delegate methods 134 | 135 | - (void)animationFinished 136 | { 137 | if ([delegate respondsToSelector:@selector(flickrCellAnimationFinished:)]) 138 | { 139 | [delegate flickrCellAnimationFinished:self]; 140 | } 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /Classes/Controllers/FlickrCellDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrCellDelegate.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FlickrCell; 12 | 13 | @protocol FlickrCellDelegate 14 | 15 | @required 16 | - (void)flickrCellAnimationFinished:(FlickrCell *)cell; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/Controllers/FlickrController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrController.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RSSDelegate.h" 11 | #import "FlickrCellDelegate.h" 12 | 13 | @class RSS; 14 | 15 | @interface FlickrController : UITableViewController 16 | { 17 | @private 18 | RSS *rss; 19 | NSArray *flickrItems; 20 | UINavigationController *navigationController; 21 | } 22 | 23 | @property (nonatomic, readonly) UINavigationController *navigationController; 24 | 25 | - (void)reloadFeed; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Classes/Controllers/FlickrController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrController.m 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import "FlickrController.h" 10 | #import "RSS.h" 11 | #import "Definitions.h" 12 | #import "FlickrItem.h" 13 | #import "FlickrCell.h" 14 | #import "FlickrItemController.h" 15 | #import "AsyncTableAppDelegate.h" 16 | #import "Reachability.h" 17 | 18 | @interface FlickrController (Private) 19 | - (void)loadContentForVisibleCells; 20 | @end 21 | 22 | 23 | @implementation FlickrController 24 | 25 | @synthesize navigationController; 26 | 27 | - (id)init 28 | { 29 | if (self = [super initWithStyle:UITableViewStylePlain]) 30 | { 31 | navigationController = [[UINavigationController alloc] initWithRootViewController:self]; 32 | self.title = @"Flickr RSS Feed"; 33 | rss = [[RSS alloc] init]; 34 | rss.delegate = self; 35 | NSURL *url = [[NSURL alloc] initWithString:NEWS_FEED_URL]; 36 | rss.url = url; 37 | [url release]; 38 | 39 | UIBarButtonItem *reloadButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh 40 | target:self 41 | action:@selector(reloadFeed)]; 42 | self.navigationItem.rightBarButtonItem = reloadButton; 43 | [reloadButton release]; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)dealloc 49 | { 50 | [navigationController release]; 51 | [flickrItems release]; 52 | [rss setDelegate:nil]; 53 | [rss release]; 54 | [super dealloc]; 55 | } 56 | 57 | #pragma mark - 58 | #pragma mark Public methods 59 | 60 | - (void)reloadFeed 61 | { 62 | // Check if the remote server is available 63 | Reachability *reachManager = [Reachability sharedReachability]; 64 | AsyncTableAppDelegate *appDelegate = [AsyncTableAppDelegate sharedAppDelegate]; 65 | [reachManager setHostName:@"www.flickr.com"]; 66 | NetworkStatus remoteHostStatus = [reachManager remoteHostStatus]; 67 | if (remoteHostStatus == NotReachable) 68 | { 69 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 70 | NSString *msg = @"Flickr is not reachable! This requires Internet connectivity."; 71 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Network Problem" 72 | message:msg 73 | delegate:nil 74 | cancelButtonTitle:@"OK" 75 | otherButtonTitles:nil]; 76 | [alert show]; 77 | [alert release]; 78 | return; 79 | } 80 | else if (remoteHostStatus == ReachableViaWiFiNetwork) 81 | { 82 | [appDelegate.downloadQueue setMaxConcurrentOperationCount:4]; 83 | } 84 | else if (remoteHostStatus == ReachableViaCarrierDataNetwork) 85 | { 86 | [appDelegate.downloadQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount]; 87 | } 88 | 89 | [appDelegate showLoadingView]; 90 | [rss fetch]; 91 | } 92 | 93 | #pragma mark - 94 | #pragma mark RSSDelegate methods 95 | 96 | - (void)feed:(RSS *)feed didFindItems:(NSArray *)items 97 | { 98 | [flickrItems release]; 99 | flickrItems = [items retain]; 100 | [self.tableView reloadData]; 101 | [self loadContentForVisibleCells]; 102 | [[AsyncTableAppDelegate sharedAppDelegate] hideLoadingView]; 103 | } 104 | 105 | - (void)feed:(RSS *)feed didFailWithError:(NSString *)errorMsg 106 | { 107 | [[AsyncTableAppDelegate sharedAppDelegate] hideLoadingView]; 108 | } 109 | 110 | #pragma mark - 111 | #pragma mark FlickrCellDelegate methods 112 | 113 | - (void)flickrCellAnimationFinished:(FlickrCell *)cell 114 | { 115 | NSIndexPath *indexPath = [self.tableView indexPathForCell:cell]; 116 | FlickrItem *item = [flickrItems objectAtIndex:indexPath.row]; 117 | FlickrItemController *controller = [[FlickrItemController alloc] init]; 118 | controller.item = item; 119 | controller.title = item.title; 120 | [self.navigationController pushViewController:controller animated:YES]; 121 | [controller release]; 122 | } 123 | 124 | #pragma mark - 125 | #pragma mark UIScrollViewDelegate methods 126 | 127 | // These methods are adapted from 128 | // http://idevkit.com/forums/tutorials-code-samples-sdk/2-dynamic-content-loading-uitableview.html 129 | 130 | - (void)loadContentForVisibleCells 131 | { 132 | NSArray *cells = [self.tableView visibleCells]; 133 | [cells retain]; 134 | for (int i = 0; i < [cells count]; i++) 135 | { 136 | // Go through each cell in the array and call its loadContent method if it responds to it. 137 | FlickrCell *flickrCell = (FlickrCell *)[[cells objectAtIndex: i] retain]; 138 | [flickrCell loadImage]; 139 | [flickrCell release]; 140 | flickrCell = nil; 141 | } 142 | [cells release]; 143 | } 144 | 145 | 146 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; 147 | { 148 | // Method is called when the decelerating comes to a stop. 149 | // Pass visible cells to the cell loading function. If possible change 150 | // scrollView to a pointer to your table cell to avoid compiler warnings 151 | [self loadContentForVisibleCells]; 152 | } 153 | 154 | 155 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; 156 | { 157 | if (!decelerate) 158 | { 159 | [self loadContentForVisibleCells]; 160 | } 161 | } 162 | 163 | #pragma mark - 164 | #pragma mark Table view methods 165 | 166 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 167 | { 168 | return 1; 169 | } 170 | 171 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 172 | { 173 | return [flickrItems count]; 174 | } 175 | 176 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 177 | { 178 | FlickrItem *item = [flickrItems objectAtIndex:indexPath.row]; 179 | static NSString *identifier = @"FlickrItemCell"; 180 | FlickrCell *cell = (FlickrCell *)[tableView dequeueReusableCellWithIdentifier:identifier]; 181 | if (cell == nil) 182 | { 183 | cell = [[[FlickrCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease]; 184 | cell.delegate = self; 185 | } 186 | cell.item = item; 187 | return cell; 188 | } 189 | 190 | //- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath 191 | //{ 192 | // FlickrCell *flickrCell = (FlickrCell *)cell; 193 | // [flickrCell loadImage]; 194 | //} 195 | 196 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 197 | { 198 | FlickrCell *cell = (FlickrCell *)[self.tableView cellForRowAtIndexPath:indexPath]; 199 | [cell toggleImage]; 200 | } 201 | 202 | #pragma mark - 203 | #pragma mark UIViewController methods 204 | 205 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 206 | { 207 | return NO; 208 | } 209 | 210 | - (void)didReceiveMemoryWarning 211 | { 212 | [super didReceiveMemoryWarning]; 213 | } 214 | 215 | - (void)viewWillAppear:(BOOL)animated 216 | { 217 | self.tableView.rowHeight = 76.0; 218 | 219 | // Unselect the selected row if any 220 | // http://forums.macrumors.com/showthread.php?t=577677 221 | NSIndexPath* selection = [self.tableView indexPathForSelectedRow]; 222 | if (selection) 223 | { 224 | [self.tableView deselectRowAtIndexPath:selection animated:YES]; 225 | } 226 | } 227 | 228 | @end 229 | -------------------------------------------------------------------------------- /Classes/Controllers/FlickrItemController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrItemController.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlickrItemDelegate.h" 11 | 12 | @class FlickrItem; 13 | @class ASINetworkQueue; 14 | 15 | @interface FlickrItemController : UIViewController 16 | { 17 | @private 18 | IBOutlet UIImageView *photoView; 19 | IBOutlet UIProgressView *progressView; 20 | IBOutlet UIButton *saveButton; 21 | FlickrItem *item; 22 | ASINetworkQueue *downloadQueue; 23 | } 24 | 25 | @property (nonatomic, retain) FlickrItem *item; 26 | 27 | - (IBAction)save; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Classes/Controllers/FlickrItemController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrItemController.m 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import "FlickrItemController.h" 10 | #import "FlickrItem.h" 11 | #import "ASIHTTPRequest.h" 12 | #import "ASINetworkQueue.h" 13 | 14 | @implementation FlickrItemController 15 | 16 | @synthesize item; 17 | 18 | #pragma mark - 19 | #pragma mark Constructor and destructor 20 | 21 | - (id)init 22 | { 23 | if (self = [super initWithNibName:@"FlickrItem" bundle:nil]) 24 | { 25 | downloadQueue = [[ASINetworkQueue alloc] init]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)dealloc 31 | { 32 | [downloadQueue release]; 33 | [item setDelegate:nil]; 34 | [item release]; 35 | [super dealloc]; 36 | } 37 | 38 | #pragma mark - 39 | #pragma mark Overridden setter 40 | 41 | - (void)setItem:(FlickrItem *)newItem 42 | { 43 | if (item != newItem) 44 | { 45 | [item setDelegate:nil]; 46 | [item release]; 47 | item = nil; 48 | item = [newItem retain]; 49 | 50 | if (item != nil) 51 | { 52 | item.delegate = self; 53 | saveButton.enabled = NO; 54 | saveButton.alpha = 0.5; 55 | } 56 | } 57 | } 58 | 59 | #pragma mark - 60 | #pragma mark IBAction methods 61 | 62 | - (IBAction)save 63 | { 64 | UIImageWriteToSavedPhotosAlbum(photoView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); 65 | } 66 | 67 | #pragma mark - 68 | #pragma mark UIImageWriteToSavedPhotosAlbum delegate method 69 | 70 | - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo; 71 | { 72 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Image Saved" 73 | message:@"The image has been saved in your photo album!" 74 | delegate:nil 75 | cancelButtonTitle:nil 76 | otherButtonTitles:@"OK", nil]; 77 | [alert show]; 78 | [alert release]; 79 | } 80 | 81 | #pragma mark - 82 | #pragma mark FlickrItemDelegate methods 83 | 84 | - (void)flickrItem:(FlickrItem *)item didLoadImage:(UIImage *)image 85 | { 86 | photoView.image = image; 87 | saveButton.enabled = YES; 88 | saveButton.alpha = 1.0; 89 | } 90 | 91 | - (void)flickrItem:(FlickrItem *)item couldNotLoadImageError:(NSError *)error 92 | { 93 | } 94 | 95 | #pragma mark - 96 | #pragma mark ASINetworkQueue delegate methods 97 | 98 | - (void)requestDone:(ASIHTTPRequest *)request 99 | { 100 | NSData *data = [request responseData]; 101 | UIImage *remoteImage = [[UIImage alloc] initWithData:data]; 102 | photoView.image = remoteImage; 103 | progressView.hidden = YES; 104 | [remoteImage release]; 105 | } 106 | 107 | - (void)requestWentWrong:(ASIHTTPRequest *)request 108 | { 109 | // NSError *error = [request error]; 110 | } 111 | 112 | #pragma mark - 113 | #pragma mark UIViewController overridden methods 114 | 115 | - (void)viewDidAppear:(BOOL)animated 116 | { 117 | // Begin the asynchronous downloading of the image. 118 | progressView.progress = 0.0; 119 | NSURL *url = [NSURL URLWithString:item.imageURL]; 120 | 121 | // Somehow, updating the progress view cannot be properly done 122 | // without a queue (thread issues?), so here we use a local 123 | // queue which updates the UIProgressView instance. 124 | ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; 125 | [downloadQueue setDelegate:self]; 126 | [downloadQueue setRequestDidFinishSelector:@selector(requestDone:)]; 127 | [downloadQueue setRequestDidFailSelector:@selector(requestWentWrong:)]; 128 | [downloadQueue setShowAccurateProgress:YES]; 129 | [downloadQueue setDownloadProgressDelegate:progressView]; 130 | [downloadQueue addOperation:request]; 131 | [downloadQueue go]; 132 | } 133 | 134 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 135 | { 136 | return NO; 137 | } 138 | 139 | - (void)didReceiveMemoryWarning 140 | { 141 | [super didReceiveMemoryWarning]; 142 | } 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /Classes/Helpers/RSS.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSS.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2008 akosma software. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RSSDelegate.h" 11 | 12 | @interface RSS : NSObject 13 | { 14 | @private 15 | NSMutableArray *newsItems; 16 | NSObject *delegate; 17 | NSURL *url; 18 | NSXMLParser *xmlParser; 19 | 20 | NSString *currentElement; 21 | NSMutableString *currentTitle; 22 | NSMutableString *currentDate; 23 | NSMutableString *currentSummary; 24 | NSMutableString *currentLink; 25 | NSMutableString *currentImage; 26 | NSMutableString *currentThumbnail; 27 | } 28 | 29 | @property (nonatomic, assign) NSObject *delegate; 30 | @property (nonatomic, retain) NSMutableArray *newsItems; 31 | @property (nonatomic, retain) NSURL *url; 32 | 33 | - (void)fetch; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Classes/Helpers/RSS.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSS.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2008 akosma software. All rights reserved. 7 | // 8 | 9 | #import "RSS.h" 10 | #import "FlickrItem.h" 11 | #import "ASIHTTPRequest.h" 12 | #import "AsyncTableAppDelegate.h" 13 | 14 | @implementation RSS 15 | 16 | @synthesize url; 17 | @synthesize delegate; 18 | @synthesize newsItems; 19 | 20 | - (void)dealloc 21 | { 22 | delegate = nil; 23 | [url release]; 24 | [currentElement release]; 25 | [currentTitle release]; 26 | [currentDate release]; 27 | [currentSummary release]; 28 | [currentLink release]; 29 | [currentImage release]; 30 | [currentThumbnail release]; 31 | [xmlParser release]; 32 | [newsItems release]; 33 | [super dealloc]; 34 | } 35 | 36 | - (void)fetch 37 | { 38 | ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:url]; 39 | [request setDelegate:self]; 40 | [request setDidFinishSelector:@selector(requestDone:)]; 41 | [request setDidFailSelector:@selector(requestWentWrong:)]; 42 | NSOperationQueue *queue = [AsyncTableAppDelegate sharedAppDelegate].downloadQueue; 43 | [queue addOperation:request]; 44 | [request release]; 45 | } 46 | 47 | - (void)requestDone:(ASIHTTPRequest *)request 48 | { 49 | NSData *data = [request responseData]; 50 | [newsItems release]; 51 | newsItems = nil; 52 | newsItems = [[NSMutableArray alloc] init]; 53 | 54 | xmlParser = [[NSXMLParser alloc] initWithData:data]; 55 | [xmlParser setDelegate:self]; 56 | [xmlParser setShouldProcessNamespaces:NO]; 57 | [xmlParser setShouldReportNamespacePrefixes:NO]; 58 | [xmlParser setShouldResolveExternalEntities:NO]; 59 | [xmlParser parse]; 60 | } 61 | 62 | - (void)requestWentWrong:(ASIHTTPRequest *)request 63 | { 64 | NSError *error = [request error]; 65 | 66 | if([delegate respondsToSelector:@selector(feed:didFailWithError:)]) 67 | { 68 | [delegate feed:self didFailWithError:[error description]]; 69 | } 70 | } 71 | 72 | #pragma mark NSXMLParserDelegate methods 73 | 74 | // The following code is adapted from 75 | // http://theappleblog.com/2008/08/04/tutorial-build-a-simple-rss-reader-for-iphone 76 | 77 | - (void)parserDidStartDocument:(NSXMLParser *)parser 78 | { 79 | } 80 | 81 | - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError 82 | { 83 | if([delegate respondsToSelector:@selector(feed:didFailWithError:)]) 84 | { 85 | NSString *errorMsg = [NSString stringWithFormat:@"Unable to download feed from web site (Error code %i )", [parseError code]]; 86 | [delegate feed:self didFailWithError:errorMsg]; 87 | } 88 | } 89 | 90 | - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 91 | namespaceURI:(NSString *)namespaceURI 92 | qualifiedName:(NSString *)qName 93 | attributes:(NSDictionary *)attributeDict 94 | { 95 | currentElement = [elementName copy]; 96 | 97 | if ([currentElement isEqualToString:@"item"]) 98 | { 99 | [currentTitle release]; 100 | currentTitle = nil; 101 | [currentDate release]; 102 | currentDate = nil; 103 | [currentSummary release]; 104 | currentSummary = nil; 105 | [currentLink release]; 106 | currentLink = nil; 107 | [currentImage release]; 108 | currentImage = nil; 109 | [currentThumbnail release]; 110 | currentThumbnail = nil; 111 | 112 | currentTitle = [[NSMutableString alloc] init]; 113 | currentDate = [[NSMutableString alloc] init]; 114 | currentSummary = [[NSMutableString alloc] init]; 115 | currentLink = [[NSMutableString alloc] init]; 116 | currentImage = [[NSMutableString alloc] init]; 117 | currentThumbnail = [[NSMutableString alloc] init]; 118 | } 119 | else if ([currentElement isEqualToString:@"media:thumbnail"]) 120 | { 121 | [currentThumbnail appendString:[attributeDict objectForKey:@"url"]]; 122 | } 123 | else if ([currentElement isEqualToString:@"enclosure"]) 124 | { 125 | [currentImage appendString:[attributeDict objectForKey:@"url"]]; 126 | } 127 | } 128 | 129 | - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 130 | namespaceURI:(NSString *)namespaceURI 131 | qualifiedName:(NSString *)qName 132 | { 133 | 134 | if ([elementName isEqualToString:@"item"]) 135 | { 136 | FlickrItem *item = [[FlickrItem alloc] init]; 137 | [currentTitle replaceOccurrencesOfString:@"\n" 138 | withString:@"" 139 | options:NSCaseInsensitiveSearch 140 | range:NSMakeRange(0, [currentTitle length])]; 141 | item.title = currentTitle; 142 | item.link = currentLink; 143 | item.summary = currentSummary; 144 | item.date = currentDate; 145 | item.imageURL = currentImage; 146 | item.thumbnailURL = currentThumbnail; 147 | 148 | [newsItems addObject:item]; 149 | [item release]; 150 | } 151 | } 152 | 153 | - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string 154 | { 155 | if ([currentElement isEqualToString:@"title"]) 156 | { 157 | [currentTitle appendString:string]; 158 | } 159 | else if ([currentElement isEqualToString:@"link"]) 160 | { 161 | [currentLink appendString:string]; 162 | } 163 | else if ([currentElement isEqualToString:@"description"]) 164 | { 165 | [currentSummary appendString:string]; 166 | } 167 | else if ([currentElement isEqualToString:@"pubDate"]) 168 | { 169 | [currentDate appendString:string]; 170 | } 171 | } 172 | 173 | - (void)parserDidEndDocument:(NSXMLParser *)parser 174 | { 175 | if ([delegate respondsToSelector:@selector(feed:didFindItems:)]) 176 | { 177 | [delegate feed:self didFindItems:newsItems]; 178 | } 179 | } 180 | 181 | @end 182 | -------------------------------------------------------------------------------- /Classes/Helpers/RSSDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSSDelegate.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2008 akosma software. All rights reserved. 7 | // 8 | 9 | #import 10 | @class RSS; 11 | 12 | @protocol RSSDelegate 13 | 14 | @required 15 | - (void)feed:(RSS *)feed didFindItems:(NSArray *)items; 16 | - (void)feed:(RSS *)feed didFailWithError:(NSString *)errorMsg; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/Models/FlickrItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrItem.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlickrItemDelegate.h" 11 | 12 | @interface FlickrItem : NSObject 13 | { 14 | @private 15 | NSString *title; 16 | NSString *link; 17 | NSString *summary; 18 | NSString *date; 19 | NSString *imageURL; 20 | NSString *thumbnailURL; 21 | UIImage *thumbnail; 22 | 23 | // Why NSObject instead of "id"? Because this way 24 | // we can ask if it "respondsToSelector:" before invoking 25 | // any delegate method... 26 | NSObject *delegate; 27 | } 28 | 29 | @property (nonatomic, copy) NSString *title; 30 | @property (nonatomic, copy) NSString *link; 31 | @property (nonatomic, copy) NSString *summary; 32 | @property (nonatomic, copy) NSString *date; 33 | @property (nonatomic, copy) NSString *imageURL; 34 | @property (nonatomic, copy) NSString *thumbnailURL; 35 | @property (nonatomic, retain) UIImage *thumbnail; 36 | @property (nonatomic, assign) id delegate; 37 | 38 | - (BOOL)hasLoadedThumbnail; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Classes/Models/FlickrItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrItem.m 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | #import "FlickrItem.h" 10 | #import "ASIHTTPRequest.h" 11 | #import "AsyncTableAppDelegate.h" 12 | 13 | @interface FlickrItem (Private) 14 | - (void)loadURL:(NSURL *)url; 15 | @end 16 | 17 | 18 | @implementation FlickrItem 19 | 20 | @synthesize title; 21 | @synthesize link; 22 | @synthesize summary; 23 | @synthesize date; 24 | @synthesize imageURL; 25 | @synthesize thumbnailURL; 26 | @synthesize thumbnail; 27 | @synthesize delegate; 28 | 29 | - (void)dealloc 30 | { 31 | delegate = nil; 32 | [thumbnail release]; 33 | [imageURL release]; 34 | [thumbnailURL release]; 35 | [title release]; 36 | [link release]; 37 | [summary release]; 38 | [date release]; 39 | [super dealloc]; 40 | } 41 | 42 | #pragma mark - 43 | #pragma mark Public methods 44 | 45 | - (BOOL)hasLoadedThumbnail 46 | { 47 | return (thumbnail != nil); 48 | } 49 | 50 | #pragma mark - 51 | #pragma mark Overridden setters 52 | 53 | - (UIImage *)thumbnail 54 | { 55 | if (thumbnail == nil) 56 | { 57 | NSURL *url = [NSURL URLWithString:self.thumbnailURL]; 58 | [self loadURL:url]; 59 | } 60 | return thumbnail; 61 | } 62 | 63 | #pragma mark - 64 | #pragma mark ASIHTTPRequest delegate methods 65 | 66 | - (void)requestDone:(ASIHTTPRequest *)request 67 | { 68 | NSData *data = [request responseData]; 69 | UIImage *remoteImage = [[UIImage alloc] initWithData:data]; 70 | self.thumbnail = remoteImage; 71 | if ([delegate respondsToSelector:@selector(flickrItem:didLoadThumbnail:)]) 72 | { 73 | [delegate flickrItem:self didLoadThumbnail:self.thumbnail]; 74 | } 75 | [remoteImage release]; 76 | } 77 | 78 | - (void)requestWentWrong:(ASIHTTPRequest *)request 79 | { 80 | NSError *error = [request error]; 81 | if ([delegate respondsToSelector:@selector(flickrItem:couldNotLoadImageError:)]) 82 | { 83 | [delegate flickrItem:self couldNotLoadImageError:error]; 84 | } 85 | } 86 | 87 | #pragma mark - 88 | #pragma mark Private methods 89 | 90 | - (void)loadURL:(NSURL *)url 91 | { 92 | ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:url]; 93 | [request setDelegate:self]; 94 | [request setDidFinishSelector:@selector(requestDone:)]; 95 | [request setDidFailSelector:@selector(requestWentWrong:)]; 96 | NSOperationQueue *queue = [AsyncTableAppDelegate sharedAppDelegate].downloadQueue; 97 | [queue addOperation:request]; 98 | [request release]; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /Classes/Models/FlickrItemDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlickrItemDelegate.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | @class FlickrItem; 10 | 11 | @protocol FlickrItemDelegate 12 | 13 | @required 14 | - (void)flickrItem:(FlickrItem *)item couldNotLoadImageError:(NSError *)error; 15 | 16 | @optional 17 | - (void)flickrItem:(FlickrItem *)item didLoadImage:(UIImage *)image; 18 | - (void)flickrItem:(FlickrItem *)item didLoadThumbnail:(UIImage *)image; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Definitions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Definitions.h 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright 2009 akosma software. All rights reserved. 7 | // 8 | 9 | // URL of the public Flickr RSS feed 10 | #define NEWS_FEED_URL @"http://api.flickr.com/services/feeds/photos_public.gne?format=rss2" 11 | -------------------------------------------------------------------------------- /Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/ASIFormDataRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // ASIFormDataRequest.h 3 | // asi-http-request 4 | // 5 | // Created by Ben Copsey on 07/11/2008. 6 | // Copyright 2008 All-Seeing Interactive. All rights reserved. 7 | // 8 | 9 | #import "ASIHTTPRequest.h" 10 | 11 | @interface ASIFormDataRequest : ASIHTTPRequest { 12 | 13 | // Parameters that will be POSTed to the url 14 | NSMutableDictionary *postData; 15 | 16 | // Files that will be POSTed to the url 17 | NSMutableDictionary *fileData; 18 | 19 | } 20 | 21 | #pragma mark setup request 22 | 23 | // Add a POST variable to the request 24 | - (void)setPostValue:(id )value forKey:(NSString *)key; 25 | 26 | // Add the contents of a local file to the request 27 | - (void)setFile:(NSString *)filePath forKey:(NSString *)key; 28 | 29 | // Add the contents of an NSData object to the request 30 | - (void)setData:(NSData *)data forKey:(NSString *)key; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/ASIFormDataRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // ASIFormDataRequest.m 3 | // asi-http-request 4 | // 5 | // Created by Ben Copsey on 07/11/2008. 6 | // Copyright 2008 All-Seeing Interactive. All rights reserved. 7 | // 8 | 9 | #import "ASIFormDataRequest.h" 10 | 11 | 12 | @implementation ASIFormDataRequest 13 | 14 | #pragma mark init / dealloc 15 | 16 | - (id)initWithURL:(NSURL *)newURL 17 | { 18 | self = [super initWithURL:newURL]; 19 | postData = nil; 20 | fileData = nil; 21 | return self; 22 | } 23 | 24 | - (void)dealloc 25 | { 26 | [postData release]; 27 | [fileData release]; 28 | [super dealloc]; 29 | } 30 | 31 | #pragma mark setup request 32 | 33 | - (void)setPostValue:(id )value forKey:(NSString *)key 34 | { 35 | if (!postData) { 36 | postData = [[NSMutableDictionary alloc] init]; 37 | } 38 | [postData setValue:[value description] forKey:key]; 39 | [self setRequestMethod:@"POST"]; 40 | } 41 | 42 | - (void)setFile:(NSString *)filePath forKey:(NSString *)key 43 | { 44 | if (!fileData) { 45 | fileData = [[NSMutableDictionary alloc] init]; 46 | } 47 | NSMutableDictionary *file = [[[NSMutableDictionary alloc] init] autorelease]; 48 | [file setObject:[NSData dataWithContentsOfFile:filePath options:NSUncachedRead error:NULL] forKey:@"data"]; 49 | [file setObject:[filePath lastPathComponent] forKey:@"filename"]; 50 | [fileData setValue:file forKey:key]; 51 | [self setRequestMethod:@"POST"]; 52 | } 53 | 54 | - (void)setData:(NSData *)data forKey:(NSString *)key 55 | { 56 | if (!fileData) { 57 | fileData = [[NSMutableDictionary alloc] init]; 58 | } 59 | NSMutableDictionary *file = [[[NSMutableDictionary alloc] init] autorelease]; 60 | [file setObject:data forKey:@"data"]; 61 | [file setObject:@"file" forKey:@"filename"]; 62 | [fileData setValue:file forKey:key]; 63 | [self setRequestMethod:@"POST"]; 64 | } 65 | 66 | - (void)buildPostBody 67 | { 68 | if (!postData && ! fileData) { 69 | [super buildPostBody]; 70 | return; 71 | } 72 | 73 | NSMutableData *body = [[[NSMutableData alloc] init] autorelease]; 74 | 75 | // Set your own boundary string only if really obsessive. We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. 76 | NSString *stringBoundary = @"0xKhTmLbOuNdArY"; 77 | 78 | [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",stringBoundary]]; 79 | 80 | [body appendData:[[NSString stringWithFormat:@"--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; 81 | 82 | // Adds post data 83 | NSData *endItemBoundary = [[NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]; 84 | NSEnumerator *e = [postData keyEnumerator]; 85 | NSString *key; 86 | int i=0; 87 | while (key = [e nextObject]) { 88 | [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key] dataUsingEncoding:NSUTF8StringEncoding]]; 89 | [body appendData:[[postData objectForKey:key] dataUsingEncoding:NSUTF8StringEncoding]]; 90 | i++; 91 | if (i != [postData count] || [fileData count] > 0) { //Only add the boundary if this is not the last item in the post body 92 | [body appendData:endItemBoundary]; 93 | } 94 | } 95 | 96 | // Adds files to upload 97 | NSData *contentTypeHeader = [[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]; 98 | e = [fileData keyEnumerator]; 99 | i=0; 100 | while (key = [e nextObject]) { 101 | NSDictionary *fileInfo = [fileData objectForKey:key]; 102 | [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",key,[fileInfo objectForKey:@"filename"]] dataUsingEncoding:NSUTF8StringEncoding]]; 103 | [body appendData:contentTypeHeader]; 104 | [body appendData: [fileInfo objectForKey:@"data"]]; 105 | i++; 106 | // Only add the boundary if this is not the last item in the post body 107 | if (i != [fileData count]) { 108 | [body appendData:endItemBoundary]; 109 | } 110 | } 111 | 112 | [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; 113 | 114 | [self setPostBody:body]; 115 | 116 | 117 | [super buildPostBody]; 118 | } 119 | 120 | 121 | 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/ASIHTTPRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // ASIHTTPRequest.h 3 | // 4 | // Created by Ben Copsey on 04/10/2007. 5 | // Copyright 2007-2008 All-Seeing Interactive. All rights reserved. 6 | // 7 | // A guide to the main features is available at: 8 | // http://allseeing-i.com/ASIHTTPRequest 9 | // 10 | // Portions are based on the ImageClient example from Apple: 11 | // See: http://developer.apple.com/samplecode/ImageClient/listing37.html 12 | 13 | 14 | // Dammit, importing frameworks when you are targetting two platforms is a PITA 15 | #if TARGET_OS_IPHONE 16 | #import 17 | #endif 18 | #import 19 | 20 | typedef enum _ASINetworkErrorType { 21 | ASIConnectionFailureErrorType = 1, 22 | ASIRequestTimedOutErrorType = 2, 23 | ASIAuthenticationErrorType = 3, 24 | ASIRequestCancelledErrorType = 4, 25 | ASIUnableToCreateRequestErrorType = 5, 26 | ASIInternalErrorWhileBuildingRequestType = 6, 27 | ASIInternalErrorWhileApplyingCredentialsType = 7, 28 | ASIFileManagementError = 8 29 | 30 | } ASINetworkErrorType; 31 | 32 | @interface ASIHTTPRequest : NSOperation { 33 | 34 | // The url for this operation, should include GET params in the query string where appropriate 35 | NSURL *url; 36 | 37 | // The delegate, you need to manage setting and talking to your delegate in your subclasses 38 | id delegate; 39 | 40 | // HTTP method to use (GET / POST / PUT / DELETE). Defaults to GET 41 | NSString *requestMethod; 42 | 43 | // Request body 44 | NSData *postBody; 45 | 46 | // Dictionary for custom HTTP request headers 47 | NSMutableDictionary *requestHeaders; 48 | 49 | // Will be populated with HTTP response headers from the server 50 | NSDictionary *responseHeaders; 51 | 52 | // Can be used to manually insert cookie headers to a request, but it's more likely that sessionCookies will do this for you 53 | NSMutableArray *requestCookies; 54 | 55 | // Will be populated with cookies 56 | NSArray *responseCookies; 57 | 58 | // If use useCookiePersistance is true, network requests will present valid cookies from previous requests 59 | BOOL useCookiePersistance; 60 | 61 | // If useKeychainPersistance is true, network requests will attempt to read credentials from the keychain, and will save them in the keychain when they are successfully presented 62 | BOOL useKeychainPersistance; 63 | 64 | // If useSessionPersistance is true, network requests will save credentials and reuse for the duration of the session (until clearSession is called) 65 | BOOL useSessionPersistance; 66 | 67 | // If allowCompressedResponse is true, requests will inform the server they can accept compressed data, and will automatically decompress gzipped responses. Default is true. 68 | BOOL allowCompressedResponse; 69 | 70 | // When downloadDestinationPath is set, the result of this request will be downloaded to the file at this location 71 | // If downloadDestinationPath is not set, download data will be stored in memory 72 | NSString *downloadDestinationPath; 73 | 74 | //The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath 75 | NSString *temporaryFileDownloadPath; 76 | 77 | // Used for writing data to a file when downloadDestinationPath is set 78 | NSOutputStream *outputStream; 79 | 80 | // When the request fails or completes successfully, complete will be true 81 | BOOL complete; 82 | 83 | // If an error occurs, error will contain an NSError 84 | // If error code is = ASIConnectionFailureErrorType (1, Connection failure occurred) - inspect [[error userInfo] objectForKey:NSUnderlyingErrorKey] for more information 85 | NSError *error; 86 | 87 | // If an authentication error occurs, we give the delegate a chance to handle it, ignoreError will be set to true 88 | BOOL ignoreError; 89 | 90 | // Username and password used for authentication 91 | NSString *username; 92 | NSString *password; 93 | 94 | // Domain used for NTLM authentication 95 | NSString *domain; 96 | 97 | // Delegate for displaying upload progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself) 98 | id uploadProgressDelegate; 99 | 100 | // Delegate for displaying download progress (usually an NSProgressIndicator, but you can supply a different object and handle this yourself) 101 | id downloadProgressDelegate; 102 | 103 | // Whether we've seen the headers of the response yet 104 | BOOL haveExaminedHeaders; 105 | 106 | // Data we receive will be stored here. Data may be compressed unless allowCompressedResponse is false - you should use [request responseData] instead in most cases 107 | NSMutableData *rawResponseData; 108 | 109 | // Used for sending and receiving data 110 | CFHTTPMessageRef request; 111 | CFReadStreamRef readStream; 112 | 113 | // Authentication currently being used for prompting and resuming 114 | CFHTTPAuthenticationRef requestAuthentication; 115 | NSMutableDictionary *requestCredentials; 116 | 117 | // HTTP status code, eg: 200 = OK, 404 = Not found etc 118 | int responseStatusCode; 119 | 120 | // Size of the response 121 | unsigned long long contentLength; 122 | 123 | // Size of the POST payload 124 | unsigned long long postLength; 125 | 126 | // The total amount of downloaded data 127 | unsigned long long totalBytesRead; 128 | 129 | // Last amount of data read (used for incrementing progress) 130 | unsigned long long lastBytesRead; 131 | // Last amount of data sent (used for incrementing progress) 132 | unsigned long long lastBytesSent; 133 | 134 | // Realm for authentication when credentials are required 135 | NSString *authenticationRealm; 136 | 137 | // This lock will block the request until the delegate supplies authentication info 138 | NSConditionLock *authenticationLock; 139 | 140 | // This lock prevents the operation from being cancelled at an inopportune moment 141 | NSLock *cancelledLock; 142 | 143 | // Called on the delegate when the request completes successfully 144 | SEL didFinishSelector; 145 | 146 | // Called on the delegate when the request fails 147 | SEL didFailSelector; 148 | 149 | // Used for recording when something last happened during the request, we will compare this value with the current date to time out requests when appropriate 150 | NSDate *lastActivityTime; 151 | 152 | // Number of seconds to wait before timing out - default is 10 153 | NSTimeInterval timeOutSeconds; 154 | 155 | // Autorelease pool for the main loop, since it's highly likely that this operation will run in a thread 156 | NSAutoreleasePool *pool; 157 | 158 | // Will be YES when a HEAD request will handle the content-length before this request starts 159 | BOOL shouldResetProgressIndicators; 160 | 161 | // Used by HEAD requests when showAccurateProgress is YES to preset the content-length for this request 162 | ASIHTTPRequest *mainRequest; 163 | 164 | // When NO, this request will only update the progress indicator when it completes 165 | // When YES, this request will update the progress indicator according to how much data it has received so far 166 | // The default for requests is YES 167 | // Also see the comments in ASINetworkQueue.h 168 | BOOL showAccurateProgress; 169 | 170 | // Used to ensure the progress indicator is only incremented once when showAccurateProgress = NO 171 | BOOL updatedProgress; 172 | 173 | // Prevents the body of the post being built more than once (largely for subclasses) 174 | BOOL haveBuiltPostBody; 175 | 176 | unsigned long long uploadBufferSize; 177 | 178 | NSStringEncoding defaultResponseEncoding; 179 | NSStringEncoding responseEncoding; 180 | } 181 | 182 | #pragma mark init / dealloc 183 | 184 | // Should be an HTTP or HTTPS url, may include username and password if appropriate 185 | - (id)initWithURL:(NSURL *)newURL; 186 | 187 | #pragma mark setup request 188 | 189 | // Add a custom header to the request 190 | - (void)addRequestHeader:(NSString *)header value:(NSString *)value; 191 | 192 | - (void)buildPostBody; 193 | 194 | #pragma mark get information about this request 195 | 196 | // Returns the contents of the result as an NSString (not appropriate for binary data - used responseData instead) 197 | - (NSString *)responseString; 198 | 199 | // Response data, automatically uncompressed where appropriate 200 | - (NSData *)responseData; 201 | 202 | // Returns true if the response was gzip compressed 203 | - (BOOL)isResponseCompressed; 204 | 205 | #pragma mark request logic 206 | 207 | // Start loading the request 208 | - (void)loadRequest; 209 | 210 | // Cancel loading and clean up 211 | - (void)cancelLoad; 212 | 213 | #pragma mark upload/download progress 214 | 215 | // Called on main thread to update progress delegates 216 | - (void)updateProgressIndicators; 217 | - (void)resetUploadProgress:(unsigned long long)value; 218 | - (void)updateUploadProgress; 219 | - (void)resetDownloadProgress:(unsigned long long)value; 220 | - (void)updateDownloadProgress; 221 | 222 | // Called when authorisation is needed, as we only find out we don't have permission to something when the upload is complete 223 | - (void)removeUploadProgressSoFar; 224 | 225 | // Helper method for interacting with progress indicators to abstract the details of different APIS (NSProgressIndicator and UIProgressView) 226 | + (void)setProgress:(double)progress forProgressIndicator:(id)indicator; 227 | 228 | #pragma mark handling request complete / failure 229 | 230 | // Called when a request completes successfully, lets the delegate now via didFinishSelector 231 | - (void)requestFinished; 232 | 233 | // Called when a request fails, and lets the delegate now via didFailSelector 234 | - (void)failWithError:(NSError *)theError; 235 | 236 | #pragma mark http authentication stuff 237 | 238 | // Reads the response headers to find the content length, and returns true if the request needs a username and password (or if those supplied were incorrect) 239 | - (BOOL)readResponseHeadersReturningAuthenticationFailure; 240 | 241 | // Apply credentials to this request 242 | - (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials; 243 | 244 | // Attempt to obtain credentials for this request from the URL, username and password or keychain 245 | - (NSMutableDictionary *)findCredentials; 246 | 247 | // Unlock (unpause) the request thread so it can resume the request 248 | // Should be called by delegates when they have populated the authentication information after an authentication challenge 249 | - (void)retryWithAuthentication; 250 | 251 | // Apply authentication information and resume the request after an authentication challenge 252 | - (void)attemptToApplyCredentialsAndResume; 253 | 254 | 255 | #pragma mark stream status handlers 256 | 257 | // CFnetwork event handlers 258 | - (void)handleNetworkEvent:(CFStreamEventType)type; 259 | - (void)handleBytesAvailable; 260 | - (void)handleStreamComplete; 261 | - (void)handleStreamError; 262 | 263 | #pragma mark managing the session 264 | 265 | + (void)setSessionCredentials:(NSMutableDictionary *)newCredentials; 266 | + (void)setSessionAuthentication:(CFHTTPAuthenticationRef)newAuthentication; 267 | 268 | #pragma mark keychain storage 269 | 270 | // Save credentials for this request to the keychain 271 | - (void)saveCredentialsToKeychain:(NSMutableDictionary *)newCredentials; 272 | 273 | // Save creddentials to the keychain 274 | + (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; 275 | 276 | // Return credentials from the keychain 277 | + (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; 278 | 279 | // Remove credentials from the keychain 280 | + (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm; 281 | 282 | // We keep track of any cookies we accept, so that we can remove them from the persistent store later 283 | + (void)setSessionCookies:(NSMutableArray *)newSessionCookies; 284 | + (NSMutableArray *)sessionCookies; 285 | 286 | // Dump all session data (authentication and cookies) 287 | + (void)clearSession; 288 | 289 | #pragma mark gzip compression 290 | 291 | // Uncompress gzipped data with zlib 292 | + (NSData *)uncompressZippedData:(NSData*)compressedData; 293 | 294 | // Uncompress gzipped data from a file into another file, used when downloading to a file 295 | + (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath; 296 | + (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest; 297 | 298 | @property (retain) NSString *username; 299 | @property (retain) NSString *password; 300 | @property (retain) NSString *domain; 301 | 302 | @property (retain,readonly) NSURL *url; 303 | @property (assign) id delegate; 304 | @property (assign) id uploadProgressDelegate; 305 | @property (assign) id downloadProgressDelegate; 306 | @property (assign) BOOL useKeychainPersistance; 307 | @property (assign) BOOL useSessionPersistance; 308 | @property (retain) NSString *downloadDestinationPath; 309 | @property (retain,readonly) NSString *temporaryFileDownloadPath; 310 | @property (assign) SEL didFinishSelector; 311 | @property (assign) SEL didFailSelector; 312 | @property (retain,readonly) NSString *authenticationRealm; 313 | @property (retain) NSError *error; 314 | @property (assign,readonly) BOOL complete; 315 | @property (retain) NSDictionary *responseHeaders; 316 | @property (retain) NSMutableDictionary *requestHeaders; 317 | @property (retain) NSMutableArray *requestCookies; 318 | @property (retain) NSArray *responseCookies; 319 | @property (assign) BOOL useCookiePersistance; 320 | @property (retain) NSDictionary *requestCredentials; 321 | @property (assign) int responseStatusCode; 322 | @property (retain) NSMutableData *rawResponseData; 323 | @property (retain) NSDate *lastActivityTime; 324 | @property (assign) NSTimeInterval timeOutSeconds; 325 | @property (retain) NSString *requestMethod; 326 | @property (retain,setter=setPostBody:) NSData *postBody; 327 | @property (assign) unsigned long long contentLength; 328 | @property (assign) unsigned long long postLength; 329 | @property (assign) BOOL shouldResetProgressIndicators; 330 | @property (retain) ASIHTTPRequest *mainRequest; 331 | @property (assign) BOOL showAccurateProgress; 332 | @property (assign,readonly) unsigned long long totalBytesRead; 333 | @property (assign) unsigned long long uploadBufferSize; 334 | @property (assign) NSStringEncoding defaultResponseEncoding; 335 | @property (assign) NSStringEncoding responseEncoding; 336 | @property (assign) BOOL allowCompressedResponse; 337 | @end 338 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/ASIHTTPRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // ASIHTTPRequest.m 3 | // 4 | // Created by Ben Copsey on 04/10/2007. 5 | // Copyright 2007-2009 All-Seeing Interactive. All rights reserved. 6 | // 7 | // A guide to the main features is available at: 8 | // http://allseeing-i.com/ASIHTTPRequest 9 | // 10 | // Portions are based on the ImageClient example from Apple: 11 | // See: http://developer.apple.com/samplecode/ImageClient/listing37.html 12 | 13 | #import "ASIHTTPRequest.h" 14 | #import "NSHTTPCookieAdditions.h" 15 | #import 16 | 17 | // We use our own custom run loop mode as CoreAnimation seems to want to hijack our threads otherwise 18 | static CFStringRef ASIHTTPRequestRunMode = CFSTR("ASIHTTPRequest"); 19 | 20 | static NSString *NetworkRequestErrorDomain = @"com.Your-Company.Your-Product.NetworkError."; 21 | 22 | static const CFOptionFlags kNetworkEvents = kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred; 23 | 24 | static CFHTTPAuthenticationRef sessionAuthentication = NULL; 25 | static NSMutableDictionary *sessionCredentials = nil; 26 | static NSMutableArray *sessionCookies = nil; 27 | 28 | 29 | static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) { 30 | [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type]; 31 | } 32 | 33 | // This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa 34 | static NSRecursiveLock *progressLock; 35 | 36 | static NSError *ASIRequestCancelledError; 37 | static NSError *ASIRequestTimedOutError; 38 | static NSError *ASIAuthenticationError; 39 | static NSError *ASIUnableToCreateRequestError; 40 | 41 | @implementation ASIHTTPRequest 42 | 43 | 44 | 45 | #pragma mark init / dealloc 46 | 47 | + (void)initialize 48 | { 49 | if (self == [ASIHTTPRequest class]) { 50 | progressLock = [[NSRecursiveLock alloc] init]; 51 | ASIRequestTimedOutError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]] retain]; 52 | ASIAuthenticationError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]] retain]; 53 | ASIRequestCancelledError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]] retain]; 54 | ASIUnableToCreateRequestError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]] retain]; 55 | } 56 | [super initialize]; 57 | } 58 | 59 | - (id)initWithURL:(NSURL *)newURL 60 | { 61 | self = [super init]; 62 | [self setRequestMethod:@"GET"]; 63 | lastBytesSent = 0; 64 | showAccurateProgress = YES; 65 | shouldResetProgressIndicators = YES; 66 | updatedProgress = NO; 67 | [self setMainRequest:nil]; 68 | [self setPassword:nil]; 69 | [self setUsername:nil]; 70 | [self setRequestHeaders:nil]; 71 | authenticationRealm = nil; 72 | outputStream = nil; 73 | requestAuthentication = NULL; 74 | haveBuiltPostBody = NO; 75 | request = NULL; 76 | [self setAllowCompressedResponse:YES]; 77 | [self setDefaultResponseEncoding:NSISOLatin1StringEncoding]; 78 | [self setUploadBufferSize:0]; 79 | [self setResponseHeaders:nil]; 80 | [self setTimeOutSeconds:10]; 81 | [self setUseKeychainPersistance:NO]; 82 | [self setUseSessionPersistance:YES]; 83 | [self setUseCookiePersistance:YES]; 84 | [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]]; 85 | [self setDidFinishSelector:@selector(requestFinished:)]; 86 | [self setDidFailSelector:@selector(requestFailed:)]; 87 | [self setDelegate:nil]; 88 | url = [newURL retain]; 89 | cancelledLock = [[NSLock alloc] init]; 90 | return self; 91 | } 92 | 93 | - (void)dealloc 94 | { 95 | if (requestAuthentication) { 96 | CFRelease(requestAuthentication); 97 | } 98 | if (request) { 99 | CFRelease(request); 100 | } 101 | [self cancelLoad]; 102 | [mainRequest release]; 103 | [postBody release]; 104 | [requestCredentials release]; 105 | [error release]; 106 | [requestHeaders release]; 107 | [requestCookies release]; 108 | [downloadDestinationPath release]; 109 | [temporaryFileDownloadPath release]; 110 | [outputStream release]; 111 | [username release]; 112 | [password release]; 113 | [domain release]; 114 | [authenticationRealm release]; 115 | [url release]; 116 | [authenticationLock release]; 117 | [lastActivityTime release]; 118 | [responseCookies release]; 119 | [rawResponseData release]; 120 | [responseHeaders release]; 121 | [requestMethod release]; 122 | [cancelledLock release]; 123 | [super dealloc]; 124 | } 125 | 126 | 127 | #pragma mark setup request 128 | 129 | - (void)addRequestHeader:(NSString *)header value:(NSString *)value 130 | { 131 | if (!requestHeaders) { 132 | [self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]]; 133 | } 134 | [requestHeaders setObject:value forKey:header]; 135 | } 136 | 137 | -(void)setPostBody:(NSData *)body 138 | { 139 | postBody = [body retain]; 140 | postLength = [postBody length]; 141 | [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",postLength]]; 142 | if (postBody && postLength > 0 && ![requestMethod isEqualToString:@"POST"] && ![requestMethod isEqualToString:@"PUT"]) { 143 | [self setRequestMethod:@"POST"]; 144 | } 145 | } 146 | 147 | // Subclasses should override this method if they need to create POST content for this request 148 | // This function will be called either just before a request starts, or when postLength is needed, whichever comes first 149 | // postLength must be set by the time this function is complete - calling setPostBody: will do this for you 150 | - (void)buildPostBody 151 | { 152 | haveBuiltPostBody = YES; 153 | } 154 | 155 | #pragma mark get information about this request 156 | 157 | - (BOOL)isFinished 158 | { 159 | return complete; 160 | } 161 | 162 | 163 | - (void)cancel 164 | { 165 | [self failWithError:ASIRequestCancelledError]; 166 | [self cancelLoad]; 167 | complete = YES; 168 | [super cancel]; 169 | } 170 | 171 | 172 | // Call this method to get the received data as an NSString. Don't use for Binary data! 173 | - (NSString *)responseString 174 | { 175 | NSData *data = [self responseData]; 176 | if (!data) { 177 | return nil; 178 | } 179 | 180 | return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease]; 181 | } 182 | 183 | - (BOOL)isResponseCompressed 184 | { 185 | NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"]; 186 | return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound; 187 | } 188 | 189 | - (NSData *)responseData 190 | { 191 | if ([self isResponseCompressed]) { 192 | return [ASIHTTPRequest uncompressZippedData:[self rawResponseData]]; 193 | } else { 194 | return [self rawResponseData]; 195 | } 196 | } 197 | 198 | #pragma mark request logic 199 | 200 | // Create the request 201 | - (void)main 202 | { 203 | 204 | [pool release]; 205 | pool = [[NSAutoreleasePool alloc] init]; 206 | 207 | complete = NO; 208 | 209 | if (!url) { 210 | [self failWithError:ASIUnableToCreateRequestError]; 211 | return; 212 | } 213 | 214 | // Create a new HTTP request. 215 | request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)requestMethod, (CFURLRef)url, kCFHTTPVersion1_1); 216 | if (!request) { 217 | [self failWithError:ASIUnableToCreateRequestError]; 218 | return; 219 | } 220 | 221 | // If we've already talked to this server and have valid credentials, let's apply them to the request 222 | if (useSessionPersistance && sessionCredentials && sessionAuthentication) { 223 | if (!CFHTTPMessageApplyCredentialDictionary(request, sessionAuthentication, (CFMutableDictionaryRef)sessionCredentials, NULL)) { 224 | [ASIHTTPRequest setSessionAuthentication:NULL]; 225 | [ASIHTTPRequest setSessionCredentials:nil]; 226 | } 227 | } 228 | 229 | // Add cookies from the persistant (mac os global) store 230 | if (useCookiePersistance) { 231 | NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url]; 232 | if (cookies) { 233 | [requestCookies addObjectsFromArray:cookies]; 234 | } 235 | } 236 | 237 | // Apply request cookies 238 | NSArray *cookies; 239 | if ([self mainRequest]) { 240 | cookies = [[self mainRequest] requestCookies]; 241 | } else { 242 | cookies = [self requestCookies]; 243 | } 244 | if ([cookies count] > 0) { 245 | NSHTTPCookie *cookie; 246 | NSString *cookieHeader = nil; 247 | for (cookie in cookies) { 248 | if (!cookieHeader) { 249 | cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie encodedValue]]; 250 | } else { 251 | cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie encodedValue]]; 252 | } 253 | } 254 | if (cookieHeader) { 255 | [self addRequestHeader:@"Cookie" value:cookieHeader]; 256 | } 257 | } 258 | 259 | 260 | if (!haveBuiltPostBody) { 261 | [self buildPostBody]; 262 | } 263 | 264 | 265 | // Accept a compressed response 266 | if ([self allowCompressedResponse]) { 267 | [self addRequestHeader:@"Accept-Encoding" value:@"gzip"]; 268 | } 269 | 270 | // Add custom headers 271 | NSDictionary *headers; 272 | 273 | //Add headers from the main request if this is a HEAD request generated by an ASINetwork Queue 274 | if ([self mainRequest]) { 275 | headers = [mainRequest requestHeaders]; 276 | } else { 277 | headers = [self requestHeaders]; 278 | } 279 | NSString *header; 280 | for (header in headers) { 281 | CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[requestHeaders objectForKey:header]); 282 | } 283 | 284 | 285 | // If this is a post request and we have data to send, add it to the request 286 | if ([self postBody]) { 287 | CFHTTPMessageSetBody(request, (CFDataRef)postBody); 288 | } 289 | 290 | [self loadRequest]; 291 | 292 | } 293 | 294 | 295 | // Start the request 296 | - (void)loadRequest 297 | { 298 | 299 | [cancelledLock lock]; 300 | 301 | if ([self isCancelled]) { 302 | [cancelledLock unlock]; 303 | return; 304 | } 305 | 306 | [authenticationLock release]; 307 | authenticationLock = [[NSConditionLock alloc] initWithCondition:1]; 308 | 309 | complete = NO; 310 | totalBytesRead = 0; 311 | lastBytesRead = 0; 312 | 313 | // If we're retrying a request after an authentication failure, let's remove any progress we made 314 | if (lastBytesSent > 0 && uploadProgressDelegate) { 315 | [self removeUploadProgressSoFar]; 316 | } 317 | 318 | lastBytesSent = 0; 319 | if (shouldResetProgressIndicators) { 320 | contentLength = 0; 321 | } 322 | [self setResponseHeaders:nil]; 323 | [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]]; 324 | 325 | // Create the stream for the request. 326 | readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,readStream); 327 | if (!readStream) { 328 | [cancelledLock unlock]; 329 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]]; 330 | return; 331 | } 332 | 333 | // Set the client 334 | CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; 335 | if (!CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt)) { 336 | CFRelease(readStream); 337 | readStream = NULL; 338 | [cancelledLock unlock]; 339 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to setup read stream",NSLocalizedDescriptionKey,nil]]]; 340 | return; 341 | } 342 | 343 | // Schedule the stream 344 | CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode); 345 | 346 | // Start the HTTP connection 347 | if (!CFReadStreamOpen(readStream)) { 348 | CFReadStreamSetClient(readStream, 0, NULL, NULL); 349 | CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode); 350 | CFRelease(readStream); 351 | readStream = NULL; 352 | [cancelledLock unlock]; 353 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]]; 354 | return; 355 | } 356 | [cancelledLock unlock]; 357 | 358 | 359 | if (uploadProgressDelegate && shouldResetProgressIndicators) { 360 | double amount = 1; 361 | if (showAccurateProgress) { 362 | amount = postLength; 363 | } 364 | [self resetUploadProgress:amount]; 365 | } 366 | 367 | 368 | 369 | // Record when the request started, so we can timeout if nothing happens 370 | [self setLastActivityTime:[NSDate date]]; 371 | 372 | // Wait for the request to finish 373 | while (!complete) { 374 | 375 | // This may take a while, so we'll release the pool each cycle to stop a giant backlog of autoreleased objects building up 376 | [pool release]; 377 | pool = [[NSAutoreleasePool alloc] init]; 378 | 379 | NSDate *now = [NSDate date]; 380 | 381 | // See if we need to timeout 382 | if (lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) { 383 | 384 | // Prevent timeouts before 128KB has been sent when the size of data to upload is greater than 128KB 385 | // This is to workaround the fact that kCFStreamPropertyHTTPRequestBytesWrittenCount is the amount written to the buffer, not the amount actually sent 386 | // This workaround prevents erroneous timeouts in low bandwidth situations (eg iPhone) 387 | if (contentLength <= uploadBufferSize || (uploadBufferSize > 0 && lastBytesSent > uploadBufferSize)) { 388 | [self failWithError:ASIRequestTimedOutError]; 389 | [self cancelLoad]; 390 | complete = YES; 391 | break; 392 | } 393 | } 394 | 395 | // See if our NSOperationQueue told us to cancel 396 | if ([self isCancelled]) { 397 | break; 398 | } 399 | 400 | [self updateProgressIndicators]; 401 | 402 | // This thread should wait for 1/4 second for the stream to do something. We'll stop early if it does. 403 | CFRunLoopRunInMode(ASIHTTPRequestRunMode,0.25,YES); 404 | } 405 | 406 | [pool release]; 407 | pool = nil; 408 | } 409 | 410 | // Cancel loading and clean up 411 | - (void)cancelLoad 412 | { 413 | [cancelledLock lock]; 414 | if (readStream) { 415 | CFReadStreamClose(readStream); 416 | CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); 417 | CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode); 418 | CFRelease(readStream); 419 | readStream = NULL; 420 | } 421 | 422 | if (rawResponseData) { 423 | [self setRawResponseData:nil]; 424 | 425 | // If we were downloading to a file, let's remove it 426 | } else if (temporaryFileDownloadPath) { 427 | [outputStream close]; 428 | [[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:NULL]; 429 | } 430 | 431 | [self setResponseHeaders:nil]; 432 | [cancelledLock unlock]; 433 | } 434 | 435 | 436 | 437 | #pragma mark upload/download progress 438 | 439 | 440 | - (void)updateProgressIndicators 441 | { 442 | 443 | //Only update progress if this isn't a HEAD request used to preset the content-length 444 | if (!mainRequest) { 445 | if (showAccurateProgress || (complete && !updatedProgress)) { 446 | [self updateUploadProgress]; 447 | [self updateDownloadProgress]; 448 | } 449 | } 450 | 451 | } 452 | 453 | 454 | - (void)setUploadProgressDelegate:(id)newDelegate 455 | { 456 | uploadProgressDelegate = newDelegate; 457 | 458 | // If the uploadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews 459 | SEL selector = @selector(setMaxValue:); 460 | if ([uploadProgressDelegate respondsToSelector:selector]) { 461 | double max = 1.0; 462 | NSMethodSignature *signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 463 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 464 | [invocation setTarget:uploadProgressDelegate]; 465 | [invocation setSelector:selector]; 466 | [invocation setArgument:&max atIndex:2]; 467 | [invocation invoke]; 468 | 469 | } 470 | } 471 | 472 | - (void)setDownloadProgressDelegate:(id)newDelegate 473 | { 474 | downloadProgressDelegate = newDelegate; 475 | 476 | // If the downloadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews 477 | SEL selector = @selector(setMaxValue:); 478 | if ([downloadProgressDelegate respondsToSelector:selector]) { 479 | double max = 1.0; 480 | NSMethodSignature *signature = [[downloadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 481 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 482 | [invocation setSelector:@selector(setMaxValue:)]; 483 | [invocation setArgument:&max atIndex:2]; 484 | [invocation invokeWithTarget:downloadProgressDelegate]; 485 | } 486 | } 487 | 488 | 489 | - (void)resetUploadProgress:(unsigned long long)value 490 | { 491 | [progressLock lock]; 492 | //We're using a progress queue or compatible controller to handle progress 493 | if ([uploadProgressDelegate respondsToSelector:@selector(incrementUploadSizeBy:)]) { 494 | SEL selector = @selector(incrementUploadSizeBy:); 495 | NSMethodSignature *signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 496 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 497 | [invocation setTarget:uploadProgressDelegate]; 498 | [invocation setSelector:selector]; 499 | [invocation setArgument:&value atIndex:2]; 500 | [invocation invoke]; 501 | } else { 502 | [ASIHTTPRequest setProgress:0 forProgressIndicator:uploadProgressDelegate]; 503 | } 504 | [progressLock unlock]; 505 | } 506 | 507 | - (void)updateUploadProgress 508 | { 509 | [cancelledLock lock]; 510 | if ([self isCancelled]) { 511 | return; 512 | } 513 | unsigned long long byteCount = [[(NSNumber *)CFReadStreamCopyProperty (readStream, kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease] unsignedLongLongValue]; 514 | 515 | // If this is the first time we've written to the buffer, byteCount will be the size of the buffer (currently seems to be 128KB on both Mac and iPhone) 516 | // We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written 517 | if (byteCount > 0 && uploadBufferSize == 0 && byteCount != postLength) { 518 | [self setUploadBufferSize:byteCount]; 519 | SEL selector = @selector(setUploadBufferSize:); 520 | if ([uploadProgressDelegate respondsToSelector:selector]) { 521 | NSMethodSignature *signature = nil; 522 | signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 523 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 524 | [invocation setTarget:uploadProgressDelegate]; 525 | [invocation setSelector:selector]; 526 | [invocation setArgument:&byteCount atIndex:2]; 527 | [invocation invoke]; 528 | } 529 | } 530 | 531 | 532 | 533 | [cancelledLock unlock]; 534 | if (byteCount > lastBytesSent) { 535 | [self setLastActivityTime:[NSDate date]]; 536 | } 537 | 538 | if (uploadProgressDelegate) { 539 | 540 | // We're using a progress queue or compatible controller to handle progress 541 | if ([uploadProgressDelegate respondsToSelector:@selector(incrementUploadProgressBy:)]) { 542 | unsigned long long value = 0; 543 | if (showAccurateProgress) { 544 | if (byteCount == postLength) { 545 | value = byteCount+uploadBufferSize; 546 | } else if (lastBytesSent > 0) { 547 | value = ((byteCount-uploadBufferSize)-(lastBytesSent-uploadBufferSize)); 548 | } else { 549 | value = 0; 550 | } 551 | } else { 552 | value = 1; 553 | updatedProgress = YES; 554 | } 555 | SEL selector = @selector(incrementUploadProgressBy:); 556 | NSMethodSignature *signature = nil; 557 | signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 558 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 559 | [invocation setTarget:uploadProgressDelegate]; 560 | [invocation setSelector:selector]; 561 | [invocation setArgument:&value atIndex:2]; 562 | [invocation invoke]; 563 | 564 | // We aren't using a queue, we should just set progress of the indicator 565 | } else { 566 | [ASIHTTPRequest setProgress:(double)(1.0*(byteCount-uploadBufferSize)/(postLength-uploadBufferSize)) forProgressIndicator:uploadProgressDelegate]; 567 | } 568 | 569 | } 570 | lastBytesSent = byteCount; 571 | 572 | } 573 | 574 | 575 | - (void)resetDownloadProgress:(unsigned long long)value 576 | { 577 | [progressLock lock]; 578 | // We're using a progress queue or compatible controller to handle progress 579 | if ([downloadProgressDelegate respondsToSelector:@selector(incrementDownloadSizeBy:)]) { 580 | SEL selector = @selector(incrementDownloadSizeBy:); 581 | NSMethodSignature *signature = [[downloadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 582 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 583 | [invocation setTarget:downloadProgressDelegate]; 584 | [invocation setSelector:selector]; 585 | [invocation setArgument:&value atIndex:2]; 586 | [invocation invoke]; 587 | 588 | } else { 589 | [ASIHTTPRequest setProgress:0 forProgressIndicator:downloadProgressDelegate]; 590 | } 591 | [progressLock unlock]; 592 | } 593 | 594 | - (void)updateDownloadProgress 595 | { 596 | unsigned long long bytesReadSoFar = totalBytesRead; 597 | 598 | // We won't update download progress until we've examined the headers, since we might need to authenticate 599 | if (responseHeaders) { 600 | 601 | if (bytesReadSoFar > lastBytesRead) { 602 | [self setLastActivityTime:[NSDate date]]; 603 | } 604 | 605 | if (downloadProgressDelegate) { 606 | 607 | 608 | // We're using a progress queue or compatible controller to handle progress 609 | if ([downloadProgressDelegate respondsToSelector:@selector(incrementDownloadProgressBy:)]) { 610 | 611 | NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init]; 612 | 613 | unsigned long long value = 0; 614 | if (showAccurateProgress) { 615 | value = bytesReadSoFar-lastBytesRead; 616 | } else { 617 | value = 1; 618 | updatedProgress = YES; 619 | } 620 | 621 | 622 | 623 | SEL selector = @selector(incrementDownloadProgressBy:); 624 | NSMethodSignature *signature = [[downloadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 625 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 626 | [invocation setTarget:downloadProgressDelegate]; 627 | [invocation setSelector:selector]; 628 | [invocation setArgument:&value atIndex:2]; 629 | [invocation invoke]; 630 | 631 | [thePool release]; 632 | 633 | // We aren't using a queue, we should just set progress of the indicator to 0 634 | } else if (contentLength > 0) { 635 | [ASIHTTPRequest setProgress:(double)(1.0*bytesReadSoFar/contentLength) forProgressIndicator:downloadProgressDelegate]; 636 | } 637 | } 638 | 639 | lastBytesRead = bytesReadSoFar; 640 | } 641 | 642 | } 643 | 644 | -(void)removeUploadProgressSoFar 645 | { 646 | 647 | // We're using a progress queue or compatible controller to handle progress 648 | if ([uploadProgressDelegate respondsToSelector:@selector(decrementUploadProgressBy:)]) { 649 | unsigned long long value = 0-lastBytesSent; 650 | SEL selector = @selector(decrementUploadProgressBy:); 651 | NSMethodSignature *signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 652 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 653 | [invocation setTarget:uploadProgressDelegate]; 654 | [invocation setSelector:selector]; 655 | [invocation setArgument:&value atIndex:2]; 656 | [invocation invoke]; 657 | 658 | // We aren't using a queue, we should just set progress of the indicator to 0 659 | } else { 660 | [ASIHTTPRequest setProgress:0 forProgressIndicator:uploadProgressDelegate]; 661 | } 662 | } 663 | 664 | 665 | + (void)setProgress:(double)progress forProgressIndicator:(id)indicator 666 | { 667 | 668 | SEL selector; 669 | [progressLock lock]; 670 | 671 | // Cocoa Touch: UIProgressView 672 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 673 | selector = @selector(setProgress:); 674 | NSMethodSignature *signature = [[indicator class] instanceMethodSignatureForSelector:selector]; 675 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 676 | [invocation setSelector:selector]; 677 | float progressFloat = (float)progress; // UIProgressView wants a float for the progress parameter 678 | [invocation setArgument:&progressFloat atIndex:2]; 679 | 680 | // If we're running in the main thread, update the progress straight away. Otherwise, it's not that urgent 681 | [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:indicator waitUntilDone:[NSThread isMainThread]]; 682 | 683 | 684 | // Cocoa: NSProgressIndicator 685 | } else if ([indicator respondsToSelector:@selector(setDoubleValue:)]) { 686 | selector = @selector(setDoubleValue:); 687 | NSMethodSignature *signature = [[indicator class] instanceMethodSignatureForSelector:selector]; 688 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 689 | [invocation setSelector:selector]; 690 | [invocation setArgument:&progress atIndex:2]; 691 | 692 | // If we're running in the main thread, update the progress straight away. Otherwise, it's not that urgent 693 | [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:indicator waitUntilDone:[NSThread isMainThread]]; 694 | 695 | } 696 | [progressLock unlock]; 697 | } 698 | 699 | 700 | #pragma mark handling request complete / failure 701 | 702 | // Subclasses can override this method to process the result in the same thread 703 | // If not overidden, it will call the didFinishSelector on the delegate, if one has been setup 704 | - (void)requestFinished 705 | { 706 | if (didFinishSelector && ![self isCancelled] && [delegate respondsToSelector:didFinishSelector]) { 707 | [delegate performSelectorOnMainThread:didFinishSelector withObject:self waitUntilDone:[NSThread isMainThread]]; 708 | } 709 | } 710 | 711 | 712 | 713 | // Subclasses can override this method to perform error handling in the same thread 714 | // If not overidden, it will call the didFailSelector on the delegate (by default requestFailed:)` 715 | - (void)failWithError:(NSError *)theError 716 | { 717 | complete = YES; 718 | if (!error) { 719 | 720 | // If this is a HEAD request created by an ASINetworkQueue, make the main request fail 721 | if ([self mainRequest]) { 722 | ASIHTTPRequest *mRequest = [self mainRequest]; 723 | [mRequest setError:theError]; 724 | if ([mRequest didFailSelector] && ![self isCancelled] && [[mRequest delegate] respondsToSelector:[mRequest didFailSelector]]) { 725 | [[mRequest delegate] performSelectorOnMainThread:[mRequest didFailSelector] withObject:mRequest waitUntilDone:[NSThread isMainThread]]; 726 | } 727 | 728 | } else { 729 | [self setError:theError]; 730 | if (didFailSelector && ![self isCancelled] && [delegate respondsToSelector:didFailSelector]) { 731 | [delegate performSelectorOnMainThread:didFailSelector withObject:self waitUntilDone:[NSThread isMainThread]]; 732 | } 733 | } 734 | 735 | } 736 | } 737 | 738 | 739 | #pragma mark http authentication 740 | 741 | - (BOOL)readResponseHeadersReturningAuthenticationFailure 742 | { 743 | BOOL isAuthenticationChallenge = NO; 744 | CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader); 745 | if (CFHTTPMessageIsHeaderComplete(headers)) { 746 | [self setResponseHeaders:(NSDictionary *)CFHTTPMessageCopyAllHeaderFields(headers)]; 747 | [self setResponseStatusCode:CFHTTPMessageGetResponseStatusCode(headers)]; 748 | 749 | // Is the server response a challenge for credentials? 750 | isAuthenticationChallenge = (responseStatusCode == 401); 751 | 752 | // We won't reset the download progress delegate if we got an authentication challenge 753 | if (!isAuthenticationChallenge) { 754 | 755 | // See if we got a Content-length header 756 | NSString *cLength = [responseHeaders valueForKey:@"Content-Length"]; 757 | if (cLength) { 758 | contentLength = CFStringGetIntValue((CFStringRef)cLength); 759 | if (mainRequest) { 760 | [mainRequest setContentLength:contentLength]; 761 | } 762 | if (downloadProgressDelegate && showAccurateProgress && shouldResetProgressIndicators) { 763 | [self resetDownloadProgress:contentLength]; 764 | } 765 | } 766 | 767 | // Handle response text encoding 768 | // If the Content-Type header specified an encoding, we'll use that, otherwise we use defaultStringEncoding (which defaults to NSISOLatin1StringEncoding) 769 | NSString *contentType = [[self responseHeaders] objectForKey:@"Content-Type"]; 770 | NSStringEncoding encoding = [self defaultResponseEncoding]; 771 | if (contentType) { 772 | NSArray *parts = [contentType componentsSeparatedByString:@"="]; 773 | NSString *IANAEncoding = [parts objectAtIndex:[parts count]-1]; 774 | if (IANAEncoding) { 775 | CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding); 776 | if (cfEncoding != kCFStringEncodingInvalidId) { 777 | encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); 778 | } 779 | } 780 | } 781 | [self setResponseEncoding:encoding]; 782 | 783 | // Handle cookies 784 | NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseHeaders forURL:url]; 785 | [self setResponseCookies:cookies]; 786 | 787 | if (useCookiePersistance) { 788 | 789 | // Store cookies in global persistent store 790 | [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:url mainDocumentURL:nil]; 791 | 792 | // We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later 793 | if (!sessionCookies) { 794 | [ASIHTTPRequest setSessionCookies:[[[NSMutableArray alloc] init] autorelease]]; 795 | NSHTTPCookie *cookie; 796 | for (cookie in cookies) { 797 | [[ASIHTTPRequest sessionCookies] addObject:cookie]; 798 | } 799 | } 800 | } 801 | 802 | } 803 | 804 | } 805 | CFRelease(headers); 806 | return isAuthenticationChallenge; 807 | } 808 | 809 | 810 | - (void)saveCredentialsToKeychain:(NSMutableDictionary *)newCredentials 811 | { 812 | NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] 813 | password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] 814 | persistence:NSURLCredentialPersistencePermanent]; 815 | 816 | if (authenticationCredentials) { 817 | [ASIHTTPRequest saveCredentials:authenticationCredentials forHost:[url host] port:[[url port] intValue] protocol:[url scheme] realm:authenticationRealm]; 818 | } 819 | } 820 | 821 | - (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials 822 | { 823 | 824 | if (newCredentials && requestAuthentication && request) { 825 | // Apply whatever credentials we've built up to the old request 826 | if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) { 827 | 828 | //If we have credentials and they're ok, let's save them to the keychain 829 | if (useKeychainPersistance) { 830 | [self saveCredentialsToKeychain:newCredentials]; 831 | } 832 | if (useSessionPersistance) { 833 | 834 | [ASIHTTPRequest setSessionAuthentication:requestAuthentication]; 835 | [ASIHTTPRequest setSessionCredentials:newCredentials]; 836 | } 837 | [self setRequestCredentials:newCredentials]; 838 | return TRUE; 839 | } 840 | } 841 | return FALSE; 842 | } 843 | 844 | - (NSMutableDictionary *)findCredentials 845 | { 846 | NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease]; 847 | 848 | // Is an account domain needed? (used currently for NTLM only) 849 | if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { 850 | [newCredentials setObject:domain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; 851 | } 852 | 853 | // Get the authentication realm 854 | [authenticationRealm release]; 855 | authenticationRealm = nil; 856 | if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { 857 | authenticationRealm = (NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication); 858 | } 859 | 860 | // First, let's look at the url to see if the username and password were included 861 | NSString *user = [url user]; 862 | NSString *pass = [url password]; 863 | 864 | // If the username and password weren't in the url 865 | if (!user || !pass) { 866 | 867 | // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request 868 | if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) { 869 | user = [[self mainRequest] username]; 870 | pass = [[self mainRequest] password]; 871 | 872 | // Let's try to use the ones set in this object 873 | } else if (username && password) { 874 | user = username; 875 | pass = password; 876 | } 877 | 878 | } 879 | 880 | 881 | 882 | // Ok, that didn't work, let's try the keychain 883 | if ((!user || !pass) && useKeychainPersistance) { 884 | NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForHost:[url host] port:443 protocol:[url scheme] realm:authenticationRealm]; 885 | if (authenticationCredentials) { 886 | user = [authenticationCredentials user]; 887 | pass = [authenticationCredentials password]; 888 | } 889 | 890 | } 891 | 892 | // If we have a username and password, let's apply them to the request and continue 893 | if (user && pass) { 894 | 895 | [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername]; 896 | [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword]; 897 | return newCredentials; 898 | } 899 | return nil; 900 | } 901 | 902 | // Called by delegate to resume loading once authentication info has been populated 903 | - (void)retryWithAuthentication 904 | { 905 | [authenticationLock lockWhenCondition:1]; 906 | [authenticationLock unlockWithCondition:2]; 907 | } 908 | 909 | - (void)attemptToApplyCredentialsAndResume 910 | { 911 | 912 | // Read authentication data 913 | if (!requestAuthentication) { 914 | CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader); 915 | requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); 916 | CFRelease(responseHeader); 917 | } 918 | 919 | if (!requestAuthentication) { 920 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]]; 921 | return; 922 | } 923 | 924 | // See if authentication is valid 925 | CFStreamError err; 926 | if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) { 927 | 928 | CFRelease(requestAuthentication); 929 | requestAuthentication = NULL; 930 | 931 | // check for bad credentials, so we can give the delegate a chance to replace them 932 | if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) { 933 | 934 | [self setRequestCredentials:nil]; 935 | 936 | ignoreError = YES; 937 | [self setLastActivityTime:nil]; 938 | if ([delegate respondsToSelector:@selector(authorizationNeededForRequest:)]) { 939 | [delegate performSelectorOnMainThread:@selector(authorizationNeededForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; 940 | [authenticationLock lockWhenCondition:2]; 941 | [authenticationLock unlock]; 942 | 943 | // Hopefully, the delegate gave us some credentials, let's apply them and reload 944 | [self attemptToApplyCredentialsAndResume]; 945 | return; 946 | } 947 | } 948 | [self failWithError:ASIAuthenticationError]; 949 | return; 950 | } 951 | 952 | [self cancelLoad]; 953 | 954 | if (requestCredentials) { 955 | if ([self applyCredentials:requestCredentials]) { 956 | [self loadRequest]; 957 | } else { 958 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; 959 | } 960 | 961 | // Are a user name & password needed? 962 | } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) { 963 | 964 | NSMutableDictionary *newCredentials = [self findCredentials]; 965 | 966 | //If we have some credentials to use let's apply them to the request and continue 967 | if (newCredentials) { 968 | 969 | if ([self applyCredentials:newCredentials]) { 970 | [self loadRequest]; 971 | } else { 972 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; 973 | } 974 | return; 975 | } 976 | 977 | // We've got no credentials, let's ask the delegate to sort this out 978 | ignoreError = YES; 979 | if ([delegate respondsToSelector:@selector(authorizationNeededForRequest:)]) { 980 | [delegate performSelectorOnMainThread:@selector(authorizationNeededForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]]; 981 | [authenticationLock lockWhenCondition:2]; 982 | [authenticationLock unlock]; 983 | [self attemptToApplyCredentialsAndResume]; 984 | return; 985 | } 986 | 987 | // The delegate isn't interested, we'll have to give up 988 | [self failWithError:ASIAuthenticationError]; 989 | return; 990 | } 991 | 992 | } 993 | 994 | #pragma mark stream status handlers 995 | 996 | 997 | - (void)handleNetworkEvent:(CFStreamEventType)type 998 | { 999 | // Dispatch the stream events. 1000 | switch (type) { 1001 | case kCFStreamEventHasBytesAvailable: 1002 | [self handleBytesAvailable]; 1003 | break; 1004 | 1005 | case kCFStreamEventEndEncountered: 1006 | [self handleStreamComplete]; 1007 | break; 1008 | 1009 | case kCFStreamEventErrorOccurred: 1010 | [self handleStreamError]; 1011 | break; 1012 | 1013 | default: 1014 | break; 1015 | } 1016 | } 1017 | 1018 | 1019 | - (void)handleBytesAvailable 1020 | { 1021 | 1022 | if (!responseHeaders) { 1023 | if ([self readResponseHeadersReturningAuthenticationFailure]) { 1024 | [self attemptToApplyCredentialsAndResume]; 1025 | return; 1026 | } 1027 | } 1028 | 1029 | UInt8 buffer[2048]; 1030 | CFIndex bytesRead = CFReadStreamRead(readStream, buffer, sizeof(buffer)); 1031 | 1032 | 1033 | // Less than zero is an error 1034 | if (bytesRead < 0) { 1035 | [self handleStreamError]; 1036 | 1037 | // If zero bytes were read, wait for the EOF to come. 1038 | } else if (bytesRead) { 1039 | 1040 | totalBytesRead += bytesRead; 1041 | 1042 | // Are we downloading to a file? 1043 | if (downloadDestinationPath) { 1044 | if (!outputStream) { 1045 | [temporaryFileDownloadPath release]; 1046 | temporaryFileDownloadPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] retain]; 1047 | outputStream = [[NSOutputStream alloc] initToFileAtPath:temporaryFileDownloadPath append:NO]; 1048 | [outputStream open]; 1049 | } 1050 | [outputStream write:buffer maxLength:bytesRead]; 1051 | 1052 | //Otherwise, let's add the data to our in-memory store 1053 | } else { 1054 | [rawResponseData appendBytes:buffer length:bytesRead]; 1055 | } 1056 | } 1057 | } 1058 | 1059 | - (void)handleStreamComplete 1060 | { 1061 | //Try to read the headers (if this is a HEAD request handleBytesAvailable available may not be called) 1062 | if (!responseHeaders) { 1063 | if ([self readResponseHeadersReturningAuthenticationFailure]) { 1064 | [self attemptToApplyCredentialsAndResume]; 1065 | return; 1066 | } 1067 | } 1068 | [progressLock lock]; 1069 | complete = YES; 1070 | [self updateProgressIndicators]; 1071 | 1072 | if (readStream) { 1073 | CFReadStreamClose(readStream); 1074 | CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL); 1075 | CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), ASIHTTPRequestRunMode); 1076 | CFRelease(readStream); 1077 | readStream = NULL; 1078 | } 1079 | 1080 | NSError *fileError = nil; 1081 | 1082 | // Close the output stream as we're done writing to the file 1083 | if (temporaryFileDownloadPath) { 1084 | [outputStream close]; 1085 | 1086 | // Decompress the file (if necessary) directly to the destination path 1087 | if ([self isResponseCompressed]) { 1088 | int decompressionStatus = [ASIHTTPRequest uncompressZippedDataFromFile:temporaryFileDownloadPath toFile:downloadDestinationPath]; 1089 | if (decompressionStatus != Z_OK) { 1090 | fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed with code %hi",temporaryFileDownloadPath,decompressionStatus],NSLocalizedDescriptionKey,nil]]; 1091 | } 1092 | 1093 | //Remove the temporary file 1094 | NSError *removeError = nil; 1095 | [[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:&removeError]; 1096 | if (removeError) { 1097 | fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at %@ with error: %@",temporaryFileDownloadPath,removeError],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]]; 1098 | } 1099 | } else { 1100 | 1101 | //Remove any file at the destination path 1102 | NSError *moveError = nil; 1103 | if ([[NSFileManager defaultManager] fileExistsAtPath:downloadDestinationPath]) { 1104 | [[NSFileManager defaultManager] removeItemAtPath:downloadDestinationPath error:&moveError]; 1105 | if (moveError) { 1106 | fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Unable to remove file at path '%@'",downloadDestinationPath],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]]; 1107 | } 1108 | } 1109 | 1110 | //Move the temporary file to the destination path 1111 | if (!fileError) { 1112 | [[NSFileManager defaultManager] moveItemAtPath:temporaryFileDownloadPath toPath:downloadDestinationPath error:&moveError]; 1113 | if (moveError) { 1114 | fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",temporaryFileDownloadPath,downloadDestinationPath],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]]; 1115 | } 1116 | } 1117 | } 1118 | } 1119 | [progressLock unlock]; 1120 | if (fileError) { 1121 | [self failWithError:fileError]; 1122 | } else { 1123 | [self requestFinished]; 1124 | } 1125 | } 1126 | 1127 | 1128 | - (void)handleStreamError 1129 | { 1130 | NSError *underlyingError = [(NSError *)CFReadStreamCopyError(readStream) autorelease]; 1131 | 1132 | [self cancelLoad]; 1133 | 1134 | if (!error) { // We may already have handled this error 1135 | 1136 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"A connection failure occurred",NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]]; 1137 | } 1138 | } 1139 | 1140 | #pragma mark managing the session 1141 | 1142 | + (void)setSessionCredentials:(NSMutableDictionary *)newCredentials 1143 | { 1144 | [sessionCredentials release]; 1145 | sessionCredentials = [newCredentials retain]; 1146 | } 1147 | 1148 | + (void)setSessionAuthentication:(CFHTTPAuthenticationRef)newAuthentication 1149 | { 1150 | if (sessionAuthentication) { 1151 | CFRelease(sessionAuthentication); 1152 | } 1153 | sessionAuthentication = newAuthentication; 1154 | if (newAuthentication) { 1155 | CFRetain(sessionAuthentication); 1156 | } 1157 | } 1158 | 1159 | #pragma mark keychain storage 1160 | 1161 | + (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm 1162 | { 1163 | NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host 1164 | port:port 1165 | protocol:protocol 1166 | realm:realm 1167 | authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; 1168 | 1169 | 1170 | NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage]; 1171 | [storage setDefaultCredential:credentials forProtectionSpace:protectionSpace]; 1172 | } 1173 | 1174 | + (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm 1175 | { 1176 | NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host 1177 | port:port 1178 | protocol:protocol 1179 | realm:realm 1180 | authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; 1181 | 1182 | 1183 | NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage]; 1184 | return [storage defaultCredentialForProtectionSpace:protectionSpace]; 1185 | } 1186 | 1187 | + (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm 1188 | { 1189 | NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host 1190 | port:port 1191 | protocol:protocol 1192 | realm:realm 1193 | authenticationMethod:NSURLAuthenticationMethodDefault] autorelease]; 1194 | 1195 | 1196 | NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage]; 1197 | [storage removeCredential:[storage defaultCredentialForProtectionSpace:protectionSpace] forProtectionSpace:protectionSpace]; 1198 | 1199 | } 1200 | 1201 | 1202 | + (NSMutableArray *)sessionCookies 1203 | { 1204 | return sessionCookies; 1205 | } 1206 | 1207 | + (void)setSessionCookies:(NSMutableArray *)newSessionCookies 1208 | { 1209 | // Remove existing cookies from the persistent store 1210 | for (NSHTTPCookie *cookie in [ASIHTTPRequest sessionCookies]) { 1211 | [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; 1212 | } 1213 | [sessionCookies release]; 1214 | sessionCookies = [newSessionCookies retain]; 1215 | } 1216 | 1217 | // Dump all session data (authentication and cookies) 1218 | + (void)clearSession 1219 | { 1220 | [ASIHTTPRequest setSessionAuthentication:NULL]; 1221 | [ASIHTTPRequest setSessionCredentials:nil]; 1222 | [ASIHTTPRequest setSessionCookies:nil]; 1223 | } 1224 | 1225 | 1226 | #pragma mark gzip data handling 1227 | 1228 | // 1229 | // Contributed by Shaun Harrison of Enormego, see: http://developers.enormego.com/view/asihttprequest_gzip 1230 | // Based on this: http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html 1231 | // 1232 | + (NSData *)uncompressZippedData:(NSData*)compressedData 1233 | { 1234 | if ([compressedData length] == 0) return compressedData; 1235 | 1236 | unsigned full_length = [compressedData length]; 1237 | unsigned half_length = [compressedData length] / 2; 1238 | 1239 | NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length]; 1240 | BOOL done = NO; 1241 | int status; 1242 | 1243 | z_stream strm; 1244 | strm.next_in = (Bytef *)[compressedData bytes]; 1245 | strm.avail_in = [compressedData length]; 1246 | strm.total_out = 0; 1247 | strm.zalloc = Z_NULL; 1248 | strm.zfree = Z_NULL; 1249 | 1250 | if (inflateInit2(&strm, (15+32)) != Z_OK) return nil; 1251 | 1252 | while (!done) { 1253 | // Make sure we have enough room and reset the lengths. 1254 | if (strm.total_out >= [decompressed length]) { 1255 | [decompressed increaseLengthBy: half_length]; 1256 | } 1257 | strm.next_out = [decompressed mutableBytes] + strm.total_out; 1258 | strm.avail_out = [decompressed length] - strm.total_out; 1259 | 1260 | // Inflate another chunk. 1261 | status = inflate (&strm, Z_SYNC_FLUSH); 1262 | if (status == Z_STREAM_END) { 1263 | done = YES; 1264 | } else if (status != Z_OK) { 1265 | break; 1266 | } 1267 | } 1268 | if (inflateEnd (&strm) != Z_OK) return nil; 1269 | 1270 | // Set real length. 1271 | if (done) { 1272 | [decompressed setLength: strm.total_out]; 1273 | return [NSData dataWithData: decompressed]; 1274 | } else { 1275 | return nil; 1276 | } 1277 | } 1278 | 1279 | 1280 | + (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath 1281 | { 1282 | // Get a FILE struct for the source file 1283 | FILE *source = fdopen([[NSFileHandle fileHandleForReadingAtPath:sourcePath] fileDescriptor], "r"); 1284 | 1285 | // Create an empty file at the destination path 1286 | [[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil]; 1287 | 1288 | // Get a FILE struct for the destination path 1289 | FILE *dest = fdopen([[NSFileHandle fileHandleForWritingAtPath:destinationPath] fileDescriptor], "w"); 1290 | 1291 | // Uncompress data in source and save in destination 1292 | int status = [ASIHTTPRequest uncompressZippedDataFromSource:source toDestination:dest]; 1293 | 1294 | // Close the files 1295 | fclose(source); 1296 | fclose(dest); 1297 | return status; 1298 | } 1299 | 1300 | // 1301 | // From the zlib sample code by Mark Adler, code here: 1302 | // http://www.zlib.net/zpipe.c 1303 | // 1304 | #define CHUNK 16384 1305 | + (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest 1306 | { 1307 | int ret; 1308 | unsigned have; 1309 | z_stream strm; 1310 | unsigned char in[CHUNK]; 1311 | unsigned char out[CHUNK]; 1312 | 1313 | /* allocate inflate state */ 1314 | strm.zalloc = Z_NULL; 1315 | strm.zfree = Z_NULL; 1316 | strm.opaque = Z_NULL; 1317 | strm.avail_in = 0; 1318 | strm.next_in = Z_NULL; 1319 | ret = inflateInit2(&strm, (15+32)); 1320 | if (ret != Z_OK) 1321 | return ret; 1322 | 1323 | /* decompress until deflate stream ends or end of file */ 1324 | do { 1325 | strm.avail_in = fread(in, 1, CHUNK, source); 1326 | if (ferror(source)) { 1327 | (void)inflateEnd(&strm); 1328 | return Z_ERRNO; 1329 | } 1330 | if (strm.avail_in == 0) 1331 | break; 1332 | strm.next_in = in; 1333 | 1334 | /* run inflate() on input until output buffer not full */ 1335 | do { 1336 | strm.avail_out = CHUNK; 1337 | strm.next_out = out; 1338 | ret = inflate(&strm, Z_NO_FLUSH); 1339 | assert(ret != Z_STREAM_ERROR); /* state not clobbered */ 1340 | switch (ret) { 1341 | case Z_NEED_DICT: 1342 | ret = Z_DATA_ERROR; /* and fall through */ 1343 | case Z_DATA_ERROR: 1344 | case Z_MEM_ERROR: 1345 | (void)inflateEnd(&strm); 1346 | return ret; 1347 | } 1348 | have = CHUNK - strm.avail_out; 1349 | if (fwrite(&out, 1, have, dest) != have || ferror(dest)) { 1350 | (void)inflateEnd(&strm); 1351 | return Z_ERRNO; 1352 | } 1353 | } while (strm.avail_out == 0); 1354 | 1355 | /* done when inflate() says it's done */ 1356 | } while (ret != Z_STREAM_END); 1357 | 1358 | /* clean up and return */ 1359 | (void)inflateEnd(&strm); 1360 | return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; 1361 | } 1362 | 1363 | 1364 | @synthesize username; 1365 | @synthesize password; 1366 | @synthesize domain; 1367 | @synthesize url; 1368 | @synthesize delegate; 1369 | @synthesize uploadProgressDelegate; 1370 | @synthesize downloadProgressDelegate; 1371 | @synthesize useKeychainPersistance; 1372 | @synthesize useSessionPersistance; 1373 | @synthesize useCookiePersistance; 1374 | @synthesize downloadDestinationPath; 1375 | @synthesize temporaryFileDownloadPath; 1376 | @synthesize didFinishSelector; 1377 | @synthesize didFailSelector; 1378 | @synthesize authenticationRealm; 1379 | @synthesize error; 1380 | @synthesize complete; 1381 | @synthesize requestHeaders; 1382 | @synthesize responseHeaders; 1383 | @synthesize responseCookies; 1384 | @synthesize requestCookies; 1385 | @synthesize requestCredentials; 1386 | @synthesize responseStatusCode; 1387 | @synthesize rawResponseData; 1388 | @synthesize lastActivityTime; 1389 | @synthesize timeOutSeconds; 1390 | @synthesize requestMethod; 1391 | @synthesize postBody; 1392 | @synthesize contentLength; 1393 | @synthesize postLength; 1394 | @synthesize shouldResetProgressIndicators; 1395 | @synthesize mainRequest; 1396 | @synthesize totalBytesRead; 1397 | @synthesize showAccurateProgress; 1398 | @synthesize uploadBufferSize; 1399 | @synthesize defaultResponseEncoding; 1400 | @synthesize responseEncoding; 1401 | @synthesize allowCompressedResponse; 1402 | @end 1403 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/ASINetworkQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // ASINetworkQueue.h 3 | // asi-http-request 4 | // 5 | // Created by Ben Copsey on 07/11/2008. 6 | // Copyright 2008 All-Seeing Interactive. All rights reserved. 7 | // 8 | 9 | 10 | @interface ASINetworkQueue : NSOperationQueue { 11 | 12 | // Delegate will get didFail + didFinish messages (if set), as well as authorizationNeededForRequest messages 13 | id delegate; 14 | 15 | // Will be called when a request completes with the request as the argument 16 | SEL requestDidFinishSelector; 17 | 18 | // Will be called when a request fails with the request as the argument 19 | SEL requestDidFailSelector; 20 | 21 | // Will be called when the queue finishes with the queue as the argument 22 | SEL queueDidFinishSelector; 23 | 24 | // Upload progress indicator, probably an NSProgressIndicator or UIProgressView 25 | id uploadProgressDelegate; 26 | 27 | // Total amount uploaded so far for all requests in this queue 28 | unsigned long long uploadProgressBytes; 29 | 30 | // Total amount to be uploaded for all requests in this queue - requests add to this figure as they work out how much data they have to transmit 31 | unsigned long long uploadProgressTotalBytes; 32 | 33 | // Download progress indicator, probably an NSProgressIndicator or UIProgressView 34 | id downloadProgressDelegate; 35 | 36 | // Total amount downloaded so far for all requests in this queue 37 | unsigned long long downloadProgressBytes; 38 | 39 | // Total amount to be downloaded for all requests in this queue - requests add to this figure as they receive Content-Length headers 40 | unsigned long long downloadProgressTotalBytes; 41 | 42 | // When YES, the queue will cancel all requests when a request fails. Default is YES 43 | BOOL shouldCancelAllRequestsOnFailure; 44 | 45 | //Number of real requests (excludes HEAD requests created to manage showAccurateProgress) 46 | int requestsCount; 47 | 48 | // When NO, this request will only update the progress indicator when it completes 49 | // When YES, this request will update the progress indicator according to how much data it has recieved so far 50 | // When YES, the queue will first perform HEAD requests for all GET requests in the queue, so it can calculate the total download size before it starts 51 | // NO means better performance, because it skips this step for GET requests, and it won't waste time updating the progress indicator until a request completes 52 | // Set to YES if the size of a requests in the queue varies greatly for much more accurate results 53 | // Default for requests in the queue is NO 54 | BOOL showAccurateProgress; 55 | 56 | 57 | } 58 | 59 | // Used internally to manage HEAD requests when showAccurateProgress is YES, do not use! 60 | - (void)addHEADOperation:(NSOperation *)operation; 61 | 62 | // Called at the start of a request to add on the size of this upload to the total 63 | - (void)incrementUploadSizeBy:(unsigned long long)bytes; 64 | 65 | // Called during a request when data is written to the upload stream to increment the progress indicator 66 | - (void)incrementUploadProgressBy:(unsigned long long)bytes; 67 | 68 | // Called at the start of a request to add on the size of this download to the total 69 | - (void)incrementDownloadSizeBy:(unsigned long long)bytes; 70 | 71 | // Called during a request when data is received to increment the progress indicator 72 | - (void)incrementDownloadProgressBy:(unsigned long long)bytes; 73 | 74 | // Called during a request when authorisation fails to cancel any progress so far 75 | - (void)decrementUploadProgressBy:(unsigned long long)bytes; 76 | 77 | // Called when the first chunk of data is written to the upload buffer 78 | // We ignore the first part chunk when tracking upload progress, as kCFStreamPropertyHTTPRequestBytesWrittenCount reports the amount of data written to the buffer, not the amount sent 79 | // This is to workaround the first 128KB of data appearing in an upload progress delegate immediately 80 | - (void)setUploadBufferSize:(unsigned long long)bytes; 81 | 82 | // All ASINetworkQueues are paused when created so that total size can be calculated before the queue starts 83 | // This method will start the queue 84 | - (void)go; 85 | 86 | @property (assign,setter=setUploadProgressDelegate:) id uploadProgressDelegate; 87 | @property (assign,setter=setDownloadProgressDelegate:) id downloadProgressDelegate; 88 | 89 | @property (assign) SEL requestDidFinishSelector; 90 | @property (assign) SEL requestDidFailSelector; 91 | @property (assign) SEL queueDidFinishSelector; 92 | @property (assign) BOOL shouldCancelAllRequestsOnFailure; 93 | @property (assign) id delegate; 94 | @property (assign) BOOL showAccurateProgress; 95 | @end 96 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/ASINetworkQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // ASINetworkQueue.m 3 | // asi-http-request 4 | // 5 | // Created by Ben Copsey on 07/11/2008. 6 | // Copyright 2008 All-Seeing Interactive. All rights reserved. 7 | // 8 | 9 | #import "ASINetworkQueue.h" 10 | #import "ASIHTTPRequest.h" 11 | 12 | 13 | @implementation ASINetworkQueue 14 | 15 | - (id)init 16 | { 17 | self = [super init]; 18 | 19 | delegate = NULL; 20 | requestDidFinishSelector = NULL; 21 | requestDidFailSelector = NULL; 22 | queueDidFinishSelector = NULL; 23 | shouldCancelAllRequestsOnFailure = YES; 24 | 25 | uploadProgressDelegate = nil; 26 | uploadProgressBytes = 0; 27 | uploadProgressTotalBytes = 0; 28 | 29 | downloadProgressDelegate = nil; 30 | downloadProgressBytes = 0; 31 | downloadProgressTotalBytes = 0; 32 | 33 | requestsCount = 0; 34 | 35 | showAccurateProgress = NO; 36 | 37 | [self setMaxConcurrentOperationCount:4]; 38 | [self setSuspended:YES]; 39 | 40 | return self; 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | [super dealloc]; 46 | } 47 | 48 | - (void)go 49 | { 50 | if (!showAccurateProgress) { 51 | if (downloadProgressDelegate) { 52 | [self incrementDownloadSizeBy:requestsCount]; 53 | } 54 | if (uploadProgressDelegate) { 55 | [self incrementUploadSizeBy:requestsCount]; 56 | } 57 | } 58 | [self setSuspended:NO]; 59 | } 60 | 61 | - (void)cancelAllOperations 62 | { 63 | requestsCount = 0; 64 | uploadProgressBytes = 0; 65 | uploadProgressTotalBytes = 0; 66 | downloadProgressBytes = 0; 67 | downloadProgressTotalBytes = 0; 68 | [super cancelAllOperations]; 69 | } 70 | 71 | - (void)setUploadProgressDelegate:(id)newDelegate 72 | { 73 | uploadProgressDelegate = newDelegate; 74 | 75 | // If the uploadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews 76 | SEL selector = @selector(setMaxValue:); 77 | if ([uploadProgressDelegate respondsToSelector:selector]) { 78 | double max = 1.0; 79 | NSMethodSignature *signature = [[uploadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 80 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 81 | [invocation setSelector:selector]; 82 | [invocation setArgument:&max atIndex:2]; 83 | [invocation invokeWithTarget:uploadProgressDelegate]; 84 | } 85 | } 86 | 87 | 88 | - (void)setDownloadProgressDelegate:(id)newDelegate 89 | { 90 | downloadProgressDelegate = newDelegate; 91 | 92 | // If the downloadProgressDelegate is an NSProgressIndicator, we set it's MaxValue to 1.0 so we can treat it similarly to UIProgressViews 93 | SEL selector = @selector(setMaxValue:); 94 | if ([downloadProgressDelegate respondsToSelector:selector]) { 95 | double max = 1.0; 96 | NSMethodSignature *signature = [[downloadProgressDelegate class] instanceMethodSignatureForSelector:selector]; 97 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 98 | [invocation setSelector:@selector(setMaxValue:)]; 99 | [invocation setArgument:&max atIndex:2]; 100 | [invocation invokeWithTarget:downloadProgressDelegate]; 101 | } 102 | } 103 | 104 | - (void)addHEADOperation:(NSOperation *)operation 105 | { 106 | if ([operation isKindOfClass:[ASIHTTPRequest class]]) { 107 | 108 | ASIHTTPRequest *request = (ASIHTTPRequest *)operation; 109 | [request setRequestMethod:@"HEAD"]; 110 | [request setQueuePriority:10]; 111 | [request setShowAccurateProgress:YES]; 112 | if (uploadProgressDelegate) { 113 | [request setUploadProgressDelegate:self]; 114 | } else { 115 | [request setUploadProgressDelegate:NULL]; 116 | } 117 | if (downloadProgressDelegate) { 118 | [request setDownloadProgressDelegate:self]; 119 | } else { 120 | [request setDownloadProgressDelegate:NULL]; 121 | } 122 | [request setDelegate:self]; 123 | [super addOperation:request]; 124 | } 125 | } 126 | 127 | // Only add ASIHTTPRequests to this queue!! 128 | - (void)addOperation:(NSOperation *)operation 129 | { 130 | if ([operation isKindOfClass:[ASIHTTPRequest class]]) { 131 | 132 | requestsCount++; 133 | 134 | ASIHTTPRequest *request = (ASIHTTPRequest *)operation; 135 | 136 | if (showAccurateProgress) { 137 | 138 | // If this is a GET request and we want accurate progress, perform a HEAD request first to get the content-length 139 | if ([[request requestMethod] isEqualToString:@"GET"]) { 140 | ASIHTTPRequest *HEADRequest = [[[ASIHTTPRequest alloc] initWithURL:[request url]] autorelease]; 141 | [HEADRequest setMainRequest:request]; 142 | [self addHEADOperation:HEADRequest]; 143 | 144 | //Tell the request not to reset the progress indicator when it gets a content-length, as we will get the length from the HEAD request 145 | [request setShouldResetProgressIndicators:NO]; 146 | [request addDependency:HEADRequest]; 147 | 148 | // If we want to track uploading for this request accurately, we need to add the size of the post content to the total 149 | } else if (uploadProgressDelegate) { 150 | [request buildPostBody]; 151 | uploadProgressTotalBytes += [request postLength]; 152 | } 153 | } 154 | [request setShowAccurateProgress:showAccurateProgress]; 155 | 156 | if (uploadProgressDelegate) { 157 | 158 | // For uploads requests, we always work out the total upload size before the queue starts, so we tell the request not to reset the progress indicator when starting each request 159 | [request setShouldResetProgressIndicators:NO]; 160 | [request setUploadProgressDelegate:self]; 161 | } else { 162 | [request setUploadProgressDelegate:NULL]; 163 | } 164 | if (downloadProgressDelegate) { 165 | [request setDownloadProgressDelegate:self]; 166 | } else { 167 | [request setDownloadProgressDelegate:NULL]; 168 | } 169 | [request setDelegate:self]; 170 | [request setDidFailSelector:@selector(requestDidFail:)]; 171 | [request setDidFinishSelector:@selector(requestDidFinish:)]; 172 | [super addOperation:request]; 173 | } 174 | 175 | } 176 | 177 | - (void)requestDidFail:(ASIHTTPRequest *)request 178 | { 179 | requestsCount--; 180 | if (requestDidFailSelector) { 181 | [delegate performSelector:requestDidFailSelector withObject:request]; 182 | } 183 | if (shouldCancelAllRequestsOnFailure && requestsCount > 0) { 184 | [self cancelAllOperations]; 185 | } 186 | } 187 | 188 | - (void)requestDidFinish:(ASIHTTPRequest *)request 189 | { 190 | requestsCount--; 191 | if (requestDidFinishSelector) { 192 | [delegate performSelector:requestDidFinishSelector withObject:request]; 193 | } 194 | if (requestsCount == 0) { 195 | if (queueDidFinishSelector) { 196 | [delegate performSelector:queueDidFinishSelector withObject:self]; 197 | } 198 | } 199 | } 200 | 201 | 202 | - (void)setUploadBufferSize:(unsigned long long)bytes 203 | { 204 | if (!uploadProgressDelegate) { 205 | return; 206 | } 207 | uploadProgressTotalBytes -= bytes; 208 | [self incrementUploadProgressBy:0]; 209 | } 210 | 211 | - (void)incrementUploadSizeBy:(unsigned long long)bytes 212 | { 213 | if (!uploadProgressDelegate) { 214 | return; 215 | } 216 | uploadProgressTotalBytes += bytes; 217 | [self incrementUploadProgressBy:0]; 218 | } 219 | 220 | - (void)decrementUploadProgressBy:(unsigned long long)bytes 221 | { 222 | if (!uploadProgressDelegate || uploadProgressTotalBytes == 0) { 223 | return; 224 | } 225 | uploadProgressBytes -= bytes; 226 | 227 | double progress = (uploadProgressBytes*1.0)/(uploadProgressTotalBytes*1.0); 228 | [ASIHTTPRequest setProgress:progress forProgressIndicator:uploadProgressDelegate]; 229 | } 230 | 231 | 232 | - (void)incrementUploadProgressBy:(unsigned long long)bytes 233 | { 234 | if (!uploadProgressDelegate || uploadProgressTotalBytes == 0) { 235 | return; 236 | } 237 | uploadProgressBytes += bytes; 238 | 239 | double progress = (uploadProgressBytes*1.0)/(uploadProgressTotalBytes*1.0); 240 | [ASIHTTPRequest setProgress:progress forProgressIndicator:uploadProgressDelegate]; 241 | 242 | } 243 | 244 | - (void)incrementDownloadSizeBy:(unsigned long long)bytes 245 | { 246 | if (!downloadProgressDelegate) { 247 | return; 248 | } 249 | downloadProgressTotalBytes += bytes; 250 | [self incrementDownloadProgressBy:0]; 251 | } 252 | 253 | - (void)incrementDownloadProgressBy:(unsigned long long)bytes 254 | { 255 | if (!downloadProgressDelegate || downloadProgressTotalBytes == 0) { 256 | return; 257 | } 258 | downloadProgressBytes += bytes; 259 | double progress = (downloadProgressBytes*1.0)/(downloadProgressTotalBytes*1.0); 260 | [ASIHTTPRequest setProgress:progress forProgressIndicator:downloadProgressDelegate]; 261 | } 262 | 263 | // Since this queue takes over as the delegate for all requests it contains, it should forward authorisation requests to its own delegate 264 | - (void)authorizationNeededForRequest:(ASIHTTPRequest *)request 265 | { 266 | if ([delegate respondsToSelector:@selector(authorizationNeededForRequest:)]) { 267 | [delegate performSelector:@selector(authorizationNeededForRequest:) withObject:request]; 268 | } 269 | } 270 | 271 | 272 | - (BOOL)respondsToSelector:(SEL)selector 273 | { 274 | if (selector == @selector(authorizationNeededForRequest:)) { 275 | if ([delegate respondsToSelector:@selector(authorizationNeededForRequest:)]) { 276 | return YES; 277 | } 278 | return NO; 279 | } 280 | return [super respondsToSelector:selector]; 281 | } 282 | 283 | 284 | 285 | @synthesize uploadProgressDelegate; 286 | @synthesize downloadProgressDelegate; 287 | @synthesize requestDidFinishSelector; 288 | @synthesize requestDidFailSelector; 289 | @synthesize queueDidFinishSelector; 290 | @synthesize shouldCancelAllRequestsOnFailure; 291 | @synthesize delegate; 292 | @synthesize showAccurateProgress; 293 | 294 | @end 295 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/NSHTTPCookieAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSHTTPCookieAdditions.h 3 | // asi-http-request 4 | // 5 | // Created by Ben Copsey on 12/09/2008. 6 | // Copyright 2008 All-Seeing Interactive. All rights reserved. 7 | // 8 | 9 | 10 | @interface NSHTTPCookie (ValueEncodingAdditions) 11 | 12 | - (NSString *)encodedValue; 13 | - (NSString *)decodedValue; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Externals/ASIHTTPRequest/NSHTTPCookieAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSHTTPCookieAdditions.m 3 | // asi-http-request 4 | // 5 | // Created by Ben Copsey on 12/09/2008. 6 | // Copyright 2008 All-Seeing Interactive. All rights reserved. 7 | // 8 | 9 | #import "NSHTTPCookieAdditions.h" 10 | 11 | @implementation NSHTTPCookie (ValueEncodingAdditions) 12 | 13 | - (NSString *)decodedValue 14 | { 15 | NSMutableString *s = [NSMutableString stringWithString:[[self value] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 16 | //Also swap plus signs for spaces 17 | [s replaceOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [s length])]; 18 | return [NSString stringWithString:s]; 19 | } 20 | 21 | - (NSString *)encodedValue 22 | { 23 | return [[self value] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 24 | } 25 | 26 | @end 27 | 28 | 29 | -------------------------------------------------------------------------------- /Externals/Reachability/Reachability.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | File: Reachability.h 4 | Abstract: SystemConfiguration framework wrapper. 5 | 6 | Version: 1.5 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. 9 | ("Apple") in consideration of your agreement to the following terms, and your 10 | use, installation, modification or redistribution of this Apple software 11 | constitutes acceptance of these terms. If you do not agree with these terms, 12 | please do not use, install, modify or redistribute this Apple software. 13 | 14 | In consideration of your agreement to abide by the following terms, and subject 15 | to these terms, Apple grants you a personal, non-exclusive license, under 16 | Apple's copyrights in this original Apple software (the "Apple Software"), to 17 | use, reproduce, modify and redistribute the Apple Software, with or without 18 | modifications, in source and/or binary forms; provided that if you redistribute 19 | the Apple Software in its entirety and without modifications, you must retain 20 | this notice and the following text and disclaimers in all such redistributions 21 | of the Apple Software. 22 | Neither the name, trademarks, service marks or logos of Apple Inc. may be used 23 | to endorse or promote products derived from the Apple Software without specific 24 | prior written permission from Apple. Except as expressly stated in this notice, 25 | no other rights or licenses, express or implied, are granted by Apple herein, 26 | including but not limited to any patent rights that may be infringed by your 27 | derivative works or by other works in which the Apple Software may be 28 | incorporated. 29 | 30 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 31 | WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 32 | WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 33 | PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 34 | COMBINATION WITH YOUR PRODUCTS. 35 | 36 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 37 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 38 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 | ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR 40 | DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF 41 | CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF 42 | APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2008 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import 49 | #import 50 | 51 | @class Reachability; 52 | 53 | @interface Reachability : NSObject { 54 | 55 | @private 56 | BOOL _networkStatusNotificationsEnabled; 57 | 58 | NSString *_hostName; 59 | NSString *_address; 60 | 61 | NSMutableDictionary *_reachabilityQueries; 62 | } 63 | 64 | /* 65 | An enumeration that defines the return values of the network state 66 | of the device. 67 | */ 68 | typedef enum { 69 | NotReachable = 0, 70 | ReachableViaCarrierDataNetwork, 71 | ReachableViaWiFiNetwork 72 | } NetworkStatus; 73 | 74 | 75 | // Set to YES to register for changes in network status. Otherwise reachability queries 76 | // will be handled synchronously. 77 | @property BOOL networkStatusNotificationsEnabled; 78 | // The remote host whose reachability will be queried. 79 | // Either this or 'addressName' must be set. 80 | @property (nonatomic, retain) NSString *hostName; 81 | // The IP address of the remote host whose reachability will be queried. 82 | // Either this or 'hostName' must be set. 83 | @property (nonatomic, retain) NSString *address; 84 | // A cache of ReachabilityQuery objects, which encapsulate a SCNetworkReachabilityRef, a host or address, and a run loop. The keys are host names or addresses. 85 | @property (nonatomic, assign) NSMutableDictionary *reachabilityQueries; 86 | 87 | // This class is intended to be used as a singleton. 88 | + (Reachability *)sharedReachability; 89 | 90 | // Is self.hostName is not nil, determines its reachability. 91 | // If self.hostName is nil and self.address is not nil, determines the reachability of self.address. 92 | - (NetworkStatus)remoteHostStatus; 93 | // Is the device able to communicate with Internet hosts? If so, through which network interface? 94 | - (NetworkStatus)internetConnectionStatus; 95 | // Is the device able to communicate with hosts on the local WiFi network? (Typically these are Bonjour hosts). 96 | - (NetworkStatus)localWiFiConnectionStatus; 97 | 98 | /* 99 | When reachability change notifications are posted, the callback method 'ReachabilityCallback' is called 100 | and posts a notification that the client application can observe to learn about changes. 101 | */ 102 | static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info); 103 | 104 | @end 105 | 106 | @interface ReachabilityQuery : NSObject 107 | { 108 | @private 109 | SCNetworkReachabilityRef _reachabilityRef; 110 | CFMutableArrayRef _runLoops; 111 | NSString *_hostNameOrAddress; 112 | } 113 | // Keep around each network reachability query object so that we can 114 | // register for updates from those objects. 115 | @property (nonatomic) SCNetworkReachabilityRef reachabilityRef; 116 | @property (nonatomic, retain) NSString *hostNameOrAddress; 117 | @property (nonatomic) CFMutableArrayRef runLoops; 118 | 119 | - (void)scheduleOnRunLoop:(NSRunLoop *)inRunLoop; 120 | 121 | @end 122 | 123 | -------------------------------------------------------------------------------- /Externals/Reachability/Reachability.m: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | File: Reachability.m 4 | Abstract: SystemConfiguration framework wrapper. 5 | 6 | Version: 1.5 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. 9 | ("Apple") in consideration of your agreement to the following terms, and your 10 | use, installation, modification or redistribution of this Apple software 11 | constitutes acceptance of these terms. If you do not agree with these terms, 12 | please do not use, install, modify or redistribute this Apple software. 13 | 14 | In consideration of your agreement to abide by the following terms, and subject 15 | to these terms, Apple grants you a personal, non-exclusive license, under 16 | Apple's copyrights in this original Apple software (the "Apple Software"), to 17 | use, reproduce, modify and redistribute the Apple Software, with or without 18 | modifications, in source and/or binary forms; provided that if you redistribute 19 | the Apple Software in its entirety and without modifications, you must retain 20 | this notice and the following text and disclaimers in all such redistributions 21 | of the Apple Software. 22 | Neither the name, trademarks, service marks or logos of Apple Inc. may be used 23 | to endorse or promote products derived from the Apple Software without specific 24 | prior written permission from Apple. Except as expressly stated in this notice, 25 | no other rights or licenses, express or implied, are granted by Apple herein, 26 | including but not limited to any patent rights that may be infringed by your 27 | derivative works or by other works in which the Apple Software may be 28 | incorporated. 29 | 30 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 31 | WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 32 | WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 33 | PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 34 | COMBINATION WITH YOUR PRODUCTS. 35 | 36 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 37 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 38 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 | ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR 40 | DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF 41 | CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF 42 | APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2008 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import 49 | #import 50 | #import 51 | #import 52 | #import 53 | #include 54 | 55 | #import "Reachability.h" 56 | #import 57 | 58 | static NSString *kLinkLocalAddressKey = @"169.254.0.0"; 59 | static NSString *kDefaultRouteKey = @"0.0.0.0"; 60 | 61 | static Reachability *_sharedReachability; 62 | 63 | // A class extension that declares internal methods for this class. 64 | @interface Reachability() 65 | - (BOOL)isAdHocWiFiNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags; 66 | - (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags; 67 | - (BOOL)isReachableWithoutRequiringConnection:(SCNetworkReachabilityFlags)flags; 68 | - (SCNetworkReachabilityRef)reachabilityRefForHostName:(NSString *)hostName; 69 | - (SCNetworkReachabilityRef)reachabilityRefForAddress:(NSString *)address; 70 | - (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)outAddress; 71 | - (void)stopListeningForReachabilityChanges; 72 | @end 73 | 74 | @implementation Reachability 75 | 76 | @synthesize networkStatusNotificationsEnabled = _networkStatusNotificationsEnabled; 77 | @synthesize hostName = _hostName; 78 | @synthesize address = _address; 79 | @synthesize reachabilityQueries = _reachabilityQueries; 80 | 81 | + (Reachability *)sharedReachability 82 | { 83 | if (!_sharedReachability) { 84 | _sharedReachability = [[Reachability alloc] init]; 85 | // Clients of Reachability will typically call [[Reachability sharedReachability] setHostName:] 86 | // before calling one of the status methods. 87 | _sharedReachability.hostName = nil; 88 | _sharedReachability.address = nil; 89 | _sharedReachability.networkStatusNotificationsEnabled = NO; 90 | _sharedReachability.reachabilityQueries = [[NSMutableDictionary alloc] init]; 91 | } 92 | return _sharedReachability; 93 | } 94 | 95 | - (void) dealloc 96 | { 97 | [self stopListeningForReachabilityChanges]; 98 | 99 | [_sharedReachability.reachabilityQueries release]; 100 | [_sharedReachability release]; 101 | [super dealloc]; 102 | } 103 | 104 | - (BOOL)isReachableWithoutRequiringConnection:(SCNetworkReachabilityFlags)flags 105 | { 106 | // kSCNetworkReachabilityFlagsReachable indicates that the specified nodename or address can 107 | // be reached using the current network configuration. 108 | BOOL isReachable = flags & kSCNetworkReachabilityFlagsReachable; 109 | 110 | // This flag indicates that the specified nodename or address can 111 | // be reached using the current network configuration, but a 112 | // connection must first be established. 113 | // 114 | // If the flag is false, we don't have a connection. But because CFNetwork 115 | // automatically attempts to bring up a WWAN connection, if the WWAN reachability 116 | // flag is present, a connection is not required. 117 | BOOL noConnectionRequired = !(flags & kSCNetworkReachabilityFlagsConnectionRequired); 118 | if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) { 119 | noConnectionRequired = YES; 120 | } 121 | 122 | return (isReachable && noConnectionRequired) ? YES : NO; 123 | } 124 | 125 | // Returns whether or not the current host name is reachable with the current network configuration. 126 | - (BOOL)isHostReachable:(NSString *)host 127 | { 128 | if (!host || ![host length]) { 129 | return NO; 130 | } 131 | 132 | SCNetworkReachabilityFlags flags; 133 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [host UTF8String]); 134 | BOOL gotFlags = SCNetworkReachabilityGetFlags(reachability, &flags); 135 | 136 | CFRelease(reachability); 137 | 138 | if (!gotFlags) { 139 | return NO; 140 | } 141 | 142 | return [self isReachableWithoutRequiringConnection:flags]; 143 | } 144 | 145 | // This returns YES if the address 169.254.0.0 is reachable without requiring a connection. 146 | - (BOOL)isAdHocWiFiNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags 147 | { 148 | // Look in the cache of reachability queries for one that matches this query. 149 | ReachabilityQuery *query = [self.reachabilityQueries objectForKey:kLinkLocalAddressKey]; 150 | SCNetworkReachabilityRef adHocWiFiNetworkReachability = query.reachabilityRef; 151 | 152 | // If a cached reachability query was not found, create one. 153 | if (!adHocWiFiNetworkReachability) { 154 | 155 | // Build a sockaddr_in that we can pass to the address reachability query. 156 | struct sockaddr_in sin; 157 | 158 | bzero(&sin, sizeof(sin)); 159 | sin.sin_len = sizeof(sin); 160 | sin.sin_family = AF_INET; 161 | // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 162 | sin.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); 163 | 164 | adHocWiFiNetworkReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&sin); 165 | 166 | query = [[[ReachabilityQuery alloc] init] autorelease]; 167 | query.hostNameOrAddress = kLinkLocalAddressKey; 168 | query.reachabilityRef = adHocWiFiNetworkReachability; 169 | 170 | // Add the reachability query to the cache. 171 | [self.reachabilityQueries setObject:query forKey:kLinkLocalAddressKey]; 172 | } 173 | 174 | // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. 175 | // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register 176 | // to receive notifications from it in the current run loop, which may be different than the run loop 177 | // that was previously used when registering the SCNetworkReachabilityRef for notifications. 178 | // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. 179 | // By default, they are not enabled. 180 | [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; 181 | 182 | SCNetworkReachabilityFlags addressReachabilityFlags; 183 | BOOL gotFlags = SCNetworkReachabilityGetFlags(adHocWiFiNetworkReachability, &addressReachabilityFlags); 184 | if (!gotFlags) { 185 | // There was an error getting the reachability flags. 186 | return NO; 187 | } 188 | 189 | // Callers of this method might want to use the reachability flags, so if an 'out' parameter 190 | // was passed in, assign the reachability flags to it. 191 | if (outFlags) { 192 | *outFlags = addressReachabilityFlags; 193 | } 194 | 195 | return [self isReachableWithoutRequiringConnection:addressReachabilityFlags]; 196 | } 197 | 198 | // ReachabilityCallback is registered as the callback for network state changes in startListeningForReachabilityChanges. 199 | static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) 200 | { 201 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 202 | 203 | // Post a notification to notify the client that the network reachability changed. 204 | [[NSNotificationCenter defaultCenter] postNotificationName:@"kNetworkReachabilityChangedNotification" object:nil]; 205 | 206 | [pool release]; 207 | } 208 | 209 | // Perform a reachability query for the address 0.0.0.0. If that address is reachable without 210 | // requiring a connection, a network interface is available. We'll have to do more work to 211 | // determine which network interface is available. 212 | - (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags 213 | { 214 | ReachabilityQuery *query = [self.reachabilityQueries objectForKey:kDefaultRouteKey]; 215 | SCNetworkReachabilityRef defaultRouteReachability = query.reachabilityRef; 216 | 217 | // If a cached reachability query was not found, create one. 218 | if (!defaultRouteReachability) { 219 | 220 | struct sockaddr_in zeroAddress; 221 | bzero(&zeroAddress, sizeof(zeroAddress)); 222 | zeroAddress.sin_len = sizeof(zeroAddress); 223 | zeroAddress.sin_family = AF_INET; 224 | 225 | defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress); 226 | 227 | ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease]; 228 | query.hostNameOrAddress = kDefaultRouteKey; 229 | query.reachabilityRef = defaultRouteReachability; 230 | 231 | [self.reachabilityQueries setObject:query forKey:kDefaultRouteKey]; 232 | } 233 | 234 | // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. 235 | // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register 236 | // to receive notifications from it in the current run loop, which may be different than the run loop 237 | // that was previously used when registering the SCNetworkReachabilityRef for notifications. 238 | // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. 239 | // By default, they are not enabled. 240 | [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; 241 | 242 | SCNetworkReachabilityFlags flags; 243 | BOOL gotFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); 244 | if (!gotFlags) { 245 | return NO; 246 | } 247 | 248 | BOOL isReachable = [self isReachableWithoutRequiringConnection:flags]; 249 | 250 | // Callers of this method might want to use the reachability flags, so if an 'out' parameter 251 | // was passed in, assign the reachability flags to it. 252 | if (outFlags) { 253 | *outFlags = flags; 254 | } 255 | 256 | return isReachable; 257 | } 258 | 259 | // Be a good citizen and unregister for network state changes when the application terminates. 260 | - (void)stopListeningForReachabilityChanges 261 | { 262 | // Walk through the cache that holds SCNetworkReachabilityRefs for reachability 263 | // queries to particular hosts or addresses. 264 | NSEnumerator *enumerator = [self.reachabilityQueries objectEnumerator]; 265 | ReachabilityQuery *reachabilityQuery; 266 | 267 | while (reachabilityQuery = [enumerator nextObject]) { 268 | 269 | CFArrayRef runLoops = reachabilityQuery.runLoops; 270 | NSUInteger runLoopCounter, maxRunLoops = CFArrayGetCount(runLoops); 271 | 272 | for (runLoopCounter = 0; runLoopCounter < maxRunLoops; runLoopCounter++) { 273 | CFRunLoopRef nextRunLoop = (CFRunLoopRef)CFArrayGetValueAtIndex(runLoops, runLoopCounter); 274 | 275 | SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityQuery.reachabilityRef, nextRunLoop, kCFRunLoopDefaultMode); 276 | } 277 | 278 | CFArrayRemoveAllValues(reachabilityQuery.runLoops); 279 | } 280 | } 281 | 282 | /* 283 | Create a SCNetworkReachabilityRef for hostName, which lets us determine if hostName 284 | is currently reachable, and lets us register to receive notifications when the 285 | reachability of hostName changes. 286 | */ 287 | - (SCNetworkReachabilityRef)reachabilityRefForHostName:(NSString *)hostName 288 | { 289 | if (!hostName || ![hostName length]) { 290 | return NULL; 291 | } 292 | 293 | // Look in the cache for an existing SCNetworkReachabilityRef for hostName. 294 | ReachabilityQuery *cachedQuery = [self.reachabilityQueries objectForKey:hostName]; 295 | SCNetworkReachabilityRef reachabilityRefForHostName = cachedQuery.reachabilityRef; 296 | 297 | if (reachabilityRefForHostName) { 298 | return reachabilityRefForHostName; 299 | } 300 | 301 | // Didn't find an existing SCNetworkReachabilityRef for hostName, so create one ... 302 | reachabilityRefForHostName = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [hostName UTF8String]); 303 | 304 | NSAssert1(reachabilityRefForHostName != NULL, @"Failed to create SCNetworkReachabilityRef for host: %@", hostName); 305 | 306 | ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease]; 307 | query.hostNameOrAddress = hostName; 308 | query.reachabilityRef = reachabilityRefForHostName; 309 | 310 | // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. 311 | // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register 312 | // to receive notifications from it in the current run loop, which may be different than the run loop 313 | // that was previously used when registering the SCNetworkReachabilityRef for notifications. 314 | // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. 315 | // By default, they are not enabled. 316 | [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; 317 | 318 | // ... and add it to the cache. 319 | [self.reachabilityQueries setObject:query forKey:hostName]; 320 | return reachabilityRefForHostName; 321 | } 322 | 323 | /* 324 | Create a SCNetworkReachabilityRef for the IP address in addressString, which lets us determine if 325 | the address is currently reachable, and lets us register to receive notifications when the 326 | reachability of the address changes. 327 | */ 328 | - (SCNetworkReachabilityRef)reachabilityRefForAddress:(NSString *)addressString 329 | { 330 | if (!addressString || ![addressString length]) { 331 | return NULL; 332 | } 333 | 334 | struct sockaddr_in address; 335 | 336 | BOOL gotAddress = [self addressFromString:addressString address:&address]; 337 | if (!gotAddress) { 338 | // The attempt to convert addressString to a sockaddr_in failed. 339 | NSAssert1(gotAddress != NO, @"Failed to convert an IP address string to a sockaddr_in: %@", addressString); 340 | return NULL; 341 | } 342 | 343 | // Look in the cache for an existing SCNetworkReachabilityRef for addressString. 344 | ReachabilityQuery *cachedQuery = [self.reachabilityQueries objectForKey:addressString]; 345 | SCNetworkReachabilityRef reachabilityRefForAddress = cachedQuery.reachabilityRef; 346 | 347 | if (reachabilityRefForAddress) { 348 | return reachabilityRefForAddress; 349 | } 350 | 351 | // Didn't find an existing SCNetworkReachabilityRef for addressString, so create one. 352 | reachabilityRefForAddress = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)&address); 353 | 354 | NSAssert1(reachabilityRefForAddress != NULL, @"Failed to create SCNetworkReachabilityRef for address: %@", addressString); 355 | 356 | ReachabilityQuery *query = [[[ReachabilityQuery alloc] init] autorelease]; 357 | query.hostNameOrAddress = addressString; 358 | query.reachabilityRef = reachabilityRefForAddress; 359 | 360 | // If necessary, register for notifcations for the SCNetworkReachabilityRef on the current run loop. 361 | // If an existing SCNetworkReachabilityRef was found in the cache, we can reuse it and register 362 | // to receive notifications from it in the current run loop, which may be different than the run loop 363 | // that was previously used when registering the SCNetworkReachabilityRef for notifications. 364 | // -scheduleOnRunLoop: will schedule only if network status notifications are enabled in the Reachability instance. 365 | // By default, they are not enabled. 366 | [query scheduleOnRunLoop:[NSRunLoop currentRunLoop]]; 367 | 368 | // ... and add it to the cache. 369 | [self.reachabilityQueries setObject:query forKey:addressString]; 370 | return reachabilityRefForAddress; 371 | } 372 | 373 | - (NetworkStatus)remoteHostStatus 374 | { 375 | /* 376 | If the current host name or address is reachable, determine which network interface it is reachable through. 377 | If the host is reachable and the reachability flags include kSCNetworkReachabilityFlagsIsWWAN, it 378 | is reachable through the carrier data network. If the host is reachable and the reachability 379 | flags do not include kSCNetworkReachabilityFlagsIsWWAN, it is reachable through the WiFi network. 380 | */ 381 | 382 | SCNetworkReachabilityRef reachabilityRef = nil; 383 | if (self.hostName) { 384 | reachabilityRef = [self reachabilityRefForHostName:self.hostName]; 385 | 386 | } else if (self.address) { 387 | reachabilityRef = [self reachabilityRefForAddress:self.address]; 388 | 389 | } else { 390 | NSAssert(self.hostName != nil && self.address != nil, @"No hostName or address specified. Cannot determine reachability."); 391 | return NotReachable; 392 | } 393 | 394 | if (!reachabilityRef) { 395 | return NotReachable; 396 | } 397 | 398 | SCNetworkReachabilityFlags reachabilityFlags; 399 | BOOL gotFlags = SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags); 400 | if (!gotFlags) { 401 | return NotReachable; 402 | } 403 | 404 | BOOL reachable = [self isReachableWithoutRequiringConnection:reachabilityFlags]; 405 | 406 | if (!reachable) { 407 | return NotReachable; 408 | } 409 | if (reachabilityFlags & ReachableViaCarrierDataNetwork) { 410 | return ReachableViaCarrierDataNetwork; 411 | } 412 | 413 | return ReachableViaWiFiNetwork; 414 | } 415 | 416 | - (NetworkStatus)internetConnectionStatus 417 | { 418 | /* 419 | To determine if the device has an Internet connection, query the address 420 | 0.0.0.0. If it's reachable without requiring a connection, first check 421 | for the kSCNetworkReachabilityFlagsIsDirect flag, which tell us if the connection 422 | is to an ad-hoc WiFi network. If it is not, the device can access the Internet. 423 | The next thing to determine is how the device can access the Internet, which 424 | can either be through the carrier data network (EDGE or other service) or through 425 | a WiFi connection. 426 | 427 | Note: Knowing that the device has an Internet connection is not the same as 428 | knowing if the device can reach a particular host. To know that, use 429 | -[Reachability remoteHostStatus]. 430 | */ 431 | 432 | SCNetworkReachabilityFlags defaultRouteFlags; 433 | BOOL defaultRouteIsAvailable = [self isNetworkAvailableFlags:&defaultRouteFlags]; 434 | if (defaultRouteIsAvailable) { 435 | 436 | if (defaultRouteFlags & kSCNetworkReachabilityFlagsIsDirect) { 437 | 438 | // The connection is to an ad-hoc WiFi network, so Internet access is not available. 439 | return NotReachable; 440 | } 441 | else if (defaultRouteFlags & ReachableViaCarrierDataNetwork) { 442 | return ReachableViaCarrierDataNetwork; 443 | } 444 | 445 | return ReachableViaWiFiNetwork; 446 | } 447 | 448 | return NotReachable; 449 | } 450 | 451 | - (NetworkStatus)localWiFiConnectionStatus 452 | { 453 | SCNetworkReachabilityFlags selfAssignedAddressFlags; 454 | 455 | /* 456 | To determine if the WiFi connection is to a local ad-hoc network, 457 | check the availability of the address 169.254.x.x. That's an address 458 | in the self-assigned range, and the device will have a self-assigned IP 459 | when it's connected to a ad-hoc WiFi network. So to test if the device 460 | has a self-assigned IP, look for the kSCNetworkReachabilityFlagsIsDirect flag 461 | in the address query. If it's present, we know that the WiFi connection 462 | is to an ad-hoc network. 463 | */ 464 | // This returns YES if the address 169.254.0.0 is reachable without requiring a connection. 465 | BOOL hasLinkLocalNetworkAccess = [self isAdHocWiFiNetworkAvailableFlags:&selfAssignedAddressFlags]; 466 | 467 | if (hasLinkLocalNetworkAccess && (selfAssignedAddressFlags & kSCNetworkReachabilityFlagsIsDirect)) { 468 | return ReachableViaWiFiNetwork; 469 | } 470 | 471 | return NotReachable; 472 | } 473 | 474 | // Convert an IP address from an NSString to a sockaddr_in * that can be used to create 475 | // the reachability request. 476 | - (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)address 477 | { 478 | if (!IPAddress || ![IPAddress length]) { 479 | return NO; 480 | } 481 | 482 | memset((char *) address, sizeof(struct sockaddr_in), 0); 483 | address->sin_family = AF_INET; 484 | address->sin_len = sizeof(struct sockaddr_in); 485 | 486 | int conversionResult = inet_aton([IPAddress UTF8String], &address->sin_addr); 487 | if (conversionResult == 0) { 488 | NSAssert1(conversionResult != 1, @"Failed to convert the IP address string into a sockaddr_in: %@", IPAddress); 489 | return NO; 490 | } 491 | 492 | return YES; 493 | } 494 | 495 | @end 496 | 497 | @interface ReachabilityQuery () 498 | - (CFRunLoopRef)startListeningForReachabilityChanges:(SCNetworkReachabilityRef)reachability onRunLoop:(CFRunLoopRef)runLoop; 499 | @end 500 | 501 | @implementation ReachabilityQuery 502 | 503 | @synthesize reachabilityRef = _reachabilityRef; 504 | @synthesize runLoops = _runLoops; 505 | @synthesize hostNameOrAddress = _hostNameOrAddress; 506 | 507 | - (id)init 508 | { 509 | self = [super init]; 510 | if (self != nil) { 511 | self.runLoops = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); 512 | } 513 | return self; 514 | } 515 | 516 | - (void)dealloc 517 | { 518 | CFRelease(self.runLoops); 519 | [super dealloc]; 520 | } 521 | 522 | - (BOOL)isScheduledOnRunLoop:(CFRunLoopRef)runLoop 523 | { 524 | NSUInteger runLoopCounter, maxRunLoops = CFArrayGetCount(self.runLoops); 525 | 526 | for (runLoopCounter = 0; runLoopCounter < maxRunLoops; runLoopCounter++) { 527 | CFRunLoopRef nextRunLoop = (CFRunLoopRef)CFArrayGetValueAtIndex(self.runLoops, runLoopCounter); 528 | 529 | if (nextRunLoop == runLoop) { 530 | return YES; 531 | } 532 | } 533 | 534 | return NO; 535 | } 536 | 537 | - (void)scheduleOnRunLoop:(NSRunLoop *)inRunLoop 538 | { 539 | // Only register for network state changes if the client has specifically enabled them. 540 | if ([[Reachability sharedReachability] networkStatusNotificationsEnabled] == NO) { 541 | return; 542 | } 543 | 544 | if (!inRunLoop) { 545 | return; 546 | } 547 | 548 | CFRunLoopRef runLoop = [inRunLoop getCFRunLoop]; 549 | 550 | // Notifications of status changes for each reachability query can be scheduled on multiple run loops. 551 | // To support that, register for notifications for each runLoop. 552 | // -isScheduledOnRunLoop: iterates over all of the run loops that have previously been used 553 | // to register for notifications. If one is found that matches the passed in runLoop argument, there's 554 | // no need to register for notifications again. If one is not found, register for notifications 555 | // using the current runLoop. 556 | if (![self isScheduledOnRunLoop:runLoop]) { 557 | 558 | CFRunLoopRef notificationRunLoop = [self startListeningForReachabilityChanges:self.reachabilityRef onRunLoop:runLoop]; 559 | if (notificationRunLoop) { 560 | CFArrayAppendValue(self.runLoops, notificationRunLoop); 561 | } 562 | } 563 | } 564 | 565 | // Register to receive changes to the 'reachability' query so that we can update the 566 | // user interface when the network state changes. 567 | - (CFRunLoopRef)startListeningForReachabilityChanges:(SCNetworkReachabilityRef)reachability onRunLoop:(CFRunLoopRef)runLoop 568 | { 569 | if (!reachability) { 570 | return NULL; 571 | } 572 | 573 | if (!runLoop) { 574 | return NULL; 575 | } 576 | 577 | SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL}; 578 | SCNetworkReachabilitySetCallback(reachability, ReachabilityCallback, &context); 579 | SCNetworkReachabilityScheduleWithRunLoop(reachability, runLoop, kCFRunLoopDefaultMode); 580 | 581 | return runLoop; 582 | } 583 | 584 | 585 | @end 586 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | com.yourcompany.${PRODUCT_NAME:identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | NSMainNibFile 28 | MainWindow 29 | 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Version:** 1.0
\ 2 | **Author:** Adrian Kosmaczewski
\ 3 | **Date:** March 8th, 2009
\ 4 | **Programming Languages:** Objective-C
\ 5 | **Tools:** Xcode
\ 6 | **Platforms:** Mac OS X Leopard 10.5.6, iPhone OS 2.0+
\ 7 | **Repository:** 8 | Github
\ 9 | **License:** None, public domain, whatever you want :)
10 | 11 | ![image](http://kosmaczewski.net/wp-content/uploads/2009/03/asynctable-161x300.png) 12 | 13 | This project features a sample application showing one possible way to 14 | load images asynchronously in a UITableView, using Apple’s Reachability 15 | class and the 16 | ASIHTTPRequest 17 | framework. It also features a simple Core Animation effect similar 18 | to the one featured in the iTunes iPhone application. 19 | -------------------------------------------------------------------------------- /Resources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosma/async-uitableview/f4e916d355ac8dcfbb271fe42dd8416cd9840730/Resources/Default.png -------------------------------------------------------------------------------- /Resources/FlickrItem.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 528 5 | 9G55 6 | 677 7 | 949.43 8 | 353.00 9 | 10 | YES 11 | 12 | 13 | 14 | YES 15 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 16 | 17 | 18 | YES 19 | 20 | YES 21 | 22 | 23 | YES 24 | 25 | 26 | 27 | YES 28 | 29 | IBFilesOwner 30 | 31 | 32 | IBFirstResponder 33 | 34 | 35 | 36 | 292 37 | 38 | YES 39 | 40 | 41 | 274 42 | {320, 416} 43 | 44 | NO 45 | NO 46 | 1 47 | NO 48 | 49 | 50 | 51 | 265 52 | {{228, 359}, {72, 37}} 53 | 54 | NO 55 | NO 56 | 5.000000e-01 57 | NO 58 | 0 59 | 0 60 | 61 | Helvetica-Bold 62 | 1.500000e+01 63 | 16 64 | 65 | 1 66 | Save 67 | Save 68 | Save 69 | Save 70 | 71 | 1 72 | MSAxIDEAA 73 | 74 | 75 | 1 76 | MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA 77 | 78 | 79 | 80 | 81 | 292 82 | {{20, 203}, {280, 9}} 83 | 84 | NO 85 | YES 86 | YES 87 | 88 | 89 | {320, 416} 90 | 91 | 92 | 3 93 | MQA 94 | 95 | 2 96 | 97 | 98 | 99 | 100 | NO 101 | 102 | 103 | 104 | 105 | 106 | YES 107 | 108 | 109 | view 110 | 111 | 112 | 113 | 4 114 | 115 | 116 | 117 | photoView 118 | 119 | 120 | 121 | 5 122 | 123 | 124 | 125 | saveButton 126 | 127 | 128 | 129 | 9 130 | 131 | 132 | 133 | save 134 | 135 | 136 | 7 137 | 138 | 10 139 | 140 | 141 | 142 | progressView 143 | 144 | 145 | 146 | 12 147 | 148 | 149 | 150 | 151 | YES 152 | 153 | 0 154 | 155 | YES 156 | 157 | 158 | 159 | 160 | 161 | 1 162 | 163 | 164 | YES 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -1 173 | 174 | 175 | RmlsZSdzIE93bmVyA 176 | 177 | 178 | -2 179 | 180 | 181 | 182 | 183 | 3 184 | 185 | 186 | 187 | 188 | 8 189 | 190 | 191 | 192 | 193 | 11 194 | 195 | 196 | 197 | 198 | 199 | 200 | YES 201 | 202 | YES 203 | -1.CustomClassName 204 | -2.CustomClassName 205 | 1.IBEditorWindowLastContentRect 206 | 1.IBPluginDependency 207 | 11.IBPluginDependency 208 | 3.IBPluginDependency 209 | 8.IBPluginDependency 210 | 211 | 212 | YES 213 | FlickrItemController 214 | UIResponder 215 | {{354, 376}, {320, 480}} 216 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 217 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 218 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 219 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 220 | 221 | 222 | 223 | YES 224 | 225 | YES 226 | 227 | 228 | YES 229 | 230 | 231 | 232 | 233 | YES 234 | 235 | YES 236 | 237 | 238 | YES 239 | 240 | 241 | 242 | 12 243 | 244 | 245 | 246 | YES 247 | 248 | FlickrItemController 249 | UIViewController 250 | 251 | save 252 | id 253 | 254 | 255 | YES 256 | 257 | YES 258 | photoView 259 | progressView 260 | saveButton 261 | 262 | 263 | YES 264 | UIImageView 265 | UIProgressView 266 | UIButton 267 | 268 | 269 | 270 | IBProjectSource 271 | Classes/Controllers/FlickrItemController.h 272 | 273 | 274 | 275 | 276 | 0 277 | ../AsyncTable.xcodeproj 278 | 3 279 | 280 | 281 | -------------------------------------------------------------------------------- /Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akosma/async-uitableview/f4e916d355ac8dcfbb271fe42dd8416cd9840730/Resources/Icon.png -------------------------------------------------------------------------------- /Resources/MainWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 528 5 | 9G55 6 | 677 7 | 949.43 8 | 353.00 9 | 10 | YES 11 | 12 | 13 | YES 14 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 15 | 16 | 17 | YES 18 | 19 | YES 20 | 21 | 22 | YES 23 | 24 | 25 | 26 | YES 27 | 28 | IBFilesOwner 29 | 30 | 31 | IBFirstResponder 32 | 33 | 34 | 35 | 36 | 292 37 | {320, 480} 38 | 39 | 1 40 | MSAxIDEAA 41 | 42 | NO 43 | NO 44 | 45 | 46 | 47 | 48 | 49 | YES 50 | 51 | 52 | delegate 53 | 54 | 55 | 56 | 4 57 | 58 | 59 | 60 | window 61 | 62 | 63 | 64 | 14 65 | 66 | 67 | 68 | 69 | YES 70 | 71 | 0 72 | 73 | YES 74 | 75 | 76 | 77 | 78 | 79 | -1 80 | 81 | 82 | RmlsZSdzIE93bmVyA 83 | 84 | 85 | 3 86 | 87 | 88 | AsyncTable App Delegate 89 | 90 | 91 | -2 92 | 93 | 94 | 95 | 96 | 12 97 | 98 | 99 | 100 | 101 | 102 | 103 | YES 104 | 105 | YES 106 | -1.CustomClassName 107 | -2.CustomClassName 108 | 12.IBEditorWindowLastContentRect 109 | 12.IBPluginDependency 110 | 3.CustomClassName 111 | 3.IBPluginDependency 112 | 113 | 114 | YES 115 | UIApplication 116 | UIResponder 117 | {{525, 346}, {320, 480}} 118 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 119 | AsyncTableAppDelegate 120 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 121 | 122 | 123 | 124 | YES 125 | 126 | YES 127 | 128 | 129 | YES 130 | 131 | 132 | 133 | 134 | YES 135 | 136 | YES 137 | 138 | 139 | YES 140 | 141 | 142 | 143 | 23 144 | 145 | 146 | 147 | YES 148 | 149 | AsyncTableAppDelegate 150 | NSObject 151 | 152 | YES 153 | 154 | YES 155 | controller 156 | window 157 | 158 | 159 | YES 160 | FlickrController 161 | UIWindow 162 | 163 | 164 | 165 | IBProjectSource 166 | Classes/AppDelegate/AsyncTableAppDelegate.h 167 | 168 | 169 | 170 | AsyncTableAppDelegate 171 | NSObject 172 | 173 | IBUserSource 174 | 175 | 176 | 177 | 178 | FlickrController 179 | UITableViewController 180 | 181 | IBProjectSource 182 | Classes/Controllers/FlickrController.h 183 | 184 | 185 | 186 | 187 | 0 188 | AsyncTable.xcodeproj 189 | 3 190 | 191 | 192 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AsyncTable 4 | // 5 | // Created by Adrian on 3/8/09. 6 | // Copyright akosma software 2009. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) { 12 | 13 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 14 | int retVal = UIApplicationMain(argc, argv, nil, nil); 15 | [pool release]; 16 | return retVal; 17 | } 18 | --------------------------------------------------------------------------------