PageRenderTime 48ms CodeModel.GetById 14ms app.highlight 30ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://macfuse.googlecode.com/
Objective C | 248 lines | 150 code | 49 blank | 49 comment | 27 complexity | 4ec1881d3c8c5b93e128797988e8d43b 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 "KSTicketStore.h"
 16#import "KSTicket.h"
 17#import <fcntl.h>
 18#import <unistd.h>
 19
 20
 21// Implementation note: Tickets are stored internally in a dictionary
 22// keyed by each ticket's ProductID.  This means that no two tickets
 23// can have the same ProductID, which is exactly the behavior that we
 24// want.
 25
 26
 27@interface KSTicketStore (PrivateMethods)
 28- (NSMutableDictionary *)readTicketMap;
 29- (BOOL)writeTicketMap:(NSMutableDictionary *)ticketMap;
 30- (NSMutableDictionary *)atomicReadTicketMap;
 31- (BOOL)atomicStoreTicket:(KSTicket *)ticket;
 32- (BOOL)atomicDeleteTicket:(KSTicket *)ticket;
 33@end
 34
 35
 36@implementation KSTicketStore
 37
 38+ (id)ticketStoreWithPath:(NSString *)path {
 39  return [[[self alloc] initWithPath:path] autorelease];
 40}
 41
 42- (id)initWithPath:(NSString *)path {
 43  if ((self = [super init])) {
 44    path_ = [path copy];
 45    if (path_ == nil) {
 46      [self release];
 47      return nil;
 48    }
 49  }
 50  return self;
 51}
 52
 53- (id)init {
 54  return [self initWithPath:nil];
 55}
 56
 57- (void)dealloc {
 58  [path_ release];
 59  [super dealloc];
 60}
 61
 62- (NSString *)description {
 63  return [NSString stringWithFormat:@"<%@:%p path=%@>",
 64          [self class], self, path_];
 65}
 66
 67- (NSString *)path {
 68  return path_;
 69}
 70
 71- (int)ticketCount {
 72  return [[self tickets] count];
 73}
 74
 75// We never want to return nil here. If we don't have any tickets, then we'll
 76// set an empty array of them, and return that empty array.
 77- (NSArray *)tickets {
 78  NSDictionary *map = [self atomicReadTicketMap];
 79  return map ? [map allValues] : [NSArray array];
 80}
 81
 82- (KSTicket *)ticketForProductID:(NSString *)productid {
 83  if (productid == nil) return nil;
 84  productid = [productid lowercaseString];
 85  KSTicket *ticket = nil;
 86  NSEnumerator *ticketEnumerator = [[self tickets] objectEnumerator];
 87  while ((ticket = [ticketEnumerator nextObject])) {
 88    // Do case insensitive but case preserving comparison by lowercasing
 89    if ([productid isEqualToString:[[ticket productID] lowercaseString]])
 90      break;
 91  }
 92  return ticket;
 93}
 94
 95- (BOOL)storeTicket:(KSTicket *)ticket {
 96  return [self atomicStoreTicket:ticket];
 97}
 98
 99- (BOOL)deleteTicket:(KSTicket *)ticket {
100  return [self atomicDeleteTicket:ticket];
101}
102
103- (BOOL)deleteTicketForProductID:(NSString *)productid {
104  KSTicket *ticket = [self ticketForProductID:productid];
105  return [self deleteTicket:ticket];
106}
107
108@end  // KSTicketStore
109
110
111@implementation KSTicketStore (PrivateMethods)
112
113- (NSMutableDictionary *)readTicketMap {
114  _GTMDevAssert(path_ != nil, @"path_ must not be nil");
115  NSData *data = [NSData dataWithContentsOfFile:path_];
116  if ([data length] == 0) return nil;
117  NSDictionary *map = nil;
118  
119  @try {
120    map = [NSKeyedUnarchiver unarchiveObjectWithData:data];
121  // COV_NF_START
122  }
123  @catch (id ex) {
124    GTMLoggerError(@"Caught exception unarchiving ticket store at %@: %@",
125                   path_, ex);
126  }
127  // COV_NF_END
128  
129  return [[map mutableCopy] autorelease];
130}
131
132- (BOOL)writeTicketMap:(NSMutableDictionary *)ticketMap {
133  _GTMDevAssert(path_ != nil, @"path_ must not be nil");
134  if (ticketMap == nil) return NO;
135  BOOL ok = NO;
136  
137  @try {
138    ok = [NSKeyedArchiver archiveRootObject:ticketMap toFile:path_];
139  // COV_NF_START
140  }
141  @catch (id ex) {
142    GTMLoggerError(@"Caught exception archiving ticket map to %@: %@",
143                   path_, ex);
144  }
145  // COV_NF_END
146  
147  return ok;
148}
149
150// Locking
151//
152// The next few "atomic" methods use these macros to lock the ticket store file
153// at path_ before accessing it. We do this with a simple advisory lock. The 
154// lock file itself will be |path_| with a file extension of ".lock".
155//
156// Also, we do this using macros to make clear the very simple nature of this
157// locking mechanism: these locks are not recursive, and aren't smart enough to
158// "NOP" if the process already has the lock. If we wrapped this logic up in a
159// full-blown object, one may be led to think that the locking mechanism is much
160// more sophisticated than it really is.
161// 
162// The LOCK_STORE macro simple opens the ticket store path_ with the O_EXLOCK
163// flag set. If this fails, it returns NO. The UNLOCK_STORE() macro simply
164// closes the fd. These two methods must be balanced, and there must be no code
165// paths that could lead to a LOCK_STORE() being called w/o a corresponding 
166// UNLOCK_STORE(). Note that since these locks are based on FDs, when the
167// process exists (even if it crashes), the advisory locks will be released.
168#define LOCK_STORE() \
169  NSString *_lockPath_ = [path_ stringByAppendingPathExtension:@"lock"]; \
170  int _oflags_ = O_CREAT | O_RDONLY | O_EXLOCK; \
171  int _lockFD_ = open([_lockPath_ fileSystemRepresentation], _oflags_, 0444); \
172  if (_lockFD_ < 0) return NO  // COV_NF_LINE
173
174#define UNLOCK_STORE() \
175  if (_lockFD_ >= 0) close(_lockFD_)
176
177- (NSMutableDictionary *)atomicReadTicketMap {
178  LOCK_STORE();
179  NSMutableDictionary *map = [self readTicketMap];
180  UNLOCK_STORE();
181  return map;
182}
183
184// Takes the lock and adds |ticket| to the on-disk ticket store.
185- (BOOL)atomicStoreTicket:(KSTicket *)ticket {
186  _GTMDevAssert(path_ != nil, @"path_ must not be nil");
187  if (ticket == nil) return NO;
188  
189  LOCK_STORE();
190  
191  NSMutableDictionary *map = [self readTicketMap];
192  if (map == nil) map = [NSMutableDictionary dictionary];
193  // Store in a case insensitive but case preserving manner
194  [map setObject:ticket forKey:[[ticket productID] lowercaseString]];
195  BOOL ok = [self writeTicketMap:map];
196  
197  UNLOCK_STORE();
198  
199  return ok;
200}
201
202// Takes the lock and deletes |ticket| from the on-disk ticket store.
203- (BOOL)atomicDeleteTicket:(KSTicket *)ticket {
204  _GTMDevAssert(path_ != nil, @"path_ must not be nil");
205  if (ticket == nil) return NO;
206  
207  LOCK_STORE();
208  
209  NSMutableDictionary *map = [self readTicketMap];
210  // Delete in a case insensitive manner
211  [map removeObjectForKey:[[ticket productID] lowercaseString]];
212  BOOL ok = [self writeTicketMap:map];
213  
214  UNLOCK_STORE();
215  
216  return ok;
217}
218
219@end  // PrivateMethods
220
221
222@implementation NSArray (TicketRetrieving)
223
224- (NSDictionary *)ticketsByURL {
225  NSMutableDictionary *urlToTickets = [NSMutableDictionary dictionary];
226  
227  KSTicket *ticket = nil;
228  NSEnumerator *ticketEnumerator = [self objectEnumerator];
229  while ((ticket = [ticketEnumerator nextObject])) {
230    // Return nil if we find an object in the array that's not a KSTicket
231    if (![ticket isKindOfClass:[KSTicket class]]) {
232      // COV_NF_START
233      GTMLoggerError(@"%@ is not a KSTicket, returning nil", ticket);
234      return nil;
235      // COV_NF_END
236    }
237    NSURL *key = [ticket serverURL];
238    NSMutableArray *tickets = [urlToTickets objectForKey:key];
239    if (tickets == nil) tickets = [NSMutableArray array];
240    [tickets addObject:ticket];
241    [urlToTickets setObject:tickets forKey:key];
242  }
243  
244  return [urlToTickets count] > 0 ? urlToTickets : nil;
245}
246
247@end  // TicketStoreQuerying
248