/* DBKVarLenRecordsFile.m * * Copyright (C) 2005 Free Software Foundation, Inc. * * Author: Enrico Sersale * Date: June 2005 * * This file is part of the GNUstep GWorkspace application * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #include "DBKVarLenRecordsFile.h" #include "DBKBTreeNode.h" #define FIRST_OFFSET 512 @implementation DBKVarLenRecordsFile - (void)dealloc { if (handle) { [handle closeFile]; RELEASE (handle); } RELEASE (freeOffsetsTree); RELEASE (cacheDict); RELEASE (offsets); [super dealloc]; } - (id)initWithPath:(NSString *)path cacheLength:(unsigned)len { self = [super init]; if (self) { NSMutableData *data = [NSMutableData dataWithCapacity: 1]; NSFileManager *fm = [NSFileManager defaultManager]; BOOL exists, isdir; exists = [fm fileExistsAtPath: path isDirectory: &isdir]; if (exists == NO) { if ([fm createDirectoryAtPath: path attributes: nil] == NO) { DESTROY (self); [NSException raise: NSInvalidArgumentException format: @"cannot create directory at: %@", path]; return self; } isdir = YES; } if (isdir == NO) { DESTROY (self); [NSException raise: NSInvalidArgumentException format: @"%@ is not a directory!", path]; return self; } else { NSString *recordsPath = [path stringByAppendingPathComponent: @"records"]; NSString *freePath = [path stringByAppendingPathComponent: @"free"]; exists = [fm fileExistsAtPath: recordsPath isDirectory: &isdir]; if (isdir) { DESTROY (self); [NSException raise: NSInvalidArgumentException format: @"%@ is a directory!", recordsPath]; return self; } else if (exists == NO) { if ([fm createFileAtPath: recordsPath contents: nil attributes: nil] == NO) { DESTROY (self); [NSException raise: NSInvalidArgumentException format: @"cannot create file at: %@", recordsPath]; return self; } } cacheDict = [NSMutableDictionary new]; offsets = [NSMutableArray new]; maxlen = len; autoflush = YES; ulen = sizeof(unsigned); llen = sizeof(unsigned long); handle = [NSFileHandle fileHandleForUpdatingAtPath: recordsPath]; RETAIN (handle); [data setLength: FIRST_OFFSET]; [handle writeData: data]; [handle seekToEndOfFile]; eof = [handle offsetInFile]; freeOffsetsTree = [[DBKBTree alloc] initWithPath: freePath order: 16 delegate: self]; } } return self; } - (void)setAutoflush:(BOOL)value { autoflush = value; } - (BOOL)autoflush { return autoflush; } - (void)flushIfNeeded { if (([cacheDict count] >= maxlen) && autoflush) { [self flush]; } } - (void)flush { int i; for (i = 0; i < [offsets count]; i++) { CREATE_AUTORELEASE_POOL (arp); NSNumber *offset = [offsets objectAtIndex: i]; NSData *dictdata = [cacheDict objectForKey: offset]; unsigned datalen = [dictdata length]; NSMutableData *data = [NSMutableData dataWithCapacity: 1]; unsigned long ofst; [data appendBytes: &datalen length: ulen]; [data appendData: dictdata]; [handle seekToFileOffset: [offset unsignedLongValue]]; [handle writeData: data]; ofst = [handle offsetInFile]; if (ofst > eof) { eof = ofst; } RELEASE (arp); } [cacheDict removeAllObjects]; [offsets removeAllObjects]; } - (NSData *)dataAtOffset:(NSNumber *)offset { NSData *data = [cacheDict objectForKey: offset]; if (data == nil) { unsigned long ofst = [offset unsignedLongValue]; unsigned datalen; [handle seekToFileOffset: ofst]; data = [handle readDataOfLength: ulen]; [data getBytes: &datalen range: NSMakeRange(0, ulen)]; data = [handle readDataOfLength: datalen]; } return data; } - (NSNumber *)writeData:(NSData *)data { NSNumber *offset = [self offsetForNewData: data]; [self writeData: data atOffset: offset]; return offset; } - (void)writeData:(NSData *)data atOffset:(NSNumber *)offset { int index = [self insertionIndexForOffset: offset]; if (index != -1) { [offsets insertObject: offset atIndex: index]; } [cacheDict setObject: data forKey: offset]; if (([cacheDict count] > maxlen) && autoflush) { [self flush]; } } - (void)deleteDataAtOffset:(NSNumber *)offset { NSData *data = [cacheDict objectForKey: offset]; if (data) { [cacheDict removeObjectForKey: offset]; [offsets removeObject: offset]; } else { CREATE_AUTORELEASE_POOL(arp); unsigned long ofst = [offset unsignedLongValue]; NSData *lndata; unsigned datalen; DBKBFreeNodeEntry *entry; [handle seekToFileOffset: ofst]; lndata = [handle readDataOfLength: ulen]; [lndata getBytes: &datalen range: NSMakeRange(0, ulen)]; entry = [DBKBFreeNodeEntry entryWithLength: datalen atOffset: ofst]; [freeOffsetsTree begin]; [freeOffsetsTree insertKey: entry]; [freeOffsetsTree end]; RELEASE (arp); } } - (NSNumber *)offsetForNewData:(NSData *)data { NSNumber *offset = [self freeOffsetForData: data]; if (offset == nil) { unsigned count = [offsets count]; unsigned long coffs = 0; if (count > 0) { NSNumber *key = [offsets objectAtIndex: (count - 1)]; NSData *dictData = [cacheDict objectForKey: key]; coffs = [key unsignedLongValue] + ulen + [dictData length]; } offset = [NSNumber numberWithUnsignedLong: ((coffs > eof) ? coffs : eof)]; } return offset; } - (int)insertionIndexForOffset:(NSNumber *)offset { CREATE_AUTORELEASE_POOL(arp); unsigned count = [offsets count]; int ins = 0; if (count) { NSNumber *ofst = nil; int first = 0; int last = count; int pos = 0; NSComparisonResult result; while (1) { if (first == last) { ins = first; break; } pos = (first + last) / 2; ofst = [offsets objectAtIndex: pos]; result = [ofst compare: offset]; if (result == NSOrderedSame) { RELEASE (arp); return -1; } else if (result == NSOrderedAscending) { first = pos + 1; } else { last = pos; } } } RELEASE (arp); return ins; } - (NSNumber *)freeOffsetForData:(NSData *)data { CREATE_AUTORELEASE_POOL(arp); DBKBFreeNodeEntry *entry = [DBKBFreeNodeEntry entryWithLength: [data length] atOffset: 0]; DBKBFreeNodeEntry *freeEntry = nil; NSNumber *offset = nil; DBKBTreeNode *node; BOOL exists; int index; [freeOffsetsTree begin]; node = [freeOffsetsTree nodeOfKey: entry getIndex: &index didExist: &exists]; if (node && [[node keys] count]) { freeEntry = [node successorKeyInNode: &node forKeyAtIndex: index]; } if (freeEntry) { offset = RETAIN ([freeEntry offsetNum]); [freeOffsetsTree deleteKey: freeEntry]; } [freeOffsetsTree end]; RELEASE (arp); return TEST_AUTORELEASE (offset); } // // DBKBTreeDelegate methods // - (unsigned long)nodesize { return 512; } - (NSArray *)keysFromData:(NSData *)data withLength:(unsigned *)dlen { NSMutableArray *keys = [NSMutableArray array]; NSRange range; unsigned kcount; int i; range = NSMakeRange(0, ulen); [data getBytes: &kcount range: range]; range.location += ulen; range.length = llen; for (i = 0; i < kcount; i++) { CREATE_AUTORELEASE_POOL(arp); DBKBFreeNodeEntry *entry; unsigned long length; unsigned long offset; [data getBytes: &length range: range]; range.location += llen; [data getBytes: &offset range: range]; range.location += llen; entry = [[DBKBFreeNodeEntry alloc] initWithLength: length atOffset: offset]; [keys addObject: entry]; RELEASE (entry); RELEASE (arp); } *dlen = range.location; return keys; } - (NSData *)dataFromKeys:(NSArray *)keys { CREATE_AUTORELEASE_POOL(arp); NSMutableData *data = [NSMutableData dataWithCapacity: 1]; unsigned kcount = [keys count]; int i; [data appendData: [NSData dataWithBytes: &kcount length: ulen]]; for (i = 0; i < kcount; i++) { DBKBFreeNodeEntry *entry = [keys objectAtIndex: i]; unsigned long length = [entry length]; unsigned long offset = [entry offset]; [data appendData: [NSData dataWithBytes: &length length: llen]]; [data appendData: [NSData dataWithBytes: &offset length: llen]]; } RETAIN (data); RELEASE (arp); return [data autorelease]; } - (NSComparisonResult)compareNodeKey:(id)akey withKey:(id)bkey { NSComparisonResult result = [[akey lengthNum] compare: [bkey lengthNum]]; if (result == NSOrderedSame) { result = [[akey offsetNum] compare: [bkey offsetNum]]; } return result; } @end @implementation DBKBFreeNodeEntry - (void)dealloc { RELEASE (lengthNum); RELEASE (offsetNum); [super dealloc]; } + (id)entryWithLength:(unsigned long)len atOffset:(unsigned long)ofst { return AUTORELEASE ([[DBKBFreeNodeEntry alloc] initWithLength: len atOffset: ofst]); } - (id)initWithLength:(unsigned long)len atOffset:(unsigned long)ofst { self = [super init]; if (self) { ASSIGN (lengthNum, [NSNumber numberWithUnsignedLong: len]); ASSIGN (offsetNum, [NSNumber numberWithUnsignedLong: ofst]); } return self; } - (NSNumber *)lengthNum { return lengthNum; } - (unsigned long)length { return [lengthNum unsignedLongValue]; } - (NSNumber *)offsetNum { return offsetNum; } - (unsigned long)offset { return [offsetNum unsignedLongValue]; } - (unsigned)hash { return ([lengthNum hash] + [offsetNum hash]); } - (BOOL)isEqual:(id)other { if (other == self) { return YES; } if ([other isKindOfClass: [DBKBFreeNodeEntry class]]) { return ([lengthNum isEqual: [other lengthNum]] && [offsetNum isEqual: [other offsetNum]]); } return NO; } @end