PageRenderTime 72ms CodeModel.GetById 16ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 0ms

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