//
//  AppDelegate.m
//  nitoTV4Installer
//
//  Created by Kevin Bradley on 1/24/18.
//  Copyright © 2018 Kevin Bradley. All rights reserved.
//

#import "AppDelegate.h"
#import "ATVDeviceController.h"

@interface AppDelegate ()

@property (weak) IBOutlet NSWindow *window;
//@property (readwrite, assign) BOOL atvAvailable;
//@property (nonatomic, strong) NSString *bundleID;
@property (nonatomic, strong) IBOutlet ATVDeviceController *deviceController;
@end

@implementation AppDelegate

@synthesize window,workingPath, sshSession, isSending, downloading, downloadFile;
@synthesize deviceController;

static NSString *appleTVAddress = nil;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    
    //deviceController = [[ATVDeviceController alloc] init];
    //variables that was a carry over from code i migrated this from, can probably be pruned, dont think i use it.
    appleTVAddress = APPLE_TV_ADDRESS;
    NSLog(@"SELECTED_VALUE: %@", SELECTED_VALUE);
    if (SELECTED_VALUE != nil)
    {
        [deviceController.theComboBox selectItemWithTitle:SELECTED_VALUE];
        
        [deviceController menuItemSelected:deviceController.theComboBox];
    }
    
    NSLog(@"appleTVAddress: %@", appleTVAddress);
    
    if ([[appleTVAddress componentsSeparatedByString:@":"] count] < 2)
    {
        [self resetServerSettings];
        
    }
    
}

- (NSString *)appSupportFolder
{
    NSString *supportFolder = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Application Support/nitoDownload"];
    NSLog(@"dir: %@", supportFolder);
    if (![FM fileExistsAtPath:supportFolder])
    {
        [FM createDirectoryAtPath:supportFolder withIntermediateDirectories:YES attributes:nil error:nil];
    }
    return supportFolder;
}

+ (NSArray *)returnForProcess:(NSString *)call
{
    if (call==nil)
        return 0;
    char line[200];
    
    FILE* fp = popen([call UTF8String], "r");
    NSMutableArray *lines = [[NSMutableArray alloc]init];
    if (fp)
    {
        while (fgets(line, sizeof line, fp))
        {
            NSString *s = [NSString stringWithCString:line encoding:NSUTF8StringEncoding];
            s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
            [lines addObject:s];
        }
    }
    pclose(fp);
    return lines;
}
//59293ffbdee698488aba799a96fcfe95e1165fa10f760362a2ce910576134e39
- (BOOL)verifyChecksum:(NSString *)checksum onFile:(NSString *)file
{
    NSString *processReturn = [[AppDelegate returnForProcess:[NSString stringWithFormat:@"/usr/bin/shasum -a 256 \"%@\" | cut -c 1-64", file]] componentsJoinedByString:@"\n"];
    NSLog(@"return: -%@-", processReturn);
    return [processReturn isEqualToString:checksum];
    
}

- (void)downloadFile:(NSString *)thePayload toLocation:(NSString *)downloadLocation
{
    statusLabel.stringValue = [NSString stringWithFormat:@"Downloading: %@...", thePayload.lastPathComponent];
    
    //get the stream we want to download
    
    
    
    [downloadFile downloadFileWithURL:[NSURL URLWithString:thePayload] toLocation:downloadLocation progress:^(double percentComplete) {
        
        [self setDownloadProgress:percentComplete];
        
    } completed:^(NSString *downloadedFile) {
        //[self hideProgress];
        self.downloading = false;
        
        [self setDownloadProgress:0];
        statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", downloadedFile.lastPathComponent];
        
        
        [self handleDownloadFile:downloadedFile];
        
        
        
        //[self uploadFile:downloadedFile];
        
    }];
}

