diff options
Diffstat (limited to 'Source/SPExportInitializer.m')
-rw-r--r-- | Source/SPExportInitializer.m | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m new file mode 100644 index 00000000..0ceccbd6 --- /dev/null +++ b/Source/SPExportInitializer.m @@ -0,0 +1,626 @@ +// +// $Id$ +// +// SPExporterInitializer.m +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on March 31, 2010 +// Copyright (c) 2010 Stuart Connolly. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// More info at <http://code.google.com/p/sequel-pro/> + +#import <MCPKit/MCPKit.h> + +#import "SPExportInitializer.h" +#import "SPStringAdditions.h" +#import "SPTableData.h" +#import "TableDocument.h" +#import "TablesList.h" +#import "SPGrowlController.h" +#import "SPMainThreadTrampoline.h" +#import "TableDocument.h" +#import "CustomQuery.h" +#import "SPFileHandle.h" +#import "SPAlertSheets.h" + +#import "SPCSVExporter.h" +#import "SPSQLExporter.h" +#import "SPXMLExporter.h" +#import "SPDotExporter.h" + +@implementation SPExportController (SPExportInitializer) + +/** + * Initializes the export process by analysing the selected criteria. + */ +- (void)initializeExportUsingSelectedOptions +{ + NSArray *dataArray = nil; + + // Get rid of the cached connection encoding + if (sqlPreviousConnectionEncoding) [sqlPreviousConnectionEncoding release], sqlPreviousConnectionEncoding = nil; + + createCustomFilename = ([exportCustomFilenameButton state] && (![[exportCustomFilenameTokenField stringValue] isEqualToString:@""])); + + // First determine what type of export the user selected + for (NSToolbarItem *item in [exportToolbar items]) + { + if ([[item itemIdentifier] isEqualToString:[exportToolbar selectedItemIdentifier]]) { + exportType = [item tag]; + break; + } + } + + // Determine what data to use (filtered result, custom query result or selected table(s)) for the export operation + exportSource = (exportType == SPDotExport) ? SPTableExport : ([exportInputMatrix selectedRow] + 1); + + NSMutableArray *exportTables = [NSMutableArray array]; + + // Set whether or not we are to export to multiple files + [self setExportToMultipleFiles:[exportFilePerTableCheck state]]; + + // Get the data depending on the source + switch (exportSource) + { + case SPFilteredExport: + dataArray = [tableContentInstance currentResult]; + break; + case SPQueryExport: + dataArray = [customQueryInstance currentResult]; + break; + case SPTableExport: + // Create an array of tables to export + for (NSMutableArray *table in tables) + { + if (exportType == SPSQLExport) { + if ([[table objectAtIndex:1] boolValue] || [[table objectAtIndex:2] boolValue] || [[table objectAtIndex:3] boolValue]) { + [exportTables addObject:table]; + } + } + else if (exportType == SPDotExport) { + [exportTables addObject:[table objectAtIndex:0]]; + } + else { + if ([[table objectAtIndex:2] boolValue]) { + [exportTables addObject:[table objectAtIndex:0]]; + } + } + } + + break; + } + + // Set the export type label + switch (exportType) + { + case SPSQLExport: + exportTypeLabel = @"SQL"; + break; + case SPCSVExport: + exportTypeLabel = @"CSV"; + break; + case SPXMLExport: + exportTypeLabel = @"XML"; + break; + case SPDotExport: + exportTypeLabel = @"Dot"; + break; + } + + // Begin the export based on the source + switch (exportSource) + { + case SPFilteredExport: + case SPQueryExport: + [self exportTables:nil orDataArray:dataArray]; + break; + case SPTableExport: + [self exportTables:exportTables orDataArray:nil]; + break; + } +} + +/** + * Exports the contents of the supplied array of tables or data array. + */ +- (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray +{ + NSUInteger i; + SPFileHandle *singleFileHandle = nil; + BOOL singleFileHeaderHasBeenWritten = NO; + + // Change query logging mode + [tableDocumentInstance setQueryMode:SPImportExportQueryMode]; + + // Start the notification timer to allow notifications to be shown even if frontmost for long queries + [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Export Finished"]; + + // Reset the interface + [[exportProgressTitle onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; + [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Writing...", @"text showing that app is writing text file")]; + + [[exportProgressText onMainThread] displayIfNeeded]; + [[exportProgressIndicator onMainThread] setDoubleValue:0]; + [[exportProgressIndicator onMainThread] displayIfNeeded]; + + // Open the progress sheet + [NSApp beginSheet:exportProgressWindow + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; + + // CSV export + if (exportType == SPCSVExport) { + + SPCSVExporter *csvExporter = nil; + + // If the user has selected to only export to a single file or this is a filtered or custom query + // export, create the single file now and assign it to all subsequently created exporters. + if ((![self exportToMultipleFiles]) || (exportSource == SPFilteredExport) || (exportSource == SPQueryExport)) { + + NSString *filename = @""; + + // Create custom filename if required + if (createCustomFilename) { + filename = [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil]; + } + else { + // Determine what the file name should be + switch (exportSource) + { + case SPFilteredExport: + filename = [NSString stringWithFormat:@"%@_view", [tableDocumentInstance table]]; + break; + case SPQueryExport: + filename = @"query_result"; + break; + case SPTableExport: + filename = [tableDocumentInstance database]; + break; + } + } + + singleFileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:filename]]; + } + + // Start the export process depending on the data source + if (exportSource == SPTableExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + // Loop through the tables, creating an exporter for each + for (NSString *table in exportTables) + { + csvExporter = [self initializeCSVExporterForTable:table orDataArray:nil]; + + // If required create a single file handle for all CSV exports + if (![self exportToMultipleFiles]) { + [csvExporter setExportOutputFileHandle:singleFileHandle]; + + if (!singleFileHeaderHasBeenWritten) { + + NSMutableString *lineEnding = [NSMutableString stringWithString:[exportCSVLinesTerminatedField stringValue]]; + + // Escape tabs, line endings and carriage returns + [lineEnding replaceOccurrencesOfString:@"\\t" withString:@"\t" + options:NSLiteralSearch + range:NSMakeRange(0, [lineEnding length])]; + + + [lineEnding replaceOccurrencesOfString:@"\\n" withString:@"\n" + options:NSLiteralSearch + range:NSMakeRange(0, [lineEnding length])]; + + [lineEnding replaceOccurrencesOfString:@"\\r" withString:@"\r" + options:NSLiteralSearch + range:NSMakeRange(0, [lineEnding length])]; + + // Write the file header and the first table name + [singleFileHandle writeData:[[NSMutableString stringWithFormat:@"%@: %@ %@: %@ %@: %@%@%@%@ %@%@%@", + NSLocalizedString(@"Host", @"csv export host heading"), + [tableDocumentInstance host], + NSLocalizedString(@"Database", @"csv export database heading"), + [tableDocumentInstance database], + NSLocalizedString(@"Generation Time", @"csv export generation time heading"), + [NSDate date], + lineEnding, + lineEnding, + NSLocalizedString(@"Table", @"csv export table heading"), + table, + lineEnding, + lineEnding] dataUsingEncoding:[connection encoding]]]; + + singleFileHeaderHasBeenWritten = YES; + } + } + + [exporters addObject:csvExporter]; + } + } + else { + csvExporter = [self initializeCSVExporterForTable:nil orDataArray:dataArray]; + + [csvExporter setExportOutputFileHandle:singleFileHandle]; + + [exporters addObject:csvExporter]; + } + } + // SQL export + else if (exportType == SPSQLExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + SPSQLExporter *sqlExporter = [[SPSQLExporter alloc] initWithDelegate:self]; + + [sqlExporter setSqlDatabaseHost:[tableDocumentInstance host]]; + [sqlExporter setSqlDatabaseName:[tableDocumentInstance database]]; + [sqlExporter setSqlDatabaseVersion:[tableDocumentInstance mySQLVersion]]; + + [sqlExporter setSqlOutputIncludeUTF8BOM:[exportUseUTF8BOMButton state]]; + [sqlExporter setSqlOutputEncodeBLOBasHex:[exportSQLBLOBFieldsAsHexCheck state]]; + [sqlExporter setSqlOutputCompressFile:[exportCompressOutputFile state]]; + [sqlExporter setSqlOutputIncludeErrors:[exportSQLIncludeErrorsCheck state]]; + + // Set generic properties + [sqlExporter setConnection:connection]; + [sqlExporter setExportOutputEncoding:[connection encoding]]; + [sqlExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; + + // Cache the current connection encoding then change it to UTF-8 to allow SQL dumps to work + sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[tableDocumentInstance connectionEncoding]]; + sqlPreviousConnectionEncodingViaLatin1 = [tableDocumentInstance connectionEncodingViaLatin1:nil]; + + [tableDocumentInstance setConnectionEncoding:@"utf8" reloadingViews:NO]; + + NSMutableArray *tableTypes = [[NSMutableArray alloc] init]; + NSMutableDictionary *infoDict = [[NSMutableDictionary alloc] init]; + + // Build the table information dictionary as well as the table array with item type + for (NSArray *table in exportTables) + { + [infoDict setObject:[tableDataInstance informationForTable:[table objectAtIndex:0]] forKey:[table objectAtIndex:0]]; + } + + [sqlExporter setSqlTableInformation:infoDict]; + [sqlExporter setSqlExportTables:exportTables]; + + // Set the exporter's max progress + [sqlExporter setExportMaxProgress:((NSInteger)[exportProgressIndicator bounds].size.width)]; + + // Set the progress bar's max value + [exportProgressIndicator setMaxValue:[sqlExporter exportMaxProgress]]; + + [infoDict release]; + [tableTypes release]; + + NSString *filename = @""; + + // Create custom filename if required + filename = (createCustomFilename) ? [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil] : [NSString stringWithFormat:@"%@_%@", [tableDocumentInstance database], [[NSDate date] descriptionWithCalendarFormat:@"%Y-%m-%d" timeZone:nil locale:nil]]; + + SPFileHandle *fileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:[filename stringByAppendingPathExtension:([exportCompressOutputFile state]) ? @"gz" : @"sql"]]]; + + [sqlExporter setExportOutputFileHandle:fileHandle]; + + [exporters addObject:sqlExporter]; + + [sqlExporter release]; + } + // XML export + else if (exportType == SPXMLExport) { + + SPXMLExporter *xmlExporter = nil; + + // If the user has selected to only export to a single file or this is a filtered or custom query + // export, create the single file now and assign it to all subsequently created exporters. + if ((![self exportToMultipleFiles]) || (exportSource == SPFilteredExport) || (exportSource == SPQueryExport)) { + + NSString *filename = @""; + + // Create custom filename if required + if (createCustomFilename) { + filename = [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil]; + } + else { + // Determine what the file name should be + switch (exportSource) + { + case SPFilteredExport: + filename = [NSString stringWithFormat:@"%@_view", [tableDocumentInstance table]]; + break; + case SPQueryExport: + filename = @"query_result"; + break; + case SPTableExport: + filename = [tableDocumentInstance database]; + break; + } + } + + singleFileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:[filename stringByAppendingPathExtension:@"xml"]]]; + + // Write the file header + [self writeXMLHeaderToFileHandle:singleFileHandle]; + } + + // Start the export process depending on the data source + if (exportSource == SPTableExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + // Loop through the tables, creating an exporter for each + for (NSString *table in exportTables) + { + xmlExporter = [self initializeXMLExporterForTable:table orDataArray:nil]; + + // If required create a single file handle for all XML exports + if (![self exportToMultipleFiles]) { + [xmlExporter setExportOutputFileHandle:singleFileHandle]; + + if (!singleFileHeaderHasBeenWritten) { + + // Write the file header + [self writeXMLHeaderToFileHandle:singleFileHandle]; + + singleFileHeaderHasBeenWritten = YES; + } + } + + [exporters addObject:xmlExporter]; + } + } + else { + xmlExporter = [self initializeXMLExporterForTable:nil orDataArray:dataArray]; + + [xmlExporter setExportOutputFileHandle:singleFileHandle]; + + [exporters addObject:xmlExporter]; + } + } + // Dot export + else if (exportType == SPDotExport) { + + // Cache the number of tables being exported + exportTableCount = [exportTables count]; + + SPDotExporter *dotExporter = [[SPDotExporter alloc] initWithDelegate:self]; + + [dotExporter setDotTableData:tableDataInstance]; + + [dotExporter setDotDatabaseHost:[tableDocumentInstance host]]; + [dotExporter setDotDatabaseName:[tableDocumentInstance database]]; + [dotExporter setDotDatabaseVersion:[tableDocumentInstance mySQLVersion]]; + + // Set generic properties + [dotExporter setConnection:connection]; + [dotExporter setExportOutputEncoding:[connection encoding]]; + [dotExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; + + // Cache the current connection encoding then change it to UTF-8 to allow SQL dumps to work + sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[tableDocumentInstance connectionEncoding]]; + sqlPreviousConnectionEncodingViaLatin1 = [tableDocumentInstance connectionEncodingViaLatin1:nil]; + + [tableDocumentInstance setConnectionEncoding:@"utf8" reloadingViews:NO]; + + [dotExporter setDotExportTables:exportTables]; + + // Set the exporter's max progress + [dotExporter setExportMaxProgress:(NSInteger)[exportProgressIndicator bounds].size.width]; + + // Set the progress bar's max value + [exportProgressIndicator setMaxValue:[dotExporter exportMaxProgress]]; + + NSString *filename = @""; + + // Create custom filename if required + if (createCustomFilename) { + filename = [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil]; + } + else { + filename = [tableDocumentInstance database]; + } + + SPFileHandle *fileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:[filename stringByAppendingPathExtension:@"dot"]]]; + + [dotExporter setExportOutputFileHandle:fileHandle]; + + [exporters addObject:dotExporter]; + + [dotExporter release]; + } + + // Add the first exporter to the operation queue + [operationQueue addOperation:[exporters objectAtIndex:0]]; + + // Remove the exporter we just added to the operation queue from our list of exporters + // so we know it's already been done. + [exporters removeObjectAtIndex:0]; +} + +/** + * Initialises a CSV exporter for the supplied table name or data array. + */ +- (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray +{ + NSString *filename = @""; + SPFileHandle *fileHandle = nil; + + SPCSVExporter *csvExporter = [[SPCSVExporter alloc] initWithDelegate:self]; + + // Depeding on the export source, set the table name or data array + if (exportSource == SPTableExport) { + [csvExporter setCsvTableName:table]; + } + else { + [csvExporter setCsvDataArray:dataArray]; + } + + [csvExporter setCsvTableData:tableDataInstance]; + + [csvExporter setCsvOutputFieldNames:[exportCSVIncludeFieldNamesCheck state]]; + [csvExporter setCsvFieldSeparatorString:[exportCSVFieldsTerminatedField stringValue]]; + [csvExporter setCsvEnclosingCharacterString:[exportCSVFieldsWrappedField stringValue]]; + [csvExporter setCsvLineEndingString:[exportCSVLinesTerminatedField stringValue]]; + [csvExporter setCsvEscapeString:[exportCSVFieldsEscapedField stringValue]]; + [csvExporter setCsvNULLString:[exportCSVNULLValuesAsTextField stringValue]]; + + // If required create separate files + if ([self exportToMultipleFiles]) { + + if (createCustomFilename) { + + // Create custom filename based on the selected format + filename = [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:table]; + + // If the user chose to use a custom filename format and we exporting to multiple files, make + // sure the table name is included to ensure the output files are unique. + filename = ([[exportCustomFilenameTokenField stringValue] rangeOfString:@"table" options:NSLiteralSearch].location == NSNotFound) ? [filename stringByAppendingFormat:@"_%@", table] : filename; + } + else { + filename = table; + } + + fileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:filename]]; + + [csvExporter setExportOutputFileHandle:fileHandle]; + } + + // Set generic properties + [csvExporter setConnection:connection]; + [csvExporter setExportOutputEncoding:[connection encoding]]; + [csvExporter setExportMaxProgress:((NSInteger)[exportProgressIndicator bounds].size.width)]; + [csvExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; + + // Set the progress bar's max value + [exportProgressIndicator setMaxValue:[csvExporter exportMaxProgress]]; + + return [csvExporter autorelease]; +} + +/** + * Initialises a XML exporter for the supplied table name or data array. + */ +- (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray +{ + NSString *filename = @""; + SPFileHandle *fileHandle = nil; + + SPXMLExporter *xmlExporter = [[SPXMLExporter alloc] initWithDelegate:self]; + + // Depeding on the export source, set the table name or data array + if (exportSource == SPTableExport) { + [xmlExporter setXmlTableName:table]; + } + else { + [xmlExporter setXmlDataArray:dataArray]; + } + + // Regardless of the export source, set exporter's table name as it's used in the output + // of table and table content exports. + [xmlExporter setXmlTableName:[tablesListInstance tableName]]; + + // If required create separate files + if ((exportSource == SPTableExport) && exportToMultipleFiles && (exportTableCount > 0)) { + filename = [[exportPathField stringValue] stringByAppendingPathComponent:table]; + + fileHandle = [self getFileHandleForFilePath:filename]; + + // Write the file header + [self writeXMLHeaderToFileHandle:fileHandle]; + + [xmlExporter setExportOutputFileHandle:fileHandle]; + } + + // Set generic properties + [xmlExporter setConnection:connection]; + [xmlExporter setExportOutputEncoding:[connection encoding]]; + [xmlExporter setExportMaxProgress:((NSInteger)[exportProgressIndicator bounds].size.width)]; + [xmlExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; + + // Set the progress bar's max value + [exportProgressIndicator setMaxValue:[xmlExporter exportMaxProgress]]; + + return [xmlExporter autorelease]; +} + +/** + * Writes the XML file header to the supplied file handle. + */ +- (void)writeXMLHeaderToFileHandle:(SPFileHandle *)fileHandle +{ + NSMutableString *header = [NSMutableString string]; + + [header setString:@"<?xml version=\"1.0\"?>\n\n"]; + [header appendString:@"<!--\n-\n"]; + [header appendString:@"- Sequel Pro XML dump\n"]; + [header appendString:[NSString stringWithFormat:@"- Version %@\n-\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]]; + [header appendString:[NSString stringWithFormat:@"- %@\n- %@\n-\n", SPHomePageURL, SPDevURL]]; + [header appendString:[NSString stringWithFormat:@"- Host: %@ (MySQL %@)\n", [tableDocumentInstance host], [tableDocumentInstance mySQLVersion]]]; + [header appendString:[NSString stringWithFormat:@"- Database: %@\n", [tableDocumentInstance database]]]; + [header appendString:[NSString stringWithFormat:@"- Generation Time: %@\n", [NSDate date]]]; + [header appendString:@"-\n-->\n\n"]; + + if (exportSource == SPTableExport) { + [header appendString:[NSString stringWithFormat:@"<%@>\n\n", [[tableDocumentInstance database] HTMLEscapeString]]]; + } + + [fileHandle writeData:[header dataUsingEncoding:NSUTF8StringEncoding]]; +} + +/** + * Returns a file handle for writing at the supplied path. + */ +- (SPFileHandle *)getFileHandleForFilePath:(NSString *)filePath +{ + SPFileHandle *fileHandle = nil; + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:filePath]) { + if ((![fileManager isWritableFileAtPath:filePath]) || (!(fileHandle = [SPFileHandle fileHandleForWritingAtPath:filePath]))) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + NSLocalizedString(@"Couldn't replace the file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be replaced")); + return nil; + } + } + // Otherwise attempt to create a file + else { + if (![fileManager createFileAtPath:filePath contents:[NSData data] attributes:nil]) { + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + NSLocalizedString(@"Couldn't write to file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be written")); + return nil; + } + + // Retrieve a filehandle for the file, attempting to delete it on failure. + fileHandle = [SPFileHandle fileHandleForWritingAtPath:filePath]; + + if (!fileHandle) { + [[NSFileManager defaultManager] removeFileAtPath:filePath handler:nil]; + + SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, [tableDocumentInstance parentWindow], self, nil, nil, + NSLocalizedString(@"Couldn't write to file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be written")); + return nil; + } + } + + return fileHandle; +} + +@end |