// Copyright 1997-2000 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OHDownloader.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OmniAppKit/OmniAppKit.h>
#import <OWF/OWF.h>
#import <OmniBase/system.h>

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Support.subproj/OHDownloader.m,v 1.5 2000/01/20 00:27:59 kc Exp $")

@interface OHDownloader (Private)
+ (void)controllerRequestsTerminate:(NSNotification *)notification;
+ (void)controllerWillTerminate:(NSNotification *)notification;
- (NSString *)uniqueFilenameFromAddress:(OWAddress *)address;
- (void)present;
@end

@implementation OHDownloader

NSString *OHDownloaderTaskHeaderName = @"Downloading files";
static NSLock *downloadDirectoryLock;

+ (void)didLoad;
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerRequestsTerminate:) name:OFControllerRequestsTerminateNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerWillTerminate:) name:OFControllerWillTerminateNotification object:nil];
}

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    if (initialized)
        return;
    initialized = YES;

    downloadDirectoryLock = [[NSLock alloc] init];
}

+ (NSString *)suggestedFilenameForAddress:(OWAddress *)address;
{
    NSString *filename;
    OWURL *url;

    url = [address url];
    filename = [OWURL decodeURLString:[[url path] lastPathComponent]];
    if (!filename || ![filename length]) {
        if ([[url scheme] isEqualToString:@"http"])
            filename = @"index.html";
        else
            filename = @"download";
    }

    return filename;
}
                                               
+ (NSString *)runSavePanelOnAddress:(OWAddress *)address asFileType:(NSString *)fileType;
{
    NSSavePanel *savePanel;
    NSString *directory;
    NSString *filename;
    BOOL okayButtonPressed;
    static BOOL alreadyPrompted = NO;

    savePanel = [NSSavePanel savePanel];
    [savePanel setRequiredFileType:fileType ? fileType : @""];
    [savePanel setTreatsFilePackagesAsDirectories:NO];
    [savePanel setTitle:fileType ? [@"Save " stringByAppendingString:[fileType uppercaseString]] : @"Save As"];

    directory = [self downloadDirectoryPath];
    filename = [self suggestedFilenameForAddress: address];
    if (fileType)
        filename = [NSString stringWithFormat:@"%@.%@", [filename stringByDeletingPathExtension], @".", fileType];

    okayButtonPressed = [savePanel runModalForDirectory:(alreadyPrompted ? nil : directory) file:filename] == NSOKButton;
#ifdef YELLOW_BOX
    // Rhapsody automatically remembers your last save directory if you pass in @"", OpenStep does not.
    alreadyPrompted = YES;
#endif
    return okayButtonPressed ? [[[savePanel filename] copy] autorelease]: nil;
}


+ (NSString *)downloadDirectoryPath;
{
    NSString *downloadDirectoryPath;
    NSFileManager *fileManager;

    fileManager = [NSFileManager defaultManager];

    [downloadDirectoryLock lock];

    downloadDirectoryPath = [[[OFUserDefaults sharedUserDefaults] stringForKey:@"OHDownloadDirectory"] stringByExpandingTildeInPath];

    if (downloadDirectoryPath && [downloadDirectoryPath length] && ![fileManager fileExistsAtPath:downloadDirectoryPath])
        [fileManager createDirectoryAtPath:downloadDirectoryPath attributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:0777] forKey:@"NSPosixPermissions"]];

    if (![fileManager fileExistsAtPath:downloadDirectoryPath]) {
        [downloadDirectoryLock unlock];
        return [[NSFileManager defaultManager] scratchDirectoryPath];
    }

    [downloadDirectoryLock unlock];
    return downloadDirectoryPath;
}


+ (void)downloadAddressAndPromptForFilename:(OWAddress *)address;
{
    OHDownloader *downloader;
    NSString *filename;

    filename = [self runSavePanelOnAddress:address asFileType:nil];
    if (!filename)
        return;
    
    downloader = [[OHDownloader alloc] init];
    [downloader setRequiredFilename:filename];
    [downloader setPresentsWhenFinished:NO];
    [OWWebPipeline startPipelineWithContent:address target:downloader];
    [downloader release];
}


// Init and dealloc

+ (OHDownloader *)downloader;
{
    return [[[self alloc] init] autorelease];
}