- (void)processFile:(NSString *)thePayload
{
    //we're already downloading, cancel
    //TODO: make downloading NSOperation/NSOperationQueue based
    if (self.downloading == true)
    {
        [downloadFile cancel];
        self.downloading = false;
        statusLabel.stringValue = @"";
        [self.progressInd setDoubleValue:0];
        [self.progressInd setHidden:TRUE];
        self.mainButton.enabled = TRUE;
        return;
    }
    //create instance of downloader class
    downloadFile = [KBDownloadFile new];;
    //self.downloadButton.title = @"Cancel";
    self.downloading = true;
    
    NSString *downloadLocation = [[self appSupportFolder] stringByAppendingPathComponent:[thePayload lastPathComponent]];
    
    if ([FM fileExistsAtPath:downloadLocation])
    {
        NSString *hash = @"59293ffbdee698488aba799a96fcfe95e1165fa10f760362a2ce910576134e39";
        switch (self.versionState) {
            case KBInstallVersionStateEleven:
            case KBInstallVersionStateTenTwo:
                
                hash = @"59293ffbdee698488aba799a96fcfe95e1165fa10f760362a2ce910576134e39";
                break;
                
            case KBInstallVersionStateNine:
            case KBInstallVersionStateTenOne:
                
                hash = @"327c7cea49282109e23c9415d318121f91b79ca7bb813c8724a1c0752e8311af";
                break;
                
            default:
                break;
        }
        
        
        BOOL verify = [self verifyChecksum:hash onFile:downloadLocation];
        
        if (!verify)
        {
            NSLog(@"invalid selection");
            NSAlert *alert = [NSAlert alertWithMessageText:@"This payload does not match our checksum, if you did not modify the payload you should not proceed any further! Otherwise press continue."
                                             defaultButton:@"Cancel"
                                           alternateButton:@"Continue"
                                               otherButton:@"Download Again"
                                 informativeTextWithFormat:@""];
            
            alert.alertStyle = NSCriticalAlertStyle;
            NSInteger button = [alert runModal];
            
            switch (button) {
                    
                    case NSAlertAlternateReturn:
                    
                        [self handleDownloadFile:downloadLocation];
                    break;
                    
                    case NSAlertOtherReturn:
                    
                        [self downloadFile:thePayload toLocation:downloadLocation];
                    break;
                    
                    case NSAlertDefaultReturn:
                    self.mainButton.enabled = YES;
                    break;
                    
                default:
                    break;
            }
        
        
        } else {
             [self handleDownloadFile:downloadLocation];
        }
        
        /*
       if ([self verifyChecksum:hash onFile:downloadLocation])
       {
           NSLog(@"file validated!");
           
           [self handleDownloadFile:downloadLocation];
           return;
           //completion(true);
           
       }
         */
    }

    
}

- (void)handleDownloadFile:(NSString *)downloadedFile
{
    [self setDownloadProgress:0];
    statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", downloadedFile.lastPathComponent];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        
        
        [self uploadFile:downloadedFile withCompletion:^(BOOL success) {
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                if (success)
                {
                    NSLog(@"file uploaded successfully!");
                    statusLabel.stringValue = @"File uploaded successfully!";
                    statusLabel.stringValue = @"Installing payload...";
                    NSString *tarString = [self tarPathForCurrentMode];
                    NSString *bootstrap = [NSString stringWithFormat:@"/var/mobile/Documents/%@", downloadedFile.lastPathComponent];
                    
                    NSString *installCommand = [NSString stringWithFormat:@"%@ fxpv %@ -C / ; /usr/libexec/substrate ; /usr/bin/uicache  ; /bin/bash /usr/libexec/nito/firmware.sh ; rm /var/mobile/Library/Preferences/Featured.plist", tarString, bootstrap];
                    
                   
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
                        
                        NSString *returnString = [self sendCommandString:installCommand];
                        NSLog(@"return string: %@", returnString);
                        NSString *logFile = [[self appSupportFolder] stringByAppendingPathComponent:@"install.log"];
                        
                        [returnString writeToFile:logFile atomically:YES encoding:NSUTF8StringEncoding error:nil];
                        
                        if (self.versionState == KBInstallVersionStateNine)
                        {
                            [self sendCommandString:@"/usr/bin/killall -9 PineBoard HeadBoard lsd nitoTV"];
                        }
                        
                        dispatch_async(dispatch_get_main_queue(), ^{
                            [[NSWorkspace sharedWorkspace] openFile:logFile];
                            
                            statusLabel.stringValue = @"Payload installed?!?!";
                            
                            
                            [self.progressInd stopAnimation:nil];
                            self.mainButton.enabled = TRUE;
                        });
                    });
                    
                } else {
                    statusLabel.stringValue = @"File failed to upload!";
                    [self.progressInd stopAnimation:nil];
                    self.mainButton.enabled = TRUE;
                }
                
            });
            
        }];
        
    });
    
   
   
}

