Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Revert "cleanup". I'm not sure why it deleted everything, but that c…

…learly wasn't the intended change.

This reverts commit f381ea9.
  • Loading branch information...
commit 60a240bec0eec7cfbeb54d3f6e804409f68d509d 1 parent f381ea9
Evan Schoenberg authored
Showing with 32,223 additions and 0 deletions.
  1. +1 −0  .gitattributes
  2. +12 −0 .gitignore
  3. +3,301 −0 AppSalesMobile.xcodeproj/project.pbxproj
  4. +76 −0 AppSalesMobile.xcodeproj/xcuserdata/ole.xcuserdatad/xcschemes/AppSalesMobile.xcscheme
  5. +22 −0 AppSalesMobile.xcodeproj/xcuserdata/ole.xcuserdatad/xcschemes/xcschememanagement.plist
  6. +8 −0 AppSalesMobile_Prefix.pch
  7. BIN  Artwork/AppSalesIcon.lineform/323A186F-C7FA-4A23-A55D-EC0573904EC5-9890-0000AFB9A8ACC62E.png
  8. BIN  Artwork/AppSalesIcon.lineform/data
  9. BIN  Artwork/AppSalesIcon512.png
  10. BIN  CalculatorButton2Highlighted.png
  11. BIN  CalculatorButton2Normal.png
  12. BIN  CalculatorButton3Highlighted.png
  13. BIN  CalculatorButton3Normal.png
  14. BIN  CalculatorButton4Highlighted.png
  15. BIN  CalculatorButton4Normal.png
  16. BIN  CalculatorButtonHighlighted.png
  17. BIN  CalculatorButtonNormal.png
  18. BIN  CalculatorClick.caf
  19. +26 −0 Classes/AbstractDayOrWeekController.h
  20. +87 −0 Classes/AbstractDayOrWeekController.m
  21. +38 −0 Classes/App.h
  22. +115 −0 Classes/App.m
  23. +32 −0 Classes/AppCell.h
  24. +111 −0 Classes/AppCell.m
  25. +21 −0 Classes/AppIconManager.h
  26. +96 −0 Classes/AppIconManager.m
  27. +24 −0 Classes/AppManager.h
  28. +95 −0 Classes/AppManager.m
  29. +43 −0 Classes/AppSalesMobileAppDelegate.h
  30. +64 −0 Classes/AppSalesMobileAppDelegate.m
  31. +409 −0 Classes/AsyncSocket.h
  32. +2,935 −0 Classes/AsyncSocket.m
  33. +52 −0 Classes/CalculatorView.h
  34. +508 −0 Classes/CalculatorView.m
  35. +48 −0 Classes/CountriesController.h
  36. +187 −0 Classes/CountriesController.m
  37. +58 −0 Classes/Country.h
  38. +186 −0 Classes/Country.m
  39. +52 −0 Classes/CountryCell.h
  40. +147 −0 Classes/CountryCell.m
  41. +65 −0 Classes/CurrencyManager.h
  42. +309 −0 Classes/CurrencyManager.m
  43. +44 −0 Classes/CurrencySelectionDialog.h
  44. +105 −0 Classes/CurrencySelectionDialog.m
  45. +14 −0 Classes/DDData.h
  46. +203 −0 Classes/DDData.m
  47. +12 −0 Classes/DDNumber.h
  48. +74 −0 Classes/DDNumber.m
  49. +52 −0 Classes/DDRange.h
  50. +70 −0 Classes/DDRange.m
  51. +42 −0 Classes/DashboardGraphView.h
  52. +571 −0 Classes/DashboardGraphView.m
  53. +38 −0 Classes/DashboardViewController.h
  54. +440 −0 Classes/DashboardViewController.m
  55. +92 −0 Classes/Day.h
  56. +568 −0 Classes/Day.m
  57. +38 −0 Classes/DayCell.h
  58. +60 −0 Classes/DayCell.m
  59. +31 −0 Classes/DayOrWeekCell.h
  60. +134 −0 Classes/DayOrWeekCell.m
  61. +38 −0 Classes/DaysController.h
  62. +156 −0 Classes/DaysController.m
  63. +41 −0 Classes/EntriesController.h
  64. +87 −0 Classes/EntriesController.m
  65. +61 −0 Classes/Entry.h
  66. +146 −0 Classes/Entry.m
  67. +43 −0 Classes/EntryCell.h
  68. +93 −0 Classes/EntryCell.m
  69. +50 −0 Classes/Google Translate ObjC/JSON.h
  70. +43 −0 Classes/Google Translate ObjC/LKConstants.h
  71. +21 −0 Classes/Google Translate ObjC/LKGoogleTranslator.h
  72. +81 −0 Classes/Google Translate ObjC/LKGoogleTranslator.m
  73. +68 −0 Classes/Google Translate ObjC/NSObject+SBJSON.h
  74. +53 −0 Classes/Google Translate ObjC/NSObject+SBJSON.m
  75. +58 −0 Classes/Google Translate ObjC/NSString+SBJSON.h
  76. +56 −0 Classes/Google Translate ObjC/NSString+SBJSON.m
  77. +75 −0 Classes/Google Translate ObjC/SBJSON.h
  78. +212 −0 Classes/Google Translate ObjC/SBJSON.m
  79. +86 −0 Classes/Google Translate ObjC/SBJsonBase.h
  80. +78 −0 Classes/Google Translate ObjC/SBJsonBase.m
  81. +87 −0 Classes/Google Translate ObjC/SBJsonParser.h
  82. +475 −0 Classes/Google Translate ObjC/SBJsonParser.m
  83. +129 −0 Classes/Google Translate ObjC/SBJsonWriter.h
  84. +228 −0 Classes/Google Translate ObjC/SBJsonWriter.m
  85. +19 −0 Classes/GraphView.h
  86. +55 −0 Classes/GraphView.m
  87. +43 −0 Classes/HTTPAuthenticationRequest.h
  88. +208 −0 Classes/HTTPAuthenticationRequest.m
  89. +67 −0 Classes/HTTPConnection.h
  90. +1,500 −0 Classes/HTTPConnection.m
  91. +41 −0 Classes/HTTPResponse.h
  92. +104 −0 Classes/HTTPResponse.m
  93. +64 −0 Classes/HTTPServer.h
  94. +367 −0 Classes/HTTPServer.m
  95. +40 −0 Classes/HelpBrowser.h
  96. +79 −0 Classes/HelpBrowser.m
  97. +23 −0 Classes/ImportExportViewController.h
  98. +213 −0 Classes/ImportExportViewController.m
  99. +18 −0 Classes/MovableView.h
  100. +50 −0 Classes/MovableView.m
  101. +23 −0 Classes/MyHTTPConnection.h
  102. +190 −0 Classes/MyHTTPConnection.m
  103. +17 −0 Classes/NSData+Compression.h
  104. +179 −0 Classes/NSData+Compression.m
  105. +19 −0 Classes/NSDateFormatter+SharedInstances.h
  106. +62 −0 Classes/NSDateFormatter+SharedInstances.m
  107. +10 −0 Classes/NSDictionary+HTTP.h
  108. +61 −0 Classes/NSDictionary+HTTP.m
  109. +11 −0 Classes/NSString+UnescapeHtml.h
  110. +77 −0 Classes/NSString+UnescapeHtml.m
  111. +45 −0 Classes/PadRootViewController.h
  112. +334 −0 Classes/PadRootViewController.m
  113. 0  Classes/PasswordKeeper.h
  114. 0  Classes/PasswordKeeper.m
  115. +53 −0 Classes/ProductCell.h
  116. +141 −0 Classes/ProductCell.m
  117. +23 −0 Classes/ProgressHUD.h
  118. +89 −0 Classes/ProgressHUD.m
  119. +122 −0 Classes/Reachability.h
  120. +585 −0 Classes/Reachability.m
  121. +295 −0 Classes/RegexKitLite.h
  122. +2,636 −0 Classes/RegexKitLite.m
  123. +17 −0 Classes/RegionsGraphView.h
  124. +256 −0 Classes/RegionsGraphView.m
  125. +46 −0 Classes/ReportManager.h
  126. +625 −0 Classes/ReportManager.m
  127. +49 −0 Classes/Review.h
  128. +151 −0 Classes/Review.m
  129. +30 −0 Classes/ReviewCell.h
  130. +103 −0 Classes/ReviewCell.m
  131. +36 −0 Classes/ReviewManager.h
  132. +532 −0 Classes/ReviewManager.m
  133. +22 −0 Classes/ReviewSummaryView.h
  134. +96 −0 Classes/ReviewSummaryView.m
  135. +26 −0 Classes/ReviewsController.h
  136. +157 −0 Classes/ReviewsController.m
  137. +21 −0 Classes/ReviewsListController.h
  138. +125 −0 Classes/ReviewsListController.m
  139. +176 −0 Classes/ReviewsPane.m
  140. +25 −0 Classes/ReviewsPaneController.h
  141. +172 −0 Classes/ReviewsPaneController.m
  142. +66 −0 Classes/RootViewController.h
  143. +383 −0 Classes/RootViewController.m
  144. +41 −0 Classes/SFHFKeychainUtils.h
  145. +405 −0 Classes/SFHFKeychainUtils.m
  146. +57 −0 Classes/SettingsViewController.h
  147. +168 −0 Classes/SettingsViewController.m
  148. +21 −0 Classes/SingleReviewController.h
  149. +60 −0 Classes/SingleReviewController.m
  150. +42 −0 Classes/StatisticsViewController.h
  151. +471 −0 Classes/StatisticsViewController.m
  152. +20 −0 Classes/TrendGraphView.h
  153. +201 −0 Classes/TrendGraphView.m
  154. +34 −0 Classes/UIDevice+iPad.h
  155. +55 −0 Classes/UIDevice+iPad.m
  156. +38 −0 Classes/WeekCell.h
  157. +70 −0 Classes/WeekCell.m
  158. +55 −0 Classes/WeeksController.h
  159. +589 −0 Classes/WeeksController.m
  160. +48 −0 Classes/ZipArchive.h
  161. +337 −0 Classes/ZipArchive.mm
  162. +132 −0 Classes/crypt.h
  163. +177 −0 Classes/ioapi.c
  164. +75 −0 Classes/ioapi.h
  165. +15 −0 Classes/localhostAddresses.h
  166. +50 −0 Classes/localhostAddresses.m
  167. +281 −0 Classes/mztools.c
  168. +31 −0 Classes/mztools.h
  169. +1,598 −0 Classes/unzip.c
  170. +354 −0 Classes/unzip.h
  171. +1,219 −0 Classes/zip.c
  172. +235 −0 Classes/zip.h
  173. +37 −0 English.lproj/ImportHelp.html
  174. +52 −0 English.lproj/ImportTemplate.html
  175. BIN  English.lproj/Localizable.strings
  176. +8 −0 Entitlements.plist
  177. +37 −0 German.lproj/ImportHelp.html
  178. +52 −0 German.lproj/ImportTemplate.html
  179. BIN  German.lproj/Localizable.strings
  180. BIN  Images/5stars.png
  181. BIN  Images/5stars@2x.png
  182. BIN  Images/5stars_gray.png
  183. BIN  Images/5stars_gray@2x.png
  184. BIN  Images/Background.png
  185. BIN  Images/Blue.png
  186. BIN  Images/Blueish.png
  187. BIN  Images/CalculatorBackground.png
  188. BIN  Images/CalendarButtonHighlighted.png
  189. BIN  Images/CalendarButtonNormal.png
  190. BIN  Images/DateButtonHighlight.png
  191. BIN  Images/DateButtonNormal.png
  192. BIN  Images/Day.png
  193. BIN  Images/Day_Highlighted.png
  194. BIN  Images/Default-Landscape.png
  195. BIN  Images/Default-Portrait.png
  196. BIN  Images/Default.png
  197. BIN  Images/Default@2x.png
  198. BIN  Images/Download.png
  199. BIN  Images/GraphBackground.png
  200. BIN  Images/GraphBackground@2x.png
  201. BIN  Images/GraphDetailBottom.png
  202. BIN  Images/GraphDetailTop.png
  203. BIN  Images/GraphScrollBackground.png
  204. BIN  Images/Graphs.png
  205. BIN  Images/Graphs@2x.png
  206. BIN  Images/Graphs_Highlighted.png
  207. BIN  Images/Graphs_Highlighted@2x.png
  208. BIN  Images/Gray.png
  209. BIN  Images/Green.png
  210. BIN  Images/HUDFrame.png
  211. BIN  Images/Icon114.png
  212. BIN  Images/Icon57.png
  213. BIN  Images/Icon72.png
  214. BIN  Images/ImportExport.png
  215. BIN  Images/ImportExport@2x.png
  216. BIN  Images/ImportExport_Highlighted.png
  217. BIN  Images/ImportExport_Highlighted@2x.png
  218. BIN  Images/LightGray.png
  219. BIN  Images/PaneBackground.png
  220. BIN  Images/PaneButtonHighlighted.png
  221. BIN  Images/PaneButtonNormal.png
  222. BIN  Images/PickerOverlay.png
  223. BIN  Images/Product.png
  224. BIN  Images/ProductMask.png
  225. BIN  Images/Purchase.png
  226. BIN  Images/Return.png
  227. BIN  Images/ReviewBackground.png
  228. BIN  Images/Settings2.png
  229. BIN  Images/Settings2@2x.png
  230. BIN  Images/Settings2_Highlighted.png
  231. BIN  Images/Settings2_Highlighted@2x.png
  232. BIN  Images/Star.png
  233. BIN  Images/Star@2x.png
  234. BIN  Images/Star_Highlighted.png
  235. BIN  Images/Star_Highlighted@2x.png
  236. BIN  Images/TB_About.png
  237. BIN  Images/TB_Calculator.png
  238. BIN  Images/TB_Filter.png
  239. BIN  Images/TB_Graphs.png
  240. BIN  Images/TB_ImportExport.png
  241. BIN  Images/TB_Settings.png
  242. BIN  Images/Week.png
  243. BIN  Images/Week_Highlighted.png
  244. BIN  Images/White.png
  245. BIN  ImagesFlags/ad.png
  246. BIN  ImagesFlags/ad@2x.png
  247. BIN  ImagesFlags/ae.png
  248. BIN  ImagesFlags/ae@2x.png
  249. BIN  ImagesFlags/af.png
  250. BIN  ImagesFlags/af@2x.png
  251. BIN  ImagesFlags/ag.png
  252. BIN  ImagesFlags/ag@2x.png
  253. BIN  ImagesFlags/ai.png
  254. BIN  ImagesFlags/ai@2x.png
  255. BIN  ImagesFlags/al.png
  256. BIN  ImagesFlags/al@2x.png
  257. BIN  ImagesFlags/am.png
  258. BIN  ImagesFlags/am@2x.png
  259. BIN  ImagesFlags/an.png
  260. BIN  ImagesFlags/an@2x.png
  261. BIN  ImagesFlags/ao.png
  262. BIN  ImagesFlags/ao@2x.png
  263. BIN  ImagesFlags/aq.png
  264. BIN  ImagesFlags/aq@2x.png
  265. BIN  ImagesFlags/ar.png
  266. BIN  ImagesFlags/ar@2x.png
  267. BIN  ImagesFlags/as.png
  268. BIN  ImagesFlags/as@2x.png
  269. BIN  ImagesFlags/at.png
  270. BIN  ImagesFlags/at@2x.png
  271. BIN  ImagesFlags/au.png
  272. BIN  ImagesFlags/au@2x.png
  273. BIN  ImagesFlags/aw.png
  274. BIN  ImagesFlags/aw@2x.png
  275. BIN  ImagesFlags/ax.png
  276. BIN  ImagesFlags/ax@2x.png
  277. BIN  ImagesFlags/az.png
  278. BIN  ImagesFlags/az@2x.png
  279. BIN  ImagesFlags/ba.png
  280. BIN  ImagesFlags/ba@2x.png
  281. BIN  ImagesFlags/bb.png
  282. BIN  ImagesFlags/bb@2x.png
  283. BIN  ImagesFlags/bd.png
  284. BIN  ImagesFlags/bd@2x.png
  285. BIN  ImagesFlags/be.png
  286. BIN  ImagesFlags/be@2x.png
  287. BIN  ImagesFlags/bf.png
  288. BIN  ImagesFlags/bf@2x.png
  289. BIN  ImagesFlags/bg.png
  290. BIN  ImagesFlags/bg@2x.png
  291. BIN  ImagesFlags/bh.png
  292. BIN  ImagesFlags/bh@2x.png
  293. BIN  ImagesFlags/bi.png
  294. BIN  ImagesFlags/bi@2x.png
  295. BIN  ImagesFlags/bj.png
  296. BIN  ImagesFlags/bj@2x.png
  297. BIN  ImagesFlags/bl.png
  298. BIN  ImagesFlags/bl@2x.png
  299. BIN  ImagesFlags/bm.png
  300. BIN  ImagesFlags/bm@2x.png
