sage-iphone-app / Classes / AlephCell.m

//
//  AlephCell.m
//  iSage
//
//  Created by Ivan Andrus on 4/29/11.
//  Copyright 2011 Ivan Andrus. All rights reserved.
//

#import "AlephCell.h"
#import "URLutils.h"
#import "JSONKit.h"

@implementation AlephCell

@dynamic status;
@dynamic alephid;
@dynamic creation_time;
@dynamic input;
@dynamic output;
@dynamic html_output;
@dynamic language;
@dynamic image;

@synthesize numRequests;
@synthesize isWorking;


- (void)alloc {
    isWorking = NO;
}

- (NSString*)alephURL{
    NSString* alephURL = [[NSUserDefaults standardUserDefaults] stringForKey:@"sageServer"];
    if ( [alephURL isEqual:@"CustomURL"] ) {
        alephURL = [[NSUserDefaults standardUserDefaults] stringForKey:@"customSageServer"];
    }
    return alephURL;
}

- (BOOL)canDisplayOutput{

    switch ( [self.status intValue] ) {
        case AlephCellStatusHasOutput:
        case AlephCellStatusSyntaxError:
            return YES;

        case AlephCellStatusNetworkError:
        case AlephCellStatusJSONParseError:
        case AlephCellStatusUninitialized:
        case AlephCellStatusHasInput:
        case AlephCellStatusHasAlephID:
        default:
            return NO;
    }
}

// TODO: should make the caller control isWorking instead of forcing them to make the request
- (NSDictionary*)needsRequest:(BOOL)retryErrors {
    NSLog(@"needsRequest");
    // TODO: reset errors here

    // The only time it's safe to make another request over the top of the first
    const int status = [self.status intValue];
    if ( isWorking && status != AlephCellStatusHasAlephID && !retryErrors ) {
        return nil;
    }

    switch ( status ) {

        case AlephCellStatusNetworkError:
        case AlephCellStatusJSONParseError:
            // Handle Error cases
            if ( !retryErrors ) {
                isWorking = NO;
                return nil;
            }
            // Fall through to the computation request

        case AlephCellStatusHasInput:
            // Request an id
            // NSLog(@"request id for:%@",self.input);
            isWorking = YES;
            numRequests++;
            self.alephid = [NSString createUUID];
            return [NSDictionary dictionaryWithObjectsAndKeys:
                    [NSURL URLWithString:[NSString stringWithFormat:@"%@eval",[self alephURL]]], @"URL",
                    [NSDictionary dictionaryWithObjectsAndKeys:
                     self.alephid, @"session_id",
                     [NSString createUUID], @"msg_id",
                     @"true", @"sage_mode",
                     [self.input JSONString], @"commands",
                     nil], @"post_data",
                    nil];

        case AlephCellStatusHasAlephID:
            // Request status
            // NSLog(@"working on %@",self.alephid);
            isWorking = YES;
            numRequests++;
            return [NSDictionary dictionaryWithObject:
                    [NSURL URLWithString:[NSString stringWithFormat:@"%@output_poll?computation_id=%@",
                                          [self alephURL], self.alephid]]
                                               forKey:@"URL"];

        case AlephCellStatusHasOutput:
        case AlephCellStatusSyntaxError:
        case AlephCellStatusUninitialized:
        default:
            isWorking = NO;
            return nil;
    }
    return nil;
}


- (void)clearError{
    isWorking = NO;
    numRequests = 0;
    if ( self.input ) {
        self.status = [NSNumber numberWithInt:AlephCellStatusHasInput];
    } else {
        self.status = [NSNumber numberWithInt:AlephCellStatusUninitialized];
    }
}

- (void)setError:(BOOL)wasNetworkError{
    isWorking = NO;
    self.status = [NSNumber numberWithInt:
                   (wasNetworkError ? AlephCellStatusNetworkError : AlephCellStatusJSONParseError)];
}