- (void)hideProgress
{
    dispatch_async(dispatch_get_main_queue(), ^{
        statusLabel.stringValue = @"";
        [[self progressInd] stopAnimation:nil];
        [[self progressInd] setDoubleValue:0];
        [[self progressInd] setHidden:true];
    });
    
}

- (void)setDownloadProgress:(double)theProgress
{
    dispatch_async(dispatch_get_main_queue(), ^{
        
        if (theProgress == 0)
        {
            [self.progressInd setIndeterminate:TRUE];
            [self.progressInd setHidden:FALSE];
            [self.progressInd setNeedsDisplay:YES];
            [self.progressInd setUsesThreadedAnimation:YES];
            [self.progressInd startAnimation:self];
            return;
        }
        [self.progressInd setIndeterminate:FALSE];
        [self.progressInd startAnimation:self];
        [self.progressInd setHidden:FALSE];
        [self.progressInd setNeedsDisplay:YES];
        [self.progressInd setDoubleValue:theProgress];
    });
    
}

- (void)setStatusText:(NSString *)statusText
{
    dispatch_async(dispatch_get_main_queue(), ^{
       
        statusLabel.stringValue = statusText;
    });
}

- (BOOL)atvAvailable
{
    return _atvAvailable;
}

- (void)setAtvAvailable:(BOOL)atvAvailable
{
    _atvAvailable = atvAvailable;
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application
}

- (NSString *)tarPathForCurrentMode
{
    NSString *path = nil;
    switch (self.versionState) {
        case KBInstallVersionStateNine:
            
            path= @"/usr/bin/tar";
            break;
            
        case KBInstallVersionStateTenOne:
            
            path= @"/tmp/usr/bin/tar"; //need to verify
            break;
            
        case KBInstallVersionStateTenTwo:
            
            path= @"/usr/bin/tar";
            break;
            
        case KBInstallVersionStateEleven:
            
            path= @"/jb/usr/bin/tar";
            break;
        default:
            break;
    }
    return path;
}

- (NSString *)lsPathForCurrentMode
{
    NSString *path = nil;
    switch (self.versionState) {
        case KBInstallVersionStateNine:
            
            path= @"/bin/ls";
            break;
            
        case KBInstallVersionStateTenOne:
            
            path= @"/tmp/bin/ls";
            break;
            
        case KBInstallVersionStateTenTwo:
            
            path= @"/usr/bin/ls";
            break;
            
        case KBInstallVersionStateEleven:
            
             path= @"/jb/bin/ls";
            break;
        default:
            break;
    }
    return path;
}

- (BOOL)checkForFile:(NSString *)file
{
    //@"ls /usr/bin/ | grep appinst"
    NSString *lsCommand = [self lsPathForCurrentMode];
    NSString *path = [file stringByDeletingLastPathComponent];
    NSString *bareFile = [file lastPathComponent];
    NSString *theReturn = [self sendCommandString:[NSString stringWithFormat:@"%@ -a %@ | grep %@", lsCommand, path, bareFile]];
    if (theReturn != nil)
    { return (TRUE);
    } else { return (FALSE);}
    
    return (FALSE);
}


- (void)checkJailbreakWithCompletion:(void(^)(BOOL jailbroken))completion
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        
        BOOL jb = [self isJailbroken];
        if (jb)
        {
            NSLog(@"we outchea");
        } else {
            NSLog(@"nope!");
        }
        completion(jb);
    });
}

- (BOOL)isJailbroken
{
    NSError *error = nil;
    if (APPLE_TV_ADDRESS != nil)
    {
        ObjSSH *ssh = [ObjSSH connectToHost:APPLE_TV_ADDRESS withUsername:@"root" password:@"alpine" error:&error];
        if (error)
        {
            NSLog(@"error: %@", [error localizedDescription]);
            if ([[error localizedDescription] isEqualToString:@"Failed to connect"])
            {
                [ssh disconnect];
                
                return (FALSE);
            }
            
            [ssh disconnect];
            
        }
    } else {
        return (FALSE);
    }
    
    
    return (TRUE);
}

//if they changed password from alpine this is used to get the proper password to connect