+ (OHDownloader *)downloaderDownloadingAddressNoPresent:(OWAddress *)address;
{
    OHDownloader *downloader;

    downloader = [[OHDownloader alloc] init];
    [downloader setPresentsWhenFinished:NO];
    [OWWebPipeline startPipelineWithContent:address target:downloader];

    return [downloader autorelease];
}

+ (OHDownloader *)downloaderOpenWorkspaceOnFileFromAddress:(OWAddress *)address;
{
    OHDownloader *downloader;

    downloader = [[OHDownloader alloc] init];
    [downloader setLaunchWhenFinished:NO];
    [downloader setRequiredDirectoryPath:[self downloadDirectoryPath]];

    [OWWebPipeline startPipelineWithContent:address target:downloader];

    return [downloader autorelease];
}

- init;
{
    if (![super init])
	return nil;

    noDataStreamYetLock = [[NSLock alloc] init];
    [noDataStreamYetLock lock];
    shouldPresent = YES;
    shouldLaunch = YES;
    requiredFilename = nil;
    return self;
}

- (void)dealloc;
{
    [OWPipeline cancelTarget:self];
    [dataStream release];
    [noDataStreamYetLock unlock];
    [noDataStreamYetLock release];
    [requiredFilename release];
    [requiredDirectoryPath release];
    [super dealloc];
}

//

- (void)setRequiredFilename:(NSString *)filename;
{
    if (requiredFilename == filename)
        return;
    [requiredFilename release];
    requiredFilename = [filename retain];
}


- (void)setRequiredDirectoryPath:(NSString *)directoryName;
{
    if (requiredDirectoryPath == directoryName)
        return;
    [requiredDirectoryPath release];
    requiredDirectoryPath = [directoryName retain];
}


- (void)setPresentsWhenFinished:(BOOL)presentFlag;
{
    shouldPresent = presentFlag;
}

- (void)setLaunchWhenFinished:(BOOL)launchFlag;
{
    shouldLaunch = launchFlag;
}

- (NSString *)filename;
{
    return [dataStream filename];
}

- (void)blockUntilDownloadComplete;
{
    [noDataStreamYetLock lock];
    [noDataStreamYetLock unlock];
    [dataStream waitForDataEnd];
}

// OWTarget protocol

- (OWContentType *)targetContentType;
{
    return [OWSourceProcessor sourceContentType];
}

- (OWTargetContentDisposition)pipeline:(OWPipeline *)aPipeline hasContent:(id <OWContent>)someContent;
{
    id <OWContent> originalContent;
    OWContentType *originalContentType;
    NSString *filename;

    originalContent = (id <OWContent>)[(OWContentContainer *)someContent content];
    originalContentType = [originalContent contentType];
    if ([originalContent conformsToProtocol:@protocol(OWAddress)]) {
	[aPipeline addContent:originalContent];
	[aPipeline startProcessingContent];
        return OWTargetContentDisposition_ContentUpdatedOrTargetChanged;
    }
    if (![originalContent isKindOfClass:[OWDataStream class]]) {
	[aPipeline processor:nil hasErrorName:@"Bad stream" reason:[NSString stringWithFormat:@"Cannot download %@", [originalContentType shortDescription]]];
	[noDataStreamYetLock unlock];
        return OWTargetContentDisposition_ContentRejectedCancelPipeline;
    }
    
    dataStream = [originalContent retain];
    
    if (requiredFilename)
	filename = requiredFilename;
    else
	filename = [self uniqueFilenameFromAddress:[aPipeline lastAddress]];

    if (![dataStream pipeToFilename:filename contentCache:[aPipeline contentCacheForLastAddress]]) {
        // Didn't save to our requiredFilename, but that's okay, we'll copy the file at data stream's filename to our requiredFilename later.
    }
    [noDataStreamYetLock unlock];

    return OWTargetContentDisposition_ContentAccepted;
}

- (OWTargetContentDisposition)pipeline:(OWPipeline *)aPipeline hasAlternateContent:(id <OWContent>)someContent;
{
    [aPipeline processor:nil hasErrorName:@"Bad stream" reason:[NSString stringWithFormat:@"Cannot download %@", [[someContent contentType] contentTypeString]]];
    [noDataStreamYetLock unlock];
    return OWTargetContentDisposition_ContentRejectedCancelPipeline;
}

