diff options
author | stuconnolly <stuart02@gmail.com> | 2010-05-24 18:07:43 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2010-05-24 18:07:43 +0000 |
commit | bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d (patch) | |
tree | 1cf7d41f091854e8e2288946684267ce0f8ceaf4 /Source/SPCSVExporter.m | |
parent | d48005bd9b34f2fb1afd31f7487b7bbf8b9b978f (diff) | |
download | sequelpro-bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d.tar.gz sequelpro-bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d.tar.bz2 sequelpro-bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d.zip |
Merge export redesign branch back into trunk.
Includes a completely redesign approach to all export data types based on the use of NSOperation subclasses. CSV, SQL, XML and dot export types are currently functional, while the source files for PDF and HTML export types exist they are to be implemented, but are currently hidden from the interface.
Also includes the following:
- Completely redesigned export interface.
- The ability to customize CSV NULL values.
- The ability to specify whether the UTF-8 BOM should be used in SQL dumps.
- The ability to specify whether BLOB fields are output as hex or plain text during SQL dumps. Defaults to hex.
- Exporting currently selected tables via the tables list context menu.
Outstanding issues:
- Not all progress indicators for all export types are functional (or functioning correctly).
- A few issues related to the introduction of only exporting the content and create and drop syntax of specific tables during SQL dumps.
Needs some serious testing and benchmarking to ensure it replicates the current export functionality.
Diffstat (limited to 'Source/SPCSVExporter.m')
-rw-r--r-- | Source/SPCSVExporter.m | 178 |
1 files changed, 139 insertions, 39 deletions
diff --git a/Source/SPCSVExporter.m b/Source/SPCSVExporter.m index 4937e7c9..e6342e68 100644 --- a/Source/SPCSVExporter.m +++ b/Source/SPCSVExporter.m @@ -23,24 +23,44 @@ // // More info at <http://code.google.com/p/sequel-pro/> +#import <MCPKit/MCPKit.h> + #import "SPCSVExporter.h" #import "SPArrayAdditions.h" +#import "SPStringAdditions.h" +#import "SPFileHandle.h" +#import "SPTableData.h" +#import "SPExportUtilities.h" @implementation SPCSVExporter +@synthesize delegate; @synthesize csvDataArray; -@synthesize csvDataResult; - +@synthesize csvTableName; @synthesize csvOutputFieldNames; @synthesize csvFieldSeparatorString; @synthesize csvEnclosingCharacterString; @synthesize csvEscapeString; @synthesize csvLineEndingString; @synthesize csvNULLString; -@synthesize csvTableColumnNumericStatus; +@synthesize csvTableData; + +/** + * Initialise an instance of SPCSVExporter using the supplied delegate. + */ +- (id)initWithDelegate:(NSObject *)exportDelegate +{ + if ((self = [super init])) { + SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPCSVExporterProtocol)); + + [self setDelegate:exportDelegate]; + } + + return self; +} /** - * Start the CSV data conversion process. This method is automatically called when an instance of this object + * Start the CSV export process. This method is automatically called when an instance of this class * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. */ - (void)main @@ -49,44 +69,61 @@ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableString *csvString = [NSMutableString string]; - NSMutableString *csvData = [NSMutableString string]; NSMutableString *csvCellString = [NSMutableString string]; + + NSMutableArray *tableColumnNumericStatus = [NSMutableArray array]; - NSArray *csvRow; - NSScanner *csvNumericTester; + NSArray *csvRow = nil; + NSScanner *csvNumericTester = nil; + MCPStreamingResult *streamingResult = nil; NSString *escapedEscapeString, *escapedFieldSeparatorString, *escapedEnclosingString, *escapedLineEndString, *dataConversionString; id csvCell; BOOL csvCellIsNumeric; BOOL quoteFieldSeparators = [[self csvEnclosingCharacterString] isEqualToString:@""]; - NSUInteger i, totalRows, csvCellCount = 0; + NSUInteger i, totalRows, lastProgressValue, csvCellCount = 0; + + // Check to see if we have at least a table name or data array + if ((![self csvTableName]) && (![self csvDataArray]) || + ([[self csvTableName] isEqualToString:@""]) && ([[self csvDataArray] count] == 0)) + { + [pool release]; + return; + } // Check that we have all the required info before starting the export if ((![self csvOutputFieldNames]) || (![self csvFieldSeparatorString]) || (![self csvEscapeString]) || - (![self csvLineEndingString]) || - (![self csvTableColumnNumericStatus])) + (![self csvLineEndingString])) { + [pool release]; return; } - // Check that the CSV output options are not just empty strings or empty arrays + // Check that the CSV output options are not just empty strings if (([[self csvFieldSeparatorString] isEqualToString:@""]) || ([[self csvEscapeString] isEqualToString:@""]) || - ([[self csvLineEndingString] isEqualToString:@""]) || - ([[self csvTableColumnNumericStatus] count] == 0)) + ([[self csvLineEndingString] isEqualToString:@""])) { + [pool release]; return; } - - // Check that we have at least some data to export - if ((![self csvDataArray]) && (![self csvDataResult])) return; - + + // Inform the delegate that the export process is about to begin + [delegate performSelectorOnMainThread:@selector(csvExportProcessWillBegin:) withObject:self waitUntilDone:NO]; + // Mark the process as running [self setExportProcessIsRunning:YES]; + lastProgressValue = 0; + + // Make a streaming request for the data if the data array isn't set + if ((![self csvDataArray]) && [self csvTableName]) { + streamingResult = [connection streamingQueryString:[NSString stringWithFormat:@"SELECT * FROM %@", [[self csvTableName] backtickQuotedString]] useLowMemoryBlockingStreaming:[self exportUsingLowMemoryBlockingStreaming]]; + } + // Detect and restore special characters being used as terminating or line end strings NSMutableString *tempSeparatorString = [NSMutableString stringWithString:[self csvFieldSeparatorString]]; @@ -135,17 +172,62 @@ // headers as the first row, decide whether to skip the first row. NSUInteger currentRowIndex = 0; - [csvData setString:@""]; + [csvString setString:@""]; + if ([self csvDataArray]) totalRows = [[self csvDataArray] count]; if (([self csvDataArray]) && (![self csvOutputFieldNames])) currentRowIndex++; + if ([self csvTableName] && (![self csvDataArray])) { + + NSDictionary *tableDetails = [[NSDictionary alloc] init]; + + // Determine whether the supplied table is actually a table or a view via the CREATE TABLE command, and get the table details + MCPResult *queryResult = [connection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [[self csvTableName] backtickQuotedString]]]; + + [queryResult setReturnDataAsStrings:YES]; + + if ([queryResult numOfRows]) { + tableDetails = [NSDictionary dictionaryWithDictionary:[queryResult fetchRowAsDictionary]]; + + tableDetails = [NSDictionary dictionaryWithDictionary:([tableDetails objectForKey:@"Create View"]) ? [[self csvTableData] informationForView:[self csvTableName]] : [[self csvTableData] informationForTable:[self csvTableName]]]; + } + + // Retrieve the table details via the data class, and use it to build an array containing column numeric status + for (NSDictionary *column in [tableDetails objectForKey:@"columns"]) + { + NSString *tableColumnTypeGrouping = [column objectForKey:@"typegrouping"]; + + [tableColumnNumericStatus addObject:[NSNumber numberWithBool:([tableColumnTypeGrouping isEqualToString:@"bit"] || + [tableColumnTypeGrouping isEqualToString:@"integer"] || + [tableColumnTypeGrouping isEqualToString:@"float"])]]; + } + + [tableDetails release]; + } + // Drop into the processing loop NSAutoreleasePool *csvExportPool = [[NSAutoreleasePool alloc] init]; NSUInteger currentPoolDataLength = 0; + // Inform the delegate that we are about to start writing the data to disk + [delegate performSelectorOnMainThread:@selector(csvExportProcessWillBeginWritingData:) withObject:self waitUntilDone:NO]; + while (1) { + // Check for cancellation flag + if ([self isCancelled]) { + if (streamingResult) { + [connection cancelCurrentQuery]; + [streamingResult cancelResultLoad]; + } + + [csvExportPool release]; + [pool release]; + + return; + } + // Retrieve the next row from the supplied data, either directly from the array... if ([self csvDataArray]) { csvRow = NSArrayObjectAtIndex([self csvDataArray], currentRowIndex); @@ -154,11 +236,11 @@ else { // If still requested to read the field names, get the field names if ([self csvOutputFieldNames]) { - csvRow = [[self csvDataResult] fetchFieldNames]; + csvRow = [streamingResult fetchFieldNames]; [self setCsvOutputFieldNames:NO]; } else { - csvRow = [[self csvDataResult] fetchNextRowAsArray]; + csvRow = [streamingResult fetchNextRowAsArray]; if (!csvRow) break; } @@ -171,6 +253,14 @@ for (i = 0 ; i < csvCellCount; i++) { + // Check for cancellation flag + if ([self isCancelled]) { + [csvExportPool release]; + [pool release]; + + return; + } + csvCell = NSArrayObjectAtIndex(csvRow, i); // For NULL objects supplied from a queryResult, add an unenclosed null string as per prefs @@ -208,8 +298,8 @@ } else { // If an array of bools supplying information as to whether the column is numeric has been supplied, use it. - if ([self csvTableColumnNumericStatus] != nil) { - csvCellIsNumeric = [NSArrayObjectAtIndex([self csvTableColumnNumericStatus], i) boolValue]; + if ([tableColumnNumericStatus count] > 0) { + csvCellIsNumeric = [NSArrayObjectAtIndex(tableColumnNumericStatus, i) boolValue]; } // Otherwise, first test whether this cell contains data else if ([NSArrayObjectAtIndex(csvRow, i) isKindOfClass:[NSData class]]) { @@ -270,40 +360,49 @@ } // Append the line ending to the string for this row, and record the length processed for pool flushing - [csvString appendString:[self csvLineEndingString]]; - [csvData appendString:csvString]; - + [csvString appendString:[self csvLineEndingString]]; currentPoolDataLength += [csvString length]; + // Write it to the fileHandle + [[self exportOutputFileHandle] writeData:[csvString dataUsingEncoding:[self exportOutputEncoding]]]; + currentRowIndex++; - // Update the progress value - if (totalRows) [self setExportProgressValue:(((i + 1) * 100) / totalRows)]; + // Update the progress + if (totalRows && (currentRowIndex * ([self exportMaxProgress] / totalRows)) > lastProgressValue) { + + NSInteger progress = (currentRowIndex * ([self exportMaxProgress] / totalRows)); + + [self setExportProgressValue:progress]; + + lastProgressValue = progress; + } + + // Inform the delegate that the export's progress has been updated + [delegate performSelectorOnMainThread:@selector(csvExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; // If an array was supplied and we've processed all rows, break if ([self csvDataArray] && (totalRows == currentRowIndex)) break; // Drain the autorelease pool as required to keep memory usage low if (currentPoolDataLength > 250000) { - [csvExportPool drain]; + [csvExportPool release]; csvExportPool = [[NSAutoreleasePool alloc] init]; } } - - // Assign the resulting CSV data to the expoter's export data - [self setExportData:csvData]; + + // Write data to disk + [[self exportOutputFileHandle] synchronizeFile]; // Mark the process as not running [self setExportProcessIsRunning:NO]; - // Call the delegate's didEndSelector while passing this exporter to it - [[self delegate] performSelectorOnMainThread:[self didEndSelector] withObject:self waitUntilDone:NO]; + // Inform the delegate that the export process is complete + [delegate performSelectorOnMainThread:@selector(csvExportProcessComplete:) withObject:self waitUntilDone:NO]; [pool release]; } - @catch(NSException *e) { - - } + @catch(NSException *e) {} } /** @@ -311,14 +410,15 @@ */ - (void)dealloc { - [csvDataArray release], csvDataArray = nil; - [csvDataResult release], csvDataResult = nil; + if (csvDataArray) [csvDataArray release], csvDataArray = nil; + if (csvTableName) [csvTableName release], csvTableName = nil; + [csvFieldSeparatorString release], csvFieldSeparatorString = nil; [csvEnclosingCharacterString release], csvEnclosingCharacterString = nil; [csvEscapeString release], csvEscapeString = nil; [csvLineEndingString release], csvLineEndingString = nil; [csvNULLString release], csvNULLString = nil; - [csvTableColumnNumericStatus release], csvTableColumnNumericStatus = nil; + [csvTableData release], csvTableData = nil; [super dealloc]; } |