/******************************************************************************
 * $Id$
 *
 * Copyright (c) 2006-2007 Transmission authors and contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/

#import "Torrent.h"
#import "StringAdditions.h"

#define BAR_HEIGHT 12.0

#define MAX_PIECES 324
#define BLANK_PIECE -99

@interface Torrent (Private)

- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
        publicTorrent: (NSNumber *) publicTorrent
        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
        limitSpeedCustom: (NSNumber *) limitCustom
        checkUpload: (NSNumber *) checkUpload uploadLimit: (NSNumber *) uploadLimit
        checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
		pex: (NSNumber *) pex
        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue;

- (void) createFileList;
- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings
        withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
        fileSize: (uint64_t) size state: (int) state flatList: (NSMutableArray *) flatList;
- (NSImage *) advancedBar;
- (void) trashFile: (NSString *) path;

@end

@implementation Torrent

// Used to optimize drawing. They contain packed RGBA pixels for every color needed.
#define BE OSSwapBigToHostConstInt32

static uint32_t kRed   = BE(0xFF6450FF), //255, 100, 80
                kBlue = BE(0x50A0FFFF), //80, 160, 255
                kBlue2 = BE(0x1E46B4FF), //30, 70, 180
                kGray  = BE(0x969696FF), //150, 150, 150
                kGreen1 = BE(0x99FFCCFF), //153, 255, 204
                kGreen2 = BE(0x66FF99FF), //102, 255, 153
                kGreen3 = BE(0x00FF66FF), //0, 255, 102
                kWhite = BE(0xFFFFFFFF); //255, 255, 255

- (id) initWithPath: (NSString *) path forceDeleteTorrent: (BOOL) delete lib: (tr_handle_t *) lib
{
    self = [self initWithHash: nil path: path lib: lib
            publicTorrent: delete ? [NSNumber numberWithBool: NO] : nil
            dateAdded: nil dateCompleted: nil
            ratioSetting: nil ratioLimit: nil
            limitSpeedCustom: nil
            checkUpload: nil uploadLimit: nil
            checkDownload: nil downloadLimit: nil
			pex: nil
            waitToStart: nil orderValue: nil];
    
    if (self)
    {
        fUseIncompleteFolder = [fDefaults boolForKey: @"UseIncompleteDownloadFolder"];
        fIncompleteFolder = [[[fDefaults stringForKey: @"IncompleteDownloadFolder"] stringByExpandingTildeInPath] retain];
        
        if (!fPublicTorrent)
            [self trashFile: path];
    }
    return self;
}

- (id) initWithHistory: (NSDictionary *) history lib: (tr_handle_t *) lib
{
    self = [self initWithHash: [history objectForKey: @"TorrentHash"]
                path: [history objectForKey: @"TorrentPath"] lib: lib
                publicTorrent: [history objectForKey: @"PublicCopy"]
                dateAdded: [history objectForKey: @"Date"]
				dateCompleted: [history objectForKey: @"DateCompleted"]
                ratioSetting: [history objectForKey: @"RatioSetting"]
                ratioLimit: [history objectForKey: @"RatioLimit"]
                limitSpeedCustom: [history objectForKey: @"LimitSpeedCustom"]
                checkUpload: [history objectForKey: @"CheckUpload"]
                uploadLimit: [history objectForKey: @"UploadLimit"]
                checkDownload: [history objectForKey: @"CheckDownload"]
                downloadLimit: [history objectForKey: @"DownloadLimit"]
				pex: [history objectForKey: @"Pex"]
                waitToStart: [history objectForKey: @"WaitToStart"]
                orderValue: [history objectForKey: @"OrderValue"]];
    
    if (self)
    {
        //download folders
        NSString * downloadFolder;
        if (!(downloadFolder = [history objectForKey: @"DownloadFolder"]))
            downloadFolder = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
        
        NSNumber * useIncompleteFolder;
        if ((useIncompleteFolder = [history objectForKey: @"UseIncompleteFolder"]))
        {
            if ((fUseIncompleteFolder = [useIncompleteFolder boolValue]))
            {
                NSString * incompleteFolder;
                if (incompleteFolder = [history objectForKey: @"IncompleteFolder"])
                    fIncompleteFolder = [incompleteFolder retain];
                else
                    fIncompleteFolder = [[[fDefaults stringForKey: @"IncompleteDownloadFolder"]
                                            stringByExpandingTildeInPath] retain];
            }
        }
        else
            fUseIncompleteFolder = NO;
        
        [self setDownloadFolder: downloadFolder];
        
        //start transfer
        BOOL start = YES;
        NSNumber * active;
        NSString * paused;
        if ((active = [history objectForKey: @"Active"]))
            start = [active boolValue];
        else if ((paused = [history objectForKey: @"Paused"])) //old way of storing if active
            start = [paused isEqualToString: @"NO"];
        else;
        
        if (start)
        {
            fStat = tr_torrentStat(fHandle);
            [self startTransfer];
        }
    }
    return self;
}

- (NSDictionary *) history
{
    NSMutableDictionary * history = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                    [NSNumber numberWithBool: fPublicTorrent], @"PublicCopy",
                    [self hashString], @"TorrentHash",
                    fDownloadFolder, @"DownloadFolder",
                    [NSNumber numberWithBool: fUseIncompleteFolder], @"UseIncompleteFolder",
                    [NSNumber numberWithBool: [self isActive]], @"Active",
                    [self dateAdded], @"Date",
                    [NSNumber numberWithInt: fRatioSetting], @"RatioSetting",
                    [NSNumber numberWithFloat: fRatioLimit], @"RatioLimit",
                    [NSNumber numberWithInt: fCheckUpload], @"CheckUpload",
                    [NSNumber numberWithInt: fUploadLimit], @"UploadLimit",
                    [NSNumber numberWithInt: fCheckDownload], @"CheckDownload",
                    [NSNumber numberWithInt: fDownloadLimit], @"DownloadLimit",
                    [NSNumber numberWithBool: fWaitToStart], @"WaitToStart",
                    [self orderValue], @"OrderValue", nil];
    
    if (fUseIncompleteFolder)
        [history setObject: fIncompleteFolder forKey: @"IncompleteFolder"];

    if (fPublicTorrent)
        [history setObject: [self publicTorrentLocation] forKey: @"TorrentPath"];
	
	if (![self privateTorrent])
		[history setObject: [NSNumber numberWithBool: fPex] forKey: @"Pex"];
	
	if (fDateCompleted)
		[history setObject: [self dateAdded] forKey: @"DateCompleted"];
	
    return history;
}

- (void) dealloc
{
    if (fHandle)
    {
        tr_torrentClose(fLib, fHandle);
        
        if (fDownloadFolder)
            [fDownloadFolder release];
        if (fIncompleteFolder)
            [fIncompleteFolder release];
        
        if (fPublicTorrentLocation)
            [fPublicTorrentLocation release];
        
        tr_torrentRemoveSaved(fHandle);
        
        [fDateAdded release];
		if (fDateCompleted)
			[fDateCompleted release];
        
        if (fAnnounceDate)
            [fAnnounceDate release];
        
        [fIcon release];
        [fIconFlipped release];
        [fIconSmall release];
        
        [fProgressString release];
        [fStatusString release];
        [fShortStatusString release];
        [fRemainingTimeString release];
        
        [fFileList release];
        [fFileFlatList release];
        
        [fBitmap release];
        free(fPieces);
    }
    [super dealloc];
}

- (void) setDownloadFolder: (NSString *) path
{
    if (path)
        fDownloadFolder = [path copy];
    
    if (!fUseIncompleteFolder || [[NSFileManager defaultManager] fileExistsAtPath:
                                    [path stringByAppendingPathComponent: [self name]]])
        tr_torrentSetFolder(fHandle, [path UTF8String]);
    else
        tr_torrentSetFolder(fHandle, [fIncompleteFolder UTF8String]);
}

- (NSString *) downloadFolder
{
    return [NSString stringWithUTF8String: tr_torrentGetFolder(fHandle)];
}

- (void) getAvailability: (int8_t *) tab size: (int) size
{
    tr_torrentAvailability(fHandle, tab, size);
}

- (void) getAmountFinished: (float *) tab size: (int) size
{
    tr_torrentAmountFinished(fHandle, tab, size);
}

- (void) update
{
    fStat = tr_torrentStat(fHandle);
    
    //notification when downloading finished
    if ([self justFinished])
    {
        BOOL canMove = YES;
        
        //move file from incomplete folder to download folder
        if (fUseIncompleteFolder && ![[self downloadFolder] isEqualToString: fDownloadFolder]
            && (canMove = [self alertForMoveFolderAvailable]))
        {
            tr_torrentStop(fHandle);
            if ([[NSFileManager defaultManager] movePath: [[self downloadFolder] stringByAppendingPathComponent: [self name]]
                                    toPath: [fDownloadFolder stringByAppendingPathComponent: [self name]] handler: nil])
                tr_torrentSetFolder(fHandle, [fDownloadFolder UTF8String]);
            tr_torrentStart(fHandle);
        }
        
        if (!canMove)
        {
            fUseIncompleteFolder = NO;
            
            [fDownloadFolder release];
            fDownloadFolder = fIncompleteFolder;
            fIncompleteFolder = nil;
        }
		
		if (fDateCompleted)
			[fDateCompleted release];
		fDateCompleted = [[NSDate alloc] init];
        
        fStat = tr_torrentStat(fHandle);
        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self];
    }
    
    //check to stop for ratio
    float stopRatio;
    if ([self isSeeding] && (stopRatio = [self actualStopRatio]) != INVALID
			&& [self ratio] >= stopRatio)
    {
        [self stopTransfer];
        fStat = tr_torrentStat(fHandle);
        
        fFinishedSeeding = YES;
        
        [self setRatioSetting: NSOffState];
        [[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentStoppedForRatio" object: self];
    }
	
	NSMutableString * progressString = [NSMutableString stringWithString: @""],
					* remainingTimeString = [NSMutableString stringWithString: @""],
					* statusString = [NSMutableString string],
					* shortStatusString = [NSMutableString string];

    if (![self allDownloaded])
        [progressString appendFormat: NSLocalizedString(@"%@ of %@ (%.2f%%)", "Torrent -> progress string"),
                            [NSString stringForFileSize: [self downloadedValid]],
                            [NSString stringForFileSize: [self size]], 100.0 * [self progress]];
    else
        [progressString appendFormat: NSLocalizedString(@"%@, uploaded %@ (Ratio: %@)", "Torrent -> progress string"),
                [NSString stringForFileSize: [self size]], [NSString stringForFileSize: [self uploadedTotal]],
                [NSString stringForRatio: [self ratio]]];

    BOOL wasChecking = fChecking;
    fChecking = NO;
    switch (fStat->status)
    {
        NSString * tempString;
        
        case TR_STATUS_PAUSE:
            if (fWaitToStart)
            {
                tempString = ![self allDownloaded]
                        ? [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis]
                        : [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
            }
            else if (fFinishedSeeding)
                tempString = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
            else
                tempString = NSLocalizedString(@"Paused", "Torrent -> status string");
            
            [statusString setString: tempString];
            [shortStatusString setString: tempString];
            
            break;

        case TR_STATUS_CHECK:
            tempString = [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
            
            [statusString setString: tempString];
            [shortStatusString setString: tempString];
            [remainingTimeString setString: tempString];
            
            fChecking = YES;
            
            break;

        case TR_STATUS_DOWNLOAD:
            [statusString setString: @""];
            if ([self totalPeers] != 1)
                [statusString appendFormat: NSLocalizedString(@"Downloading from %d of %d peers",
                                                "Torrent -> status string"), [self peersUploading], [self totalPeers]];
            else
                [statusString appendFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
                                                "Torrent -> status string"), [self peersUploading]];
            
            int eta = [self eta];
            if (eta < 0)
            {
                [remainingTimeString setString: NSLocalizedString(@"Unknown", "Torrent -> remaining time")];
                [progressString appendString: NSLocalizedString(@" - remaining time unknown", "Torrent -> progress string")];
            }
            else
            {
                if (eta < 60)
                    [remainingTimeString appendFormat: NSLocalizedString(@"%d sec", "Torrent -> remaining time"), eta];
                else if (eta < 3600) //60 * 60
                    [remainingTimeString appendFormat: NSLocalizedString(@"%d min %02d sec", "Torrent -> remaining time"),
                                                            eta / 60, eta % 60];
                else if (eta < 86400) //24 * 60 * 60
                    [remainingTimeString appendFormat: NSLocalizedString(@"%d hr %02d min", "Torrent -> remaining time"),
                                                            eta / 3600, (eta / 60) % 60];
                else
                {
					int days = eta / 86400;
                    if (days > 1)
                        [remainingTimeString appendFormat: NSLocalizedString(@"%d days %d hr", "Torrent -> remaining time"),
                                                                days, (eta / 3600) % 24];
                    else
                        [remainingTimeString appendFormat: NSLocalizedString(@"1 day %d hr", "Torrent -> remaining time"),
                                                                (eta / 3600) % 24];
                }
                
                [progressString appendFormat: NSLocalizedString(@" - %@ remaining", "Torrent -> progress string"),
                                                                    remainingTimeString];
            }
            
            break;

        case TR_STATUS_SEED:
            [statusString setString: @""];
            if ([self totalPeers] != 1)
                [statusString appendFormat: NSLocalizedString(@"Seeding to %d of %d peers", "Torrent -> status string"),
                                                [self peersDownloading], [self totalPeers]];
            else
                [statusString appendFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
                                                [self peersDownloading]];
            
            break;

        case TR_STATUS_STOPPING:
            tempString = [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
        
            [statusString setString: tempString];
            [shortStatusString setString: tempString];
            
            break;
    }
    
    if (wasChecking && !fChecking)
        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
    
    if ([self isError])
    {
        [statusString setString: [NSLocalizedString(@"Error: ", "Torrent -> status string") stringByAppendingString:
                                    [self errorMessage]]];
    }
    
    BOOL wasError = fError;
    if ((fError = fStat->cannotConnect))
    {
        if (!wasError && [self isActive])
            [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
    }

    if ([self isActive] && fStat->status != TR_STATUS_CHECK )
    {
        NSString * stringToAppend = @"";
        if (![self allDownloaded])
        {
            stringToAppend = [NSString stringWithFormat: NSLocalizedString(@"DL: %@, ", "Torrent -> status string"),
                                [NSString stringForSpeed: [self downloadRate]]];
            [shortStatusString setString: @""];
        }
        else
        {
            NSString * ratioString = [NSString stringForRatio: [self ratio]];
        
            [shortStatusString setString: [NSString stringWithFormat: NSLocalizedString(@"Ratio: %@, ",
                                            "Torrent -> status string"), ratioString]];
            [remainingTimeString setString: [NSLocalizedString(@"Ratio: ", "Torrent -> status string")
                                                stringByAppendingString: ratioString]];
        }
        
        stringToAppend = [stringToAppend stringByAppendingString: [NSLocalizedString(@"UL: ", "Torrent -> status string")
                                            stringByAppendingString: [NSString stringForSpeed: [self uploadRate]]]];

        [statusString appendFormat: @" - %@", stringToAppend];
        [shortStatusString appendString: stringToAppend];
    }
	
	[fProgressString setString: progressString];
	[fStatusString setString: statusString];
	[fShortStatusString setString: shortStatusString];
	[fRemainingTimeString setString: remainingTimeString];
}

- (NSDictionary *) infoForCurrentView
{
    NSMutableDictionary * info = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                    [self name], @"Name",
                                    [NSNumber numberWithBool: [self isSeeding]], @"Seeding",
                                    [NSNumber numberWithFloat: [self progress]], @"Progress",
                                    [NSNumber numberWithBool: [self isActive]], @"Active",
                                    [NSNumber numberWithBool: [self isError]], @"Error", nil];
    
    if ([self isSeeding])
    {
        [info setObject: [NSNumber numberWithFloat: [self ratio]] forKey: @"Ratio"];
        [info setObject: [NSNumber numberWithFloat: [self actualStopRatio]] forKey: @"StopRatio"];
    }
    
    if (![fDefaults boolForKey: @"SmallView"])
    {
        [info setObject: fIconFlipped forKey: @"Icon"];
        [info setObject: [self progressString] forKey: @"ProgressString"];
        [info setObject: [self statusString] forKey: @"StatusString"];
    }
    else
    {
        [info setObject: fIconSmall forKey: @"Icon"];
        [info setObject: [self remainingTimeString] forKey: @"RemainingTimeString"];
        [info setObject: [self shortStatusString] forKey: @"ShortStatusString"];
    }
    
    if ([fDefaults boolForKey: @"UseAdvancedBar"])
        [info setObject: [self advancedBar] forKey: @"AdvancedBar"];
    
    return info;
}

- (void) startTransfer
{
    fWaitToStart = NO;
    fFinishedSeeding = NO;
    
    if (![self isActive] && [self alertForFolderAvailable] && [self alertForRemainingDiskSpace])
    {
        tr_torrentStart(fHandle);
        [self update];
    }
}

- (void) stopTransfer
{
    fError = NO;
    fWaitToStart = NO;
    
    if ([self isActive])
    {
        tr_torrentStop(fHandle);
        [self update];

        [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
    }
}

- (void) stopTransferForQuit
{
    if ([self isActive])
        tr_torrentStop(fHandle);
}

- (void) sleep
{
    if ((fResumeOnWake = [self isActive]))
        tr_torrentStop(fHandle);
}

- (void) wakeUp
{
    if (fResumeOnWake)
        tr_torrentStart(fHandle);
}

- (void) announce
{
    if (![self isActive])
        return;
    
    tr_manualUpdate(fHandle);
    
    if (fAnnounceDate)
        [fAnnounceDate release];
    fAnnounceDate = [[NSDate date] retain];
}

- (NSDate *) announceDate
{
    return fAnnounceDate;
}

- (BOOL) allDownloaded
{
    return [self progress] >= 1.0;
}

- (float) ratio
{
    return fStat->ratio;
}

- (int) ratioSetting
{
	return fRatioSetting;
}

- (void) setRatioSetting: (int) setting
{
    fRatioSetting = setting;
}

- (float) ratioLimit
{
    return fRatioLimit;
}

- (void) setRatioLimit: (float) limit
{
    if (limit >= 0)
        fRatioLimit = limit;
}

- (float) actualStopRatio
{
    if (fRatioSetting == NSOnState)
        return fRatioLimit;
    else if (fRatioSetting == NSMixedState && [fDefaults boolForKey: @"RatioCheck"])
        return [fDefaults floatForKey: @"RatioLimit"];
    else
        return INVALID;
}

- (int) checkUpload
{
    return fCheckUpload;
}

- (void) setCheckUpload: (int) setting
{
    fCheckUpload = setting;
    [self updateSpeedSetting];
}

- (int) uploadLimit
{
    return fUploadLimit;
}

- (void) setUploadLimit: (int) limit
{
    fUploadLimit = limit;
    [self updateSpeedSetting];
}

- (int) checkDownload
{
    return fCheckDownload;
}

- (void) setCheckDownload: (int) setting
{
    fCheckDownload = setting;
    [self updateSpeedSetting];
}

- (int) downloadLimit
{
    return fDownloadLimit;
}

- (void) setDownloadLimit: (int) limit
{
    fDownloadLimit = limit;
    [self updateSpeedSetting];
}

- (void) updateSpeedSetting
{
    tr_setUseCustomUpload(fHandle, fCheckUpload != NSMixedState);
    tr_setUploadLimit(fHandle, fCheckUpload == NSOnState ? fUploadLimit : -1);
    
    tr_setUseCustomDownload(fHandle, fCheckDownload != NSMixedState);
    tr_setDownloadLimit(fHandle, fCheckDownload == NSOnState ? fDownloadLimit : -1);
}

- (void) setWaitToStart: (BOOL) wait
{
    fWaitToStart = wait;
}

- (BOOL) waitingToStart
{
    return fWaitToStart;
}

- (void) revealData
{
    [[NSWorkspace sharedWorkspace] selectFile: [self dataLocation] inFileViewerRootedAtPath: nil];
}

- (void) revealPublicTorrent
{
    if (fPublicTorrent)
        [[NSWorkspace sharedWorkspace] selectFile: fPublicTorrentLocation inFileViewerRootedAtPath: nil];
}

- (void) trashData
{
    [self trashFile: [self dataLocation]];
}

- (void) trashTorrent
{
    if (fPublicTorrent)
        [self trashFile: [self publicTorrentLocation]];
}

- (BOOL) alertForRemainingDiskSpace
{
    if ([self allDownloaded] || ![fDefaults boolForKey: @"RemainingSpaceWarning"])
        return YES;
    
    NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: [self downloadFolder]]
                                                                                                objectAtIndex: 0];
    NSDictionary * fsAttributes = [[NSFileManager defaultManager] fileSystemAttributesAtPath: [self downloadFolder]];
    uint64_t remainingSpace = [[fsAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue],
            torrentRemaining = [self size] - (uint64_t)[self downloadedValid];
    
    if (volumeName && remainingSpace <= torrentRemaining)
    {
        NSAlert * alert = [[NSAlert alloc] init];
        [alert setMessageText: [NSString stringWithFormat:
                                NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
                                    "Torrent file disk space alert -> title"), [self name]]];
        [alert setInformativeText: [NSString stringWithFormat:
                        NSLocalizedString(@"The transfer will be paused. Clear up space on \"%@\" to continue.",
                                            "Torrent file disk space alert -> message"), volumeName]];
        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file disk space alert -> button")];
        [alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent file disk space alert -> button")];
        [alert addButtonWithTitle: NSLocalizedString(@"Always Download", "Torrent file disk space alert -> button")];
        
        int result = [alert runModal];
        [alert release];
        
        if (result == NSAlertThirdButtonReturn)
            [fDefaults setBool: NO forKey: @"RemainingSpaceWarning"];
        
        return result != NSAlertFirstButtonReturn;
    }
    return YES;
}

- (BOOL) alertForFolderAvailable
{
    if (access(tr_torrentGetFolder(fHandle), 0))
    {
        NSAlert * alert = [[NSAlert alloc] init];
        [alert setMessageText: [NSString stringWithFormat:
                                NSLocalizedString(@"The folder for downloading \"%@\" cannot be found.",
                                    "Folder cannot be found alert -> title"), [self name]]];
        [alert setInformativeText: [NSString stringWithFormat:
                        NSLocalizedString(@"\"%@\" cannot be found. The transfer will be paused.",
                                            "Folder cannot be found alert -> message"), [self downloadFolder]]];
        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Folder cannot be found alert -> button")];
        [alert addButtonWithTitle: [NSLocalizedString(@"Choose New Location",
                                    "Folder cannot be found alert -> location button") stringByAppendingEllipsis]];
        
        if ([alert runModal] != NSAlertFirstButtonReturn)
        {
            NSOpenPanel * panel = [NSOpenPanel openPanel];
            
            [panel setPrompt: NSLocalizedString(@"Select", "Folder cannot be found alert -> prompt")];
            [panel setAllowsMultipleSelection: NO];
            [panel setCanChooseFiles: NO];
            [panel setCanChooseDirectories: YES];
            [panel setCanCreateDirectories: YES];

            [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
                                "Folder cannot be found alert -> select destination folder"), [self name]]];
            
            [[NSNotificationCenter defaultCenter] postNotificationName: @"MakeWindowKey" object: nil];
            [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: [NSApp keyWindow] modalDelegate: self
                    didEndSelector: @selector(destinationChoiceClosed:returnCode:contextInfo:) contextInfo: nil];
        }
        
        [alert release];
        
        return NO;
    }
    
    return YES;
}

- (void) destinationChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (void *) context
{
    if (code != NSOKButton)
        return;
    
    NSString * folder = [[openPanel filenames] objectAtIndex: 0];
    if (fUseIncompleteFolder)
    {
        [fIncompleteFolder release];
        fIncompleteFolder = [folder retain];
        [self setDownloadFolder: nil];
    }
    else
    {
        [fDownloadFolder release];
        fDownloadFolder = folder;
        [self setDownloadFolder: fDownloadFolder];
    }
    
    [self startTransfer];
    [self update];
    
    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateInfoSettings" object: nil];
}

- (BOOL) alertForMoveFolderAvailable
{
    if (access([fDownloadFolder UTF8String], 0))
    {
        NSAlert * alert = [[NSAlert alloc] init];
        [alert setMessageText: [NSString stringWithFormat:
                                NSLocalizedString(@"The folder for moving the completed \"%@\" cannot be found.",
                                    "Move folder cannot be found alert -> title"), [self name]]];
        [alert setInformativeText: [NSString stringWithFormat:
                                NSLocalizedString(@"\"%@\" cannot be found. The file will remain in its current location.",
                                    "Move folder cannot be found alert -> message"), fDownloadFolder]];
        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Move folder cannot be found alert -> button")];
        
        [alert runModal];
        [alert release];
        
        return NO;
    }
    
    return YES;
}

- (NSImage *) icon
{
    return fIcon;
}

- (NSImage *) iconFlipped
{
    return fIconFlipped;
}

- (NSImage *) iconSmall
{
    return fIconSmall;
}

- (NSString *) name
{
    return [NSString stringWithUTF8String: fInfo->name];
}

- (uint64_t) size
{
    return fInfo->totalSize;
}

- (NSString *) trackerAddress
{
    return [NSString stringWithFormat: @"http://%s:%d", fStat->tracker->address, fStat->tracker->port];
}

- (NSString *) trackerAddressAnnounce
{
    return [NSString stringWithUTF8String: fStat->tracker->announce];
}

- (NSString *) comment
{
    return [NSString stringWithUTF8String: fInfo->comment];
}

- (NSString *) creator
{
    return [NSString stringWithUTF8String: fInfo->creator];
}

- (NSDate *) dateCreated
{
    int date = fInfo->dateCreated;
    return date > 0 ? [NSDate dateWithTimeIntervalSince1970: date] : nil;
}

- (int) pieceSize
{
    return fInfo->pieceSize;
}

- (int) pieceCount
{
    return fInfo->pieceCount;
}

- (NSString *) hashString
{
    return [NSString stringWithUTF8String: fInfo->hashString];
}

- (BOOL) privateTorrent
{
    return TR_FLAG_PRIVATE & fInfo->flags;
}

- (NSString *) torrentLocation
{
    return [NSString stringWithUTF8String: fInfo->torrent];
}

- (NSString *) publicTorrentLocation
{
    return fPublicTorrentLocation;
}

- (NSString *) dataLocation
{
    return [[self downloadFolder] stringByAppendingPathComponent: [self name]];
}

- (BOOL) publicTorrent
{
    return fPublicTorrent;
}

- (NSString *) stateString
{
    switch( fStat->status )
    {
        case TR_STATUS_PAUSE:
            return NSLocalizedString(@"Paused", "Torrent -> status string");
            break;

        case TR_STATUS_CHECK:
            return [NSLocalizedString(@"Checking existing files", "Torrent -> status string") stringByAppendingEllipsis];
            break;

        case TR_STATUS_DOWNLOAD:
            return NSLocalizedString(@"Downloading", "Torrent -> status string");
            break;

        case TR_STATUS_SEED:
            return NSLocalizedString(@"Seeding", "Torrent -> status string");
            break;

        case TR_STATUS_STOPPING:
            return [NSLocalizedString(@"Stopping", "Torrent -> status string") stringByAppendingEllipsis];
            break;
        
        default:
            return NSLocalizedString(@"N/A", "Torrent -> status string");
    }
}

- (float) progress
{
    return fStat->progress;
}

- (int) eta
{
    return fStat->eta;
}

- (BOOL) isActive
{
    return fStat->status & TR_STATUS_ACTIVE;
}

- (BOOL) isSeeding
{
    return fStat->status == TR_STATUS_SEED;
}

- (BOOL) isPaused
{
    return fStat->status == TR_STATUS_PAUSE;
}

- (BOOL) isError
{
    return fStat->error != 0;
}

- (NSString *) errorMessage
{
    if (![self isError])
        return @"";
    
    NSString * error;
    if (!(error = [NSString stringWithUTF8String: fStat->errorString])
        && !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
        error = NSLocalizedString(@"(unreadable error)", "Torrent -> error string unreadable");
    
    return error;
}

- (BOOL) justFinished
{
    return tr_getFinished(fHandle);
}

- (NSArray *) peers
{
    int totalPeers, i;
    tr_peer_stat_t * peers = tr_torrentPeers(fHandle, & totalPeers);
    
    NSMutableArray * peerDics = [NSMutableArray arrayWithCapacity: totalPeers];
    NSMutableDictionary * dic;
    
    tr_peer_stat_t * peer;
    NSString * client;
    for (i = 0; i < totalPeers; i++)
    {
        peer = &peers[i];
        
        dic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
            [NSNumber numberWithBool: peer->isConnected], @"Connected",
            [NSNumber numberWithInt: peer->from], @"From",
            [NSString stringWithCString: (char *) peer->addr encoding: NSUTF8StringEncoding], @"IP",
            [NSString stringWithCString: (char *) peer->client encoding: NSUTF8StringEncoding], @"Client",
            [NSNumber numberWithFloat: peer->progress], @"Progress",
            [NSNumber numberWithBool: peer->isDownloading], @"UL To",
            [NSNumber numberWithBool: peer->isUploading], @"DL From",
            [NSNumber numberWithInt: peer->port], @"Port", nil];
        
        if (peer->isDownloading)
            [dic setObject: [NSNumber numberWithFloat: peer->uploadToRate] forKey: @"UL To Rate"];
        if (peer->isUploading)
            [dic setObject: [NSNumber numberWithFloat: peer->downloadFromRate] forKey: @"DL From Rate"];
        
        [peerDics addObject: dic];
    }
    
    tr_torrentPeersFree(peers, totalPeers);
    
    return peerDics;
}

- (NSString *) progressString
{
    return fProgressString;
}

- (NSString *) statusString
{
    return fStatusString;
}

- (NSString *) shortStatusString
{
    return fShortStatusString;
}

- (NSString *) remainingTimeString
{
    return fRemainingTimeString;
}

- (int) seeders
{
    return fStat->seeders;
}

- (int) leechers
{
    return fStat->leechers;
}

- (int) completedFromTracker
{
    return fStat->completedFromTracker;
}

- (int) totalPeers
{
    return fStat->peersTotal;
}

- (int) totalPeersTracker
{
    return fStat->peersFrom[TR_PEER_FROM_TRACKER];
}

- (int) totalPeersIncoming
{
    return fStat->peersFrom[TR_PEER_FROM_INCOMING];
}

- (int) totalPeersCache
{
    return fStat->peersFrom[TR_PEER_FROM_CACHE];
}

- (int) totalPeersPex
{
    return fStat->peersFrom[TR_PEER_FROM_PEX];
}

//peers uploading to you
- (int) peersUploading
{
    return fStat->peersUploading;
}

//peers downloading from you
- (int) peersDownloading
{
    return fStat->peersDownloading;
}

- (float) downloadRate
{
    return fStat->rateDownload;
}

- (float) uploadRate
{
    return fStat->rateUpload;
}

- (uint64_t) downloadedValid
{
    return fInfo->totalSize - fStat->left;
}

- (uint64_t) downloadedTotal
{
    return fStat->downloaded;
}

- (uint64_t) uploadedTotal
{
    return fStat->uploaded;
}

- (float) swarmSpeed
{
    return fStat->swarmspeed;
}

- (BOOL) pex
{
	return fPex;
}

- (void) setPex: (BOOL) setting
{
	if (![self privateTorrent])
	{
		fPex = setting;
		tr_torrentDisablePex(fHandle, !setting);
	}
}

- (NSNumber *) orderValue
{
    return [NSNumber numberWithInt: fOrderValue];
}

- (void) setOrderValue: (int) orderValue
{
    fOrderValue = orderValue;
}

- (NSArray *) fileList
{
    return fFileList;
}

- (int) fileCount
{
    return fInfo->fileCount;
}

- (BOOL) updateFileProgress
{
    BOOL change = NO;
    float * progress = tr_torrentCompletion(fHandle);
    NSNumber * progressNum;
    NSMutableDictionary * item, * dict;
    
    int i, fileCount = [self fileCount];
    for (i = 0; i < fileCount; i++)
    {
        if (!(progressNum = [[fFileFlatList objectAtIndex: i] objectForKey: @"Progress"])
                || [progressNum floatValue] != progress[i])
        {
            item = [fFileFlatList objectAtIndex: i];
            [item setObject: [NSNumber numberWithFloat: progress[i]] forKey: @"Progress"];
            change = YES;
            
            if (progress[i] == 1.0)
            {
                dict = item;
                while ((dict = [dict objectForKey: @"Parent"]))
                    [dict setObject: [NSNumber numberWithInt: [[dict objectForKey: @"Remaining"] intValue]-1]
                            forKey: @"Remaining"];
                
                [item setObject: [NSNumber numberWithInt: NSOnState] forKey: @"Check"];
                [self resetFileCheckStateForItemParent: item];
            }
        }
    }
    
    free(progress);
    
    return change;
}

- (void) setFileCheckState: (int) state forFileItem: (NSMutableDictionary *) item
{
    [item setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
    
    if (![[item objectForKey: @"IsFolder"] boolValue])
        return;
    
    NSMutableDictionary * child;
    NSEnumerator * enumerator = [[item objectForKey: @"Children"] objectEnumerator];
    while ((child = [enumerator nextObject]))
        if (state != [[child objectForKey: @"Check"] intValue])
            [self setFileCheckState: state forFileItem: child];
}

- (NSMutableDictionary *) resetFileCheckStateForItemParent: (NSMutableDictionary *) originalChild
{
    NSMutableDictionary * item;
    if (!(item = [originalChild objectForKey: @"Parent"]))
        return originalChild;
    
    int state = INVALID;
    
    NSMutableDictionary * child;
    NSEnumerator * enumerator = [[item objectForKey: @"Children"] objectEnumerator];
    while ((child = [enumerator nextObject]))
    {
        if (state == INVALID)
        {
            state = [[child objectForKey: @"Check"] intValue];
            if (state == NSMixedState)
                break;
        }
        else if (state != [[child objectForKey: @"Check"] intValue])
        {
            state = NSMixedState;
            break;
        }
        else;
    }
    
    if (state != [[item objectForKey: @"Check"] intValue])
    {
        [item setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
        return [self resetFileCheckStateForItemParent: item];
    }
    else
        return originalChild;
}

- (NSDate *) dateAdded
{
    return fDateAdded;
}

- (NSDate *) dateCompleted
{
    return fDateCompleted;
}

- (NSNumber *) stateSortKey
{
    if (![self isActive])
        return [NSNumber numberWithInt: 0];
    else if ([self isSeeding])
        return [NSNumber numberWithInt: 1];
    else
        return [NSNumber numberWithInt: 2];
}

- (NSNumber *) progressSortKey
{
    return [NSNumber numberWithFloat: [self progress]];
}

- (NSNumber *) ratioSortKey
{
    return [NSNumber numberWithFloat: [self ratio]];
}

@end

@implementation Torrent (Private)

//if a hash is given, attempt to load that; otherwise, attempt to open file at path
- (id) initWithHash: (NSString *) hashString path: (NSString *) path lib: (tr_handle_t *) lib
        publicTorrent: (NSNumber *) publicTorrent
        dateAdded: (NSDate *) dateAdded dateCompleted: (NSDate *) dateCompleted
        ratioSetting: (NSNumber *) ratioSetting ratioLimit: (NSNumber *) ratioLimit
        limitSpeedCustom: (NSNumber *) limitCustom
        checkUpload: (NSNumber *) checkUpload uploadLimit: (NSNumber *) uploadLimit
        checkDownload: (NSNumber *) checkDownload downloadLimit: (NSNumber *) downloadLimit
		pex: (NSNumber *) pex
        waitToStart: (NSNumber *) waitToStart orderValue: (NSNumber *) orderValue
{
    if (!(self = [super init]))
        return nil;

    fLib = lib;
    fDefaults = [NSUserDefaults standardUserDefaults];

    fPublicTorrent = path && (publicTorrent ? [publicTorrent boolValue] : ![fDefaults boolForKey: @"DeleteOriginalTorrent"]);
    if (fPublicTorrent)
        fPublicTorrentLocation = [path retain];

    int error;
    if (hashString)
        fHandle = tr_torrentInitSaved(fLib, [hashString UTF8String], TR_FLAG_SAVE, & error);
    
    if (!fHandle && path)
        fHandle = tr_torrentInit(fLib, [path UTF8String], NULL, TR_FLAG_SAVE, & error);

    if (!fHandle)
    {
        [self release];
        return nil;
    }
    
    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
    [nc addObserver: self selector: @selector(updateSpeedSetting:)
                name: @"UpdateSpeedSetting" object: nil];
    
    fInfo = tr_torrentInfo(fHandle);

    fDateAdded = dateAdded ? [dateAdded retain] : [[NSDate alloc] init];
	if (dateCompleted)
		fDateCompleted = [dateCompleted retain];
	
    fRatioSetting = ratioSetting ? [ratioSetting intValue] : NSMixedState;
    fRatioLimit = ratioLimit ? [ratioLimit floatValue] : [fDefaults floatForKey: @"RatioLimit"];
    fFinishedSeeding = NO;
    
    fCheckUpload = checkUpload ? [checkUpload intValue] : NSMixedState;
    fUploadLimit = uploadLimit ? [uploadLimit intValue] : [fDefaults integerForKey: @"UploadLimit"];
    fCheckDownload = checkDownload ? [checkDownload intValue] : NSMixedState;
    fDownloadLimit = downloadLimit ? [downloadLimit intValue] : [fDefaults integerForKey: @"DownloadLimit"];
    [self updateSpeedSetting];
	
	if ([self privateTorrent])
		fPex = NO;
	else
		fPex = pex ? [pex boolValue] : YES;
	tr_torrentDisablePex(fHandle, !fPex);
    
    fWaitToStart = waitToStart ? [waitToStart boolValue] : [fDefaults boolForKey: @"AutoStartDownload"];
    fOrderValue = orderValue ? [orderValue intValue] : tr_torrentCount(fLib) - 1;
    fError = NO;
    
    fIcon = [[[NSWorkspace sharedWorkspace] iconForFileType: fInfo->multifile ? NSFileTypeForHFSTypeCode('fldr')
                                                : [[self name] pathExtension]] retain];
    
    fIconFlipped = [fIcon copy];
    [fIconFlipped setFlipped: YES];
    
    fIconSmall = [fIconFlipped copy];
    [fIconSmall setScalesWhenResized: YES];
    [fIconSmall setSize: NSMakeSize(16.0, 16.0)];

    fProgressString = [[NSMutableString alloc] initWithCapacity: 50];
    fStatusString = [[NSMutableString alloc] initWithCapacity: 75];
    fShortStatusString = [[NSMutableString alloc] initWithCapacity: 30];
    fRemainingTimeString = [[NSMutableString alloc] initWithCapacity: 30];
    
    [self createFileList];
    
    //set up advanced bar
    fBitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
        pixelsWide: MAX_PIECES pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
        isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
    
    fPieces = malloc(MAX_PIECES);
    int i;
    for (i = 0; i < MAX_PIECES; i++)
        fPieces[i] = BLANK_PIECE;

    [self update];
    return self;
}

- (void) createFileList
{
    int count = [self fileCount], i;
    tr_file_t * file;
    NSMutableArray * pathComponents;
    NSString * path;
    
    NSMutableArray * fileList = [[NSMutableArray alloc] init],
                    * fileFlatList = [[NSMutableArray alloc] initWithCapacity: count];
    
    for (i = 0; i < count; i++)
    {
        file = &fInfo->files[i];
        
        pathComponents = [[[NSString stringWithUTF8String: file->name] pathComponents] mutableCopy];
        if (fInfo->multifile)
        {
            path = [pathComponents objectAtIndex: 0];
            [pathComponents removeObjectAtIndex: 0];
        }
        else
            path = @"";
        
        [self insertPath: pathComponents forSiblings: fileList withParent: nil previousPath: path
                fileSize: file->length state: NSOnState flatList: fileFlatList];
        [pathComponents autorelease];
    }
    
    fFileList = [[NSArray alloc] initWithArray: fileList];
    [fileList release];
    fFileFlatList = [[NSArray alloc] initWithArray: fileFlatList];
    [fileFlatList release];
}

- (void) insertPath: (NSMutableArray *) components forSiblings: (NSMutableArray *) siblings
        withParent: (NSMutableDictionary *) parent previousPath: (NSString *) previousPath
        fileSize: (uint64_t) size state: (int) state flatList: (NSMutableArray *) flatList
{
    NSString * name = [components objectAtIndex: 0];
    BOOL isFolder = [components count] > 1;
    
    NSMutableDictionary * dict = nil;
    if (isFolder)
    {
        NSEnumerator * enumerator = [siblings objectEnumerator];
        while ((dict = [enumerator nextObject]))
            if ([[dict objectForKey: @"Name"] isEqualToString: name] && [[dict objectForKey: @"IsFolder"] boolValue])
                break;
    }
    
    NSString * currentPath = [previousPath stringByAppendingPathComponent: name];
    
    //create new folder or item if it doesn't already exist
    if (!dict)
    {
        dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: name, @"Name",
                                [NSNumber numberWithBool: isFolder], @"IsFolder",
                                currentPath, @"Path", nil];
        
        [siblings addObject: dict];
        
        if (isFolder)
        {
            [dict setObject: [NSMutableArray array] forKey: @"Children"];
            [dict setObject: [NSNumber numberWithInt: 1] forKey: @"Remaining"];
        }
        else
        {
            [flatList addObject: dict];
            [dict setObject: [NSNumber numberWithUnsignedLongLong: size] forKey: @"Size"];
            [dict setObject: [[NSWorkspace sharedWorkspace] iconForFileType: [name pathExtension]] forKey: @"Icon"];
        }
        
        if (parent)
            [dict setObject: parent forKey: @"Parent"];
        [dict setObject: [NSNumber numberWithInt: state] forKey: @"Check"];
    }
    else
    {
        if (isFolder)
            [dict setObject: [NSNumber numberWithInt: [[dict objectForKey: @"Remaining"] intValue]+1] forKey: @"Remaining"];
        
        int dictState = [[dict objectForKey: @"Check"] intValue];
        if (dictState != NSMixedState && dictState != state)
            [dict setObject: [NSNumber numberWithInt: NSMixedState] forKey: @"Check"];
    }
    
    if (isFolder)
    {
        [components removeObjectAtIndex: 0];
        [self insertPath: components forSiblings: [dict objectForKey: @"Children"]
            withParent: dict previousPath: currentPath fileSize: size state: state flatList: flatList];
    }
}

- (NSImage *) advancedBar
{
    uint32_t * p;
    uint8_t * bitmapData = [fBitmap bitmapData];
    int bytesPerRow = [fBitmap bytesPerRow];
    
    int pieceCount = [self pieceCount];
    int8_t * piecesAvailablity = malloc(pieceCount);
    [self getAvailability: piecesAvailablity size: pieceCount];
    
    //lines 2 to 14: blue, green, or gray depending on whether we have the piece or not
    int i, h, index = 0;
    float increment = (float)pieceCount / (float)MAX_PIECES, indexValue = 0;
    uint32_t color;
    BOOL change;
    for (i = 0; i < MAX_PIECES; i++)
    {
        change = NO;
        if (piecesAvailablity[index] < 0)
        {
            if (fPieces[i] != -1)
            {
                color = kBlue;
                fPieces[i] = -1;
                change = YES;
            }
        }
        else if (piecesAvailablity[index] == 0)
        {
            if (fPieces[i] != 0)
            {
                color = kGray;
                fPieces[i] = 0;
                change = YES;
            }
        }
        else
        {
            if (piecesAvailablity[index] == 1)
            {
                if (fPieces[i] != 1)
                {
                    color = kGreen1;
                    fPieces[i] = 1;
                    change = YES;
                }
            }
            else if (piecesAvailablity[index] == 2)
            {
                if (fPieces[i] != 2)
                {
                    color = kGreen2;
                    fPieces[i] = 2;
                    change = YES;
                }
            }
            else
            {
                if (fPieces[i] != 3)
                {
                    color = kGreen3;
                    fPieces[i] = 3;
                    change = YES;
                }
            }
        }
        
        if (change)
        {
            //point to pixel (i, 2) and draw "vertically"
            p = (uint32_t *)(bitmapData + 2 * bytesPerRow) + i;
            for (h = 2; h < BAR_HEIGHT; h++)
            {
                p[0] = color;
                p = (uint32_t *)((uint8_t *)p + bytesPerRow);
            }
        }
        
        indexValue += increment;
        index = (int)indexValue;
    }
    
    //determine percentage finished and available
    int have = rintf((float)MAX_PIECES * [self progress]), avail;
    if ([self progress] >= 1.0 || ![self isActive] || [self totalPeers] <= 0)
        avail = 0;
    else
    {
        float * piecesFinished = malloc(pieceCount * sizeof(float));
        [self getAmountFinished: piecesFinished size: pieceCount];
        
        float available = 0;
        for (i = 0; i < pieceCount; i++)
            if (piecesAvailablity[i] > 0)
                available += 1.0 - piecesFinished[i];
        
        avail = rintf((float)MAX_PIECES * available / (float)pieceCount);
        if (have + avail > MAX_PIECES) //case if both end in .5 and all pieces are available
            avail--;
        
        free(piecesFinished);
    }
    
    free(piecesAvailablity);
    
    //first two lines: dark blue to show progression, green to show available
    p = (uint32_t *)bitmapData;
    for (i = 0; i < have; i++)
    {
        p[i] = kBlue2;
        p[i + bytesPerRow / 4] = kBlue2;
    }
    for (; i < avail + have; i++)
    {
        p[i] = kGreen3;
        p[i + bytesPerRow / 4] = kGreen3;
    }
    for (; i < MAX_PIECES; i++)
    {
        p[i] = kWhite;
        p[i + bytesPerRow / 4] = kWhite;
    }
    
    //actually draw image
    NSImage * bar = [[NSImage alloc] initWithSize: [fBitmap size]];
    [bar addRepresentation: fBitmap];
    [bar setScalesWhenResized: YES];
    
    return [bar autorelease];
}

- (void) trashFile: (NSString *) path
{
    //attempt to move to trash
    if (![[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation
            source: [path stringByDeletingLastPathComponent] destination: @""
            files: [NSArray arrayWithObject: [path lastPathComponent]] tag: nil])
    {
        //if cannot trash, just delete it (will work if it is on a remote volume)
        if (![[NSFileManager defaultManager] removeFileAtPath: path handler: nil])
            NSLog(@"Could not trash %@", path);
    }
}

@end