- (void)resetServerSettings
{
    [DEFAULTS removeObjectForKey:@"appleTVHost"];
    [DEFAULTS removeObjectForKey:ATV_OS];
    [DEFAULTS removeObjectForKey:ATV_API];
    [DEFAULTS setObject:@"Choose Apple TV" forKey:@"selectedValue"];
    appleTVAddress = nil;
    [[self progressInd] stopAnimation:nil];
}


- (NSString *)secureInput: (NSString *)prompt defaultValue: (NSString *)defaultValue {
    NSAlert *alert = [NSAlert alertWithMessageText: prompt
                                     defaultButton:@"OK"
                                   alternateButton:@"Cancel"
                                       otherButton:nil
                         informativeTextWithFormat:@""];
    
    NSSecureTextField *input = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
    [input setStringValue:defaultValue];
    
    [alert setAccessoryView:input];
    NSInteger button = [alert runModal];
    if (button == NSAlertDefaultReturn) {
        [input validateEditing];
        NSString *inputString = [input stringValue];
        
        return inputString;
    } else if (button == NSAlertAlternateReturn) {
        
        return nil;
    } else {
        
        return nil;
    }
}



//add a password to the keychain for SSH

- (void)addkeychainPassword:(NSString *)password
{
    //kSecProtocolTypeSSH
    [EMInternetKeychainItem addInternetKeychainItemForServer:APPLE_TV_ADDRESS    withUsername:@"root" password:password path:@"/usr/bin/ssh" port:22 protocol:kSecProtocolTypeSSH];
}

- (void)removeKeychainForHost:(NSString *)ipAddress
{
    //kSecProtocolTypeSSH
    EMInternetKeychainItem *keychainItem = [EMInternetKeychainItem internetKeychainItemForServer:ipAddress withUsername:@"root" path:@"/usr/bin/ssh" port:22 protocol:kSecProtocolTypeSSH];
    [keychainItem removeFromKeychain];
}

//fetch the password from the keychain for the specified ip address

- (NSString *)passwordForHost:(NSString *)ipAddress
{
    EMInternetKeychainItem *keychainItem = [EMInternetKeychainItem internetKeychainItemForServer:ipAddress withUsername:@"root" path:@"/usr/bin/ssh" port:22 protocol:kSecProtocolTypeSSH];
    //Grab the password.
    if (keychainItem != nil)
    {
        //Grab the password.
        NSString *password = keychainItem.password;
        
        return password;
    }
    
    NSLog(@"nothing!");
    return nil;
    
}

- (BOOL)uploadFile:(NSString *)theFile toPath:(NSString *)newPath
{
    NSLog(@"uploading file: %@", theFile);
    NSError *error = nil;
    BOOL getSession = [self connectToSSH];
    if (getSession == FALSE)
    {
        NSLog(@"failed to get session!");
        return (FALSE);
    }
    
    NSString *finalPath = [newPath stringByAppendingPathComponent:[theFile lastPathComponent]];
    
    BOOL uploadFile = [sshSession uploadFile:theFile to:finalPath error:&error];
    if (error)
    {
        NSLog(@"ERROR!: %@", error);
    }
    return (uploadFile);
    
}

- (void)uploadFile:(NSString *)theFile withCompletion:(void(^)(BOOL success))completion
{
    NSLog(@"uploading file: %@", theFile);
    NSError *error = nil;
    BOOL getSession = [self connectToSSH];
    if (getSession == FALSE)
    {
        NSLog(@"failed to get session!");
        completion(false);
    }
    BOOL uploadFile = [self uploadFile:theFile toPath:@"/var/mobile/Documents"];
    completion(uploadFile);
    
}

//upload a file over SSH to the selected AppleTV

- (BOOL)uploadFile:(NSString *)theFile
{
    NSLog(@"uploading file: %@", theFile);
    NSError *error = nil;
    BOOL getSession = [self connectToSSH];
    if (getSession == FALSE)
    {
        NSLog(@"failed to get session!");
        return (FALSE);
    }
    statusLabel.stringValue = [NSString stringWithFormat:@"Uploading file %@....", theFile.lastPathComponent];
    BOOL uploadFile = [sshSession uploadFile:theFile to:[theFile lastPathComponent] error:&error];
    if (error)
    {
        NSLog(@"ERROR!: %@", error);
    }
    return (uploadFile);
    
}

//used to send basic commands to the jailbroken AppleTV over SSH

