3 Copyright (C) 2003 Apple Computer, Inc. All rights reserved.
6 #import <WebKit/WebFileDatabase.h>
7 #import <WebKit/WebKitLogging.h>
8 #import <WebKit/WebLRUFileList.h>
9 #import <WebKit/WebNSFileManagerExtras.h>
20 #define BEGIN_EXCEPTION_HANDLER
21 #define END_EXCEPTION_HANDLER
23 #define BEGIN_EXCEPTION_HANDLER NS_DURING
24 #define END_EXCEPTION_HANDLER \
26 ERROR("Uncaught exception: %@ [%@] [%@]", [localException class], [localException reason], [localException userInfo]); \
30 static pthread_once_t databaseInitControl = PTHREAD_ONCE_INIT;
31 static NSNumber *WebFileDirectoryPOSIXPermissions;
32 static NSNumber *WebFilePOSIXPermissions;
33 static NSRunLoop *syncRunLoop;
35 #define UniqueFilePathSize (34)
36 static void UniqueFilePathForKey(id key, char *buffer);
38 #define MinThreadPriority (10)
39 static int SetThreadPriority(int priority);
43 WebFileDatabaseSetObjectOp,
44 WebFileDatabaseRemoveObjectOp,
45 } WebFileDatabaseOpcode;
49 MAX_UNSIGNED_LENGTH = 20, // long enough to hold the string representation of a 64-bit unsigned number
50 SYNC_IDLE_THRESHOLD = 10,
53 // interface WebFileDatabaseOp -------------------------------------------------------------
55 @interface WebFileDatabaseOp : NSObject
57 WebFileDatabaseOpcode opcode;
62 +(id)opWithCode:(WebFileDatabaseOpcode)opcode key:(id)key object:(id)object;
63 -(id)initWithCode:(WebFileDatabaseOpcode)opcode key:(id)key object:(id)object;
65 -(WebFileDatabaseOpcode)opcode;
68 -(void)perform:(WebFileDatabase *)target;
73 // implementation WebFileDatabaseOp -------------------------------------------------------------
75 @implementation WebFileDatabaseOp
77 +(id)opWithCode:(WebFileDatabaseOpcode)theOpcode key:(id)theKey object:(id)theObject
79 return [[[WebFileDatabaseOp alloc] initWithCode:theOpcode key:theKey object:theObject] autorelease];
82 -(id)initWithCode:(WebFileDatabaseOpcode)theOpcode key:(id)theKey object:(id)theObject
86 if ((self = [super init])) {
89 key = [theKey retain];
90 object = [theObject retain];
98 -(WebFileDatabaseOpcode)opcode
113 -(void)perform:(WebFileDatabase *)target
118 case WebFileDatabaseSetObjectOp:
119 [target performSetObject:object forKey:key];
121 case WebFileDatabaseRemoveObjectOp:
122 [target performRemoveObjectForKey:key];
125 ASSERT_NOT_REACHED();
141 // interface WebFileDatabasePrivate -----------------------------------------------------------
143 @interface WebFileDatabase (WebFileDatabasePrivate)
145 -(void)_createLRUList:(id)arg;
146 -(void)_truncateToSizeLimit:(unsigned)size;
150 // implementation WebFileDatabasePrivate ------------------------------------------------------
152 @implementation WebFileDatabase (WebFileDatabasePrivate)
154 static int SetThreadPriority(int priority)
156 struct sched_param sp;
158 memset(&sp, 0, sizeof(struct sched_param));
159 sp.sched_priority=priority;
160 if (pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp) == -1) {
161 ERROR("Failed to change priority.");
167 static void UniqueFilePathForKey(id key, char *buffer)
175 s = [[[[key description] lowercaseString] stringByStandardizingPath] lossyCString];
178 // compute first hash
180 for (cnt = 0; cnt < len; cnt++) {
181 hash1 += (hash1 << 8) + s[cnt];
183 hash1 += (hash1 << (len & 31));
185 // compute second hash
187 for (cnt = 0; cnt < len; cnt++) {
188 hash2 = (37 * hash2) ^ s[cnt];
191 snprintf(buffer, UniqueFilePathSize, "%.2lu/%.2lu/%.10lu-%.10lu.cache", ((hash1 & 0xff) >> 4), ((hash2 & 0xff) >> 4), hash1, hash2);
194 -(void)_createLRUList:(id)arg
196 SetThreadPriority(MinThreadPriority + 1); // make this a little higher priority than the syncRunLoop thread
198 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
200 BEGIN_EXCEPTION_HANDLER
202 WebLRUFileList *fileList = WebLRUFileListCreate();
203 WebLRUFileListRebuildFileDataUsingRootDirectory(fileList, [path fileSystemRepresentation]);
206 END_EXCEPTION_HANDLER
208 LOG(FileDatabaseActivity, "lru list created");
210 #if BUILDING_ON_PANTHER
217 -(void)_truncateToSizeLimit:(unsigned)size
219 NSFileManager *defaultManager;
221 if (!lru || size > [self usage]) {
226 [self removeAllObjects];
229 defaultManager = [NSFileManager defaultManager];
231 while ([self usage] > size) {
232 char uniqueKey[UniqueFilePathSize];
233 if (!WebLRUFileListGetPathOfOldestFile(lru, uniqueKey, UniqueFilePathSize)) {
236 NSString *filePath = [[NSString alloc] initWithFormat:@"%@/%s", path, uniqueKey];
237 [defaultManager _webkit_removeFileOnlyAtPath:filePath];
239 WebLRUFileListRemoveOldestFileFromList(lru);
248 // implementation WebFileDatabase -------------------------------------------------------------
250 @implementation WebFileDatabase
252 // creation functions ---------------------------------------------------------------------------
253 #pragma mark creation functions
255 +(void)_syncLoop:(id)arg
257 SetThreadPriority(MinThreadPriority);
259 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
260 NSPort *placeholderPort;
262 BEGIN_EXCEPTION_HANDLER
264 syncRunLoop = [NSRunLoop currentRunLoop];
267 BEGIN_EXCEPTION_HANDLER
268 // we specifically use an NSRunLoop here to get autorelease pool support
269 placeholderPort = [NSPort port];
270 [syncRunLoop addPort:placeholderPort forMode:NSDefaultRunLoopMode];
272 [syncRunLoop removePort:placeholderPort forMode:NSDefaultRunLoopMode];
273 END_EXCEPTION_HANDLER
276 END_EXCEPTION_HANDLER
278 #if BUILDING_ON_PANTHER
285 static void databaseInit()
287 // set file perms to owner read/write/execute only
288 WebFileDirectoryPOSIXPermissions = [[NSNumber numberWithInt:(WEB_UREAD | WEB_UWRITE | WEB_UEXEC)] retain];
290 // set file perms to owner read/write only
291 WebFilePOSIXPermissions = [[NSNumber numberWithInt:(WEB_UREAD | WEB_UWRITE)] retain];
293 [NSThread detachNewThreadSelector:@selector(_syncLoop:) toTarget:[WebFileDatabase class] withObject:nil];
296 -(id)initWithPath:(NSString *)thePath
298 pthread_once(&databaseInitControl, databaseInit);
300 [super initWithPath:thePath];
302 if (self == nil || thePath == nil) {
307 ops = [[NSMutableArray alloc] init];
308 setCache = [[NSMutableDictionary alloc] init];
309 removeCache = [[NSMutableSet alloc] init];
311 mutex = [[NSRecursiveLock alloc] init];
316 // WebFileDatabase objects are never released, so we need no dealloc implementation.
321 NSDate *fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:SYNC_IDLE_THRESHOLD];
322 timer = [[NSTimer alloc] initWithFireDate:fireDate interval:SYNC_IDLE_THRESHOLD target:self selector:@selector(lazySync:) userInfo:nil repeats:YES];
324 [syncRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
328 // database functions ---------------------------------------------------------------------------
329 #pragma mark database functions
331 -(void)setObject:(id)object forKey:(id)key
333 WebFileDatabaseOp *op;
338 touch = CFAbsoluteTimeGetCurrent();
340 LOG(FileDatabaseActivity, "%p - %@", object, key);
344 [setCache setObject:object forKey:key];
345 op = [[WebFileDatabaseOp alloc] initWithCode:WebFileDatabaseSetObjectOp key:key object:object];
353 -(void)removeObjectForKey:(id)key
355 WebFileDatabaseOp *op;
359 touch = CFAbsoluteTimeGetCurrent();
363 [removeCache addObject:key];
364 op = [[WebFileDatabaseOp alloc] initWithCode:WebFileDatabaseRemoveObjectOp key:key object:nil];
372 -(void)removeAllObjects
374 touch = CFAbsoluteTimeGetCurrent();
377 [setCache removeAllObjects];
378 [removeCache removeAllObjects];
379 [ops removeAllObjects];
381 [[NSFileManager defaultManager] _webkit_backgroundRemoveFileAtPath:path];
385 LOG(FileDatabaseActivity, "removeAllObjects");
388 -(id)objectForKey:(id)key
394 touch = CFAbsoluteTimeGetCurrent();
398 if ([removeCache containsObject:key]) {
402 if ((result = [setCache objectForKey:key])) {
409 char uniqueKey[UniqueFilePathSize];
410 UniqueFilePathForKey(key, uniqueKey);
411 NSString *filePath = [[NSString alloc] initWithFormat:@"%@/%s", path, uniqueKey];
412 NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
413 NSUnarchiver * volatile unarchiver = nil;
417 unarchiver = [[NSUnarchiver alloc] initForReadingWithData:data];
419 id fileKey = [unarchiver decodeObject];
420 if ([fileKey isEqual:key]) {
421 id object = [unarchiver decodeObject];
423 // Decoded objects go away when the unarchiver does, so we need to
424 // retain this so we can return it to our caller.
425 result = [[object retain] autorelease];
427 // if we can't update the list yet, that's too bad
428 // but not critically bad
429 WebLRUFileListTouchFileWithPath(lru, uniqueKey);
431 LOG(FileDatabaseActivity, "read disk cache file - %@", key);
437 LOG(FileDatabaseActivity, "cannot unarchive cache file - %@", key);
441 [unarchiver release];
445 LOG(Timing, "getting value for %@ took %f", key, (CFAbsoluteTimeGetCurrent() - touch));
450 -(void)performSetObject:(id)object forKey:(id)key
454 NSDictionary *attributes;
455 NSDictionary *directoryAttributes;
456 NSArchiver *archiver;
457 NSFileManager *defaultManager;
458 char uniqueKey[UniqueFilePathSize];
464 UniqueFilePathForKey(key, uniqueKey);
466 LOG(FileDatabaseActivity, "%@ - %s", key, uniqueKey);
468 data = [NSMutableData data];
469 archiver = [[NSArchiver alloc] initForWritingWithMutableData:data];
470 [archiver encodeObject:key];
471 [archiver encodeObject:object];
473 attributes = [NSDictionary dictionaryWithObjectsAndKeys:
474 NSUserName(), NSFileOwnerAccountName,
475 WebFilePOSIXPermissions, NSFilePosixPermissions,
479 directoryAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
480 NSUserName(), NSFileOwnerAccountName,
481 WebFileDirectoryPOSIXPermissions, NSFilePosixPermissions,
485 defaultManager = [NSFileManager defaultManager];
487 filePath = [[NSString alloc] initWithFormat:@"%@/%s", path, uniqueKey];
488 attributes = [defaultManager fileAttributesAtPath:filePath traverseLink:YES];
490 // update usage and truncate before writing file
491 // this has the effect of _always_ keeping disk usage under sizeLimit by clearing away space in anticipation of the write.
492 WebLRUFileListSetFileData(lru, uniqueKey, [data length], CFAbsoluteTimeGetCurrent());
493 [self _truncateToSizeLimit:[self sizeLimit]];
495 result = [defaultManager _webkit_createFileAtPathWithIntermediateDirectories:filePath contents:data attributes:attributes directoryAttributes:directoryAttributes];
498 WebLRUFileListRemoveFileWithPath(lru, uniqueKey);
505 -(void)performRemoveObjectForKey:(id)key
508 char uniqueKey[UniqueFilePathSize];
512 LOG(FileDatabaseActivity, "%@", key);
514 UniqueFilePathForKey(key, uniqueKey);
515 filePath = [[NSString alloc] initWithFormat:@"%@/%s", path, uniqueKey];
516 [[NSFileManager defaultManager] _webkit_removeFileOnlyAtPath:filePath];
517 WebLRUFileListRemoveFileWithPath(lru, uniqueKey);
521 // database management functions ---------------------------------------------------------------------------
522 #pragma mark database management functions
526 NSFileManager *manager;
527 NSDictionary *attributes;
531 manager = [NSFileManager defaultManager];
532 if ([manager fileExistsAtPath:path isDirectory:&isDir]) {
538 attributes = [NSDictionary dictionaryWithObjectsAndKeys:
539 [NSDate date], @"NSFileModificationDate",
540 NSUserName(), @"NSFileOwnerAccountName",
541 WebFileDirectoryPOSIXPermissions, @"NSFilePosixPermissions",
545 isOpen = [manager _webkit_createDirectoryAtPathWithIntermediateDirectories:path attributes:attributes];
548 // remove any leftover turds
549 [manager _webkit_backgroundRemoveLeftoverFiles:path];
552 [NSThread detachNewThreadSelector:@selector(_createLRUList:) toTarget:self withObject:nil];
562 WebLRUFileListRelease(lru);
568 -(void)lazySync:(NSTimer *)theTimer
571 // wait for lru to finish getting created
576 CFTimeInterval mark = CFAbsoluteTimeGetCurrent();
579 LOG(FileDatabaseActivity, ">>> BEFORE lazySync\n%@", WebLRUFileListDescription(lru));
581 WebFileDatabaseOp *op;
585 while (touch + SYNC_IDLE_THRESHOLD < CFAbsoluteTimeGetCurrent() && [ops count] > 0) {
594 op = [ops lastObject];
597 [ops removeLastObject];
599 [setCache removeObjectForKey:[op key]];
600 [removeCache removeObject:[op key]];
607 // come back later to finish the work...
608 if ([ops count] > 0) {
616 LOG(FileDatabaseActivity, "<<< AFTER lazySync\n%@", WebLRUFileListDescription(lru));
618 CFTimeInterval now = CFAbsoluteTimeGetCurrent();
619 LOG(FileDatabaseActivity, "lazySync ran in %.3f secs.", now - mark);
628 // wait for lru to finish getting created
632 touch = CFAbsoluteTimeGetCurrent();
634 LOG(FileDatabaseActivity, ">>> BEFORE sync\n%@", WebLRUFileListDescription(lru));
638 [ops removeAllObjects];
642 [setCache removeAllObjects];
643 [removeCache removeAllObjects];
646 [array makeObjectsPerformSelector:@selector(perform:) withObject:self];
649 LOG(FileDatabaseActivity, "<<< AFTER sync\n%@", WebLRUFileListDescription(lru));
655 return WebLRUFileListCountItems(lru);
663 return WebLRUFileListGetTotalSize(lru);
668 -(void)setSizeLimit:(unsigned)limit
671 if (limit < [self usage]) {
672 [self _truncateToSizeLimit:limit];