// // $Id$ // // SPExportController.m // sequel-pro // // Created by Ben Perry (benperry.com.au) on 21/02/09. // // 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 #import "SPExportController.h" #import "SPCSVExporter.h" #import "TablesList.h" #import "SPTableData.h" #import "TableDocument.h" #import "SPArrayAdditions.h" #import "SPStringAdditions.h" @interface SPExportController (PrivateAPI) - (void)_loadTables; - (NSString *)_htmlEscapeString:(NSString *)string; - (void)_initializeExportUsingSelectedOptions; - (BOOL)_exportTablesAsCSV:(NSArray *)exportTables usingDataExporter:(SPExporter *)exporter; @end @implementation SPExportController @synthesize connection; @synthesize exportCancelled; /** * Initializes an instance of SPExportController */ - (id)init { if ((self = [super init])) { [self setExportCancelled:NO]; tables = [[NSMutableArray alloc] init]; operationQueue = [[NSOperationQueue alloc] init]; } return self; } /** * Upon awakening select the first toolbar item */ - (void)awakeFromNib { // Upon awakening select the SQL tab [exportToolbar setSelectedItemIdentifier:[[[exportToolbar items] objectAtIndex:0] itemIdentifier]]; } #pragma mark - #pragma mark IB action methods /** * Display the export window allowing the user to select what and of what type to export. */ - (void)export { if (!exportWindow) [NSBundle loadNibNamed:@"ExportDialog" owner:self]; [self _loadTables]; [exportPathField setStringValue:NSHomeDirectory()]; [NSApp beginSheet:exportWindow modalForWindow:tableWindow modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil]; } /** * Closes the export dialog */ - (IBAction)closeSheet:(id)sender { [NSApp endSheet:exportWindow returnCode:[sender tag]]; [exportWindow orderOut:self]; } /** * */ - (IBAction)switchTab:(id)sender { if ([sender isKindOfClass:[NSToolbarItem class]]) { [exportTabBar selectTabViewItemWithIdentifier:[[sender label] lowercaseString]]; [exportFilePerTableCheck setHidden:[[sender label] isEqualToString:@"Excel"]]; [exportFilePerTableNote setHidden:[[sender label] isEqualToString:@"Excel"]]; } } /** * */ - (IBAction)switchInput:(id)sender { if ([sender isKindOfClass:[NSMatrix class]]) { [exportTableList setEnabled:([[sender selectedCell] tag] == 3)]; } } /** * */ - (IBAction)cancelExport:(id)sender { // Cancel the export operation here } /** * */ - (IBAction)changeExportOutputPath:(id)sender { NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:NO]; [panel setCanChooseDirectories:YES]; [panel setCanCreateDirectories:YES]; [panel beginSheetForDirectory:NSHomeDirectory() file:nil modalForWindow:exportWindow modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; } #pragma mark - #pragma mark Table view datasource methods - (int)numberOfRowsInTableView:(NSTableView *)aTableView; { return [tables count]; } - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { return NSArrayObjectAtIndex([tables objectAtIndex:rowIndex], ([[aTableColumn identifier] isEqualToString:@"switch"]) ? 0 : 1); } - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:0 withObject:anObject]; } #pragma mark - #pragma mark Table view delegate methods - (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex { return (aTableView != exportTableList); } - (BOOL)tableView:(NSTableView *)aTableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { return (aTableView == exportTableList); } - (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex { [aCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; } #pragma mark - #pragma mark Toolbar delegate methods - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar { NSMutableArray *items = [NSMutableArray arrayWithCapacity:6]; for (NSToolbarItem *item in [toolbar items]) { [items addObject:[item itemIdentifier]]; } return items; } #pragma mark - #pragma mark Exporter delegate methods /** * Called when CSV data conversion process is complete and the data is available. */ - (void)csvDataAvailable:(NSString *)data { } /** * Called when SQL data conversion process is complete and the data is available. */ - (void)sqlDataAvailable:(NSString *)data { } /** * Called when XML data conversion process is complete and the data is available. */ - (void)xmlDataAvailable:(NSString *)data { } #pragma mark - #pragma mark Other /** * Invoked when the user */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { // Perform the export if (returnCode == NSOKButton) { // Initialize the export after half a second to give the export sheet a chance to close [self performSelector:@selector(_initializeExportUsingSelectedOptions) withObject:nil afterDelay:0.5]; } } /** * Invoked when the user dismisses the save panel. Updates the selected directory is they clicked OK. */ - (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo { if (returnCode == NSOKButton) { [exportPathField setStringValue:[panel directory]]; } } /** * Dealloc */ - (void)dealloc { [tables release], tables = nil; [operationQueue release], operationQueue = nil; [super dealloc]; } @end @implementation SPExportController (PrivateAPI) /** * Loads all the available database tables in table view. */ - (void)_loadTables { NSUInteger i; [tables removeAllObjects]; MCPResult *queryResult = (MCPResult *)[[self connection] listTables]; if ([queryResult numOfRows]) [queryResult dataSeek:0]; for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) { [tables addObject:[NSMutableArray arrayWithObjects: [NSNumber numberWithBool:YES], NSArrayObjectAtIndex([queryResult fetchRowAsArray], 0), nil]]; } [exportTableList reloadData]; } /** * Escapes the supplied HTML string */ - (NSString *)_htmlEscapeString:(NSString *)string { NSMutableString *mutableString = [NSMutableString stringWithString:string]; [mutableString replaceOccurrencesOfString:@"&" withString:@"&" options:NSLiteralSearch range:NSMakeRange(0, [mutableString length])]; [mutableString replaceOccurrencesOfString:@"<" withString:@"<" options:NSLiteralSearch range:NSMakeRange(0, [mutableString length])]; [mutableString replaceOccurrencesOfString:@">" withString:@">" options:NSLiteralSearch range:NSMakeRange(0, [mutableString length])]; [mutableString replaceOccurrencesOfString:@"\"" withString:@""" options:NSLiteralSearch range:NSMakeRange(0, [mutableString length])]; return [NSString stringWithString:mutableString]; } /** * */ - (void)_initializeExportUsingSelectedOptions { // First determine what type of export the user selected SPExportType exportType = 0; 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 tables) for the export operation SPExportSource exportSource = ([exportInputMatrix selectedRow] + 1); NSMutableArray *exportTables = [NSMutableArray array]; // Get the data depending on the source switch (exportSource) { case SP_FILTERED_EXPORT: break; case SP_CUSTOM_QUERY_EXPORT: break; case SP_TABLE_EXPORT: // Create an array of tables to export for (NSMutableArray *table in tables) { if ([[table objectAtIndex:0] boolValue]) { [exportTables addObject:[table objectAtIndex:1]]; } } break; } SPExporter *exporter; SPCSVExporter *csvExporter; // Based on the type of export create a new instance of the corresponding exporter and set it's specific options switch (exportType) { case SP_SQL_EXPORT: break; case SP_CSV_EXPORT: csvExporter = [[SPCSVExporter alloc] initWithDelegate:self]; [csvExporter setCsvOutputFieldNames:[exportCSVIncludeFieldNamesCheck state]]; [csvExporter setCsvFieldSeparatorString:[exportCSVFieldsTerminatedField stringValue]]; [csvExporter setCsvEnclosingCharacterString:[exportCSVFieldsWrappedField stringValue]]; [csvExporter setCsvLineEndingString:[exportCSVLinesTerminatedField stringValue]]; [csvExporter setCsvEscapeString:[exportCSVFieldsEscapedField stringValue]]; [csvExporter setExportOutputEncoding:[MCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] UTF8String]]]; [csvExporter setCsvNULLString:[[NSUserDefaults standardUserDefaults] objectForKey:@"NullValue"]]; exporter = csvExporter; break; case SP_XML_EXPORT: break; case SP_PDF_EXPORT: break; case SP_HTML_EXPORT: break; case SP_EXCEL_EXPORT: break; } switch (exportSource) { case SP_FILTERED_EXPORT: break; case SP_CUSTOM_QUERY_EXPORT: break; case SP_TABLE_EXPORT: [self _exportTablesAsCSV:exportTables usingDataExporter:exporter]; break; } } /** * Exports the contents' of the supplied array of tables using the supplied exporter and export type. Note that * this method currently only supports exporting in CSV and XML formats. */ - (BOOL)_exportTablesAsCSV:(NSArray *)exportTables usingDataExporter:(SPCSVExporter *)exporter { NSUInteger tableCount, i, j; NSMutableString *errors = [NSMutableString string]; NSMutableString *infoString = [NSMutableString string]; NSDictionary *tableDetails; NSMutableArray *tableColumnNumericStatus; NSStringEncoding encoding = [[self connection] encoding]; // Reset the interface [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), @"CSV"]]; [exportProgressText setStringValue:NSLocalizedString(@"Writing...", @"text showing that app is writing text file")]; [exportProgressText displayIfNeeded]; [exportProgressIndicator setDoubleValue:0]; [exportProgressIndicator displayIfNeeded]; // Open the progress sheet [NSApp beginSheet:exportProgressWindow modalForWindow:tableWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; // Add a dump header to the dump file NSMutableString *csvLineEnd = [NSMutableString stringWithString:[exportCSVLinesTerminatedField stringValue]]; [csvLineEnd replaceOccurrencesOfString:@"\\t" withString:@"\t" options:NSLiteralSearch range:NSMakeRange(0, [csvLineEnd length])]; [csvLineEnd replaceOccurrencesOfString:@"\\n" withString:@"\n" options:NSLiteralSearch range:NSMakeRange(0, [csvLineEnd length])]; [csvLineEnd replaceOccurrencesOfString:@"\\r" withString:@"\r" options:NSLiteralSearch range:NSMakeRange(0, [csvLineEnd length])]; if ([exportTables count] > 1) { [infoString setString:[NSString stringWithFormat:@"Host: %@ Database: %@ Generation Time: %@%@%@", [tableDocumentInstance host], [tableDocumentInstance database], [NSDate date], csvLineEnd, csvLineEnd]]; } tableCount = [exportTables count]; // Loop through the tables for (i = 0 ; i < ((tableCount) && (![self exportCancelled])); i++) { // Update the progress text and reset the progress bar to indeterminate status NSString *tableName = [exportTables objectAtIndex:i]; [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %i of %i (%@): fetching data...", @"text showing that app is fetching data for table dump"), (i + 1), tableCount, tableName]]; [exportProgressText displayIfNeeded]; [exportProgressIndicator setIndeterminate:YES]; [exportProgressIndicator setUsesThreadedAnimation:YES]; [exportProgressIndicator startAnimation:self]; // For CSV exports of more than one table, output the name of the table /*if (tableCount > 1) { [fileHandle writeData:[[NSString stringWithFormat:@"Table %@%@%@", tableName, csvLineEnd, csvLineEnd] dataUsingEncoding:encoding]]; }*/ // Determine whether this table is a table or a view via the create table command, and get the table details MCPResult *queryResult = [connection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]]; if ([queryResult numOfRows]) { tableDetails = [NSDictionary dictionaryWithDictionary:[queryResult fetchRowAsDictionary]]; tableDetails = [NSDictionary dictionaryWithDictionary:([tableDetails objectForKey:@"Create View"]) ? [tableDataInstance informationForView:tableName] : [tableDataInstance informationForTable:tableName]]; } // Retrieve the table details via the data class, and use it to build an array containing column numeric status tableColumnNumericStatus = [NSMutableArray array]; for (j = 0; j < [[tableDetails objectForKey:@"columns"] count]; j++) { NSString *tableColumnTypeGrouping = [[[tableDetails objectForKey:@"columns"] objectAtIndex:j] objectForKey:@"typegrouping"]; if ([tableColumnTypeGrouping isEqualToString:@"bit"] || [tableColumnTypeGrouping isEqualToString:@"integer"] || [tableColumnTypeGrouping isEqualToString:@"float"]) { [tableColumnNumericStatus addObject:[NSNumber numberWithBool:YES]]; } else { [tableColumnNumericStatus addObject:[NSNumber numberWithBool:NO]]; } } [exporter setCsvTableColumnNumericStatus:tableColumnNumericStatus]; // Retrieve all the content within this table queryResult = [connection queryString:[NSString stringWithFormat:@"SELECT * FROM %@", [tableName backtickQuotedString]]]; // Note any errors during retrieval if (![[connection getLastErrorMessage] isEqualToString:@""]) { [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]]; } // Assign the data to the exporter [exporter setCsvDataResult:queryResult]; // Update the progress text and set the progress bar back to determinate [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %d of %d (%@): Writing...", @"text showing that app is writing data for table export"), (i + 1), tableCount, tableName]]; [exportProgressText displayIfNeeded]; [exportProgressIndicator stopAnimation:self]; [exportProgressIndicator setUsesThreadedAnimation:NO]; [exportProgressIndicator setIndeterminate:NO]; [exportProgressIndicator setDoubleValue:0]; [exportProgressIndicator displayIfNeeded]; // Start the actual data conversion process by placing the exporter on the operation queue. // Note that although it is highly likely there is no guarantee that the operation will executed // as soon as it's placed on the queue. There may be a delay if the queue is already executing it's // maximum number of concurrent operations. See the docs for more details. [operationQueue addOperation:exporter]; // Add a spacer to the file //[fileHandle writeData:[[NSString stringWithFormat:@"%@%@%@", csvLineEnd, csvLineEnd, csvLineEnd] dataUsingEncoding:encoding]]; } // Close the progress sheet [NSApp endSheet:exportProgressWindow returnCode:0]; [exportProgressWindow orderOut:self]; return YES; } @end