- (NSString *)sendCommandString:(NSString *)theCommand
{
    NSLog(@"%@", theCommand);
    NSError *error = nil;
    BOOL getSession = [self connectToSSH];
    if (getSession == FALSE)
    {
        NSLog(@"failed to get session!");
        return nil;
    }
    
    
    NSString *response = [sshSession execute:theCommand error:&error];
    
    return response;
    
}

//open the SSH session

- (BOOL)connectToSSH
{
    NSError *error = nil;
    
    if (sshSession == nil)
    {
        //NSLog(@"APPLE_TV_ADDRESS: %@", APPLE_TV_ADDRESS);
        sshSession = [ObjSSH connectToHost:APPLE_TV_ADDRESS withUsername:@"root" password:@"alpine" error:&error];
        if (sshSession == nil)
        {
            NSLog(@"error: %@ get password!", error);
            NSString *passwordForHost = [self passwordForHost:APPLE_TV_ADDRESS];
            NSString *output = nil;
            if (passwordForHost != nil)
            {
                output = passwordForHost;
                
            } else {
                output = [self secureInput:@"Enter Password" defaultValue:@""];
            }
            
            if ([output length] == 0)
            {
                NSLog(@"no password to send!! return!");
                
                return (FALSE);
                
            } else {
                
                
                
                error = nil;
                sshSession = [ObjSSH connectToHost:APPLE_TV_ADDRESS withUsername:@"root" password:output error:&error];
                
                if (error != nil)
                {
                    NSLog(@"error: %@ password failed!", error);
                    
                    [self removeKeychainForHost:APPLE_TV_ADDRESS];
                    return (FALSE);
                } else {
                    [self addkeychainPassword:output];
                }
                
            }
            
            
        }
    }
    if (sshSession != nil)
        return (TRUE);
    
    
    return (FALSE);
}

- (void)showNotJailbrokenWarning
{
    NSAlert *alert = [NSAlert alertWithMessageText:@"This Apple TV isn't jailbroken, please jailbreak it first! Would you like to visit our web site for further assistance?"
                                     defaultButton:@"Yes"
                                   alternateButton:@"Cancel"
                                       otherButton:nil
                         informativeTextWithFormat:@""];
    
    NSInteger button = [alert runModal];
    if (button == NSAlertDefaultReturn) {
        
        [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://wiki.awkwardtv.org"]];
    }
}

- (void)showATVWarning
{
    NSAlert *alert = [NSAlert alertWithMessageText:@"Only the AppleTV 4 is supported"
                                     defaultButton:@"OK"
                                   alternateButton:nil
                                       otherButton:nil
                         informativeTextWithFormat:@""];
    
    [alert runModal];
}

- (NSString *)logLocation
{
    NSString *location = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Logs/iReSign"];
    if (![FM fileExistsAtPath:location])
    {
        [FM createDirectoryAtPath:location withIntermediateDirectories:true attributes:nil error:nil];
    }
    return location;
}

//versions
//265.5 = 9.0
//301.44.3 = 10.0
//310.17 = 10.1 (either .0 or .1)
//320.20.1 = 10.2.2
//353.50 = 11.1

- (IBAction)doIt:(id)sender
{
    [self.mainButton setEnabled:NO];
    NSString *bootstrapPath = @"https://nitosoft.com/ATV4/bootstraps/";
    NSString *swVers = self.deviceDict[@"osvers"];
    if (swVers.length > 0)
    {
        NSLog(@"our sw vers: %@", swVers);
        if ([swVers floatValue] >= 11)
        {
            self.versionState = KBInstallVersionStateEleven;
            NSLog(@"11 payload!");
            bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap11.tar"];
        }
            
    } else {
        
        swVers = self.deviceDict[@"srcvers"];
        {
            NSLog(@"our src vers: %@", swVers);
            if([swVers compare:@"320.20.1" options:NSNumericSearch] != NSOrderedAscending)
            {
                self.versionState = KBInstallVersionStateTenTwo;
                NSLog(@"10.2.2 or greater!");
                bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap11.tar"];
            } else if([swVers compare:@"301.44.3" options:NSNumericSearch] != NSOrderedAscending)
            {
                NSLog(@"10.0 or greater");
                self.versionState = KBInstallVersionStateTenOne;
                bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap9.tar"];
            } else {
                NSLog(@"9.0 or greater?");
                self.versionState = KBInstallVersionStateNine;
                bootstrapPath = [bootstrapPath stringByAppendingString:@"bootstrap9.tar"];
                
                
            }
            
        }
    }
    if ([self connectToSSH])
    {
       // if ([self checkForFile:@"/var/root/.bootstrapped"]){
            
          //  NSLog(@"were already bootstrapped!");
        //} else {
        
        [self processFile:bootstrapPath];
        // [self downloadFile:bootstrapPath];
       // }
       //
        //[self downloadSyslogAndShow:YES];
    } else {
        self.mainButton.enabled = TRUE;
    }
}

- (BOOL)downloadSyslogAndShow:(BOOL)show
{
    BOOL getSession = [self connectToSSH];
    if (getSession == FALSE)
    {
        NSLog(@"failed to get session!");
        return false;
    }
    NSError *error = nil;
    NSString *newSyslog = [[self logLocation] stringByAppendingPathComponent:@"syslog.log"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:newSyslog])
    {
        [[NSFileManager defaultManager] removeItemAtPath:newSyslog error:nil];
    }
    //echo latest syslog output to a new file for downloadin..
    [self sendCommandString:@"/jb/usr/bin/syslog > syslog.log"];
    BOOL downloadFile = [sshSession downloadFile:@"/var/root/syslog.log" to:newSyslog error:&error];
    if (downloadFile)
    {
        NSLog(@"File downloaded Successfully!");
        if (show == true)
        {
            [[NSWorkspace sharedWorkspace] openFile:newSyslog];
        }
        return true;
    }
    return false;
}


