|
|
1.1 root 1: /* TableDataSource.m
2: *
3: * This data source reads a flat file table (such as Product.table or Item.table)
4: * and generates EOGeneric records that can be passed to an EOController.
5: *
6: * You may freely copy, distribute, and reuse the code in this example.
7: * NeXT disclaims any warranty of any kind, expressed or implied, as to its
8: * fitness for any particular use.
9: *
10: *
11: *
12: */
13:
14: #import <appkit/appkit.h>
15: #import <eoaccess/eoaccess.h>
16: #import "TableDataSource.h"
17: #import "TableDataSourcePrivate.h"
18: #import "DetailTableDataSource.h"
19: #include <sys/file.h>
20:
21:
22: #define @QUALIFIER_ALL @"*"
23: #define @QPROPERTY @"PROPERTY"
24: #define @QVALUE @"VALUE"
25:
26: @implementation TableDataSource
27:
28: // init a new TableDataSource with an array of EOGeneric records and the path to
29: // the table file (the flat file database!). The entity name will be found from a
30: // record in the array.
31: // Use this initialization method when you have existing records (possibly from
32: // a real database) and you want to save them to a file
33: - initWithEOGenericRecords:(NSArray *)records tablePath:(NSString *)tablePath {
34: NSString *tableFile;
35: NSMutableArray *dictArray = [NSMutableArray array];
36: NSDictionary *dict;
37: int i;
38:
39: // We need at least a record to get the entity
40: if (![records count]) {
41: NSLog(@"<initWithEOGenericRecords:tablePath:> No records");
42: return nil;
43: }
44: entity = [[records objectAtIndex:0] entity];
45: tableFile=[NSString stringWithFormat:@"%@/%@.table", tablePath, [entity name]];
46: for (i=0; i<[records count]; i++) {
47: dict = [[records objectAtIndex:i] valuesForKeys:[entity classPropertyNames]];
48: [dictArray addObject:dict];
49: }
50:
51: // Save the records to a file as a property-list string
52: if (![[[dictArray description] dataUsingEncoding:NSASCIIStringEncoding]
53: writeToFile:tableFile atomically:NO]) {
54: NSLog(@"<initWithEOGenericRecords:tablePath:> DataSource %@ failed to save", [entity name]);
55: return nil;
56: }
57: // Call the designated initialization method
58: else return [self initWithEntity:entity tablePath:tablePath];
59:
60: }
61:
62: // The designated initialization method for TableDataSource
63: // Pick an entity in the model, then initialize the source from a flat file that
64: // contains a string representation of an array of dictionaries.
65: // Note that unlike the EODatabaseSataSource, this dataSource has a "state".
66: // It cashes the eos (dictionaries) that it read from the file and also maintains
67: // a few hash tables to speed up fetch operations...
68: // Although the state of the TableDataSource is an array of dictionaries (I call it
69: // the snapshot) the TabledataSource will copy these dictionaries into an array of
70: // EOGeneric records before handing them to a controller...
71: - initWithEntity:(EOEntity *)anEntity tablePath:(NSString *)tablePath {
72: NSString *primaryKey;
73: NSArray *relations;
74: NSMutableArray *qualifiers = [NSMutableArray array];
75:
76: [super init];
77: entity = [anEntity retain];
78: primaryKey = [(EOAttribute *)[[entity primaryKeyAttributes] objectAtIndex:0] name];
79: relations = [entity relationships];
80: detailSources = [[NSMutableArray array] retain];
81: return [self initWithTable:tablePath primaryKey:primaryKey qualifierKeys:qualifiers];
82: }
83:
84: // The new NSObject world, I guess
85: - (void)dealloc {
86: [table autorelease];
87: [eos autorelease];
88: [lookupTables autorelease];
89: [qualifier autorelease];
90: [uniqueKey autorelease];
91: [detailSources autorelease];
92: [entity autorelease];
93: [orderByKey autorelease];
94: [super dealloc];
95: }
96:
97: // The path to persistent storage, here to the "table" file...
98: - (NSString *)tablePath {
99: return table;
100: }
101:
102: // The other one...
103: - setTablePath:(NSString *)aPath {
104: [table autorelease];
105: table = [aPath retain];
106: return self;
107: }
108:
109: // The supported entity, we got it from the EOmodel at initialization time
110: - (EOEntity *)entity {
111: return entity;
112: }
113:
114: // A custom way (meaning different from the EOF way) to qualify the source for a given property name and value
115: // The equivalent SQL is: WHERE( key = value)
116: - qualifyForProperty:(NSString *)key andValue:value {
117: NSMutableDictionary *qual = [NSMutableDictionary dictionary];
118:
119: if (![[self keys] containsObject:key]) {
120: NSLog(@"%@ is not a valid property of entity %@", key, [entity name]);
121: qual=nil;
122: }
123: else {
124: [qual setObject:key forKey:@QPROPERTY];
125: [qual setObject:value forKey:@QVALUE];
126: }
127: return [self setQualifier:qual];
128: }
129:
130: // Qualify for all records
131: - setEntityQualifier {
132: NSMutableDictionary *qual =[NSMutableDictionary dictionary];
133: [qual setObject:uniqueKey forKey:@QPROPERTY];
134: [qual setObject:@QUALIFIER_ALL forKey:@QVALUE];
135: return [self setQualifier:qual];
136: }
137:
138: // Set the qualifier before the fetch
139: - setQualifier:(NSMutableDictionary *)newQualifier {
140: [qualifier autorelease];
141: if (newQualifier) qualifier = [newQualifier retain];
142: else qualifier=nil;
143: //NSLog(@"New qualifier %@", [(NSMutableDictionary *)qualifier description]);
144: return self;
145: }
146:
147: - setEmptySetQualifier {
148: [qualifier autorelease];
149: qualifier=nil;
150: return self;
151: }
152:
153: - (BOOL)orderBy:(NSString *)key {
154: if (![[self keys] containsObject:key]) return NO;
155: else {
156: [orderByKey autorelease];
157: orderByKey = [key retain];
158: [eos sortUsingFunction:eoSort context:orderByKey];
159: return YES;
160: }
161: }
162:
163: - setOrderDescendantSources:(BOOL)aFlag {
164: orderDescendantSources=aFlag;
165: return self;
166: }
167:
168: // ************************************* DATASOURCE PROTOCOL ****************/
169: // The class property names (keys for the dictionary or EOGenericRecord)
170: - (NSArray *)keys {
171: NSArray *attrs = [entity attributes];
172: NSMutableArray *keys = [NSMutableArray array];
173: int i;
174:
175: for (i=0; i<[attrs count]; i++) {
176: EOAttribute *at = [attrs objectAtIndex:i];
177: [keys addObject:[at name]];
178: }
179: return keys;
180: }
181:
182: - createObject{
183: NSNumber *newId=[self findNextPrimaryKey];
184: NSMutableDictionary *newObject=[[NSMutableDictionary alloc] init];
185:
186: if (!newId) return nil;
187: else {
188: NSArray *allKeys = [self keys];
189: NSArray *relations = [entity relationships];
190: int i, count;
191:
192: for (i=0, count=[allKeys count]; i<count; i++) {
193: id key = [allKeys objectAtIndex:i];
194: [newObject setObject:@"" forKey:key];
195: }
196: for (i=0, count=[relations count]; i<count; i++) {
197: EORelationship *rel = [relations objectAtIndex:i];
198: if ([rel isToMany]) [newObject setObject:[newId description] forKey:[rel name]];
199: }
200: [newObject setObject:[newId description] forKey:uniqueKey];
201: return newObject;
202: }
203: }
204:
205: - (BOOL)insertObject:object {
206: NSNumber *objectId=[object objectForKey:uniqueKey];
207: NSArray *allKeys;
208: int i;
209: NSMutableDictionary *newObject;
210:
211: if (![self isValidNewPrimaryKey:objectId]) {
212: NSLog(@"DataSource cannot insert; primary key is invalid: %@", [(NSString *)objectId description]);
213: return NO;
214: }
215:
216: newObject=[NSMutableDictionary dictionary];
217: allKeys = [self keys];
218: for (i=0; i<[allKeys count]; i++) {
219: id key = [allKeys objectAtIndex:i];
220: id value = [object objectForKey:key];
221: if ([value isEqual:[EONull null]]) value=@"";
222: [newObject setObject:value forKey:key];
223: }
224:
225: [eos addObject:newObject];
226: if (orderByKey) [eos sortUsingFunction:eoSort context:orderByKey];
227: [self modifyLookupTables];
228: return YES;
229: }
230:
231: - (BOOL)deleteAllObjects {
232: int i;
233: for (i=[eos count]-1; i>=0; i--) {
234: [eos removeObject:[eos objectAtIndex:i]];
235: }
236: [self modifyLookupTables];
237: return YES;
238: }
239:
240: - (BOOL)deleteObject:object {
241: id primaryKeyValue = [object objectForKey:uniqueKey];
242: NSMutableArray *deletes=[[lookupTables objectForKey:uniqueKey] objectForKey:primaryKeyValue];
243: int i;
244:
245: if ( !deletes || ![deletes count]) return NO;
246: for (i=0; i<[deletes count]; i++) {
247: [eos removeObject:[deletes objectAtIndex:i]];
248: }
249: [self modifyLookupTables];
250: return YES;
251: }
252:
253: - (BOOL)updateObject:object {
254: id primaryKeyValue = [object objectForKey:uniqueKey];
255: NSMutableArray *update=[[lookupTables objectForKey:uniqueKey] objectForKey:primaryKeyValue];
256: NSArray *allKeys;
257: NSMutableDictionary *eo;
258: int i;
259: NSString *key;
260: id value;
261:
262: if (!update || [update count]!=1) return NO;
263: allKeys = [self keys];
264: eo = [update objectAtIndex:0];
265: for (i=0; i<[allKeys count]; i++) {
266: key = [allKeys objectAtIndex:i];
267: value = [object objectForKey:key];
268: if (!value || [value isEqual:[EONull null]] ) value=@"";
269: [eo setObject:value forKey:key];
270: }
271: if (orderByKey) [eos sortUsingFunction:eoSort context:orderByKey];
272: [self modifyLookupTables];
273: return YES;
274: }
275:
276: - (NSArray *)fetchObjects {
277: NSArray *records;
278: NSString *qualifierPropertyKey = [qualifier objectForKey:@QPROPERTY];
279: NSString *qualifierPropertyValue = [qualifier objectForKey:@QVALUE];
280:
281: if (qualifierPropertyKey && qualifierPropertyValue) {
282:
283: if ( [qualifierPropertyValue isEqual:@QUALIFIER_ALL] ) {
284: records =[self eosArrayToGenericRecordsArray:eos];
285: }
286: else {
287: NSMutableDictionary *hash = [lookupTables objectForKey:qualifierPropertyKey];
288: if (!hash) {
289: NSLog(@"No lookup table for qualifier key %@", qualifierPropertyKey);
290: records =nil;
291: }
292: else {
293: NSArray *qualifiedEos=[hash objectForKey:qualifierPropertyValue];
294: if (!qualifiedEos || ![qualifiedEos count]) {
295: //NSLog(@"No qualified eos where %@=%@", qualifierPropertyKey, [(NSString *)qualifierPropertyValue description]);
296: records =nil;
297: }
298: records =[self eosArrayToGenericRecordsArray:qualifiedEos];
299: }
300:
301: }
302: }
303: else {
304: //NSLog(@"nil or uncomplete qualifier %@", [(NSMutableDictionary *)qualifier description]);
305: records = [NSArray array];
306: }
307: //NSLog(@"TABLE %@ - FETCHED OBJECTS: %d", [entity name], [records count]);
308: return records;
309: }
310:
311: // This is where we save the eos to persistent storage (COMMIT in SQL)
312: - (BOOL)saveObjects{
313: NSString *tableFile=[NSString stringWithFormat:@"%@/%@.table", table, [entity name]];
314: if (![[[eos description] dataUsingEncoding:NSASCIIStringEncoding]
315: writeToFile:tableFile atomically:NO]) {
316: NSString *emess;
317:
318: if ( (!access([tableFile cString], F_OK)) && (access([tableFile cString], W_OK)) ) {
319: int ans;
320: emess = [NSString stringWithFormat:@"\'%@\' data source cannot save changes. You do not have write permission on \'%@\'. Do you want to overwrite?", [entity name], tableFile];
321: ans=NXRunAlertPanel("ERROR", [emess cString], "Overwrite", "Cancel", NULL);
322: if (ans==NX_ALERTDEFAULT) return [self forceSaveObjects];
323: else return NO;
324: }
325: else return NO;
326: }
327: else return YES;
328: }
329:
330: - (BOOL)canDelete {
331: return YES;
332: }
333:
334: // Not implemented yet...
335: // Coerce a value to the appropriate type.
336: // This method should convert to either an NSNumber, NSString, NSData,
337: // a custom type, or nil. The value return by this method may be safely
338: // passed to an EO via takeValuesFromDictionary:. This method is used
339: // by controllers to coerce values supplied from associations before
340: // those values are passed on to the EOs.
341: - coerceValue: value forKey: (NSString *)key {
342: // sorry, another day , maybe...
343: return value;
344: }
345:
346: // ****************************** MASTER DATASOURCE PROTOCOL ****************/
347: // What we are doing here is closer to a master-peer than a master-detail setup.
348: // We are creating the detail dataSource and handing it to the association.
349: // This new detail dataSource will be attached to the detail controller automatically
350: // From there, the detail controller and its DetailTableDataSource will be on their own
351: // Also notice than in an EOF master-detail setup, a master eo (meaning an eo
352: // produced by the master source) "carries" its detail eos. In other word if one
353: // writes [masterEo objectForKey:masterDetailRelationshipKey], the master eo will
354: // return an array full of detail eos (assuming that the master detail relationship
355: // was set as a class property name in EOModeler). Although it could, the TableDataSource
356: // does not support that feature...
357: - (id <EOQualifiableDataSources>)dataSourceQualifiedByKey:(NSString *)key {
358: EORelationship *rel = [entity relationshipNamed:key];
359: EOEntity *detailEntity=[rel destinationEntity];
360: DetailTableDataSource *detailSource;
361:
362: if (!rel) return nil;
363: if (!detailEntity) return nil;
364: detailSource = [[(DetailTableDataSource *)[DetailTableDataSource alloc] initWithMasterDataSource:self entity:detailEntity] autorelease];
365: if (orderByKey && orderDescendantSources) [detailSource orderBy:orderByKey];
366:
367: // We cash it (I had some grandiose plane, unused today!!)
368: [detailSources addObject:detailSource];
369: return detailSource;
370: }
371:
372: // ***************************************************************************/
373:
374: // a convenience method to fetch all the objects in the entity
375: - (NSArray *)fetchAllObjects {
376: NSMutableDictionary *qual =[NSMutableDictionary dictionary];
377: [qual setObject:uniqueKey forKey:@QPROPERTY];
378: [qual setObject:@QUALIFIER_ALL forKey:@QVALUE];
379: [self setQualifier:qual];
380: return [self fetchObjects];
381: }
382:
383: // a convenience fetch method
384: - objectForPrimaryKey:value {
385: id hash = [lookupTables objectForKey:uniqueKey];
386: NSArray *objects = [hash objectForKey:value];
387: id object;
388: EOGenericRecord *record;
389: NSMutableDictionary *primaryKeyDictionary;
390: NSString *key;
391: id copy;
392: int count, j;
393: NSArray *allKeys = [self keys];
394:
395: if ([objects count]!=1) return nil;
396: object = [objects objectAtIndex:0];
397: primaryKeyDictionary=[NSMutableDictionary dictionary];
398: [primaryKeyDictionary setObject:value forKey:uniqueKey];
399: record = [[EOGenericRecord alloc] initWithPrimaryKey:primaryKeyDictionary
400: entity:entity];
401: for (j=0, count=[allKeys count]; j<count; j++) {
402: key = [allKeys objectAtIndex:j];
403: copy = [[object objectForKey:key] copy];
404: [record setObject:[copy autorelease] forKey:key];
405: }
406: return [record autorelease];
407: }
408:
409: - addLookupTableForKey:(NSString *)key {
410: NSMutableDictionary *hash;
411:
412: hash = [self createLookupTableForQualifierKey:key];
413: [lookupTables setObject:hash forKey:key];
414: return self;
415: }
416:
417:
418:
419: @end
420:
421: // ************************************* Private Category ****************/
422: @implementation TableDataSource (Private)
423:
424: // This initialization method is called by initWithEntity:tablePath:
425: // It sets the key for the primary key and creates a key->array of objects hash
426: // tables for each element of the qualifiers array.
427: - initWithTable:(NSString *)tablePath primaryKey:(NSString *)primaryKey qualifierKeys:(NSArray *)qualifiers {
428:
429: // Create snapshot from table (eos)
430: table = [tablePath retain];
431: if (![self createSnapshot]) return nil;
432:
433: // Create a dictionary of hash tables for query (key is like @"ID", value is a lookup table)
434: uniqueKey = [primaryKey retain];
435: if ( !([self createLookupTables:qualifiers]) ) return nil;
436:
437: // set qualifier to select everything
438: // To be clean, we should have a qualifier Class, oh well...it is a rush job
439: // after all
440: orderByKey=nil;
441: orderDescendantSources = NO;
442: qualifier = [[NSMutableDictionary alloc] init];
443: [qualifier setObject:uniqueKey forKey:@QPROPERTY];
444: [qualifier setObject:@QUALIFIER_ALL forKey:@QVALUE];
445:
446: return self;
447: }
448:
449: // The flat file is read here and the objects that have been retrieved are cashed
450: // in an instance variable (of class NSMutableArray) called eos
451: - createSnapshot {
452: NSString *tableFile=[NSString stringWithFormat:@"%@/%@", table, [entity externalName]];
453: NSString *tableString;
454: NSArray *nonMutableEos;
455:
456: // read the table file that should be in a property list format
457: // Isn't foundation great or what?!!!
458: if ( !(tableString=[[NSString alloc] initWithContentsOfFile:tableFile]) ||
459: !(nonMutableEos = [tableString propertyList]) ) {
460: NSLog(@"<createSnapshot> Cannot init from table %@", tableFile);
461: return nil;
462: }
463: // Make deep mutable copy
464: else {
465: NSEnumerator *eoEnumerator = [nonMutableEos objectEnumerator];
466: NSDictionary *nonMutableEo;
467:
468: eos = [[NSMutableArray alloc] init];
469: while ( nonMutableEo = [eoEnumerator nextObject] ) {
470: [eos addObject: [[nonMutableEo mutableCopy] autorelease]];
471: }
472: }
473: return self;
474: }
475:
476: // Create the lookup tables (to speed up fetch operations)
477: // The qualifiers array contains the property names that we should hash on
478: // a lookup table is in the form key = "a property key" --> value = an array of
479: // dictionaries...
480: - createLookupTables:(NSArray *)qualifiers {
481: int i, count;
482: NSMutableDictionary *hash;
483: NSString *qualifierKey;
484:
485: lookupTables = [[NSMutableDictionary alloc] init];
486: hash = [self createLookupTableForQualifierKey:uniqueKey];
487: [lookupTables setObject:hash forKey:uniqueKey];
488:
489: for (i=0, count=[qualifiers count]; i<count; i++) {
490:
491: qualifierKey = [qualifiers objectAtIndex:i];
492: if ( ![lookupTables objectForKey:qualifierKey] ) {
493: //NSLog(@"Creating lookup table for qualifierKey %@", qualifierKey);
494: if ( !(hash = [self createLookupTableForQualifierKey:qualifierKey]) ) return nil;
495: [lookupTables setObject:hash forKey:qualifierKey];
496: }
497: }
498: return self;
499: }
500:
501: // Create a lookup table for a given property name (the key)
502: - createLookupTableForQualifierKey:(NSString *)key {
503:
504: NSMutableDictionary *hash;
505: int i, count;
506: NSDictionary *eo;
507: NSMutableArray *values;
508: id value;
509:
510: hash=[NSMutableDictionary dictionary];
511:
512: for (i=0, count=[eos count]; i<count; i++) {
513:
514: eo = [eos objectAtIndex:i];
515:
516: // get the property value for the qualifier key
517: if ( !(value = [eo objectForKey:key]) ) {
518: NSLog(@"<createLookupTableForQualifierKey:> eos dictionary does not respond to qualifier key %@", key);
519: return nil;
520: }
521:
522: // It is the first time that we encounter an eo such that propertyValue(key) = value
523: if ( !(values = [hash objectForKey:value]) ) {
524: values = [NSMutableArray array];
525: [values addObject:eo];
526: [hash setObject:values forKey:[(NSObject *)value description]];
527: }
528: // We have already found an eo such that propertyValue(key) = value
529: else {
530: [values addObject:eo];
531: }
532: }
533: //NSLog(@"HASH FOR KEY %@\n%@", key, [(NSDictionary *)hash description]);
534: return hash;
535: }
536:
537: // After insert, delete or update opeations, the lookup tables must be refreshed
538: - modifyLookupTables {
539: NSArray *allKeys=[lookupTables allKeys];
540: int i, count;
541: NSString *key;
542: NSMutableDictionary *hash;
543:
544: for (i=0, count=[allKeys count]; i<count; i++) {
545: key = [allKeys objectAtIndex:i];
546: [lookupTables removeObjectForKey:key];
547: hash = [self createLookupTableForQualifierKey:key];
548: [lookupTables setObject:hash forKey:key];
549: }
550: return self;
551: }
552:
553: // a method to transform an array of eos (state of the TableDataSource) into
554: // an array of EOGeneric records that we can hand to the outside worls...
555: - (NSMutableArray *)eosArrayToGenericRecordsArray:(NSArray *)eoArray {
556: NSMutableArray *records = [NSMutableArray array];
557: int i, eoCount;
558:
559: for (i=0, eoCount=[eoArray count]; i<eoCount; i++) {
560: NSMutableDictionary *eo;
561: NSMutableDictionary *primaryKey=[NSMutableDictionary dictionary];
562: EOGenericRecord *record;
563: NSArray *allKeys;
564: NSArray *relations;
565: int j, count;
566:
567: eo = [eoArray objectAtIndex:i];
568: allKeys = [self keys];
569: [primaryKey setObject:[eo objectForKey:uniqueKey] forKey:uniqueKey];
570: record = [[[EOGenericRecord alloc] initWithPrimaryKey:primaryKey entity:entity] autorelease];
571: for (j=0, count=[allKeys count]; j<count; j++) {
572: NSString *key = [allKeys objectAtIndex:j];
573: id copy;
574:
575: copy = [[eo objectForKey:key] copy];
576: [record setObject:[copy autorelease] forKey:key];
577: }
578: relations = [entity relationships];
579:
580: // We support master-details but not flattened attribute for now!!!
581: for (j=0, count=[relations count]; j<count; j++) {
582: EORelationship *rel = [relations objectAtIndex:j];
583: id copy;
584:
585: if ([rel isToMany]) {
586: copy = [[eo objectForKey:uniqueKey] copy];
587: [record setObject:[copy autorelease] forKey:[rel name]];
588: }
589:
590: }
591: [records addObject:record];
592: }
593: return records;
594: }
595:
596: // Unlike the EODatabaseDataSource, when a TableDataSource creates a new object
597: // It sets the primary key value for the created object.
598: // This method computes the next available primary key (the next in the sequence)
599: - (NSNumber *)findNextPrimaryKey {
600: int i, max=-1;
601:
602: for (i=0; i<[eos count]; i++) {
603: id eo;
604: int current;
605:
606: eo = [eos objectAtIndex:i];
607: current = [[eo objectForKey:uniqueKey] intValue];
608: if (current > max) max=current;
609: }
610: return [NSNumber numberWithInt:max+1];
611: }
612:
613: // make sure the the primary key of a new object handed for insertion has a valid primary key
614: // (in this case, valid means not used by a record stored in the table)
615: - (BOOL)isValidNewPrimaryKey:(NSNumber *)number {
616: int i;
617:
618: for (i=0; i<[eos count]; i++) {
619: id eo;
620:
621: eo = [eos objectAtIndex:i];
622: if ([number isEqual:[eo objectForKey:uniqueKey]]) return NO;
623: }
624: return YES;
625: }
626:
627: - (BOOL)forceSaveObjects {
628: NSString *tableFile=[NSString stringWithFormat:@"%@/%@.table", table, [entity name]];
629: NSString *emess=@"Cannot overwrite; changes will not be saved...";
630: NSString *cmd;
631:
632: cmd = [NSString stringWithFormat:@"mv %@ %@.otto", tableFile, tableFile];
633: if (system([cmd cString])) {
634: NXRunAlertPanel("ERROR", [emess cString], "OK", NULL, NULL);
635: return NO;
636: }
637: cmd = [NSString stringWithFormat:@"cp %@.otto %@; ", tableFile, tableFile];
638: if (system([cmd cString])) {
639: NXRunAlertPanel("ERROR", [emess cString], "OK", NULL, NULL);
640: return NO;
641: }
642: cmd = [NSString stringWithFormat:@"rm -f %@.otto; chmod u+w %@", tableFile, tableFile];
643: system([cmd cString]);
644:
645: if (![[[eos description] dataUsingEncoding:NSASCIIStringEncoding]
646: writeToFile:tableFile atomically:NO]) return NO;
647: else return YES;
648:
649: }
650:
651: int eoSort(id eo1, id eo2, void *context) {
652: NSString *key=(NSString *)context;
653: NSString *val1, *val2;
654:
655: if (!key) return NSOrderedSame;
656: val1 = [eo1 objectForKey:key];
657: val2 = [eo2 objectForKey:key];
658: if (!val1 || !val2) return NSOrderedSame;
659: else return [val1 compare:val2];
660: }
661:
662:
663: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.