Sorry, we could not display the entire diff because too many files (746) changed.
View
1  .gitattributes
@@ -0,0 +1 @@
+*.pbxproj -crlf -diff -merge
View
12 .gitignore
@@ -0,0 +1,12 @@
+build
+*.xcodeproj/*.pbxuser
+*.xcodeproj/*.perspectivev3
+.DS_Store
+.swp
+~.nib
+.pbxuser
+.perspective
+*.perspectivev3
+*.mode1v3
+*.xcworkspacedata
+*.xcuserstate
View
3,301 AppSalesMobile.xcodeproj/project.pbxproj
3,301 additions, 0 deletions not shown
View
76 AppSalesMobile.xcodeproj/xcuserdata/ole.xcuserdatad/xcschemes/AppSalesMobile.xcscheme
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
+ BuildableName = "AppSalesMobile.app"
+ BlueprintName = "AppSalesMobile"
+ ReferencedContainer = "container:AppSalesMobile.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ buildConfiguration = "Debug">
+ <Testables>
+ </Testables>
+ </TestAction>
+ <LaunchAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
+ displayScaleIsEnabled = "NO"
+ displayScale = "1.00"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Debug">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
+ BuildableName = "AppSalesMobile.app"
+ BlueprintName = "AppSalesMobile"
+ ReferencedContainer = "container:AppSalesMobile.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ displayScaleIsEnabled = "NO"
+ displayScale = "1.00"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Release">
+ <BuildableProductRunnable>
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
+ BuildableName = "AppSalesMobile.app"
+ BlueprintName = "AppSalesMobile"
+ ReferencedContainer = "container:AppSalesMobile.xcodeproj">
+ </BuildableReference>
+ </BuildableProductRunnable>
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
View
22 AppSalesMobile.xcodeproj/xcuserdata/ole.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>SchemeUserState</key>
+ <dict>
+ <key>AppSalesMobile.xcscheme</key>
+ <dict>
+ <key>orderHint</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <key>SuppressBuildableAutocreation</key>
+ <dict>
+ <key>1D6058900D05DD3D006BFB54</key>
+ <dict>
+ <key>primary</key>
+ <true/>
+ </dict>
+ </dict>
+</dict>
+</plist>
View
8 AppSalesMobile_Prefix.pch
@@ -0,0 +1,8 @@
+//
+// Prefix header for all source files of the 'AppSalesMobile' target in the 'AppSalesMobile' project
+//
+
+#ifdef __OBJC__
+ #import <Foundation/Foundation.h>
+ #import <UIKit/UIKit.h>
+#endif
View
BIN  Artwork/AppSalesIcon.lineform/323A186F-C7FA-4A23-A55D-EC0573904EC5-9890-0000AFB9A8ACC62E.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  Artwork/AppSalesIcon.lineform/data
Binary file not shown
View
BIN  Artwork/AppSalesIcon512.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButton2Highlighted.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButton2Normal.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButton3Highlighted.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButton3Normal.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButton4Highlighted.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButton4Normal.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButtonHighlighted.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorButtonNormal.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  CalculatorClick.caf
Binary file not shown
View
26 Classes/AbstractDayOrWeekController.h
@@ -0,0 +1,26 @@
+//
+// AbstractDayOrWeekController.h
+// AppSalesMobile
+//
+// Created by Evan Schoenberg on 1/29/09.
+// Copyright 2009 Adium X / Saltatory Software. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class RootViewController;
+
+@interface AbstractDayOrWeekController : UITableViewController {
+ IBOutlet RootViewController *rootViewController;
+ NSMutableArray *daysByMonth;
+ float maxRevenue;
+ NSDateFormatter *sectionTitleFormatter;
+}
+
+@property (retain) NSMutableArray *daysByMonth;
+@property (assign) float maxRevenue;
+@property (retain) NSDateFormatter *sectionTitleFormatter;
+
+- (void)reload;
+
+@end
View
87 Classes/AbstractDayOrWeekController.m
@@ -0,0 +1,87 @@
+//
+// AbstractDayOrWeekController.m
+// AppSalesMobile
+//
+// Created by Evan Schoenberg on 1/29/09.
+// Copyright 2009 Adium X / Saltatory Software. All rights reserved.
+//
+
+#import "AbstractDayOrWeekController.h"
+#import "Day.h"
+#import "DayCell.h"
+#import "CountriesController.h"
+#import "RootViewController.h"
+#import "CurrencyManager.h"
+#import "ReportManager.h"
+
+@implementation AbstractDayOrWeekController
+
+@synthesize daysByMonth, maxRevenue, sectionTitleFormatter;
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ daysByMonth = [NSMutableArray new];
+ sectionTitleFormatter = [NSDateFormatter new];
+ [sectionTitleFormatter setDateFormat:@"MMMM yyyy"];
+ self.maxRevenue = 0;
+ }
+ return self;
+}
+
+- (CGSize)contentSizeForViewInPopover
+{
+ return CGSizeMake(320, 480);
+}
+
+- (void)viewDidLoad
+{
+ self.tableView.rowHeight = 45.0;
+}
+
+- (void)reload
+{
+ [self.tableView reloadData];
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+ if (self.daysByMonth.count == 0)
+ return @"";
+
+ NSArray *sectionArray = [daysByMonth objectAtIndex:section];
+ if (sectionArray.count == 0)
+ return @"";
+
+ Day *firstDayInSection = [sectionArray objectAtIndex:0];
+ return [self.sectionTitleFormatter stringFromDate:firstDayInSection.date];
+}
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ NSInteger count = self.daysByMonth.count;
+ return (count > 1 ? count : 1);
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ if (self.daysByMonth.count > 0) {
+ return [[self.daysByMonth objectAtIndex:section] count];
+ }
+ return 0;
+}
+
+- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ return YES;
+}
+
+- (void)dealloc
+{
+ self.sectionTitleFormatter = nil;
+ self.daysByMonth = nil;
+ [super dealloc];
+}
+
+@end
View
38 Classes/App.h
@@ -0,0 +1,38 @@
+//
+// App.h
+// AppSalesMobile
+//
+// Created by Ole Zorn on 11.09.09.
+// Copyright 2009 omz:software. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NSString* getDocPath(); // utility methods that belong in some other file
+NSString* getPrefetchedPath();
+
+@class Review;
+
+@interface App : NSObject <NSCoding> {
+ NSMutableArray *allAppNames;
+ NSString *appID;
+ NSString *appName;
+ NSMutableDictionary *reviewsByUser;
+ float averageStars;
+}
+
+@property (readonly) NSString *appID;
+@property (readonly) NSString *appName;
+@property (readonly) NSDictionary *reviewsByUser;
+@property (readonly) NSUInteger newReviewsCount;
+@property (readonly) NSArray *allAppNames;
+@property (readonly) float averageStars;
+
+- (id) initWithID:(NSString*)identifier name:(NSString*)name;
+- (void) addOrReplaceReview:(Review*)review;
+
+- (void) resetNewReviewCount;
+
+- (void) updateApplicationName:(NSString*)newAppName; // application names can change with new updates
+
+@end
View
115 Classes/App.m
@@ -0,0 +1,115 @@
+//
+// App.m
+// AppSalesMobile
+//
+// Created by Ole Zorn on 11.09.09.
+// Copyright 2009 omz:software. All rights reserved.
+//
+
+#import "App.h"
+#import "Review.h"
+
+NSString* getDocPath() {
+ static NSString *documentsDirectory = nil;
+ if (!documentsDirectory) {
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+ documentsDirectory = [[paths objectAtIndex:0] retain];
+ }
+ return documentsDirectory;
+}
+
+@implementation App
+
+@synthesize appID, appName, reviewsByUser, averageStars;
+
+- (id)initWithCoder:(NSCoder *)coder {
+ self = [super init];
+ if (self) {
+ appID = [[coder decodeObjectForKey:@"appID"] retain];
+ appName = [[coder decodeObjectForKey:@"appName"] retain];
+ reviewsByUser = [[coder decodeObjectForKey:@"reviewsByUser"] retain];
+ allAppNames = [[coder decodeObjectForKey:@"allAppNames"] retain];
+ averageStars = [coder decodeFloatForKey:@"averageStars"];
+ }
+ return self;
+}
+
+- (void) resetNewReviewCount {
+ for (Review *review in reviewsByUser.objectEnumerator) {
+ review.newOrUpdatedReview = NO;
+ }
+}
+
+- (NSArray*) allAppNames {
+ if(! allAppNames){
+ allAppNames = [[NSMutableArray alloc] initWithObjects:self.appName, nil];
+ }
+ return allAppNames;
+}
+
+- (void) updateApplicationName:(NSString*)n {
+ if(![n isEqualToString:appName]){
+ [appName release];
+ appName = [n retain];
+ }
+ for(NSString *name in self.allAppNames){
+ if([name isEqualToString:n])
+ return;
+ }
+ [allAppNames addObject:n];
+}
+
+- (id) initWithID:(NSString*)identifier name:(NSString*)name {
+ self = [super init];
+ if (self) {
+ appID = [identifier retain];
+ appName = [name retain];
+ reviewsByUser = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+ [coder encodeObject:self.appID forKey:@"appID"];
+ [coder encodeObject:self.appName forKey:@"appName"];
+ [coder encodeObject:self.reviewsByUser forKey:@"reviewsByUser"];
+ [coder encodeFloat:self.averageStars forKey:@"averageStars"];
+ [coder encodeObject:self.allAppNames forKey:@"allAppNames"];
+}
+
+- (NSString *) description {
+ return [NSString stringWithFormat:@"App %@ (%@)", self.appName, self.appID];
+}
+
+- (void) addOrReplaceReview:(Review*)review {
+ [reviewsByUser setObject:review forKey:review.user];
+
+ double sum = 0;
+ for (Review *r in reviewsByUser.allValues) {
+ sum += r.stars;
+ }
+ averageStars = sum / reviewsByUser.count;
+}
+
+- (NSUInteger) newReviewsCount {
+ NSUInteger newReviewsCount = 0;
+ for (Review *r in reviewsByUser.allValues) {
+ if (r.newOrUpdatedReview) {
+ newReviewsCount++;
+ }
+ }
+ return newReviewsCount;
+}
+
+- (void) dealloc
+{
+ [appID release];
+ [appName release];
+ [reviewsByUser release];
+ [allAppNames release];
+ [super dealloc];
+}
+
+@end
View
32 Classes/AppCell.h
@@ -0,0 +1,32 @@
+//
+// AppCell.h
+// AppSalesMobile
+//
+// Created by Ole Zorn on 12.09.09.
+// Copyright 2009 omz:software. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class App, AppCellView;
+
+@interface AppCell : UITableViewCell {
+ App *app;
+ AppCellView *cellView; // weak reference
+}
+
+@property (nonatomic, retain) App *app;
+
+@end
+
+
+
+
+
+@interface AppCellView : UIView {
+ AppCell *cell; // weak reference
+}
+
+- (id)initWithCell:(AppCell *)appCell;
+
+@end
View
111 Classes/AppCell.m
@@ -0,0 +1,111 @@
+//
+// AppCell.m
+// AppSalesMobile
+//
+// Created by Ole Zorn on 12.09.09.
+// Copyright 2009 omz:software. All rights reserved.
+//
+
+#import "AppCell.h"
+#import "App.h"
+#import "AppIconManager.h"
+
+@implementation AppCell
+
+@synthesize app;
+
+- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+ if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
+ cellView = [[[AppCellView alloc] initWithCell:self] autorelease];
+ [self.contentView addSubview:cellView];
+ }
+ return self;
+}
+
+- (void)setApp:(App *)newApp
+{
+ [newApp retain];
+ [app release];
+ app = newApp;
+ [cellView setNeedsDisplay];
+}
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated
+{
+ [super setSelected:selected animated:animated];
+}
+
+
+- (void)dealloc
+{
+ [app release];
+
+ [super dealloc];
+}
+
+
+@end
+
+@implementation AppCellView
+
+- (id)initWithCell:(AppCell *)appCell
+{
+ [super initWithFrame:appCell.bounds];
+ self.backgroundColor = [UIColor whiteColor];
+ cell = appCell;
+ return self;
+}
+
+- (void)drawRect:(CGRect)rect
+{
+ CGContextRef c = UIGraphicsGetCurrentContext();
+ App *app = cell.app;
+
+ [[UIColor colorWithWhite:0.95 alpha:1.0] set];
+ CGContextFillRect(c, CGRectMake(0,0,45,44));
+
+ UIImage *appIcon = [[AppIconManager sharedManager] iconForAppID:app.appID];
+ [appIcon drawInRect:CGRectMake(6, 7, 28, 28)];
+ [[UIImage imageNamed:@"ProductMask.png"] drawInRect:CGRectMake(4, 6, 32, 32)];
+
+ [((cell.highlighted) ? [UIColor whiteColor] : [UIColor blackColor]) set];
+ [app.appName drawInRect:CGRectMake(50, 3, 140, 30) withFont:[UIFont boldSystemFontOfSize:17.0]];
+
+ [[UIImage imageNamed:@"5stars_gray.png"] drawInRect:CGRectMake(200, 15, 90, 15)];
+ UIImage *starsImage = [UIImage imageNamed:@"5stars.png"];
+ CGSize size = CGSizeMake(90,15);
+ if (&UIGraphicsBeginImageContextWithOptions) {
+ UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
+ } else { // ipad
+ UIGraphicsBeginImageContext(size);
+ }
+ CGContextRef ctx = UIGraphicsGetCurrentContext();
+ [starsImage drawInRect:CGRectMake(0,0,90,15)];
+ float averageStars = [app averageStars];
+ float widthOfStars = 90.0 - (averageStars / 5.0) * 90.0;
+ [[UIColor clearColor] set];
+ CGContextSetBlendMode(ctx, kCGBlendModeCopy);
+ CGContextFillRect(ctx, CGRectMake(90 - widthOfStars, 0, widthOfStars, 15));
+ UIImage *averageStarsImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ [averageStarsImage drawInRect:CGRectMake(200, 15, 90, 15)];
+ //[[UIImage imageNamed:@"5stars.png"] drawInRect:CGRectMake(200, 15, 88, 15)];
+
+ if(cell.highlighted)
+ [[UIColor whiteColor] set];
+ else if(app.newReviewsCount)
+ [[UIColor redColor] set];
+ else
+ [[UIColor darkGrayColor] set];
+
+ int numberOfReviews = [app.reviewsByUser count];
+ NSString *numberOfReviewsDescription = [NSString stringWithFormat:NSLocalizedString(@"%i reviews",nil), numberOfReviews];
+ if (app.newReviewsCount) {
+ numberOfReviewsDescription = [numberOfReviewsDescription stringByAppendingFormat:NSLocalizedString(@" (%i new)",nil), app.newReviewsCount];
+ }
+ [numberOfReviewsDescription drawInRect:CGRectMake(50, 25, 140, 15) withFont:[UIFont systemFontOfSize:12.0]];
+}
+
+@end
View
21 Classes/AppIconManager.h
@@ -0,0 +1,21 @@
+//
+// AppIconManager.h
+// AppSalesMobile
+//
+// Created by Ole Zorn on 03.07.09.
+// Copyright 2009 omz:software. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+// TODO: move into the 'controller' group?
+
+@interface AppIconManager : NSObject {
+ NSMutableDictionary *iconsByAppID;
+}
+
++ (AppIconManager *)sharedManager;
+- (UIImage *)iconForAppID:(NSString *)appID;
+- (void)downloadIconForAppID:(NSString *)appID;
+
+@end
View
96 Classes/AppIconManager.m
@@ -0,0 +1,96 @@
+//
+// AppIconManager.m
+// AppSalesMobile
+//
+// Created by Ole Zorn on 03.07.09.
+// Copyright 2009 omz:software. All rights reserved.
+//
+
+#import "AppIconManager.h"
+#import "App.h" // for getDocPath()
+
+
+@implementation AppIconManager
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ iconsByAppID = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
++ (AppIconManager *)sharedManager
+{
+ static AppIconManager *sharedManager = nil;
+ if (sharedManager == nil) {
+ sharedManager = [[self alloc] init];
+ }
+ return sharedManager;
+}
+
+- (UIImage*)iconForAppNotFound
+{
+ return [UIImage imageNamed:@"Product.png"];
+}
+
+- (UIImage *)iconForAppID:(NSString *)appID
+{
+ UIImage *cachedIcon = [iconsByAppID objectForKey:appID];
+ if (cachedIcon)
+ return cachedIcon;
+ NSString *iconPath = [getDocPath() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", appID]];
+ cachedIcon = [UIImage imageWithContentsOfFile:iconPath];
+ if (! cachedIcon) {
+ cachedIcon = [self iconForAppNotFound]; // prevent subsequent lookups
+ }
+ [iconsByAppID setObject:cachedIcon forKey:appID];
+ return cachedIcon;
+}
+
+- (void)downloadIconForAppID:(NSString *)appID
+{
+ if ([iconsByAppID objectForKey:appID] != nil) {
+ return;
+ }
+ if (appID.length < 4) {
+ NSLog(@"invalid appID: %@", appID);
+ return;
+ }
+ NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://images.appshopper.com/icons/%@/%@.png",
+ [appID substringToIndex:3], [appID substringFromIndex:3]]];
+ NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5];
+
+ NSData *imageData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:nil];
+ if (!imageData) {
+ url = [NSURL URLWithString:[NSString stringWithFormat:@"http://images.appshopper.com/icons/%@/%@.jpg",
+ [appID substringToIndex:3], [appID substringFromIndex:3]]];
+ [urlRequest setURL:url];
+ imageData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:nil];
+ }
+ if (!imageData) {
+ NSLog(@"Could not get an icon for %@", appID);
+ /* Don't try to look it up again this session.
+ * Don't write it to disk, so we'll check again on a subsequent launch */
+ [iconsByAppID setObject:[self iconForAppNotFound] forKey:appID];
+ return;
+ }
+ UIImage *icon = [UIImage imageWithData:imageData];
+ if (icon) {
+ [iconsByAppID setObject:icon forKey:appID];
+ NSString *iconPath = [getDocPath() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", appID]];
+ [imageData writeToFile:iconPath atomically:YES];
+ } else {
+ NSLog(@"Could not load icon image data for %@", appID);
+ }
+}
+
+- (void)dealloc
+{
+ [iconsByAppID release];
+ [super dealloc];
+}
+
+
+@end
View
24 Classes/AppManager.h
@@ -0,0 +1,24 @@
+
+#import <Foundation/Foundation.h>
+
+@class App;
+
+@interface AppManager : NSObject {
+ NSMutableDictionary *appsByID;
+}
+
++ (AppManager*) sharedManager;
+
+@property (readonly) NSUInteger numberOfApps;
+@property (readonly) NSArray *allApps;
+@property (readonly) NSArray *allAppIDs;
+@property (readonly) NSArray *allAppsSorted;
+@property (readonly) NSArray *allAppNamesSorted;
+
+- (NSString *)appIDForAppName:(NSString *)appName;
+- (App*) appWithID:(NSString*)appID;
+- (void) addApp:(App*)app;
+- (BOOL) createOrUpdateAppIfNeededWithID:(NSString*)appID name:(NSString*)appName;
+- (void) saveToDisk;
+
+@end
View
95 Classes/AppManager.m
@@ -0,0 +1,95 @@
+
+#import "AppManager.h"
+#import "App.h"
+
+#define APP_ENCODED_FILE_NAME @"encodedApps"
+
+@implementation AppManager
+
++ (AppManager*) sharedManager {
+ static AppManager *shared = nil;
+ if (! shared) {
+ shared = [AppManager new];
+ }
+ return shared;
+}
+
+- (id) init {
+ if (self = [super init]) {
+ NSString *reviewsFile = [getDocPath() stringByAppendingPathComponent:APP_ENCODED_FILE_NAME];
+ if ([[NSFileManager defaultManager] fileExistsAtPath:reviewsFile]) {
+ appsByID = [[NSKeyedUnarchiver unarchiveObjectWithFile:reviewsFile] retain];
+ } else {
+ appsByID = [[NSMutableDictionary alloc] init];
+ }
+ }
+ return self;
+}
+
+- (NSString *)appIDForAppName:(NSString *)appName {
+ for(App *app in self.allApps){
+ for(NSString *n in app.allAppNames){
+ if([n isEqualToString:appName])
+ return app.appID;
+ }
+ }
+ return nil;
+}
+
+- (NSArray*) allApps {
+ return appsByID.allValues;
+}
+
+- (NSArray*) allAppIDs {
+ return appsByID.allKeys;
+}
+
+
+- (App*) appWithID:(NSString*)appID {
+ return [appsByID objectForKey:appID];
+}
+
+- (void) addApp:(App*)app {
+ [appsByID setObject:app forKey:app.appID];
+}
+
+- (BOOL) createOrUpdateAppIfNeededWithID:(NSString*)appID name:(NSString*)appName {
+ App *app = [self appWithID:appID];
+ if (app == nil) {
+ app = [[App alloc] initWithID:appID name:appName];
+ [self addApp:app];
+ [app release];
+ return YES;
+ }
+ if (! [app.appName isEqualToString:appName]) {
+ [app updateApplicationName:appName]; // name of app has changed
+ }
+ return NO; // was already present
+}
+
+- (NSUInteger) numberOfApps {
+ return appsByID.count;
+}
+
+- (NSArray*) allAppsSorted {
+ NSArray *allApps = appsByID.allValues;
+ NSSortDescriptor *appSorter = [[[NSSortDescriptor alloc] initWithKey:@"appName" ascending:YES] autorelease];
+ return [allApps sortedArrayUsingDescriptors:[NSArray arrayWithObject:appSorter]];
+}
+
+- (NSArray*) allAppNamesSorted {
+ NSArray *sortedApps = self.allAppsSorted;
+ NSMutableArray *sortedNames = [NSMutableArray arrayWithCapacity:sortedApps.count];
+ for (App *app in sortedApps) {
+ [sortedNames addObject:app.appName];
+ }
+ return sortedNames;
+}
+
+- (void) saveToDisk {
+ NSString *fullPath = [getDocPath() stringByAppendingPathComponent:APP_ENCODED_FILE_NAME];
+ [NSKeyedArchiver archiveRootObject:appsByID toFile:fullPath];
+}
+
+
+@end
View
43 Classes/AppSalesMobileAppDelegate.h
@@ -0,0 +1,43 @@
+/*
+ AppSalesMobileAppDelegate.h
+ AppSalesMobile
+
+ * Copyright (c) 2008, omz:software
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY omz:software ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface AppSalesMobileAppDelegate : NSObject <UIApplicationDelegate> {
+
+ UIWindow *window;
+ UIViewController *rootViewController;
+}
+
+@property (nonatomic, retain) UIWindow *window;
+@property (nonatomic, retain) UIViewController *rootViewController;
+
+@end
+
View
64 Classes/AppSalesMobileAppDelegate.m
@@ -0,0 +1,64 @@
+/*
+ AppSalesMobileAppDelegate.m
+ AppSalesMobile
+
+ * Copyright (c) 2008, omz:software
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <organization> nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY omz:software ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "AppSalesMobileAppDelegate.h"
+#import "RootViewController.h"
+#import "PadRootViewController.h"
+#import "UIDevice+iPad.h"
+
+@implementation AppSalesMobileAppDelegate
+
+@synthesize window;
+@synthesize rootViewController;
+
+
+- (void)applicationDidFinishLaunching:(UIApplication *)application
+{
+ self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
+
+ if ([[UIDevice currentDevice] isPad]) {
+ self.rootViewController = [[[PadRootViewController alloc] initWithNibName:nil bundle:nil] autorelease];
+ } else {
+ self.rootViewController = [[[UINavigationController alloc] initWithRootViewController:[[[RootViewController alloc] initWithStyle:UITableViewStyleGrouped] autorelease]] autorelease];
+ [(UINavigationController *)rootViewController setToolbarHidden:NO];
+ }
+
+ [window addSubview:rootViewController.view];
+ [window makeKeyAndVisible];
+}
+
+- (void)dealloc
+{
+ [rootViewController release];
+ [window release];
+ [super dealloc];
+}
+
+@end
View
409 Classes/AsyncSocket.h
@@ -0,0 +1,409 @@
+//
+// AsyncSocket.h
+//
+// This class is in the public domain.
+// Originally created by Dustin Voss on Wed Jan 29 2003.
+// Updated and maintained by Deusty Designs and the Mac development community.
+//
+// http://code.google.com/p/cocoaasyncsocket/
+//
+
+#import <Foundation/Foundation.h>
+
+@class AsyncSocket;
+@class AsyncReadPacket;
+@class AsyncWritePacket;
+
+extern NSString *const AsyncSocketException;
+extern NSString *const AsyncSocketErrorDomain;
+
+enum AsyncSocketError
+{
+ AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum.
+ AsyncSocketNoError = 0, // Never used.
+ AsyncSocketCanceledError, // onSocketWillConnect: returned NO.
+ AsyncSocketConnectTimeoutError,
+ AsyncSocketReadMaxedOutError, // Reached set maxLength without completing
+ AsyncSocketReadTimeoutError,
+ AsyncSocketWriteTimeoutError
+};
+typedef enum AsyncSocketError AsyncSocketError;
+
+@interface NSObject (AsyncSocketDelegate)
+
+/**
+ * In the event of an error, the socket is closed.
+ * You may call "unreadData" during this call-back to get the last bit of data off the socket.
+ * When connecting, this delegate method may be called
+ * before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:".
+**/
+- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err;
+
+/**
+ * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects,
+ * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:".
+**/
+- (void)onSocketDidDisconnect:(AsyncSocket *)sock;
+
+/**
+ * Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have
+ * the same delegate and will call "onSocket:didConnectToHost:port:".
+**/
+- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket;
+
+/**
+ * Called when a new socket is spawned to handle a connection. This method should return the run-loop of the
+ * thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used.
+**/
+- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket;
+
+/**
+ * Called when a socket is about to connect. This method should return YES to continue, or NO to abort.
+ * If aborted, will result in AsyncSocketCanceledError.
+ *
+ * If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the
+ * CFReadStream and CFWriteStream as desired prior to connection.
+ *
+ * If the connectToAddress:error: method was called, the delegate will be able to access and configure the
+ * CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and
+ * configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method.
+**/
+- (BOOL)onSocketWillConnect:(AsyncSocket *)sock;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+**/
+- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port;
+
+/**
+ * Called when a socket has completed reading the requested data into memory.
+ * Not called if there is an error.
+**/
+- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
+
+/**
+ * Called when a socket has read in data, but has not yet completed the read.
+ * This would occur if using readToData: or readToLength: methods.
+ * It may be used to for things such as updating progress bars.
+**/
+- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(CFIndex)partialLength tag:(long)tag;
+
+/**
+ * Called when a socket has completed writing the requested data. Not called if there is an error.
+**/
+- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag;
+
+/**
+ * Called after the socket has completed SSL/TLS negotiation.
+ * This method is not called unless you use the provided startTLS method.
+**/
+- (void)onSocket:(AsyncSocket *)sock didSecure:(BOOL)flag;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface AsyncSocket : NSObject
+{
+ CFSocketRef theSocket; // IPv4 accept or connect socket
+ CFSocketRef theSocket6; // IPv6 accept or connect socket
+ CFReadStreamRef theReadStream;
+ CFWriteStreamRef theWriteStream;
+
+ CFRunLoopSourceRef theSource; // For theSocket
+ CFRunLoopSourceRef theSource6; // For theSocket6
+ CFRunLoopRef theRunLoop;
+ CFSocketContext theContext;
+ NSArray *theRunLoopModes;
+
+ NSTimer *theConnectTimer;
+
+ NSMutableArray *theReadQueue;
+ AsyncReadPacket *theCurrentRead;
+ NSTimer *theReadTimer;
+ NSMutableData *partialReadBuffer;
+
+ NSMutableArray *theWriteQueue;
+ AsyncWritePacket *theCurrentWrite;
+ NSTimer *theWriteTimer;
+
+ id theDelegate;
+ UInt16 theFlags;
+
+ long theUserData;
+}
+
+- (id)init;
+- (id)initWithDelegate:(id)delegate;
+- (id)initWithDelegate:(id)delegate userData:(long)userData;
+
+/* String representation is long but has no "\n". */
+- (NSString *)description;
+
+/**
+ * Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate
+ * before changing it. It is, of course, safe to change the delegate before connecting or accepting connections.
+**/
+- (id)delegate;
+- (BOOL)canSafelySetDelegate;
+- (void)setDelegate:(id)delegate;
+
+/* User data can be a long, or an id or void * cast to a long. */
+- (long)userData;
+- (void)setUserData:(long)userData;
+
+/* Don't use these to read or write. And don't close them, either! */
+- (CFSocketRef)getCFSocket;
+- (CFReadStreamRef)getCFReadStream;
+- (CFWriteStreamRef)getCFWriteStream;
+
+// Once one of the accept or connect methods are called, the AsyncSocket instance is locked in
+// and the other accept/connect methods can't be called without disconnecting the socket first.
+// If the attempt fails or times out, these methods either return NO or
+// call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:".
+
+// When an incoming connection is accepted, AsyncSocket invokes several delegate methods.
+// These methods are (in chronological order):
+// 1. onSocket:didAcceptNewSocket:
+// 2. onSocket:wantsRunLoopForNewSocket:
+// 3. onSocketWillConnect:
+//
+// Your server code will need to retain the accepted socket (if you want to accept it).
+// The best place to do this is probably in the onSocket:didAcceptNewSocket: method.
+//
+// After the read and write streams have been setup for the newly accepted socket,
+// the onSocket:didConnectToHost:port: method will be called on the proper run loop.
+
+/**
+ * Tells the socket to begin listening and accepting connections on the given port.
+ * When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above).
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+**/
+- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr;
+
+/**
+ * This method is the same as acceptOnPort:error: with the additional option
+ * of specifying which interface to listen on. So, for example, if you were writing code for a server that
+ * has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it
+ * to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi.
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept connections from the local machine.
+ *
+ * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method.
+**/
+- (BOOL)acceptOnAddress:(NSString *)hostaddr port:(UInt16)port error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host and port.
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2")
+**/
+- (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr;
+
+/**
+ * This method is the same as connectToHost:onPort:error: with an additional timeout option.
+ * To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method.
+**/
+- (BOOL)connectToHost:(NSString *)hostname
+ onPort:(UInt16)port
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetservice's addresses method.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+/**
+ * This method is the same as connectToAddress:error: with an additional timeout option.
+ * To not time out use a negative time interval, or simply use the connectToAddress:error: method.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+/**
+ * Disconnects immediately. Any pending reads or writes are dropped.
+**/
+- (void)disconnect;
+
+/**
+ * Disconnects after all pending reads have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending writes.
+**/
+- (void)disconnectAfterReading;
+
+/**
+ * Disconnects after all pending writes have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending reads.
+**/
+- (void)disconnectAfterWriting;
+
+/**
+ * Disconnects after all pending reads and writes have completed.
+ * After calling this, the read and write methods will do nothing.
+**/
+- (void)disconnectAfterReadingAndWriting;
+
+/* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */
+- (BOOL)isConnected;
+
+/**
+ * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected.
+ * The host will be an IP address.
+**/
+- (NSString *)connectedHost;
+- (UInt16)connectedPort;
+
+- (NSString *)localHost;
+- (UInt16)localPort;
+
+- (BOOL)isIPv4;
+- (BOOL)isIPv6;
+
+// The readData and writeData methods won't block. To not time out, use a negative time interval.
+// If they time out, "onSocket:disconnectWithError:" is called. The tag is for your convenience.
+// You can use it as an array index, step number, state id, pointer, etc., just like the socket's user data.
+
+/**
+ * This will read a certain number of bytes into memory, and call the delegate method when those bytes have been read.
+ * If there is an error, partially read data is lost.
+ * If the length is 0, this method does nothing and the delegate is not called.
+**/
+- (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * This reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes and the separator are returned by the delegate method.
+ *
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
+ * a character, the read will prematurely end.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Same as readDataToData:withTimeout:tag, with the additional restriction that the amount of data read
+ * may not surpass the given maxLength (specified in bytes).
+ *
+ * If you pass a maxLength parameter that is less than the length of the data parameter,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * If the max length is surpassed, it is treated the same as a timeout - the socket is closed.
+ *
+ * Pass -1 as maxLength if no length restriction is desired, or simply use the readDataToData:withTimeout:tag method.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(CFIndex)length tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Writes data to the socket, and calls the delegate when finished.
+ *
+ * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called.
+**/
+- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check).
+ * "tag", "done" and "total" will be filled in if they aren't NULL.
+**/
+- (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total;
+- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total;
+
+/**
+ * Secures the connection using SSL/TLS.
+ *
+ * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes
+ * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing
+ * the upgrade to TLS at the same time, without having to wait for the write to finish.
+ * Any reads or writes scheduled after this method is called will occur over the secured connection.
+ *
+ * The possible keys and values for the TLS settings are well documented.
+ * Some possible keys are:
+ * - kCFStreamSSLLevel
+ * - kCFStreamSSLAllowsExpiredCertificates
+ * - kCFStreamSSLAllowsExpiredRoots
+ * - kCFStreamSSLAllowsAnyRoot
+ * - kCFStreamSSLValidatesCertificateChain
+ * - kCFStreamSSLPeerName
+ * - kCFStreamSSLCertificates
+ * - kCFStreamSSLIsServer
+ *
+ * Please refer to Apple's documentation for associated values, as well as other possible keys.
+ *
+ * If you pass in nil or an empty dictionary, this method does nothing and the delegate will not be called.
+**/
+- (void)startTLS:(NSDictionary *)tlsSettings;
+
+/**
+ * For handling readDataToData requests, data is necessarily read from the socket in small increments.
+ * The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and
+ * store any overflow in a small internal buffer.
+ * This is termed pre-buffering, as some data may be read for you before you ask for it.
+ * If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone.
+ *
+ * The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition.
+ * It is highly recommended one leave this set to YES.
+ *
+ * This method exists in case pre-buffering needs to be disabled by default for some reason.
+ * In that case, this method exists to allow one to easily enable pre-buffering when ready.
+**/
+- (void)enablePreBuffering;
+
+/**
+ * When you create an AsyncSocket, it is added to the runloop of the current thread.
+ * So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it.
+ *
+ * If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to
+ * allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design.
+ *
+ * If, however, you need to move the socket to a separate thread at a later time, this
+ * method may be used to accomplish the task.
+ *
+ * This method must be called from the thread/runloop the socket is currently running on.
+ *
+ * Note: After calling this method, all further method calls to this object should be done from the given runloop.
+ * Also, all delegate calls will be sent on the given runloop.
+**/
+- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop;
+
+/**
+ * Allows you to configure which run loop modes the socket uses.
+ * The default set of run loop modes is NSDefaultRunLoopMode.
+ *
+ * If you'd like your socket to continue operation during other modes, you may want to add modes such as
+ * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes.
+ *
+ * Accepted sockets will automatically inherit the same run loop modes as the listening socket.
+ *
+ * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes.
+**/
+- (BOOL)setRunLoopModes:(NSArray *)runLoopModes;
+
+/**
+ * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read
+ * any data that's left on the socket.
+**/
+- (NSData *)unreadData;
+
+/* A few common line separators, for use with the readDataToData:... methods. */
++ (NSData *)CRLFData; // 0x0D0A
++ (NSData *)CRData; // 0x0D
++ (NSData *)LFData; // 0x0A
++ (NSData *)ZeroData; // 0x00
+
+@end
View
2,935 Classes/AsyncSocket.m
<
@@ -0,0 +1,2935 @@
+//
+// AsyncSocket.m
+//
+// This class is in the public domain.
+// Originally created by Dustin Voss on Wed Jan 29 2003.
+// Updated and maintained by Deusty Designs and the Mac development community.
+//
+// http://code.google.com/p/cocoaasyncsocket/
+//
+
+#import "AsyncSocket.h"
+#import <sys/socket.h>
+#import <netinet/in.h>
+#import <arpa/inet.h>
+#import <netdb.h>
+
+#if TARGET_OS_IPHONE
+// Note: You may need to add the CFNetwork Framework to your project
+#import <CFNetwork/CFNetwork.h>
+#endif
+
+#pragma mark Declarations
+
+#define DEFAULT_PREBUFFERING YES // Whether pre-buffering is enabled by default
+
+#define READQUEUE_CAPACITY 5 // Initial capacity
+#define WRITEQUEUE_CAPACITY 5 // Initial capacity
+#define READALL_CHUNKSIZE 256 // Incremental increase in buffer size
+#define WRITE_CHUNKSIZE (1024 * 4) // Limit on size of each write pass
+
+NSString *const AsyncSocketException = @"AsyncSocketException";
+NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain";
+
+// Mutex lock used by all instances of AsyncSocket, to protect getaddrinfo.
+// The man page says it is not thread-safe. (As of Mac OS X 10.4.7, and possibly earlier)
+static NSString *getaddrinfoLock = @"lock";
+
+enum AsyncSocketFlags
+{
+ kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled
+ kDidPassConnectMethod = 1 << 1, // If set, disconnection results in delegate call
+ kDidCompleteOpenForRead = 1 << 2, // If set, open callback has been called for read stream
+ kDidCompleteOpenForWrite = 1 << 3, // If set, open callback has been called for write stream
+ kStartingTLS = 1 << 4, // If set, we're waiting for TLS negotiation to complete
+ kForbidReadsWrites = 1 << 5, // If set, no new reads or writes are allowed
+ kDisconnectAfterReads = 1 << 6, // If set, disconnect after no more reads are queued
+ kDisconnectAfterWrites = 1 << 7, // If set, disconnect after no more writes are queued
+ kClosingWithError = 1 << 8, // If set, the socket is being closed due to an error
+};
+
+@interface AsyncSocket (Private)
+
+// Connecting
+- (void)startConnectTimeout:(NSTimeInterval)timeout;
+- (void)endConnectTimeout;
+
+// Socket Implementation
+- (CFSocketRef) createAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr;
+- (BOOL) createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+- (BOOL) attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr;
+- (BOOL) configureSocketAndReturnError:(NSError **)errPtr;
+- (BOOL) connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+- (void) doAcceptWithSocket:(CFSocketNativeHandle)newSocket;
+- (void) doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)err;
+
+// Stream Implementation
+- (BOOL) createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr;
+- (BOOL) createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr;
+- (BOOL) attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr;
+- (BOOL) configureStreamsAndReturnError:(NSError **)errPtr;
+- (BOOL) openStreamsAndReturnError:(NSError **)errPtr;
+- (void) doStreamOpen;
+- (BOOL) setSocketFromStreamsAndReturnError:(NSError **)errPtr;
+
+// Disconnect Implementation
+- (void) closeWithError:(NSError *)err;
+- (void) recoverUnreadData;
+- (void) emptyQueues;
+- (void) close;
+
+// Errors
+- (NSError *) getErrnoError;
+- (NSError *) getAbortError;
+- (NSError *) getStreamError;
+- (NSError *) getSocketError;
+- (NSError *) getConnectTimeoutError;
+- (NSError *) getReadMaxedOutError;
+- (NSError *) getReadTimeoutError;
+- (NSError *) getWriteTimeoutError;
+- (NSError *) errorFromCFStreamError:(CFStreamError)err;
+
+// Diagnostics
+- (BOOL) isSocketConnected;
+- (BOOL) areStreamsConnected;
+- (NSString *) connectedHost: (CFSocketRef)socket;
+- (UInt16) connectedPort: (CFSocketRef)socket;
+- (NSString *) localHost: (CFSocketRef)socket;
+- (UInt16) localPort: (CFSocketRef)socket;
+- (NSString *) addressHost: (CFDataRef)cfaddr;
+- (UInt16) addressPort: (CFDataRef)cfaddr;
+
+// Reading
+- (void) doBytesAvailable;
+- (void) completeCurrentRead;
+- (void) endCurrentRead;
+- (void) scheduleDequeueRead;
+- (void) maybeDequeueRead;
+- (void) doReadTimeout:(NSTimer *)timer;
+
+// Writing
+- (void) doSendBytes;
+- (void) completeCurrentWrite;
+- (void) endCurrentWrite;
+- (void) scheduleDequeueWrite;
+- (void) maybeDequeueWrite;
+- (void) maybeScheduleDisconnect;
+- (void) doWriteTimeout:(NSTimer *)timer;
+
+// Run Loop
+- (void) runLoopAddSource:(CFRunLoopSourceRef)source;
+- (void) runLoopRemoveSource:(CFRunLoopSourceRef)source;
+- (void) runLoopAddTimer:(NSTimer *)timer;
+- (void) runLoopRemoveTimer:(NSTimer *)timer;
+- (void) runLoopUnscheduleReadStream;
+- (void) runLoopUnscheduleWriteStream;
+
+// Security
+- (void)maybeStartTLS;
+- (void)onTLSStarted:(BOOL)flag;
+
+// Callbacks
+- (void) doCFCallback:(CFSocketCallBackType)type forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData;
+- (void) doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream;
+- (void) doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream;
+
+@end
+
+static void MyCFSocketCallback (CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *);
+static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo);
+static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo);
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The AsyncReadPacket encompasses the instructions for any given read.
+ * The content of a read packet allows the code to determine if we're:
+ * - reading to a certain length
+ * - reading to a certain separator
+ * - or simply reading the first chunk of available data
+**/
+@interface AsyncReadPacket : NSObject
+{
+ @public
+ NSMutableData *buffer;
+ CFIndex bytesDone;
+ NSTimeInterval timeout;
+ CFIndex maxLength;
+ long tag;
+ NSData *term;
+ BOOL readAllAvailableData;
+}
+- (id)initWithData:(NSMutableData *)d
+ timeout:(NSTimeInterval)t
+ tag:(long)i
+ readAllAvailable:(BOOL)a
+ terminator:(NSData *)e
+ maxLength:(CFIndex)m;
+
+- (unsigned)readLengthForTerm;
+
+- (unsigned)prebufferReadLengthForTerm;
+- (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes;
+@end
+
+@implementation AsyncReadPacket
+
+- (id)initWithData:(NSMutableData *)d
+ timeout:(NSTimeInterval)t
+ tag:(long)i
+ readAllAvailable:(BOOL)a
+ terminator:(NSData *)e
+ maxLength:(CFIndex)m
+{
+ if(self = [super init])
+ {
+ buffer = [d retain];
+ timeout = t;
+ tag = i;
+ readAllAvailableData = a;
+ term = [e copy];
+ bytesDone = 0;
+ maxLength = m;
+ }
+ return self;
+}
+
+/**
+ * For read packets with a set terminator, returns the safe length of data that can be read
+ * without going over a terminator, or the maxLength.
+ *
+ * It is assumed the terminator has not already been read.
+**/
+- (unsigned)readLengthForTerm
+{
+ NSAssert(term != nil, @"Searching for term in data when there is no term.");
+
+ // What we're going to do is look for a partial sequence of the terminator at the end of the buffer.
+ // If a partial sequence occurs, then we must assume the next bytes to arrive will be the rest of the term,
+ // and we can only read that amount.
+ // Otherwise, we're safe to read the entire length of the term.
+
+ unsigned result = [term length];
+
+ // Shortcut when term is a single byte
+ if(result == 1) return result;
+
+ // i = index within buffer at which to check data
+ // j = length of term to check against
+
+ // Note: Beware of implicit casting rules
+ // This could give you -1: MAX(0, (0 - [term length] + 1));
+
+ CFIndex i = MAX(0, (CFIndex)(bytesDone - [term length] + 1));
+ CFIndex j = MIN([term length] - 1, bytesDone);
+
+ while(i < bytesDone)
+ {
+ const void *subBuffer = [buffer bytes] + i;
+
+ if(memcmp(subBuffer, [term bytes], j) == 0)
+ {
+ result = [term length] - j;
+ break;
+ }
+
+ i++;
+ j--;
+ }
+
+ if(maxLength > 0)
+ return MIN(result, (maxLength - bytesDone));
+ else
+ return result;
+}
+
+/**
+ * Assuming pre-buffering is enabled, returns the amount of data that can be read
+ * without going over the maxLength.
+**/
+- (unsigned)prebufferReadLengthForTerm
+{
+ if(maxLength > 0)
+ return MIN(READALL_CHUNKSIZE, (maxLength - bytesDone));
+ else
+ return READALL_CHUNKSIZE;
+}
+
+/**
+ * For read packets with a set terminator, scans the packet buffer for the term.
+ * It is assumed the terminator had not been fully read prior to the new bytes.
+ *
+ * If the term is found, the number of excess bytes after the term are returned.
+ * If the term is not found, this method will return -1.
+ *
+ * Note: A return value of zero means the term was found at the very end.
+**/
+- (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes
+{
+ NSAssert(term != nil, @"Searching for term in data when there is no term.");
+
+ // We try to start the search such that the first new byte read matches up with the last byte of the term.
+ // We continue searching forward after this until the term no longer fits into the buffer.
+
+ // Note: Beware of implicit casting rules
+ // This could give you -1: MAX(0, 1 - 1 - [term length] + 1);
+
+ CFIndex i = MAX(0, (CFIndex)(bytesDone - numBytes - [term length] + 1));
+
+ while(i + [term length] <= bytesDone)
+ {
+ const void *subBuffer = [buffer bytes] + i;
+
+ if(memcmp(subBuffer, [term bytes], [term length]) == 0)
+ {
+ return bytesDone - (i + [term length]);
+ }
+
+ i++;
+ }
+
+ return -1;
+}
+
+- (void)dealloc
+{
+ [buffer release];
+ [term release];
+ [super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The AsyncWritePacket encompasses the instructions for any given write.
+**/
+@interface AsyncWritePacket : NSObject
+{
+ @public
+ NSData *buffer;
+ CFIndex bytesDone;
+ long tag;
+ NSTimeInterval timeout;
+}
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
+@end
+
+@implementation AsyncWritePacket
+
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
+{
+ if(self = [super init])
+ {
+ buffer = [d retain];
+ timeout = t;
+ tag = i;
+ bytesDone = 0;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [buffer release];
+ [super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The AsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
+ * This class my be altered to support more than just TLS in the future.
+**/
+@interface AsyncSpecialPacket : NSObject
+{
+ @public
+ NSDictionary *tlsSettings;
+}
+- (id)initWithTLSSettings:(NSDictionary *)settings;
+@end
+
+@implementation AsyncSpecialPacket
+
+- (id)initWithTLSSettings:(NSDictionary *)settings
+{
+ if(self = [super init])
+ {
+ tlsSettings = [settings copy];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [tlsSettings release];
+ [super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation AsyncSocket
+
+- (id)init
+{
+ return [self initWithDelegate:nil userData:0];
+}
+
+- (id)initWithDelegate:(id)delegate
+{
+ return [self initWithDelegate:delegate userData:0];
+}
+
+// Designated initializer.
+- (id)initWithDelegate:(id)delegate userData:(long)userData
+{
+ if(self = [super init])
+ {
+ theFlags = DEFAULT_PREBUFFERING ? kEnablePreBuffering : 0x00;
+ theDelegate = delegate;
+ theUserData = userData;
+
+ theSocket = NULL;
+ theSource = NULL;
+ theSocket6 = NULL;
+ theSource6 = NULL;
+ theRunLoop = NULL;
+ theReadStream = NULL;
+ theWriteStream = NULL;
+
+ theConnectTimer = nil;
+
+ theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY];
+ theCurrentRead = nil;
+ theReadTimer = nil;
+
+ partialReadBuffer = [[NSMutableData alloc] initWithCapacity:READALL_CHUNKSIZE];
+
+ theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY];
+ theCurrentWrite = nil;
+ theWriteTimer = nil;
+
+ // Socket context
+ NSAssert(sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext != CFStreamClientContext");
+ theContext.version = 0;
+ theContext.info = self;
+ theContext.retain = nil;
+ theContext.release = nil;
+ theContext.copyDescription = nil;
+
+ // Default run loop modes
+ theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain];
+ }
+ return self;
+}
+
+// The socket may been initialized in a connected state and auto-released, so this should close it down cleanly.
+- (void)dealloc
+{
+ [self close];
+ [theReadQueue release];
+ [theWriteQueue release];
+ [theRunLoopModes release];
+ [partialReadBuffer release];
+ [NSObject cancelPreviousPerformRequestsWithTarget:theDelegate selector:@selector(onSocketDidDisconnect:) object:self];
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ [super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Accessors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (long)userData
+{
+ return theUserData;
+}
+
+- (void)setUserData:(long)userData
+{
+ theUserData = userData;
+}
+
+- (id)delegate
+{
+ return theDelegate;
+}
+
+- (void)setDelegate:(id)delegate
+{
+ theDelegate = delegate;
+}
+
+- (BOOL)canSafelySetDelegate
+{
+ return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil);
+}
+
+- (CFSocketRef)getCFSocket
+{
+ if(theSocket)
+ return theSocket;
+ else
+ return theSocket6;
+}
+
+- (CFReadStreamRef)getCFReadStream
+{
+ return theReadStream;
+}
+
+- (CFWriteStreamRef)getCFWriteStream
+{
+ return theWriteStream;
+}
+
+- (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total
+{
+ // Check to make sure we're actually reading something right now
+ if (!theCurrentRead) return NAN;
+
+ // It's only possible to know the progress of our read if we're reading to a certain length
+ // If we're reading to data, we of course have no idea when the data will arrive
+ // If we're reading to timeout, then we have no idea when the next chunk of data will arrive.
+ BOOL hasTotal = (theCurrentRead->readAllAvailableData == NO && theCurrentRead->term == nil);
+
+ CFIndex d = theCurrentRead->bytesDone;
+ CFIndex t = hasTotal ? [theCurrentRead->buffer length] : 0;
+ if (tag != NULL) *tag = theCurrentRead->tag;
+ if (done != NULL) *done = d;
+ if (total != NULL) *total = t;
+ float ratio = (float)d/(float)t;
+ return isnan(ratio) ? 1.0 : ratio; // 0 of 0 bytes is 100% done.
+}
+
+- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total
+{
+ if (!theCurrentWrite) return NAN;
+ CFIndex d = theCurrentWrite->bytesDone;
+ CFIndex t = [theCurrentWrite->buffer length];
+ if (tag != NULL) *tag = theCurrentWrite->tag;
+ if (done != NULL) *done = d;
+ if (total != NULL) *total = t;
+ return (float)d/(float)t;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Run Loop
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)runLoopAddSource:(CFRunLoopSourceRef)source
+{
+ unsigned i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopAddSource(theRunLoop, source, runLoopMode);
+ }
+}
+
+- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source
+{
+ unsigned i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopRemoveSource(theRunLoop, source, runLoopMode);
+ }
+}
+
+- (void)runLoopAddTimer:(NSTimer *)timer
+{
+ unsigned i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
+ }
+}
+
+- (void)runLoopRemoveTimer:(NSTimer *)timer
+{
+ unsigned i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode);
+ }
+}
+
+- (void)runLoopUnscheduleReadStream
+{
+ unsigned i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, runLoopMode);
+ }
+ CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL);
+}
+
+- (void)runLoopUnscheduleWriteStream
+{
+ unsigned i, count = [theRunLoopModes count];
+ for(i = 0; i < count; i++)
+ {
+ CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i];
+ CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, runLoopMode);
+ }
+ CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * See the header file for a full explanation of pre-buffering.
+**/
+- (void)enablePreBuffering
+{
+ theFlags |= kEnablePreBuffering;
+}
+
+/**
+ * See the header file for a full explanation of this method.
+**/
+- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop
+{
+ NSAssert((theRunLoop == CFRunLoopGetCurrent()), @"moveToRunLoop must be called from within the current RunLoop!");
+
+ if(runLoop == nil)
+ {
+ return NO;
+ }
+ if(theRunLoop == [runLoop getCFRunLoop])
+ {