- (void)installFile:(NSString *)theFile withCompletionBlock:(void(^)(BOOL success))completionBlock
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        
        @autoreleasepool {
            
            BOOL success = FALSE;
            
            if ([self uploadFile:finalDestination toPath:@"/var/mobile/Documents"] == true)
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    
                    [statusLabel setStringValue:[NSString stringWithFormat:@"Installing file %@...", fileName]];
                    
                });
                
                /*
                 
                 Massively kludgy but it works, for some reason when appinst runs it doesnt go to stdout (or something) so i /need/ to redirect it to a text file, then cat that text file to check for whether or not "installed <bundle_id>" exists
                 
                 */
                
                NSString *checkResponse = [NSString stringWithFormat:@"installed %@", self.bundleID];
                
                NSString *runLine = [NSString stringWithFormat:@"/usr/bin/appinst /var/mobile/Documents/%@ 2> install.txt ; cat install.txt", fileName];
                //NSString *runLine = [NSString stringWithFormat:@"/usr/bin/appinst /var/mobile/Documents/%@", fileName];
                
                NSString *response =  [self sendCommandString:runLine];
                
                //using rangeOfString because containsString is too new for backwards compat.
                
                if ([response rangeOfString:checkResponse].location == NSNotFound)
                {
                    
                    NSString *errorLog = [[self logLocation] stringByAppendingFormat:@"/%@.log", self.bundleID];
                    
                    //remove old copies
                    if ([FM fileExistsAtPath:errorLog])
                    {
                        [FM removeItemAtPath:errorLog error:nil];
                    }
                    
                    //the response above has a bunch of garbled text in it, download the install file "proper" to get the cleaner version
                    
                    BOOL downloadFile = [sshSession downloadFile:@"/var/root/install.txt" to:errorLog error:nil];
                    if (downloadFile == false)
                    {
                        
                        //if that fails for some reason write the version with the garbage at the end
                        
                        [response writeToFile:errorLog atomically:true encoding:NSUTF8StringEncoding error:nil];
                        
                    }
                    
                    response = [NSString stringWithContentsOfFile:errorLog encoding:NSUTF8StringEncoding error:nil];
                    NSLog(@"INSTALLATION FAILED WITH LOG: %@", response);
                    
                    //grab latest relevant syslog chunk
                    [self downloadSyslogAndShow:false];
                    
                } else {
                    runLine = [NSString stringWithFormat:@"/bin/rm /var/mobile/Documents/%@", fileName];
                    [self sendCommandString:runLine];
                    success = true;
                }
                
                
            }
            
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                
                completionBlock(success);
                
                
                
            });
            
        }
        
    });
}



@end
