/core/externals/update-engine/Core/KSUpdateEngine.m

http://macfuse.googlecode.com/ · Objective C · 397 lines · 289 code · 61 blank · 47 comment · 22 complexity · 9431f4fafb05626d2fc840d980dbcd05 MD5 · raw file

  1. // Copyright 2008 Google Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "KSUpdateEngine.h"
  15. #import <unistd.h>
  16. #import "KSActionPipe.h"
  17. #import "KSActionProcessor.h"
  18. #import "KSCheckAction.h"
  19. #import "KSCommandRunner.h"
  20. #import "KSFrameworkStats.h"
  21. #import "KSOutOfBandDataAction.h"
  22. #import "KSPrefetchAction.h"
  23. #import "KSPromptAction.h"
  24. #import "KSSilentUpdateAction.h"
  25. #import "KSTicketStore.h"
  26. #import "KSUpdateEngineParameters.h"
  27. #import "GTMLogger.h"
  28. #import "GTMNSString+FindFolder.h"
  29. #import "GTMPath.h"
  30. @interface KSUpdateEngine (PrivateMethods)
  31. // Tiggers an update check for all of the tickets in the specified array. This
  32. // method is called by -updateAllProducts and -updateProductWithProductID: to
  33. // do the real work.
  34. - (void)triggerUpdateForTickets:(NSArray *)tickets;
  35. // Builds a new |stats_| dictionary, which has a mapping between a productID
  36. // and the dictionary of stats provided by the delegate (assuming the delegate
  37. // has implemented -engine:statsForProductID:).
  38. - (void)updateStatsForTickets:(NSArray *)tickets;
  39. @end
  40. // The user-defined default ticket store path. If this value is nil, then the
  41. // +defaultTicketStorePath method will generate a nice default value. This
  42. // variable is typically only used in testing situations.
  43. static NSString *gDefaultTicketStorePath = nil;
  44. @implementation KSUpdateEngine
  45. + (NSString *)defaultTicketStorePath {
  46. return gDefaultTicketStorePath;
  47. }
  48. + (void)setDefaultTicketStorePath:(NSString *)path {
  49. [gDefaultTicketStorePath autorelease];
  50. gDefaultTicketStorePath = [path copy];
  51. }
  52. + (id)engineWithDelegate:(id)delegate {
  53. NSString *storePath = [self defaultTicketStorePath];
  54. KSTicketStore *store = [KSTicketStore ticketStoreWithPath:storePath];
  55. return [self engineWithTicketStore:store delegate:delegate];
  56. }
  57. + (id)engineWithTicketStore:(KSTicketStore *)store
  58. delegate:(id)delegate {
  59. return [[[self alloc] initWithTicketStore:store
  60. delegate:delegate] autorelease];
  61. }
  62. - (id)init {
  63. return [self initWithTicketStore:nil delegate:nil];
  64. }
  65. - (id)initWithTicketStore:(KSTicketStore *)store
  66. delegate:(id)delegate {
  67. if ((self = [super init])) {
  68. store_ = [store retain];
  69. [self setDelegate:delegate];
  70. [self stopAndReset];
  71. if (store_ == nil) {
  72. GTMLoggerDebug(@"error: created with nil ticket store");
  73. [self release];
  74. return nil;
  75. }
  76. params_ = [[NSDictionary alloc] init];
  77. }
  78. return self;
  79. }
  80. - (void)dealloc {
  81. [params_ release];
  82. [store_ release];
  83. [processor_ setDelegate:nil];
  84. [processor_ release];
  85. [stats_ release];
  86. [super dealloc];
  87. }
  88. - (NSString *)description {
  89. return [NSString stringWithFormat:@"<%@:%p store=%@ delegate=%@>",
  90. [self class], self, store_, delegate_];
  91. }
  92. - (KSTicketStore *)ticketStore {
  93. return store_;
  94. }
  95. - (id)delegate {
  96. return delegate_;
  97. }
  98. - (void)setDelegate:(id)delegate {
  99. // We must retain/release our delegate because the delegate_ may be an NSProxy
  100. // which may not exist for the life of this KSUpdateEngine. In reality, this
  101. // only appears to be a problem on Tiger, but, we have to work on Tiger, too.
  102. @try {
  103. [delegate_ autorelease];
  104. delegate_ = [delegate retain];
  105. // COV_NF_START
  106. }
  107. @catch (id ex) {
  108. GTMLoggerError(@"Caught exception setting delegate: %@", ex);
  109. }
  110. // COV_NF_END
  111. }
  112. // Triggers an update for all products in the main ticket store |store_|
  113. - (void)updateAllProducts {
  114. _GTMDevAssert(store_ != nil, @"store_ must not be nil");
  115. [self triggerUpdateForTickets:[store_ tickets]];
  116. }
  117. - (void)updateProductWithProductID:(NSString *)productID {
  118. _GTMDevAssert(store_ != nil, @"store_ must not be nil");
  119. KSTicket *ticket = [store_ ticketForProductID:productID];
  120. if (ticket == nil) {
  121. GTMLoggerInfo(@"No ticket for product with Product ID %@", productID);
  122. return;
  123. }
  124. NSArray *oneTicket = [NSArray arrayWithObject:ticket];
  125. [self triggerUpdateForTickets:oneTicket];
  126. }
  127. - (BOOL)isUpdating {
  128. return [processor_ isProcessing];
  129. }
  130. - (void)stopAndReset {
  131. [processor_ stopProcessing];
  132. [processor_ autorelease];
  133. processor_ = [[KSActionProcessor alloc] initWithDelegate:self];
  134. }
  135. - (void)setParams:(NSDictionary *)params {
  136. [params_ autorelease];
  137. params_ = [params retain];
  138. }
  139. - (NSDictionary *)params {
  140. return params_;
  141. }
  142. - (KSStatsCollection *)statsCollection {
  143. return [KSFrameworkStats sharedStats];
  144. }
  145. - (void)setStatsCollection:(KSStatsCollection *)statsCollection {
  146. [KSFrameworkStats setSharedStats:statsCollection];
  147. }
  148. //
  149. // KSActionProcessor delegate callbacks
  150. //
  151. - (void)processingStarted:(KSActionProcessor *)processor {
  152. GTMLoggerInfo(@"processor=%@", processor);
  153. @try {
  154. if ([delegate_ respondsToSelector:@selector(engineStarted:)])
  155. [delegate_ engineStarted:self];
  156. }
  157. @catch (id ex) {
  158. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  159. }
  160. }
  161. - (void)processingStopped:(KSActionProcessor *)processor {
  162. GTMLoggerInfo(@"processor=%@, wasSuccesful_=%d", processor, wasSuccessful_);
  163. @try {
  164. if ([delegate_ respondsToSelector:@selector(engineFinished:wasSuccess:)])
  165. [delegate_ engineFinished:self wasSuccess:wasSuccessful_];
  166. }
  167. @catch (id ex) {
  168. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  169. }
  170. }
  171. - (void)processor:(KSActionProcessor *)processor
  172. startingAction:(KSAction *)action {
  173. GTMLoggerInfo(@"processor=%@, action=%@", processor, action);
  174. }
  175. - (void)processor:(KSActionProcessor *)processor
  176. finishedAction:(KSAction *)action
  177. successfully:(BOOL)wasOK {
  178. GTMLoggerInfo(@"processor=%@, action=%@, wasOK=%d", processor, action, wasOK);
  179. if (!wasOK) {
  180. // If any of these actions fail (in reality, the only one that can possibly
  181. // fail is the KSCheckAction), we indicate that this fetch was not
  182. // successful, and we stop everything.
  183. wasSuccessful_ = NO;
  184. [self stopAndReset];
  185. }
  186. }
  187. // We override this NSObject method to ensure that KSUpdateEngine instances are
  188. // always sent over DO byref, wrapped in an NSProtocolChecker. This means that
  189. // KSUpdateEngine delegates who access us via a vended DO object will only have
  190. // access to the methods declared in the KSUpdateEngine protocol (e.g., they
  191. // will NOT have access to the -ticketStore method).
  192. - (id)replacementObjectForPortCoder:(NSPortCoder *)encoder {
  193. NSProtocolChecker *pchecker =
  194. [NSProtocolChecker protocolCheckerWithTarget:self
  195. protocol:@protocol(KSUpdateEngine)];
  196. return [NSDistantObject proxyWithLocal:pchecker
  197. connection:[encoder connection]];
  198. }
  199. @end // KSUpdateEngine
  200. @implementation KSUpdateEngine (KSUpdateEngineActionPrivateCallbackMethods)
  201. - (NSArray *)action:(KSAction *)action shouldPrefetchProducts:(NSArray *)products {
  202. @try {
  203. if ([delegate_ respondsToSelector:@selector(engine:shouldPrefetchProducts:)])
  204. return [delegate_ engine:self shouldPrefetchProducts:products];
  205. }
  206. @catch (id ex) {
  207. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  208. }
  209. return products; // if not implemented, assume we want to prefetch everything
  210. }
  211. - (NSArray *)action:(KSAction *)action
  212. shouldSilentlyUpdateProducts:(NSArray *)products {
  213. @try {
  214. if ([delegate_ respondsToSelector:@selector(engine:shouldSilentlyUpdateProducts:)])
  215. return [delegate_ engine:self shouldSilentlyUpdateProducts:products];
  216. }
  217. @catch (id ex) {
  218. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  219. }
  220. return products; // if not implemented, assume we want to update everything
  221. }
  222. - (id<KSCommandRunner>)commandRunnerForAction:(KSAction *)action {
  223. @try {
  224. if ([delegate_ respondsToSelector:@selector(commandRunnerForEngine:)])
  225. return [delegate_ commandRunnerForEngine:self];
  226. else
  227. return [KSTaskCommandRunner commandRunner];
  228. }
  229. @catch (id ex) {
  230. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  231. }
  232. return nil;
  233. }
  234. - (void)action:(KSAction *)action
  235. starting:(KSUpdateInfo *)updateInfo {
  236. @try {
  237. // Inform the delegate that we are starting to update something
  238. if ([delegate_ respondsToSelector:@selector(engine:starting:)])
  239. [delegate_ engine:self starting:updateInfo];
  240. }
  241. @catch (id ex) {
  242. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  243. }
  244. }
  245. - (void)action:(KSAction *)action
  246. running:(KSUpdateInfo *)updateInfo
  247. progress:(NSNumber *)progress {
  248. @try {
  249. // Inform the delegate that we are starting to update something
  250. if ([delegate_ respondsToSelector:@selector(engine:running:progress:)])
  251. [delegate_ engine:self running:updateInfo progress:progress];
  252. }
  253. @catch (id ex) {
  254. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  255. }
  256. }
  257. - (void)action:(KSAction *)action
  258. finished:(KSUpdateInfo *)updateInfo
  259. wasSuccess:(BOOL)wasSuccess
  260. wantsReboot:(BOOL)wantsReboot {
  261. @try {
  262. // Inform the delegate that we finished updating something
  263. if ([delegate_ respondsToSelector:
  264. @selector(engine:finished:wasSuccess:wantsReboot:)])
  265. [delegate_ engine:self
  266. finished:updateInfo
  267. wasSuccess:wasSuccess
  268. wantsReboot:wantsReboot];
  269. }
  270. @catch (id ex) {
  271. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  272. }
  273. }
  274. - (NSArray *)action:(KSAction *)action shouldUpdateProducts:(NSArray *)products {
  275. @try {
  276. if ([delegate_ respondsToSelector:@selector(engine:shouldUpdateProducts:)])
  277. return [delegate_ engine:self shouldUpdateProducts:products];
  278. }
  279. @catch (id ex) {
  280. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  281. products = nil;
  282. }
  283. return products; // if not implemented, assume we want to update everything
  284. }
  285. @end // KSUpdateEngineActionPrivateCallbackMethods
  286. @implementation KSUpdateEngine (PrivateMethods)
  287. - (void)updateStatsForTickets:(NSArray *)tickets {
  288. // Start over with a fresh stats directory for this update.
  289. [stats_ release];
  290. stats_ = [[NSMutableDictionary alloc] init];
  291. @try {
  292. if ([delegate_ respondsToSelector:@selector(engine:statsForProductID:)]) {
  293. NSEnumerator *ticketEnumerator = [tickets objectEnumerator];
  294. KSTicket *ticket;
  295. while ((ticket = [ticketEnumerator nextObject])) {
  296. NSDictionary *stats =
  297. [delegate_ engine:self statsForProductID:[ticket productID]];
  298. if (stats) {
  299. [stats_ setObject:stats forKey:[ticket productID]];
  300. }
  301. }
  302. }
  303. }
  304. @catch (id ex) {
  305. GTMLoggerError(@"Caught exception talking to delegate: %@", ex);
  306. }
  307. }
  308. - (void)triggerUpdateForTickets:(NSArray *)tickets {
  309. _GTMDevAssert(processor_ != nil, @"processor must not be nil");
  310. [self updateStatsForTickets:tickets];
  311. // Will be set to NO if any of the KSActions fail. But note that the only
  312. // one of these KSActions that can ever fail is the KSCheckAction.
  313. wasSuccessful_ = YES;
  314. // Add the product stats to the server parameters.
  315. NSMutableDictionary *params = [[params_ mutableCopy] autorelease];
  316. if (stats_) [params setObject:stats_ forKey:kUpdateEngineProductStats];
  317. // Build a KSMultiAction pipeline:
  318. KSAction *check = [KSCheckAction actionWithTickets:tickets
  319. params:params
  320. engine:self];
  321. KSAction *oob = [KSOutOfBandDataAction actionWithEngine:self];
  322. KSAction *prefetch = [KSPrefetchAction actionWithEngine:self];
  323. KSAction *silent = [KSSilentUpdateAction actionWithEngine:self];
  324. KSAction *prompt = [KSPromptAction actionWithEngine:self];
  325. [KSActionPipe bondFrom:check to:oob];
  326. [KSActionPipe bondFrom:oob to:prefetch];
  327. [KSActionPipe bondFrom:prefetch to:silent];
  328. [KSActionPipe bondFrom:silent to:prompt];
  329. [processor_ enqueueAction:check];
  330. [processor_ enqueueAction:oob];
  331. [processor_ enqueueAction:prefetch];
  332. [processor_ enqueueAction:silent];
  333. [processor_ enqueueAction:prompt];
  334. [processor_ startProcessing];
  335. }
  336. @end // PrivateMethods