// // LogManager.m // Pennyworth // // Created by Chris Karr on 1/1/08. // Copyright 2008 Chris J. Karr. All rights reserved. // #import "LogManager.h" #import "Observation.h" #define LOG_PATH [NSString stringWithFormat:@"%@/Library/Application Support/Pennyworth/Logs/Log.log", NSHomeDirectory ()] #define OBSERVATIONS @"Observations" #define SENSOR @"Sensor" #define OBSERVATION @"Observation" #define LOGS @"Logs" #define LOG_INTERVAL 60 #define DATE_FORMAT @"yyyy-MM-dd" #define TIME_FORMAT @"hh:mm:ss" #define LOG_SCREEN @"Log Screen" #define ENABLE_SCREENSHOTS @"Enable Screenshots" @implementation LogManager @synthesize root; @synthesize logDict; @synthesize paused; @synthesize pauseTimer; @synthesize logTimer; - (NSImage *) imageWithScreenShotInRect:(NSRect)cocoaRect { PicHandle picHandle; GDHandle mainDevice; Rect rect; NSImage *image; NSImageRep *imageRep; // Convert NSRect to Rect SetRect (&rect, NSMinX (cocoaRect), NSMinY (cocoaRect), NSMaxX (cocoaRect), NSMaxY (cocoaRect)); // Get the main screen. I may want to add support for multiple screens later mainDevice = GetMainDevice (); // Capture the screen into the PicHandle. picHandle = OpenPicture (&rect); CopyBits ((BitMap *)* (**mainDevice).gdPMap, (BitMap *)* (**mainDevice).gdPMap, &rect, &rect, srcCopy, 0l); ClosePicture(); // Convert the PicHandle into an NSImage // First lock the PicHandle so it doesn't move in memory while we copy HLock((Handle)picHandle); imageRep = [NSPICTImageRep imageRepWithData:[NSData dataWithBytes:(*picHandle) length:GetHandleSize((Handle)picHandle)]]; HUnlock((Handle)picHandle); // We can release the PicHandle now that we're done with it KillPicture(picHandle); // Create an image with the representation image = [[[NSImage alloc] initWithSize:[imageRep size]] autorelease]; [image addRepresentation:imageRep]; return image; } - (NSData *) dataForScreen:(NSScreen *) screen { NSRect rect = [screen frame]; rect.origin.y = [[NSScreen mainScreen] frame].size.height - rect.size.height; NSImage * image = [self imageWithScreenShotInRect:rect]; NSData * imageData = [image TIFFRepresentation]; NSBitmapImageRep * imageRep = [NSBitmapImageRep imageRepWithData:imageData]; NSDictionary * imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.5] forKey:NSImageCompressionFactor]; imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps]; return imageData; } - (NSString *) logScreen { NSData * jpg = [self dataForScreen:[NSScreen mainScreen]]; NSString * imgPath = [NSString stringWithFormat:@"%@/%@.jpg", [LOG_PATH stringByDeletingLastPathComponent], [[NSDate date] description]]; [jpg writeToFile:imgPath atomically:YES]; return imgPath; } - (void) refresh { [NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehavior10_4]; NSDateFormatter * timeFormatter = [[NSDateFormatter alloc] init]; [timeFormatter setDateFormat:TIME_FORMAT]; NSMutableArray * childArray = [self.root mutableChildNodes]; [childArray removeAllObjects]; for (NSString * day in [[logDict allKeys] sortedArrayUsingSelector:@selector(compare:)]) { NSDictionary * dayLog = [logDict valueForKey:day]; NSTreeNode * dayNode = [NSTreeNode treeNodeWithRepresentedObject:[NSDictionary dictionaryWithObjectsAndKeys:day, @"title", nil]]; [childArray addObject:dayNode]; for (NSDictionary * eachLog in [dayLog valueForKey:OBSERVATIONS]) { NSMutableDictionary * mutableEachLog = [NSMutableDictionary dictionaryWithDictionary:eachLog]; [mutableEachLog setValue:[timeFormatter stringFromDate:[eachLog valueForKey:LOG_DATE]] forKey:@"title"]; NSMutableArray * obs = [NSMutableArray array]; for (NSString * key in [eachLog allKeys]) { NSObject * observation = [eachLog valueForKey:key]; if ([observation isKindOfClass:[NSArray class]]) { NSMutableString * value = [NSMutableString stringWithString:@"[ "]; NSArray * os = (NSArray *) observation; for (NSObject * o in os) { [value appendString:[o description]]; [value appendString:@", "]; } [value appendString:@"]"]; observation = value; } [obs addObject:[NSDictionary dictionaryWithObjectsAndKeys:key, SENSOR, observation, OBSERVATION, nil]]; } [mutableEachLog setValue:obs forKey:OBSERVATIONS]; NSTreeNode * eachNode = [NSTreeNode treeNodeWithRepresentedObject:mutableEachLog]; [[dayNode mutableChildNodes] addObject:eachNode]; } } [timeFormatter release]; } -(void) load { NSFileManager * fm = [NSFileManager defaultManager]; BOOL isDir = NO; [fm fileExistsAtPath:[LOG_PATH stringByDeletingLastPathComponent] isDirectory:&isDir]; if (!isDir) [fm createDirectoryAtPath:[LOG_PATH stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; if ([fm fileExistsAtPath:LOG_PATH isDirectory:&isDir]) { NSData * data = [NSData dataWithContentsOfFile:LOG_PATH]; NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; self.logDict = [NSMutableDictionary dictionaryWithDictionary:[unarchiver decodeObjectForKey:LOGS]]; [unarchiver release]; } else self.logDict = [NSMutableDictionary dictionary]; [self refresh]; } - (void) startLogging { self.logTimer = [NSTimer scheduledTimerWithTimeInterval:LOG_INTERVAL target:self selector:@selector(update:) userInfo:nil repeats:YES]; self.paused = [NSNumber numberWithBool:NO]; } - (void) stopLogging { [self.logTimer invalidate]; self.logTimer = nil; } - (void) observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context { if ([keyPath isEqualToString:ENABLE_LOGGING]) { if ([[NSUserDefaults standardUserDefaults] boolForKey:ENABLE_LOGGING]) [self startLogging]; else [self stopLogging]; } } - (void) awakeFromNib { [NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehavior10_4]; formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:DATE_FORMAT]; self.root = [NSTreeNode treeNodeWithRepresentedObject:[NSDictionary dictionaryWithObjectsAndKeys:@"Saved Logs", @"title", nil]]; [self load]; if ([[NSUserDefaults standardUserDefaults] boolForKey:ENABLE_LOGGING]) [self startLogging]; [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:ENABLE_LOGGING options:0 context:NULL]; } - (IBAction) pauseLogging:(id) sender { self.paused = [NSNumber numberWithBool:YES]; self.pauseTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(resumeLogging:) userInfo:nil repeats:NO]; NSRunAlertPanel (@"Logging Paused", @"Logging is now paused. Logging will resume in 30 minutes.", @"OK", nil, nil); } - (void) resumeLogging:(NSTimer *) theTimer { NSRunAlertPanel (@"Logging Resumed", @"Logging will now resume.", @"OK", nil, nil); self.paused = [NSNumber numberWithBool:NO]; self.pauseTimer = nil; } - (void) update:(NSNotification *) note { if ([paused boolValue]) return; if (![[NSUserDefaults standardUserDefaults] boolForKey:ENABLE_LOGGING]) return; [[NSNotificationCenter defaultCenter] postNotificationName:LOG_PREDICTIONS object:self]; NSMutableDictionary * dict = [NSMutableDictionary dictionary]; for (Observation * obs in [observations arrangedObjects]) [dict setValue:obs.observation forKey:obs.sensor]; [dict setValue:[NSDate date] forKey:LOG_DATE]; if ([[NSUserDefaults standardUserDefaults] boolForKey:ENABLE_SCREENSHOTS]) [dict setValue:[self logScreen] forKey:LOG_SCREEN]; NSMutableDictionary * log = [logDict valueForKey:[formatter stringFromDate:[NSDate date]]]; if (log == nil) { log = [NSMutableDictionary dictionary]; [logDict setValue:log forKey:[formatter stringFromDate:[NSDate date]]]; } NSMutableArray * obsArray = [log valueForKey:OBSERVATIONS]; if (obsArray == nil) { obsArray = [NSMutableArray array]; [log setValue:obsArray forKey:OBSERVATIONS]; } [obsArray addObject:dict]; NSMutableData * data = [NSMutableData data]; NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [archiver encodeObject:logDict forKey:LOGS]; [archiver finishEncoding]; [data writeToFile:LOG_PATH atomically:YES]; [archiver release]; [[NSSound soundNamed:@"screen-grab"] play]; [self refresh]; } - (void) deleteNodeWithContent:(id) content tree:(NSTreeNode *) tree { NSMutableArray * childs = [tree mutableChildNodes]; NSMutableArray * deletes = [NSMutableArray array]; for (NSTreeNode * node in childs) { if ([node representedObject] == content) [deletes addObject:node]; [self deleteNodeWithContent:content tree:node]; } [childs removeObjectsInArray:deletes]; } - (IBAction) deleteLog:(id) sender { if (selectedLog != nil) { NSDictionary * dict = [selectedLog content]; [self deleteNodeWithContent:dict tree:self.root]; for (NSDictionary * day in [logDict allValues]) { NSDictionary * toDelete = nil; NSMutableArray * logs = [day valueForKey:OBSERVATIONS]; for (NSDictionary * log in logs) { if ([[dict valueForKey:LOG_DATE] isEqual:[log valueForKey:LOG_DATE]]) toDelete = log; } if (toDelete != nil) [logs removeObject:toDelete]; } NSMutableData * data = [NSMutableData data]; NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [archiver encodeObject:logDict forKey:LOGS]; [archiver finishEncoding]; [data writeToFile:LOG_PATH atomically:YES]; [archiver release]; [[NSFileManager defaultManager] removeFileAtPath:[dict valueForKey:LOG_SCREEN] handler:nil]; } } - (IBAction) screenshot:(id) sender { NSDictionary * object = [selectedLog content]; NSImage * image = [[NSImage alloc] initWithContentsOfFile:[object valueForKey:LOG_SCREEN]]; NSSize size = [image size]; [image release]; [screenPanel setContentSize:NSMakeSize(size.width / 2, size.height / 2)]; [screenPanel setContentAspectRatio:size]; [screenPanel setContentMaxSize:size]; [screenPanel makeKeyAndOrderFront:sender]; } @end