/* ** LibrarySource.m ** ** Copyright (c) 2004 ** ** Author: Yen-Ju Chen ** ** 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "GNUstep.h" #include "LibrarySource.h" #include "AttributesSource.h" #include "CodeParser.h" #include "RenderHandler.h" #include "LinkReplaceHandler.h" #include "Utilities.h" #include "HRCell.h" static LibrarySource *sharedInstance = nil; /* Keys for data source */ static NSString *ItemPathKey = @"ItemPathKey"; static NSString *ItemTypeKey = @"ItemTypeKey"; static NSString *ItemNoteKey = @"ItemNoteKey"; /* Value for ItemType */ static NSString *NoteItem = @"NoteItem"; static NSString *FileItem = @"FileItem"; static NSString *UnknownItem = @"UnknownItem"; @implementation LibrarySource /* Write Accessory */ - (BOOL) _testAndCreateDirectory: (NSString *) dir { NSFileManager *fm = [NSFileManager defaultManager]; BOOL isDir; if ([fm fileExistsAtPath: dir isDirectory: &isDir]) { if (isDir == NO) { return NO; } } else { if ([fm createDirectoryAtPath: dir attributes: nil] == NO) { return NO; } } return YES; } /* For newer format */ - (unsigned int) _reIndexUniqueNumber { NSLog(@"_reIndexUniqueNumber"); unsigned int i, count = [self count]; id object; for(i = 0; i < count; i++) { [self setUniqueNumber: [self uniqueNumber] atIndex: i]; } } - (NSString *) _uniqueName: (NSString *) name { NSString *s, *f; unsigned int i, newItemCount=0; BOOL isUniqueName = YES; while(1) { s = [NSString stringWithFormat: @"%@ %d", name, newItemCount]; for (i = 0; i < [self count]; i++) { if ([s caseInsensitiveCompare: [self titleAtIndex: i]] == NSOrderedSame) { isUniqueName = NO; break; } } if (isUniqueName == YES) break; newItemCount++; isUniqueName = YES; } RETAIN(s); return AUTORELEASE(s); } /* Return path in library */ - (NSString *) _addFileIntoLibrary: (NSString *) source { NSString *path; NSDictionary *fileAttr; NSCalendarDate *creationDate; int year, month, day; NSString *libraryPath = [[self path] stringByDeletingLastPathComponent]; NSFileManager *fm = [NSFileManager defaultManager]; /* get create date of file */ fileAttr = [fm fileAttributesAtPath: source traverseLink: YES]; if (fileAttr == nil) return nil; creationDate = [[fileAttr objectForKey: NSFileCreationDate] dateWithCalendarFormat: nil timeZone: nil]; year = [creationDate yearOfCommonEra]; month = [creationDate monthOfYear]; day = [creationDate dayOfMonth]; // NSLog(@"%d/%2d/%2d", year, month, day); /* create year */ path = [libraryPath stringByAppendingPathComponent: [NSString stringWithFormat: @"%d", year]]; if ([self _testAndCreateDirectory: path] == NO) { return nil; } /* create month */ path = [path stringByAppendingPathComponent: [NSString stringWithFormat: @"%02d", month]]; if ([self _testAndCreateDirectory: path] == NO) { return nil; } /* create date */ path = [path stringByAppendingPathComponent: [NSString stringWithFormat: @"%02d", day]]; if ([self _testAndCreateDirectory: path] == NO) { return nil; } /* put file into directory */ NSString *filename = [source lastPathComponent]; NSString *extension = [source pathExtension]; NSString *newPath = [path stringByAppendingPathComponent: filename]; NSString *newFilename = AUTORELEASE([filename copy]); unsigned int acc = 0; while(1) { if (([fm fileExistsAtPath: newPath]) || ([self isDuplicatedTitle: newFilename])) { /* Searhc a new filename */ newFilename = [NSString stringWithFormat: @"%@_%d", [filename stringByDeletingPathExtension], acc]; newFilename = [newFilename stringByAppendingPathExtension: extension]; newPath = [newPath stringByDeletingLastPathComponent]; newPath = [newPath stringByAppendingPathComponent: newFilename]; acc++; } else break; } //NSLog(@"new %@", newPath); if ([fm copyPath: source toPath: newPath handler: nil] == NO) { return nil; } return AUTORELEASE([newPath copy]);; } /* over-riding superclass */ - (void) removeObjectAtIndex: (unsigned int) index { if (index >= [self count]) return; if ([self typeOfItemAtIndex: index] == ItemFileType) { /* remove file first */ NSFileManager *fm = [NSFileManager defaultManager]; NSString *libraryPath = [[self path] stringByDeletingLastPathComponent]; NSString *path = [libraryPath stringByAppendingPathComponent: [self pathOfItemAtIndex: index]]; //NSLog(@"remove path %@", path); [fm removeFileAtPath: path handler: nil]; } [super removeObjectAtIndex: index]; } - (void) setTitle: (NSString *) title atIndex: (unsigned int) index { [super setTitle: title atIndex: index]; if ([[self objectAtIndex: index forKey: ItemTypeKey] isEqualToString: NoteItem] == YES) { /* Update attributes if necessary */ unsigned int attrIndex, uid; uid = [self uniqueNumberAtIndex: index]; attrIndex = [[AttributesSource sharedSource] indexOfUniqueNumber: uid]; [[AttributesSource sharedSource] setTitle: title atIndex: attrIndex]; [self setObject: title atIndex: index forKey: ItemPathKey]; } } /* high-level methods */ - (void) replaceContentOfLink: (NSString *) oldString withString: (NSString *) newString { unsigned int i, count = [self count]; NSString *content, *newContent; for(i = 0; i < count; i++) { content = [self noteAtIndex: i]; LinkReplaceHandler *handler = [[LinkReplaceHandler alloc] init]; CodeParser *parser = [[CodeParser alloc] initWithCodeHandler: handler withString: content]; [handler setOldLink: oldString replacedBy: newString]; [parser parse]; newContent = [handler updatedString]; [self setNote: newContent atIndex: i]; RELEASE(handler); RELEASE(parser); } } /* write accessory methods */ - (unsigned int) newItem { NSString *s = [self _uniqueName: @"New Note"]; return [self newItemWithTitle: s]; } - (unsigned int) newItemWithTitle: (NSString *) title { unsigned int index = [self newTitle: title]; if (index == NSNotFound) return NSNotFound; [self setObject: title atIndex: index forKey: ItemPathKey]; [self setObject: NoteItem atIndex: index forKey: ItemTypeKey]; return index; } - (unsigned int) newItemWithFile: (NSString *) absolutePath { NSString *path; unsigned int attrIndex, uid; absolutePath = [absolutePath stringByStandardizingPath]; path = [self _addFileIntoLibrary: absolutePath]; if (path == nil) return NSNotFound; NSString *title = [path lastPathComponent]; unsigned int index = [self newTitle: title]; if (index == NSNotFound) return NSNotFound; //NSLog(@"%@", attributesFromFile(path)); NSString *libraryPath = [[self path] stringByDeletingLastPathComponent]; NSString *relativePath = [path substringFromIndex: [libraryPath length]+1]; [self setObject: relativePath atIndex: index forKey: ItemPathKey]; [self setObject: FileItem atIndex: index forKey: ItemTypeKey]; uid = [self uniqueNumberAtIndex: index]; attrIndex = [[AttributesSource sharedSource] newTitle: relativePath]; [[AttributesSource sharedSource] setAttributes: attributesFromFile(path) atIndex: attrIndex]; [[AttributesSource sharedSource] setUniqueNumber: uid atIndex: attrIndex]; return index; } - (void) setPath: (NSString *) path atIndex: (unsigned int) index { if ([[self objectAtIndex: index forKey: ItemTypeKey] isEqualToString: NoteItem]) return; [self setObject: path atIndex: index forKey: ItemPathKey]; } - (void) setNote: (NSString *) note atIndex: (unsigned int) index { if ([note length] == 0) { if ([self objectAtIndex: index forKey: ItemNoteKey]) [self removeObjectForKey: ItemNoteKey atIndex: index]; return; } [self setObject: note atIndex: index forKey: ItemNoteKey]; } /* Items methods */ - (NSString *) pathOfItemAtIndex: (unsigned int) index { return [self objectAtIndex: index forKey: ItemPathKey]; } - (NSString *) fullPathOfItemAtIndex: (unsigned int) index { id path = [self pathOfItemAtIndex: index]; if (path == nil) return nil; NSString *libraryPath = [[self path] stringByDeletingLastPathComponent]; return [libraryPath stringByAppendingPathComponent: path]; } - (ItemType) typeOfItemAtIndex: (unsigned int) index { NSString *value; if (index < [self count]) value = [self objectAtIndex: index forKey: ItemTypeKey]; else return ItemErrorType; if ([value isEqualToString: UnknownItem]) return ItemUnknownType; else if ([value isEqualToString: NoteItem]) return ItemNoteType; else if ([value isEqualToString: FileItem]) return ItemFileType; else return ItemErrorType; } - (NSString *) typeStringOfItemAtIndex: (unsigned int) index { switch ([self typeOfItemAtIndex: index]) { case ItemNoteType: return sNote; case ItemFileType: return [[self pathOfItemAtIndex: index] pathExtension]; default: return sUnknown; } } - (NSAttributedString *) renderedNoteAtIndex: (unsigned int) index { if (index < [self count]) { CodeParser *parser; NSAttributedString *as; NSString *content = [self noteAtIndex: index]; if (content == nil) return nil; // Render page RenderHandler *handler = [[RenderHandler alloc] init]; parser = [[CodeParser alloc] initWithCodeHandler: handler withString: content]; NS_DURING [parser parse]; NS_HANDLER NSLog(@"MyLibrary Error: render fails"); AUTORELEASE(handler); AUTORELEASE(parser); if ([[localException name] isEqualToString: RenderTextExceptionName]) { [localException raise]; } NS_ENDHANDLER as = [handler renderedString]; RETAIN(as); AUTORELEASE(handler); AUTORELEASE(parser); return AUTORELEASE(as); } else return nil; } - (NSString *) noteAtIndex: (unsigned int) index { return [self objectAtIndex: index forKey: ItemNoteKey]; } - (NSAttributedString *) summaryAtIndex: (unsigned int) index maximalAttributes: (int) max style: (SummaryStyle) style { NSMutableAttributedString *summary = [[NSMutableAttributedString alloc] init]; NSAttributedString *as; NSString *title, *path; NSString *s, *s1, *summaryStyle, *format; NSDictionary *attr, *dict; NSFont *font; NSColor *color; NSTextAttachment *textAttachment; HRCell *hr = [HRCell sharedHRCell]; AttributesSource *attributes = [AttributesSource sharedSource]; NSNumberFormatter *numberFormatter; int intNumber; unsigned int attrIndex, uid; unsigned int i, count; switch (style) { case SummaryBriefStyle: summaryStyle = sBriefSummary; break; case SummaryRegularStyle: summaryStyle = sRegularSummary; break; case SummaryCompleteStyle: summaryStyle = sCompleteSummary; break; } #define ADD_s_attr_INTO_summary \ as = [[NSAttributedString alloc] initWithString: s \ attributes: attr]; \ [summary appendAttributedString: as]; \ RELEASE(as); /* Title */ title = [self titleAtIndex: index]; uid = [self uniqueNumberAtIndex: index]; s = [NSString stringWithFormat: @"%@\n", title]; font = [NSFont boldSystemFontOfSize: 14]; attr = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, nil]; ADD_s_attr_INTO_summary; #if 0 /* Summary style */ if (flag == YES) s = [NSString stringWithFormat: @" (%@)\n", summaryStyle]; else s = [NSString stringWithFormat: @"\n"]; font = [NSFont boldSystemFontOfSize: 14]; color = [NSColor lightGrayColor]; attr = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, color, NSForegroundColorAttributeName, nil]; ADD_s_attr_INTO_summary; #endif /* Horizontal bar */ textAttachment = [[NSTextAttachment alloc] init]; [textAttachment setAttachmentCell: hr]; [summary appendAttributedString: [NSAttributedString attributedStringWithAttachment: textAttachment]]; RELEASE(textAttachment); s = @"\n"; font = [NSFont userFontOfSize: 12]; attr = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, nil]; ADD_s_attr_INTO_summary; path = [self pathOfItemAtIndex: index]; attrIndex = [attributes indexOfUniqueNumber: uid]; /* Make sure file does exist */ if ([path isEqualToString: title] == NO) { /* Make sure file does exist */ s = [path lastPathComponent]; /* file size */ intNumber = [[attributes attributeAtIndex: attrIndex forName: FileInfoSize] intValue]; if (intNumber) { numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setLocalizesFormat: YES]; format = [numberFormatter positiveFormat]; format = [format substringToIndex: [format length]-2]; [numberFormatter setFormat: format]; // [numberFormatter setFormat: @"#,##0"]; s1 = [numberFormatter stringForObjectValue: [NSNumber numberWithInt: intNumber]]; s = [NSString stringWithFormat: @"%@ (%@ %@)\n\n", s, s1, sBytes]; RELEASE(numberFormatter); } font = [NSFont userFontOfSize: 12]; font = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSItalicFontMask]; attr = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, nil]; ADD_s_attr_INTO_summary; } /* attributes */ dict = [attributes attributesAtIndex: attrIndex]; if (dict) { id key, value; NSString *category, *name, *type; NSMutableDictionary *d1 = [[NSMutableDictionary alloc] init]; NSArray *allKeys = [dict allKeys]; NSMutableArray *a1; count = [allKeys count]; for(i = 0; i < count; i++) { if ((i >= max) && (max >= 0)) break; key = [allKeys objectAtIndex: i]; category = categoryOfAttributeKey(key); if ([category isEqualToString: FileInfoCategory]) continue; name = nameOfAttributeKey(key); type = typeOfAttributeKey(key); value = [dict objectForKey: key]; /* format attribute */ if ([type isEqualToString: AttributeStringType]) { s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, value]; } else if ([type isEqualToString: AttributeFloatType]) { #if 0 numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setLocalizesFormat: YES]; s1 = [numberFormatter stringForObjectValue: value]; s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, s1]; RELEASE(numberFormatter); #endif s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, value]; } else if ([type isEqualToString: AttributeIntegerType]) { #if 0 numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setLocalizesFormat: YES]; format = [numberFormatter positiveFormat]; format = [format substringToIndex: [format length]-2]; [numberFormatter setFormat: format]; s1 = [numberFormatter stringForObjectValue: value]; s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, s1]; RELEASE(numberFormatter); #endif s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, value]; } else if ([type isEqualToString: AttributeCalendarDateType]) { s1 = [value descriptionWithCalendarFormat: CalendarDateDisplayFormat timeZone: nil locale: nil]; s1 = [NSString stringWithFormat: @"\t%@: \t%@", name, s1]; } else { NSLog(@"Internal Error: Invalide AttributeType for summary"); continue; } a1 = [d1 objectForKey: category]; if (a1 == nil) { a1 = [[NSMutableArray alloc] init]; [d1 setObject: a1 forKey: category]; RELEASE(a1); } [a1 addObject: s1]; } /* Go through all attributes */ allKeys = [d1 allKeys]; count = [allKeys count]; for(i = 0; i < count; i++) { category = [allKeys objectAtIndex: i]; if (count > 1) /* Put category only for multiple categories */ { s = [NSString stringWithFormat: @"%@\n", category]; font = [NSFont userFontOfSize: 12]; font = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSBoldFontMask]; attr = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, nil]; ADD_s_attr_INTO_summary; } a1 = [d1 objectForKey: category]; [a1 sortUsingSelector: @selector(compare:)]; NSEnumerator *e = [a1 objectEnumerator]; id object; font = [NSFont userFontOfSize: 12]; attr = [NSDictionary dictionaryWithObjectsAndKeys: font, NSFontAttributeName, nil]; while ((object = [e nextObject])) { s = [NSString stringWithFormat: @"%@\n", object]; ADD_s_attr_INTO_summary; } s = @"\n"; ADD_s_attr_INTO_summary; } } /* rendered note */ as = [self renderedNoteAtIndex: index]; if (as) { unsigned int length = [as length]; s = @""; switch (style) { case SummaryBriefStyle: if (length > 200) { length = 200; s = @"..."; } break; case SummaryRegularStyle: if (length > 500) { length = 500; s = @"..."; } break; case SummaryCompleteStyle: break; } as = [as attributedSubstringFromRange: NSMakeRange(0, length)]; [summary appendAttributedString: as]; ADD_s_attr_INTO_summary; } /* return */ return AUTORELEASE(summary); } /* Basic methods */ + (LibrarySource *) sharedSource { if (sharedInstance == nil) { sharedInstance = [[LibrarySource alloc] init]; } return sharedInstance; } - (BOOL) loadSourceAtPath: (NSString *) path { BOOL isDir, loadResult; NSFileManager *fm = [NSFileManager defaultManager]; if ([super loadSourceAtPath: path] == NO) return NO; NSString *libraryFile = [self path]; NSArray *array; if (libraryFile == nil) return NO; if ([fm fileExistsAtPath: libraryFile isDirectory: &isDir]) { if (isDir) { NSRunAlertPanel(sFileError, [NSString stringWithFormat: sLibraryCorrupted_, libraryFile], sQuit, nil, nil, nil); return NO; } else { /* Should be correct one */ loadResult = [self readFromFile: libraryFile]; if (loadResult == NO) { NSRunAlertPanel(sFileError, [NSString stringWithFormat: sNotValidLibrary_, libraryFile], sQuit, nil, nil, nil); return NO; } else { makeBackup(libraryFile); /* Check whether it is a new version */ if (([self count] > 0) && ([self uniqueNumberAtIndex: 0] == NSNotFound)) { [self _reIndexUniqueNumber]; } return YES; } } } else { /* Check backup file */ NSString *backup = [libraryFile stringByAppendingString: @"~"]; loadResult = [self readFromFile: backup]; if (loadResult == YES) { int result = NSRunAlertPanel(sFileError, [NSString stringWithFormat: sFileNotExistWantBackup____, sLibrary, libraryFile, sLibrary, sLibrary], sYES, sNO, nil, nil); if (result == NSAlertDefaultReturn) { /* Check whether it is a new version */ if (([self count] > 0) && ([self uniqueNumberAtIndex: 0] == NSNotFound)) { [self _reIndexUniqueNumber]; } return YES; } } } [container removeAllObjects]; [self newItemWithTitle: sHome]; return YES; } - (BOOL) unloadSourceAtPath: (NSString *) absolutePath { if ([super unloadSourceAtPath: nil] == NO) { int result = NSRunAlertPanel(sFileErrorWrite, [NSString stringWithFormat: sCannotSaveLibrary_, [self path]], sDontQuit, sQuit, nil, nil); if (result == NSAlertDefaultReturn) return NO; } return YES; } - (void) dealloc { [super dealloc]; } @end