- (OWTargetContentDisposition)pipeline:(OWPipeline *)aPipeline hasErrorContent:(id <OWContent>)someContent;
{
//    [aPipeline processor:nil hasErrorName:[NSString stringWithFormat:@"Download Error"]];
    [noDataStreamYetLock unlock];
    return OWTargetContentDisposition_ContentRejectedCancelPipeline;
}

- (void)pipelineDidBegin:(OWPipeline *)aPipeline;
{
    [[NSNotificationCenter defaultCenter] postNotificationName:OHDownloaderStartedNotification object:self];
    [self retain]; // paired with -pipelineDidEnd:
}

- (void)pipelineDidEnd:(OWPipeline *)aPipeline;
{
    [self autorelease]; // paired with -pipelineDidBegin:

    if (!dataStream)
        [noDataStreamYetLock unlock];

    if ([aPipeline state] == PipelineAborting) {
        // Aborting the data stream removes the file into which it was piped
        [dataStream dataAbort];
        [[NSNotificationCenter defaultCenter] postNotificationName:OHDownloaderAbortedNotification object:self];
    } else {
        NSString *filename;

        filename = [dataStream filename];
        OBASSERT(filename);
        if (requiredFilename && ![requiredFilename isEqualToString:filename]) {
            if (![[NSFileManager defaultManager] copyPath:filename toPath:requiredFilename handler:nil]) {
                // Guess we ought to let someone know.
                NSLog(@"Failed to copy %@ to %@", filename, requiredFilename);
            }
        }
        if (shouldPresent)
            [self mainThreadPerformSelector:@selector(present)];
        [[NSNotificationCenter defaultCenter] postNotificationName:OHDownloaderFinishedNotification object:self];
    }
}

// OWOptionalTarget protocol

- (OWContentInfo *)parentContentInfo;
{
    return [OWContentInfo headerContentInfoWithName:OHDownloaderTaskHeaderName];
}

- (NSString *)targetTypeFormatString;
{
    return @"Downloading %@";
}

@end


@implementation OHDownloader (Private)

+ (void)controllerRequestsTerminate:(NSNotification *)notification;
{
    if (![[OWContentInfo headerContentInfoWithName:OHDownloaderTaskHeaderName] activeChildTasksCount])
        return;
    
    switch (NSRunAlertPanel(@"Downloads in Progress", @"There are active downloads.  Quitting now will truncate these files.", @"Quit anyway", @"Cancel quit", NULL)) {
        case NSAlertAlternateReturn:
            [NSException raise:OFControllerRequestsCancelTerminateException format:@"The user requests that the terminate operation be cancelled."];
            break;
        case NSAlertDefaultReturn:
        case NSAlertOtherReturn:
            break;
    }
}


+ (void)controllerWillTerminate:(NSNotification *)notification;
{
    [[NSFileManager defaultManager] removeScratchDirectory];
}

- (NSString *)uniqueFilenameFromAddress:(OWAddress *)address;
{
    NSString *filename;

    filename = [[dataStream encodedContentType] pathForEncoding:[dataStream contentEncoding] givenOriginalPath:[isa suggestedFilenameForAddress:address]];

    if (requiredDirectoryPath)
        filename = [requiredDirectoryPath stringByAppendingPathComponent:filename];
    else
        filename = [[[NSFileManager defaultManager] scratchDirectoryPath] stringByAppendingPathComponent:filename];

    return [[NSFileManager defaultManager] uniqueFilenameFromName:filename];
}

- (void)present;
{
    NSWorkspace *sharedWorkspace;
    NSString *filename;
    NSString *application, *type;
    
    [self blockUntilDownloadComplete];
    filename = requiredFilename ? requiredFilename : [dataStream filename];
    OBASSERT(filename);

    // Don't try to launch if there's no application, just select it (don't open huge binary files in Edit).
    sharedWorkspace = [NSWorkspace sharedWorkspace];
    if ([sharedWorkspace getInfoForFile:filename application:&application type:&type]) {
        if ([application isEqualToString:@"Edit"] || [application isEqualToString:@"TextEdit"])
            application = nil;
    }
    
    if (shouldLaunch && application)
	[sharedWorkspace openFile:filename];
    else
	[sharedWorkspace selectFile:filename inFileViewerRootedAtPath:[filename stringByDeletingLastPathComponent]];
}

@end

DEFINE_NSSTRING(OHDownloaderStartedNotification);
DEFINE_NSSTRING(OHDownloaderFinishedNotification);
DEFINE_NSSTRING(OHDownloaderAbortedNotification);