// Return YES if should re-request this
- (BOOL)updateWithResponse:(id)response{
    NSLog(@"updateWithResponse");
    isWorking = NO;
    // NSLog(@"updateWithResponse: %@",response);

    // Fake update
    switch ( [self.status intValue] ) {

        case AlephCellStatusNetworkError:
        case AlephCellStatusJSONParseError:
            NSLog(@"Something is rotten in Denmark");
            break;

        case AlephCellStatusHasInput:
            // getting an id
            // NSLog(@"Got an id: %@ %@",[response class], response);
            if ( [response isKindOfClass:[NSString class]] ) {
                // self.alephid = (NSString*)response;
                self.status = [NSNumber numberWithInt:AlephCellStatusHasAlephID];
            } else if ([response isKindOfClass:[NSDictionary class]] &&
                       [[response objectForKey:@"session_id"] isKindOfClass:[NSString class]])
            {
                self.alephid = (NSString*)[response objectForKey:@"session_id"];
                self.status = [NSNumber numberWithInt:AlephCellStatusHasAlephID];
                NSLog(@"New style session_id: %@",self.alephid);

            } else {
                NSLog(@"This is a problem: %@",response);
                self.status = [NSNumber numberWithInt:AlephCellStatusJSONParseError];
            }
            numRequests = 0;
            break;

        case AlephCellStatusHasAlephID:

            if ( ! [response isKindOfClass:[NSDictionary class]] ) {
                self.status = [NSNumber numberWithInt:AlephCellStatusJSONParseError];
                break;
            }
            // NSLog(@"response: %@",response);
            NSArray * content = [response objectForKey:@"content"];
            if ( !content || ![content isKindOfClass:[NSArray class]] ) {
                // This means there is nothing to show.
                return YES;
            }

            // We have new output, so get rid of any old stuff we might have
            self.image = nil;
            // We add a newline before any output, so we will strip that off before returning
            self.output = @"";
            for (id resp in content) {

                if ( ![resp isKindOfClass:[NSDictionary class]] )  continue;

                NSDictionary * content = [resp objectForKey:@"content"];
                if ( ![content isKindOfClass:[NSDictionary class]] ) continue;

                NSString * messageType = [resp objectForKey:@"msg_type"];
                // TODO: factor out as much as I can
                if ( [messageType isEqual:@"extension"] ) {

                    // End of a session so we don't need to make any more requests
                    if ( [[content objectForKey:@"msg_type"] isEqualToString:@"session_end"] ) {
                        // If we haven't set the status to something else, set it to success
                        if ( [self.status isEqualToNumber:[NSNumber numberWithInt:AlephCellStatusHasAlephID]] ) {
                            self.status = [NSNumber numberWithInt:AlephCellStatusHasOutput];
                        }
                        if ([self.output length] > 0) {
                            self.output = [self.output substringFromIndex:1];
                        }
                        return NO;
                    }

                } else if ( [messageType isEqual:@"pyout"] ) {

                    id data = [content objectForKey:@"data"];
                    if ( [data isKindOfClass:[NSDictionary class]] ) {
                        self.output = [self.output stringByAppendingFormat:@"\n%@",
                                       [data objectForKey:@"text/plain"]];
                    } else if ( [data isKindOfClass:[NSString class]] ) {
                        self.output = [self.output stringByAppendingFormat:@"\n%@", data];
                    } else {
                        self.output = [self.output stringByAppendingString:@"\nUnable to parse output"];
                    }

                } else if ( [messageType isEqual:@"stream"] ) {
                    if ( [[content objectForKey:@"name"] isEqualToString:@"stdout"] ||
                         [[content objectForKey:@"name"] isEqualToString:@"stderr"] )
                    {
                        self.output = [self.output stringByAppendingFormat:@"\n%@",
                                       [content objectForKey:@"data"]];
                    }

                } else if ( [messageType isEqual:@"execute_reply"] ) {

                    NSString * status = [content objectForKey:@"status"];
                    if ( [status isEqual:@"ok"] ) {
                        // Status will be set at end_session
                        // self.status = [NSNumber numberWithInt: AlephCellStatusHasOutput];

                    } else if ( [status isEqual:@"error"] ) {
                        self.status = [NSNumber numberWithInt: AlephCellStatusSyntaxError];
                        self.output = [self.output stringByAppendingFormat:@"\n%@:",
                                       [content objectForKey:@"evalue"]];

                        // Strip escape sequences and concatenate the traceback
                        id traceback = [content objectForKey:@"traceback"];
                        if ( [traceback isKindOfClass:[NSString class]] ) {
                            self.output = [self.output stringByAppendingFormat:@"\n%@",
                                           [traceback stripEscapeSequences]];
                        } else if ( [traceback isKindOfClass:[NSDictionary class]] ) {
                            for (NSString *frame in traceback) {
                                if ( [frame isKindOfClass:[NSString class]] ) {
                                    self.output = [self.output stringByAppendingFormat:@"\n%@",
                                                   [frame stripEscapeSequences]];
                                }
                            }
                        } else {
                            self.output = [self.output stringByAppendingString:@"\nUnable to parse exception"];
                        }
                    }

                } else if ( [messageType isEqual:@"display_data"] ) {

                    NSString * fileName = [[content objectForKey:@"data"] objectForKey:@"text/filename"];
                    if ( [fileName isEqual:@"sage0.png"] ) { // TODO: Horrible hack to only load the first and ensure it's a png

                        NSString * path = [NSString stringWithFormat:@"%@files/%@/%@",
                                           [self alephURL],
                                           self.alephid,
                                           fileName];
                        NSURL * imageURL = [NSURL URLWithString:path];
                        NSData * imgData = [NSData dataWithContentsOfURL:imageURL];
                        self.image = imgData;

                    } else {
                        NSLog(@"Ignoring file with name: %@",fileName);
                    }
                }
            }
            if ([self.output length] > 0) {
                self.output = [self.output substringFromIndex:1];
            }
            // We didn't get the whole response back so ask for more
            return YES;

        case AlephCellStatusHasOutput:
            NSLog(@"This shouldn't happen either: %@",response);

        case AlephCellStatusSyntaxError:
        case AlephCellStatusUninitialized:
        default:
            // NSLog(@"nothing to request");
            isWorking = NO;
            numRequests = 0;
    }
    return NO;
}


@end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.