aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/SPCSVExporter.h89
-rw-r--r--Source/SPCSVExporter.m178
-rw-r--r--Source/SPCSVExporterDelegate.h38
-rw-r--r--Source/SPCSVExporterDelegate.m141
-rw-r--r--Source/SPCSVExporterProtocol.h65
-rw-r--r--Source/SPConstants.h39
-rw-r--r--Source/SPConstants.m3
-rw-r--r--Source/SPDotExporter.h96
-rw-r--r--Source/SPDotExporter.m227
-rw-r--r--Source/SPDotExporterDelegate.h38
-rw-r--r--Source/SPDotExporterDelegate.m98
-rw-r--r--Source/SPDotExporterProtocol.h72
-rw-r--r--Source/SPExportController.h260
-rw-r--r--Source/SPExportController.m766
-rw-r--r--Source/SPExportInitializer.h51
-rw-r--r--Source/SPExportInitializer.m626
-rw-r--r--Source/SPExportUtilities.h30
-rw-r--r--Source/SPExportUtilities.m40
-rw-r--r--Source/SPExporter.h33
-rw-r--r--Source/SPExporter.m24
-rw-r--r--Source/SPHTMLExporter.h57
-rw-r--r--Source/SPHTMLExporter.m62
-rw-r--r--Source/SPHTMLExporterDelegate.h38
-rw-r--r--Source/SPHTMLExporterDelegate.m54
-rw-r--r--Source/SPHTMLExporterProtocol.h60
-rw-r--r--Source/SPPDFExporter.h57
-rw-r--r--Source/SPPDFExporter.m61
-rw-r--r--Source/SPPDFExporterDelegate.h38
-rw-r--r--Source/SPPDFExporterDelegate.m (renamed from Source/SPExporterDataAccess.h)34
-rw-r--r--Source/SPPDFExporterProtocol.h60
-rw-r--r--Source/SPSQLExporter.h100
-rw-r--r--Source/SPSQLExporter.m772
-rw-r--r--Source/SPSQLExporterDelegate.h38
-rw-r--r--Source/SPSQLExporterDelegate.m105
-rw-r--r--Source/SPSQLExporterProtocol.h72
-rw-r--r--Source/SPStringAdditions.h5
-rw-r--r--Source/SPStringAdditions.m110
-rw-r--r--Source/SPXMLExporter.h38
-rw-r--r--Source/SPXMLExporter.m218
-rw-r--r--Source/SPXMLExporterDelegate.h38
-rw-r--r--Source/SPXMLExporterDelegate.m133
-rw-r--r--Source/SPXMLExporterProtocol.h65
-rw-r--r--Source/TableDocument.h1
-rw-r--r--Source/TableDocument.m19
44 files changed, 4640 insertions, 509 deletions
diff --git a/Source/SPCSVExporter.h b/Source/SPCSVExporter.h
index 5c78be04..b0c6c46b 100644
--- a/Source/SPCSVExporter.h
+++ b/Source/SPCSVExporter.h
@@ -25,34 +25,93 @@
#import <Cocoa/Cocoa.h>
-#import "MCPKit.h"
#import "SPExporter.h"
+#import "SPCSVExporterProtocol.h"
+@class SPTableData;
+
+/**
+ * @class SPCSVExporter SPCSVExporter.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * CSV exporter class.
+ */
@interface SPCSVExporter : SPExporter
-{
- // CSV data
+{
+ /**
+ * Exporter delegate
+ */
+ NSObject <SPCSVExporterProtocol> *delegate;
+
+ /**
+ * Data array
+ */
NSArray *csvDataArray;
- MCPStreamingResult *csvDataResult;
- // CSV options
+ /**
+ * Table name
+ */
+ NSString *csvTableName;
+
+ /**
+ * Output field names
+ */
BOOL csvOutputFieldNames;
+
+ /**
+ * CSV field separator string
+ */
NSString *csvFieldSeparatorString;
+
+ /**
+ * CSV enclosing character string
+ */
NSString *csvEnclosingCharacterString;
+
+ /**
+ * CSV escape string
+ */
NSString *csvEscapeString;
+
+ /**
+ * CSV line ending string
+ */
NSString *csvLineEndingString;
+
+ /**
+ * CSV NULL string
+ */
NSString *csvNULLString;
- NSArray *csvTableColumnNumericStatus;
+
+ /**
+ * Table data
+ */
+ SPTableData *csvTableData;
}
-@property (readwrite, retain) NSArray *csvDataArray;
-@property (readwrite, retain) MCPStreamingResult *csvDataResult;
+@property(readwrite, assign) NSObject <SPCSVExporterProtocol> *delegate;
+
+@property(readwrite, retain) NSArray *csvDataArray;
+@property(readwrite, retain) NSString *csvTableName;
+
+@property(readwrite, assign) BOOL csvOutputFieldNames;
+
+@property(readwrite, retain) NSString *csvFieldSeparatorString;
+@property(readwrite, retain) NSString *csvEnclosingCharacterString;
+@property(readwrite, retain) NSString *csvEscapeString;
+@property(readwrite, retain) NSString *csvLineEndingString;
+@property(readwrite, retain) NSString *csvNULLString;
+
+@property(readwrite, retain) SPTableData *csvTableData;
-@property (readwrite, assign) BOOL csvOutputFieldNames;
-@property (readwrite, retain) NSString *csvFieldSeparatorString;
-@property (readwrite, retain) NSString *csvEnclosingCharacterString;
-@property (readwrite, retain) NSString *csvEscapeString;
-@property (readwrite, retain) NSString *csvLineEndingString;
-@property (readwrite, retain) NSString *csvNULLString;
-@property (readwrite, retain) NSArray *csvTableColumnNumericStatus;
+/**
+ * Initialise an instance of SPCSVExporter using the supplied delegate.
+ *
+ * @param exportDelegate The exporter delegate
+ *
+ * @return The initialised instance
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate;
@end
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];
}
diff --git a/Source/SPCSVExporterDelegate.h b/Source/SPCSVExporterDelegate.h
new file mode 100644
index 00000000..11170e62
--- /dev/null
+++ b/Source/SPCSVExporterDelegate.h
@@ -0,0 +1,38 @@
+//
+// $Id$
+//
+// SPCSVExporterDelegate.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on March 21, 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 "SPExportController.h"
+#import "SPCSVExporterProtocol.h"
+
+/**
+ * @category SPCSVExporterDelegate SPCSVExporterDelegate.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * CSV exporter delegate category.
+ */
+@interface SPExportController (SPCSVExporterDelegate) <SPCSVExporterProtocol>
+
+@end
diff --git a/Source/SPCSVExporterDelegate.m b/Source/SPCSVExporterDelegate.m
new file mode 100644
index 00000000..ed5f4514
--- /dev/null
+++ b/Source/SPCSVExporterDelegate.m
@@ -0,0 +1,141 @@
+//
+// $Id$
+//
+// SPCSVExporterDelegate.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on March 21, 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 <Cocoa/Cocoa.h>
+
+#import "SPCSVExporter.h"
+#import "SPCSVExporterDelegate.h"
+#import "TableDocument.h"
+#import "SPMainThreadTrampoline.h"
+#import "SPFileHandle.h"
+
+@implementation SPExportController (SPCSVExporterDelegate)
+
+/**
+ *
+ */
+- (void)csvExportProcessWillBegin:(SPCSVExporter *)exporter
+{
+ [[exportProgressText onMainThread] displayIfNeeded];
+
+ [[exportProgressIndicator onMainThread] setIndeterminate:YES];
+ [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:YES];
+ [[exportProgressIndicator onMainThread] startAnimation:self];
+
+ // Only update the progress text if this is a table export
+ if (exportSource == SPTableExport) {
+ // Update the current table export index
+ currentTableExportIndex = (exportTableCount - [exporters count]);
+
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]];
+ }
+ else {
+ [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")];
+ }
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+}
+
+/**
+ *
+ */
+- (void)csvExportProcessComplete:(SPCSVExporter *)exporter
+{
+ NSUInteger exportCount = [exporters count];
+
+ // If required add the next exporter to the operation queue
+ if ((exportCount > 0) && (exportSource == SPTableExport)) {
+
+ // If we're only exporting to a single file then write a header for the next table
+ if (!exportToMultipleFiles) {
+
+ // If we're exporting multiple tables to a single file then append some space and the next table's
+ // name, but only if there is at least 2 exportes left.
+ [[exporter exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"%@%@%@ %@%@%@",
+ [exporter csvLineEndingString],
+ [exporter csvLineEndingString],
+ NSLocalizedString(@"Table", @"csv export table heading"),
+ [(SPCSVExporter *)[exporters objectAtIndex:0] csvTableName],
+ [exporter csvLineEndingString],
+ [exporter csvLineEndingString]] dataUsingEncoding:[exporter exportOutputEncoding]]];
+ }
+ // Otherwise close the file handle of the exporter that just finished
+ // ensuring it's data is written to disk.
+ else {
+ [[exporter exportOutputFileHandle] closeFile];
+ }
+
+ [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];
+ }
+ // Otherwise if the exporter list is empty, close the progress sheet
+ else {
+ // Close the last exporter's file handle
+ [[exporter exportOutputFileHandle] closeFile];
+
+ [NSApp endSheet:exportProgressWindow returnCode:0];
+ [exportProgressWindow orderOut:self];
+
+ // Restore query mode
+ [tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
+
+ // Display Growl notification
+ [self displayExportFinishedGrowlNotification];
+ }
+}
+
+/**
+ *
+ */
+- (void)csvExportProcessWillBeginWritingData:(SPCSVExporter *)exporter
+{
+ // Only update the progress text if this is a table export
+ if (exportSource == SPTableExport) {
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter csvTableName]]];
+ }
+ else {
+ [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")];
+ }
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+
+ [[exportProgressIndicator onMainThread] stopAnimation:self];
+ [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:NO];
+ [[exportProgressIndicator onMainThread] setIndeterminate:NO];
+ [[exportProgressIndicator onMainThread] setDoubleValue:0];
+}
+
+/**
+ *
+ */
+- (void)csvExportProcessProgressUpdated:(SPCSVExporter *)exporter
+{
+ [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]];
+}
+
+@end
diff --git a/Source/SPCSVExporterProtocol.h b/Source/SPCSVExporterProtocol.h
new file mode 100644
index 00000000..67bc470c
--- /dev/null
+++ b/Source/SPCSVExporterProtocol.h
@@ -0,0 +1,65 @@
+//
+// $Id$
+//
+// SPCSVExporterProtocol.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 15, 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/>
+
+@class SPCSVExporter;
+
+/**
+ * @protocol SPCSVExporterProtocol SPCSVExporterProtocol.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * CSV exporter delegate protocol.
+ */
+@protocol SPCSVExporterProtocol
+
+/**
+ * Called when the CSV export process is about to begin.
+ *
+ * @param SPCSVExporter The expoter calling the method.
+ */
+- (void)csvExportProcessWillBegin:(SPCSVExporter *)exporter;
+
+/**
+ * Called when the CSV export process is complete.
+ *
+ * @param SPCSVExporter The expoter calling the method.
+ */
+- (void)csvExportProcessComplete:(SPCSVExporter *)exporter;
+
+/**
+ * Called when the progress of the CSV export process is updated.
+ *
+ * @param SPCSVExporter The expoter calling the method.
+ */
+- (void)csvExportProcessProgressUpdated:(SPCSVExporter *)exporter;
+
+/**
+ * Called when the CSV export process is about to begin writing data to disk.
+ *
+ * @param SPCSVExporter The expoter calling the method.
+ */
+- (void)csvExportProcessWillBeginWritingData:(SPCSVExporter *)exporter;
+
+@end
diff --git a/Source/SPConstants.h b/Source/SPConstants.h
index d893b825..1fdcf68a 100644
--- a/Source/SPConstants.h
+++ b/Source/SPConstants.h
@@ -36,24 +36,46 @@ typedef enum {
} SPViewMode;
// Query modes
-typedef enum {
+enum {
SPInterfaceQueryMode = 0,
SPCustomQueryQueryMode = 1,
SPImportExportQueryMode = 2
-} SPQueryMode;
+};
+typedef NSUInteger SPQueryMode;
// Connection types
-typedef enum {
+enum {
SPTCPIPConnection = 0,
SPSocketConnection = 1,
SPSSHTunnelConnection = 2
-} SPConnectionType;
+};
+typedef NSUInteger SPConnectionType;
+
+// Export type constants
+enum {
+ SPSQLExport = 1,
+ SPCSVExport = 2,
+ SPXMLExport = 3,
+ SPPDFExport = 4,
+ SPHTMLExport = 5,
+ SPExcelExport = 6,
+ SPDotExport = 7
+};
+typedef NSUInteger SPExportType;
+
+// Export source constants
+enum {
+ SPFilteredExport = 1,
+ SPQueryExport = 2,
+ SPTableExport = 3
+};
+typedef NSUInteger SPExportSource;
// Table row count query usage levels
typedef enum {
- SPRowCountFetchNever = 0,
- SPRowCountFetchIfCheap = 1,
- SPRowCountFetchAlways = 2
+ SPRowCountFetchNever = 0,
+ SPRowCountFetchIfCheap = 1,
+ SPRowCountFetchAlways = 2
} SPRowCountQueryUsageLevels;
// Export type
@@ -246,6 +268,7 @@ extern NSString *SPCSVImportFirstLineIsHeader;
extern NSString *SPCSVFieldImportMappingAlignment;
extern NSString *SPImportClipboardTempFileNamePrefix;
extern NSString *SPSQLExportUseCompression;
+extern NSString *SPNoBOMforSQLdumpFile;
// Misc
extern NSString *SPContentFilters;
@@ -253,7 +276,6 @@ extern NSString *SPDocumentTaskEndNotification;
extern NSString *SPDocumentTaskStartNotification;
extern NSString *SPFieldEditorSheetFont;
extern NSString *SPLastSQLFileEncoding;
-extern NSString *SPNoBOMforSQLdumpFile;
extern NSString *SPPrintBackground;
extern NSString *SPPrintImagePreviews;
extern NSString *SPQueryFavorites;
@@ -273,6 +295,7 @@ extern NSString *SPDocumentationURL;
extern NSString *SPContactURL;
extern NSString *SPKeyboardShortcutsURL;
extern NSString *SPMySQLSearchURL;
+extern NSString *SPDevURL;
extern NSString *SPGettingConnectedDocURL;
// Toolbar constants
diff --git a/Source/SPConstants.m b/Source/SPConstants.m
index 3f5bf15a..536d4cfb 100644
--- a/Source/SPConstants.m
+++ b/Source/SPConstants.m
@@ -147,6 +147,7 @@ NSString *SPCSVImportLineTerminator = @"CSVImportLineTerminator";
NSString *SPCSVFieldImportMappingAlignment = @"CSVFieldImportMappingAlignment";
NSString *SPImportClipboardTempFileNamePrefix = @"/tmp/_SP_ClipBoard_Import_File_";
NSString *SPSQLExportUseCompression = @"SQLExportUseCompression";
+NSString *SPNoBOMforSQLdumpFile = @"NoBOMforSQLdumpFile";
// Misc
NSString *SPContentFilters = @"ContentFilters";
@@ -154,7 +155,6 @@ NSString *SPDocumentTaskEndNotification = @"DocumentTaskEnded";
NSString *SPDocumentTaskStartNotification = @"DocumentTaskStarted";
NSString *SPFieldEditorSheetFont = @"FieldEditorSheetFont";
NSString *SPLastSQLFileEncoding = @"lastSqlFileEncoding";
-NSString *SPNoBOMforSQLdumpFile = @"NoBOMforSQLdumpFile";
NSString *SPPrintBackground = @"PrintBackground";
NSString *SPPrintImagePreviews = @"PrintImagePreviews";
NSString *SPQueryFavorites = @"queryFavorites";
@@ -174,6 +174,7 @@ NSString *SPDocumentationURL = @"http://www.sequelpro.com/do
NSString *SPContactURL = @"http://www.sequelpro.com/docs/Contact_the_developers";
NSString *SPKeyboardShortcutsURL = @"http://www.sequelpro.com/docs/Keyboard_Shortcuts";
NSString *SPMySQLSearchURL = @"http://search.mysql.com/search?q=%@&site=refman-%@";
+NSString *SPDevURL = @"http://code.google.com/p/sequel-pro/";
NSString *SPGettingConnectedDocURL = @"http://www.sequelpro.com/docs/Getting_Connected";
// Toolbar constants
diff --git a/Source/SPDotExporter.h b/Source/SPDotExporter.h
new file mode 100644
index 00000000..338acc37
--- /dev/null
+++ b/Source/SPDotExporter.h
@@ -0,0 +1,96 @@
+//
+// $Id$
+//
+// SPDotExporter.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 17, 2010
+// Copyright (c) 2009 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 <Cocoa/Cocoa.h>
+
+#import "SPExporter.h"
+#import "SPDotExporterProtocol.h"
+
+@class SPTableData;
+
+/**
+ * @class SPXMLExporter SPXMLExporter.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * XML exporter class.
+ */
+@interface SPDotExporter : SPExporter
+{
+ /**
+ * Exporter delegate
+ */
+ NSObject <SPDotExporterProtocol> *delegate;
+
+ /**
+ * Table information
+ */
+ NSArray *dotExportTables;
+
+ /**
+ * Current table
+ */
+ NSString *dotExportCurrentTable;
+
+ /**
+ * Table data
+ */
+ SPTableData *dotTableData;
+
+ /**
+ * Database host
+ */
+ NSString *dotDatabaseHost;
+
+ /**
+ * Database name
+ */
+ NSString *dotDatabaseName;
+
+ /**
+ * Database version
+ */
+ NSString *dotDatabaseVersion;
+}
+
+@property(readwrite, assign) NSObject *delegate;
+@property(readwrite, retain) NSArray *dotExportTables;
+@property(readwrite, retain) NSString *dotExportCurrentTable;
+@property(readwrite, retain) SPTableData *dotTableData;
+
+@property(readwrite, retain) NSString *dotDatabaseHost;
+@property(readwrite, retain) NSString *dotDatabaseName;
+@property(readwrite, retain) NSString *dotDatabaseVersion;
+
+/**
+ * Initialise an instance of SPDotExporter using the supplied delegate.
+ *
+ * @param exportDelegate The exporter delegate
+ *
+ * @return The initialised instance
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate;
+
+@end
diff --git a/Source/SPDotExporter.m b/Source/SPDotExporter.m
new file mode 100644
index 00000000..cbb132d9
--- /dev/null
+++ b/Source/SPDotExporter.m
@@ -0,0 +1,227 @@
+//
+// $Id$
+//
+// SPDotExporter.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 17, 2010
+// Copyright (c) 2009 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 "SPDotExporter.h"
+#import "SPConstants.h"
+#import "SPFileHandle.h"
+#import "SPArrayAdditions.h"
+#import "SPTableData.h"
+#import "SPExportUtilities.h"
+
+@implementation SPDotExporter
+
+@synthesize delegate;
+@synthesize dotExportTables;
+@synthesize dotExportCurrentTable;
+@synthesize dotTableData;
+@synthesize dotDatabaseHost;
+@synthesize dotDatabaseName;
+@synthesize dotDatabaseVersion;
+
+/**
+ * Initialise an instance of SPDotExporter using the supplied delegate.
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate
+{
+ if ((self = [super init])) {
+ SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPDotExporterProtocol));
+
+ [self setDelegate:exportDelegate];
+ [self setDotExportCurrentTable:nil];
+ }
+
+ return self;
+}
+
+/**
+ * Start the Dot schema 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
+{
+ @try {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ NSMutableString *metaString = [NSMutableString string];
+
+ // Check that we have all the required info before starting the export
+ if ((![self dotExportTables]) || (![self dotTableData]) || ([[self dotExportTables] count] == 0)) {
+ [pool release];
+ return;
+ }
+
+ // Inform the delegate that the export process is about to begin
+ [delegate performSelectorOnMainThread:@selector(dotExportProcessWillBegin:) withObject:self waitUntilDone:NO];
+
+ // Mark the process as running
+ [self setExportProcessIsRunning:YES];
+
+ [metaString setString:@"// ************************************************************\n"];
+ [metaString appendString:@"// Generated by: Sequel Pro\n"];
+ [metaString appendString:[NSString stringWithFormat:@"// Version %@\n//\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]];
+ [metaString appendString:[NSString stringWithFormat:@"// %@\n// %@\n//\n", SPHomePageURL, SPDevURL]];
+ [metaString appendString:[NSString stringWithFormat:@"// Host: %@ (MySQL %@)\n", [self dotDatabaseHost], [self dotDatabaseVersion]]];
+ [metaString appendString:[NSString stringWithFormat:@"// Database: %@\n", [self dotDatabaseName]]];
+ [metaString appendString:[NSString stringWithFormat:@"// Generation Time: %@\n", [NSDate date]]];
+ [metaString appendString:@"// ************************************************************\n\n"];
+
+ [metaString appendString:@"digraph \"Database Structure\" {\n"];
+ [metaString appendString:[NSString stringWithFormat:@"\tlabel = \"ER Diagram: %@\";\n", [self dotDatabaseName]]];
+ [metaString appendString:@"\tlabelloc = t;\n"];
+ [metaString appendString:@"\tcompound = true;\n"];
+ [metaString appendString:@"\tnode [ shape = record ];\n"];
+ [metaString appendString:@"\tfontname = \"Helvetica\";\n"];
+ [metaString appendString:@"\tranksep = 1.25;\n"];
+ [metaString appendString:@"\tratio = 0.7;\n"];
+ [metaString appendString:@"\trankdir = LR;\n"];
+
+ // Write information to the file
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+
+ NSMutableArray *fkInfo = [[NSMutableArray alloc] init];
+
+ // Process the tables
+ for (NSInteger i = 0; i < [[self dotExportTables] count]; i++)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ NSString *tableName = NSArrayObjectAtIndex([self dotExportTables], i);
+ NSDictionary *tableInfo = [[self dotTableData] informationForTable:tableName];
+
+ // Set the current table
+ [self setDotExportCurrentTable:tableName];
+
+ // Inform the delegate that we are about to start fetcihing data for the current table
+ [delegate performSelectorOnMainThread:@selector(dotExportProcessWillBeginFetchingData:) withObject:self waitUntilDone:NO];
+
+ NSString *hdrColor = @"#DDDDDD";
+
+ if ([[tableInfo objectForKey:@"type"] isEqualToString:@"View"]) {
+ hdrColor = @"#DDDDFF";
+ }
+
+ [metaString setString:[NSString stringWithFormat:@"\tsubgraph \"table_%@\" {\n", tableName]];
+ [metaString appendString:@"\t\tnode [ shape = \"plaintext\" ];\n"];
+ [metaString appendString:[NSString stringWithFormat:@"\t\t\"%@\" [ label=<\n", tableName]];
+ [metaString appendString:@"\t\t\t<TABLE BORDER=\"0\" CELLSPACING=\"0\" CELLBORDER=\"1\">\n"];
+ [metaString appendString:[NSString stringWithFormat:@"\t\t\t<TR><TD COLSPAN=\"3\" BGCOLOR=\"%@\">%@</TD></TR>\n", hdrColor, tableName]];
+
+ // Get the column info
+ NSArray *columnInfo = [tableInfo objectForKey:@"columns"];
+
+ for (NSInteger j = 0; j < [columnInfo count]; j++ )
+ {
+ [metaString appendString:[NSString stringWithFormat:@"\t\t\t<TR><TD COLSPAN=\"3\" PORT=\"%@\">%@:<FONT FACE=\"Helvetica-Oblique\" POINT-SIZE=\"10\">%@</FONT></TD></TR>\n", [[columnInfo objectAtIndex:j] objectForKey:@"name"], [[columnInfo objectAtIndex:j] objectForKey:@"name"], [[columnInfo objectAtIndex:j] objectForKey:@"type"]]];
+ }
+
+ [metaString appendString:@"\t\t\t</TABLE>>\n"];
+ [metaString appendString:@"\t\t];\n"];
+ [metaString appendString:@"\t}\n"];
+
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+
+ // see about relations
+ columnInfo = [tableInfo objectForKey:@"constraints"];
+
+ for (NSInteger j = 0; j < [columnInfo count]; j++)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ // Get the column references. Currently the columns themselves are an array,
+ // while reference columns and tables are comma separated if there are more than
+ // one. Only use the first of each for the time being.
+ NSArray *ccols = [NSArrayObjectAtIndex(columnInfo, j) objectForKey:@"columns"];
+ NSString *ccol = NSArrayObjectAtIndex(columnInfo, 0);
+ NSString *rcol = [NSArrayObjectAtIndex(columnInfo, j) objectForKey:@"ref_columns"];
+
+ NSString *extra = @"";
+
+ if ([ccols count] > 1) {
+ extra = @" [ arrowhead=crow, arrowtail=odiamond ]";
+ rcol = NSArrayObjectAtIndex([rcol componentsSeparatedByString:@","], 0);
+ }
+
+ [fkInfo addObject:[NSString stringWithFormat:@"%@:%@ -> %@:%@ %@", tableName, ccol, [NSArrayObjectAtIndex(columnInfo, j) objectForKey:@"ref_table"], rcol, extra]];
+ }
+ }
+
+ // Inform the delegate that we are about to start fetching relations data for the current table
+ [delegate performSelectorOnMainThread:@selector(dotExportProcessWillBeginFetchingRelationsData:) withObject:self waitUntilDone:NO];
+
+ [metaString setString:@"edge [ arrowhead=inv, arrowtail=normal, style=dashed, color=\"#444444\" ];\n"];
+
+ // Get the relations
+ for (NSInteger i = 0; i < [fkInfo count]; i++)
+ {
+ [metaString appendString:[NSString stringWithFormat:@"%@;\n", [fkInfo objectAtIndex:i]]];
+ }
+
+ [fkInfo release];
+
+ [metaString appendString:@"}\n"];
+
+ // Write information to the file
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+
+ // Write data to disk
+ [[self exportOutputFileHandle] closeFile];
+
+ // Mark the process as not running
+ [self setExportProcessIsRunning:NO];
+
+ // Inform the delegate that the export process is complete
+ [delegate performSelectorOnMainThread:@selector(dotExportProcessComplete:) withObject:self waitUntilDone:NO];
+
+ [pool release];
+ }
+ @catch (NSException *e) { }
+}
+
+/**
+ * Dealloc
+ */
+- (void)dealloc
+{
+ delegate = nil;
+
+ [dotExportTables release], dotExportTables = nil;
+ [dotExportCurrentTable release], dotExportCurrentTable = nil;
+ [dotTableData release], dotTableData = nil;
+ [dotDatabaseHost release], dotDatabaseHost = nil;
+ [dotDatabaseName release], dotDatabaseName = nil;
+ [dotDatabaseVersion release], dotDatabaseVersion = nil;
+
+ [super dealloc];
+}
+
+@end
diff --git a/Source/SPDotExporterDelegate.h b/Source/SPDotExporterDelegate.h
new file mode 100644
index 00000000..6661bca5
--- /dev/null
+++ b/Source/SPDotExporterDelegate.h
@@ -0,0 +1,38 @@
+//
+// $Id$
+//
+// SPDotExporterDelegate.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 17, 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 "SPExportController.h"
+#import "SPDotExporterProtocol.h"
+
+/**
+ * @category SPDotExporterDelegate SPDotExporterDelegate.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * Dot exporter delegate category.
+ */
+@interface SPExportController (SPDotExporterDelegate) <SPDotExporterProtocol>
+
+@end
diff --git a/Source/SPDotExporterDelegate.m b/Source/SPDotExporterDelegate.m
new file mode 100644
index 00000000..ce74e408
--- /dev/null
+++ b/Source/SPDotExporterDelegate.m
@@ -0,0 +1,98 @@
+//
+// $Id$
+//
+// SPDotExporterDelegate.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 17, 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 "SPDotExporterDelegate.h"
+#import "SPDotExporter.h"
+#import "TableDocument.h"
+#import "SPMainThreadTrampoline.h"
+
+@implementation SPExportController (SPDotExporterDelegate)
+
+/**
+ *
+ */
+- (void)dotExportProcessWillBegin:(SPDotExporter *)exporter
+{
+ [[exportProgressTitle onMainThread] setStringValue:NSLocalizedString(@"Exporting Dot File", @"text showing that the application is exporting a Dot file")];
+ [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")];
+
+ [[exportProgressTitle onMainThread] displayIfNeeded];
+ [[exportProgressText onMainThread] displayIfNeeded];
+}
+
+/**
+ *
+ */
+- (void)dotExportProcessComplete:(SPDotExporter *)exporter
+{
+ [NSApp endSheet:exportProgressWindow returnCode:0];
+ [exportProgressWindow orderOut:self];
+
+ [tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
+
+ // Restore the connection encoding to it's pre-export value
+ [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", sqlPreviousConnectionEncoding, (sqlPreviousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO];
+
+ // Display Growl notification
+ [self displayExportFinishedGrowlNotification];
+}
+
+/**
+ *
+ */
+- (void)dotExportProcessProgressUpdated:(SPDotExporter *)exporter
+{
+
+}
+
+/**
+ *
+ */
+- (void)dotExportProcessWillBeginFetchingData:(SPDotExporter *)exporter
+{
+ // Update the current table export index
+ currentTableExportIndex = (exportTableCount - [exporters count]);
+
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]];
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+
+ [[exportProgressIndicator onMainThread] stopAnimation:self];
+ [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:NO];
+ [[exportProgressIndicator onMainThread] setIndeterminate:NO];
+ [[exportProgressIndicator onMainThread] setDoubleValue:0];
+}
+
+/**
+ *
+ */
+- (void)dotExportProcessWillBeginFetchingRelationsData:(SPDotExporter *)exporter
+{
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching relations data...", @"export label showing app is fetching relations data for a specific table"), currentTableExportIndex, exportTableCount, [exporter dotExportCurrentTable]]];
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+}
+
+@end
diff --git a/Source/SPDotExporterProtocol.h b/Source/SPDotExporterProtocol.h
new file mode 100644
index 00000000..5d2c3db3
--- /dev/null
+++ b/Source/SPDotExporterProtocol.h
@@ -0,0 +1,72 @@
+//
+// $Id$
+//
+// SPDotExporterProtocol.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 15, 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/>
+
+@class SPDotExporter;
+
+/**
+ * @protocol SPDotExporterProtocol SPDotExporterProtocol.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * Dot schema exporter delegate protocol.
+ */
+@protocol SPDotExporterProtocol
+
+/**
+ * Called when the dot export process is about to begin.
+ *
+ * @param SPDotExpoter The expoter calling the method.
+ */
+- (void)dotExportProcessWillBegin:(SPDotExporter *)exporter;
+
+/**
+ * Called when the dot export process is complete.
+ *
+ * @param SPDotExpoter The expoter calling the method.
+ */
+- (void)dotExportProcessComplete:(SPDotExporter *)exporter;
+
+/**
+ * Called when the progress of the dot export process is updated.
+ *
+ * @param SPDotExpoter The expoter calling the method.
+ */
+- (void)dotExportProcessProgressUpdated:(SPDotExporter *)exporter;
+
+/**
+ * Called when the dot export process is about to begin fetching data from the database.
+ *
+ * @param SPDotExpoter The expoter calling the method.
+ */
+- (void)dotExportProcessWillBeginFetchingData:(SPDotExporter *)exporter;
+
+/**
+ * Called when the dot export process is about to begin writing data to disk.
+ *
+ * @param SPDotExpoter The expoter calling the method.
+ */
+- (void)dotExportProcessWillBeginFetchingRelationsData:(SPDotExporter *)exporter;
+
+@end
diff --git a/Source/SPExportController.h b/Source/SPExportController.h
index 773b678d..0865fef3 100644
--- a/Source/SPExportController.h
+++ b/Source/SPExportController.h
@@ -5,6 +5,7 @@
// sequel-pro
//
// Created by Ben Perry (benperry.com.au) on 21/02/09.
+// Modified by Stuart Connolly (stuconnolly.com)
//
// 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
@@ -23,118 +24,231 @@
// More info at <http://code.google.com/p/sequel-pro/>
#import <Cocoa/Cocoa.h>
-#import <MCPKit/MCPKit.h>
-#import "SPExporterDataAccess.h"
+#import "SPConstants.h"
-#import "SPLogger.h"
+@class MCPConnection, BWAnchoredButtonBar;
-// Export type constants
-enum {
- SP_SQL_EXPORT = 1,
- SP_CSV_EXPORT = 2,
- SP_XML_EXPORT = 3,
- SP_PDF_EXPORT = 4,
- SP_HTML_EXPORT = 5,
- SP_EXCEL_EXPORT = 6
-};
-typedef NSUInteger SPExportType;
-
-// Export source constants
-enum {
- SP_FILTERED_EXPORT = 1,
- SP_CUSTOM_QUERY_EXPORT = 2,
- SP_TABLE_EXPORT = 3
-};
-typedef NSUInteger SPExportSource;
-
-@interface SPExportController : NSObject <SPExporterDataAccess>
+/**
+ * @class SPExportController SPExportController.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * Export controller.
+ */
+@interface SPExportController : NSWindowController
{
- // Table document
+ // Controllers
IBOutlet id tableDocumentInstance;
-
- // Tables list
+ IBOutlet id tableContentInstance;
+ IBOutlet id customQueryInstance;
IBOutlet id tablesListInstance;
-
- // Table data
IBOutlet id tableDataInstance;
// Export window
- IBOutlet id exportWindow;
- IBOutlet id exportToolbar;
- IBOutlet id exportPathField;
- IBOutlet id exportTableList;
- IBOutlet id exportTabBar;
- IBOutlet id exportInputMatrix;
- IBOutlet id exportFilePerTableCheck;
- IBOutlet id exportFilePerTableNote;
- IBOutlet id exportProcessLowMemory;
+ IBOutlet NSButton *exportButton;
+ IBOutlet NSToolbar *exportToolbar;
+ IBOutlet NSTextField *exportPathField;
+ IBOutlet NSTableView *exportTableList;
+ IBOutlet NSTabView *exportTabBar;
+ IBOutlet NSMatrix *exportInputMatrix;
+ IBOutlet NSButton *exportFilePerTableCheck;
+ IBOutlet NSTextField *exportFilePerTableNote;
+ IBOutlet NSButton *exportSelectAllTablesButton;
+ IBOutlet NSButton *exportDeselectAllTablesButton;
+ IBOutlet NSButton *exportRefreshTablesButton;
+ IBOutlet NSScrollView *exportTablelistScrollView;
+
+ // Errors sheet
+ IBOutlet NSWindow *errorsWindow;
+ IBOutlet NSTextView *errorsTextView;
+
+ // Advanced options view
+ IBOutlet NSButton *exportAdvancedOptionsViewButton;
+ IBOutlet NSView *exportAdvancedOptionsView;
+ IBOutlet NSButton *exportAdvancedOptionsViewLabelButton;
+ IBOutlet NSButton *exportUseUTF8BOMButton;
+ IBOutlet NSButton *exportCompressOutputFile;
+ IBOutlet NSButton *exportProcessLowMemoryButton;
+ IBOutlet NSTextField *exportCSVNULLValuesAsTextField;
+
+ IBOutlet BWAnchoredButtonBar *exportTableListButtonBar;
// Export progress sheet
- IBOutlet id exportProgressWindow;
- IBOutlet id exportProgressTitle;
- IBOutlet id exportProgressText;
- IBOutlet id exportProgressIndicator;
+ IBOutlet NSWindow *exportProgressWindow;
+ IBOutlet NSTextField *exportProgressTitle;
+ IBOutlet NSTextField *exportProgressText;
+ IBOutlet NSProgressIndicator *exportProgressIndicator;
+
+ // Custom filename view
+ IBOutlet NSView *exportCustomFilenameView;
+ IBOutlet NSButton *exportCustomFilenameButton;
+ IBOutlet NSTokenField *exportCustomFilenameTokenField;
+ IBOutlet NSTokenField *exportCustomFilenameTokensField;
+ IBOutlet NSTextField *exportCustomFilenameExampleTextField;
// SQL
- IBOutlet id exportSQLIncludeStructureCheck;
- IBOutlet id exportSQLIncludeDropSyntaxCheck;
- IBOutlet id exportSQLIncludeErrorsCheck;
+ IBOutlet NSButton *exportSQLIncludeStructureCheck;
+ IBOutlet NSButton *exportSQLIncludeDropSyntaxCheck;
+ IBOutlet NSButton *exportSQLIncludeContentCheck;
+ IBOutlet NSButton *exportSQLIncludeErrorsCheck;
+ IBOutlet NSButton *exportSQLBLOBFieldsAsHexCheck;
// Excel
- IBOutlet id exportExcelSheetOrFilePerTableMatrix;
+ IBOutlet NSMatrix *exportExcelSheetOrFilePerTableMatrix;
// CSV
- IBOutlet id exportCSVIncludeFieldNamesCheck;
- IBOutlet id exportCSVFieldsTerminatedField;
- IBOutlet id exportCSVFieldsWrappedField;
- IBOutlet id exportCSVFieldsEscapedField;
- IBOutlet id exportCSVLinesTerminatedField;
+ IBOutlet NSButton *exportCSVIncludeFieldNamesCheck;
+ IBOutlet NSComboBox *exportCSVFieldsTerminatedField;
+ IBOutlet NSComboBox *exportCSVFieldsWrappedField;
+ IBOutlet NSComboBox *exportCSVFieldsEscapedField;
+ IBOutlet NSComboBox *exportCSVLinesTerminatedField;
// HTML
- IBOutlet id exportHTMLIncludeStructureCheck;
- IBOutlet id exportHTMLIncludeHeadAndBodyTagsCheck;
-
- // XML
- IBOutlet id exportXMLIncludeStructureCheck;
+ IBOutlet NSButton *exportHTMLIncludeStructureCheck;
// PDF
- IBOutlet id exportPDFIncludeStructureCheck;
-
- // Token name view
- IBOutlet id tokenNameView;
- IBOutlet id tokenNameField;
- IBOutlet id tokenNameTokensField;
- IBOutlet id exampleNameLabel;
+ IBOutlet NSButton *exportPDFIncludeStructureCheck;
- // Cancellation flag
+ /**
+ * Cancellation flag
+ */
BOOL exportCancelled;
- // Current database's tables
+ /**
+ * Multi-file export flag
+ */
+ BOOL exportToMultipleFiles;
+
+ /**
+ * Create custom filename flag
+ */
+ BOOL createCustomFilename;
+
+ /**
+ * Number of tables being exported
+ */
+ NSUInteger exportTableCount;
+
+ /**
+ * Index of the current table being exported
+ */
+ NSUInteger currentTableExportIndex;
+
+ /**
+ * Export type label
+ */
+ NSString *exportTypeLabel;
+
+ /**
+ * Export filename
+ */
+ NSString *exportFilename;
+
+ /**
+ * Current database's tables
+ */
NSMutableArray *tables;
- // Database connection
+ /**
+ * Database connection
+ */
MCPConnection *connection;
- // Concurrent operation queue
+ /**
+ * Concurrent operation queue
+ */
NSOperationQueue *operationQueue;
- // Table/export operation mapping
- NSMutableDictionary *tableExportMapping;
+ /**
+ * Exporters
+ */
+ NSMutableArray *exporters;
+
+ /**
+ * Global export file handle
+ */
+ NSFileHandle *exportFileHandle;
+
+ /**
+ * Export type
+ */
+ SPExportType exportType;
+
+ /**
+ * Export source
+ */
+ SPExportSource exportSource;
+
+ /**
+ * Available filename tokens
+ */
+ NSString *availableFilenameTokens;
+
+ /**
+ * Display advanced view flag
+ */
+ BOOL showAdvancedView;
+
+ /**
+ * User defaults
+ */
+ NSUserDefaults *prefs;
+
+ /**
+ * Current toolbar item
+ */
+ NSToolbarItem *currentToolbarItem;
+
+ /**
+ * Previous connection encoding
+ */
+ NSString *sqlPreviousConnectionEncoding;
+
+ /**
+ * Previous connection encoding was via Latin1
+ */
+ BOOL sqlPreviousConnectionEncodingViaLatin1;
- // Top-level nib objects that require releasing on dealloc
- NSMutableArray *nibObjectsToRelease;
+ NSInteger heightOffset;
+ NSUInteger windowMinWidth;
+ NSUInteger windowMinHeigth;
}
-@property (readwrite, assign) BOOL exportCancelled;
-@property (readwrite, assign) MCPConnection *connection;
+/**
+ * @property exportCancelled Export cancellation flag
+ */
+@property(readwrite, assign) BOOL exportCancelled;
+
+/**
+ * @property exportToMultipleFiles Export to multiple files flag
+ */
+@property(readwrite, assign) BOOL exportToMultipleFiles;
+
+/**
+ * @property connection Database connection
+ */
+@property(readwrite, assign) MCPConnection *connection;
-// IB action methods
- (void)export;
+- (void)exportTables:(NSArray *)table asFormat:(SPExportType)format;
+- (void)openExportErrorsSheetWithString:(NSString *)errors;
+- (void)displayExportFinishedGrowlNotification;
+- (NSString *)expandCustomFilenameFormatFromString:(NSString *)format usingTableName:(NSString *)table;
+
+// IB action methods
- (IBAction)closeSheet:(id)sender;
- (IBAction)switchTab:(id)sender;
- (IBAction)switchInput:(id)sender;
- (IBAction)cancelExport:(id)sender;
- (IBAction)changeExportOutputPath:(id)sender;
+- (IBAction)refreshTableList:(id)sender;
+- (IBAction)selectDeselectAllTables:(id)sender;
+- (IBAction)toggleCustomFilenameFormat:(id)sender;
+- (IBAction)toggleAdvancedExportOptionsView:(id)sender;
+
+- (IBAction)toggleSQLIncludeStructure:(id)sender;
+- (IBAction)toggleSQLIncludeContent:(id)sender;
+- (IBAction)toggleSQLIncludeDropSyntax:(id)sender;
@end
diff --git a/Source/SPExportController.m b/Source/SPExportController.m
index 559e8071..83c87ea6 100644
--- a/Source/SPExportController.m
+++ b/Source/SPExportController.m
@@ -5,6 +5,7 @@
// sequel-pro
//
// Created by Ben Perry (benperry.com.au) on 21/02/09.
+// Modified by Stuart Connolly (stuconnolly.com)
//
// 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
@@ -22,39 +23,68 @@
//
// More info at <http://code.google.com/p/sequel-pro/>
+#import <MCPKit/MCPKit.h>
+
#import "SPExportController.h"
-#import "SPCSVExporter.h"
+#import "SPExportInitializer.h"
#import "TablesList.h"
#import "SPTableData.h"
-#import "TableDocument.h"
+#import "TableContent.h"
#import "SPArrayAdditions.h"
#import "SPStringAdditions.h"
#import "SPConstants.h"
+#import "SPGrowlController.h"
@interface SPExportController (PrivateAPI)
-- (void)_initializeExportUsingSelectedOptions;
-- (BOOL)_exportTables:(NSArray *)exportTables asType:(SPExportType)type toMultipleFiles:(BOOL)multipleFiles;
+- (void)_toggleExportButton;
+- (void)_resizeWindowByHeightDelta:(NSInteger)delta;
@end
@implementation SPExportController
@synthesize connection;
+@synthesize exportToMultipleFiles;
@synthesize exportCancelled;
+#pragma mark -
+#pragma mark Initialization
+
/**
* Initializes an instance of SPExportController
*/
- (id)init
{
- if ((self = [super init])) {
+ if (self = [super initWithWindowNibName:@"ExportDialog"]) {
+
[self setExportCancelled:NO];
+ [self setExportToMultipleFiles:YES];
+
+ exportType = 0;
+ exportTableCount = 0;
+ currentTableExportIndex = 0;
+
+ exportFilename = @"";
+ exportTypeLabel = @"";
+
+ createCustomFilename = NO;
+ sqlPreviousConnectionEncodingViaLatin1 = NO;
tables = [[NSMutableArray alloc] init];
+ exporters = [[NSMutableArray alloc] init];
operationQueue = [[NSOperationQueue alloc] init];
- tableExportMapping = [NSMutableDictionary dictionary];
- nibObjectsToRelease = [[NSMutableArray alloc] init];
+
+ showAdvancedView = NO;
+
+ heightOffset = 0;
+ windowMinWidth = [[self window] minSize].width;
+ windowMinHeigth = [[self window] minSize].height;
+
+ prefs = [NSUserDefaults standardUserDefaults];
+
+ // Default filename tokens
+ availableFilenameTokens = @"host,database,table,date,time";
}
return self;
@@ -64,9 +94,12 @@
* Upon awakening select the first toolbar item
*/
- (void)awakeFromNib
-{
+{
// Upon awakening select the SQL tab
[exportToolbar setSelectedItemIdentifier:[[[exportToolbar items] objectAtIndex:0] itemIdentifier]];
+
+ // Select the 'selected tables' option
+ [exportInputMatrix selectCellAtRow:2 column:0];
}
#pragma mark -
@@ -76,52 +109,166 @@
* Display the export window allowing the user to select what and of what type to export.
*/
- (void)export
-{
+{
+ [self exportTables:nil asFormat:0];
+}
- // If the dialog hasn't been loaded yet, do so, retaining a reference to the top-level objects that need releasing.
- if (!exportWindow)
- {
- NSArray *exportDialogTopLevelObjects = nil;
- NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"ExportDialog" bundle:[NSBundle mainBundle]];
- [nibLoader instantiateNibWithOwner:self topLevelObjects:&exportDialogTopLevelObjects];
- [nibObjectsToRelease addObjectsFromArray:exportDialogTopLevelObjects];
- [nibLoader release];
+/**
+ * Displays the export window with the supplied tables and export type/format selected.
+ */
+- (void)exportTables:(NSArray *)exportTables asFormat:(SPExportType)format
+{
+ [self refreshTableList:self];
+
+ if (exportTables && format) {
+
+ // Select the correct tab according to the supplied export type
+ [exportToolbar setSelectedItemIdentifier:[[[exportToolbar items] objectAtIndex:(format - 1)] itemIdentifier]];
+
+ // Select the 'selected tables' source option
+ [exportInputMatrix selectCellAtRow:2 column:0];
+
+ // Disable all tables
+ for (NSMutableArray *table in tables)
+ {
+ [table replaceObjectAtIndex:1 withObject:[NSNumber numberWithBool:NO]];
+ [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:NO]];
+ [table replaceObjectAtIndex:3 withObject:[NSNumber numberWithBool:NO]];
+ }
+
+ // Select the supplied tables
+ for (NSMutableArray *table in tables)
+ {
+ for (NSString *exportTable in exportTables)
+ {
+ if ([exportTable isEqualToString:[table objectAtIndex:0]]) {
+ [table replaceObjectAtIndex:1 withObject:[NSNumber numberWithBool:YES]];
+ [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:YES]];
+ [table replaceObjectAtIndex:3 withObject:[NSNumber numberWithBool:YES]];
+ }
+ }
+ }
+
+ [exportTableList reloadData];
+
+ // Ensure interface validation
+ [self switchTab:[[exportToolbar items] objectAtIndex:(format - 1)]];
}
- NSUInteger i;
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
- [tables removeAllObjects];
+ // If found the set the default path to the user's desktop, otherwise use their home directory
+ [exportPathField setStringValue:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory()];
- MCPResult *queryResult = (MCPResult *)[[self connection] listTables];
+ [NSApp beginSheet:[self window]
+ modalForWindow:[tableDocumentInstance parentWindow]
+ modalDelegate:self
+ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ contextInfo:nil];
+}
+
+/**
+ * Opens the errors sheet and displays the supplied errors string.
+ */
+- (void)openExportErrorsSheetWithString:(NSString *)errors
+{
+ [errorsTextView setString:@""];
+ [errorsTextView setString:errors];
- if ([queryResult numOfRows]) [queryResult dataSeek:0];
+ [NSApp beginSheet:errorsWindow
+ modalForWindow:[tableDocumentInstance parentWindow]
+ modalDelegate:self
+ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ contextInfo:nil];
+}
+
+/**
+ * Displays the export finished Growl notification.
+ */
+- (void)displayExportFinishedGrowlNotification
+{
+ // Export finished Growl notification
+ [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Export Finished"
+ description:[NSString stringWithFormat:NSLocalizedString(@"Finished exporting to %@", @"description for finished exporting growl notification"), exportFilename]
+ window:[tableDocumentInstance parentWindow]
+ notificationName:@"Export Finished"];
+}
+
+/**
+ * Expands the custom filename format based on the selected tokens.
+ */
+- (NSString *)expandCustomFilenameFormatFromString:(NSString *)format usingTableName:(NSString *)table
+{
+ NSMutableString *string = [NSMutableString stringWithString:format];
- for ( i = 0 ; i < [queryResult numOfRows] ; i++ )
- {
- [tables addObject:[NSMutableArray arrayWithObjects:
- [NSNumber numberWithBool:YES],
- NSArrayObjectAtIndex([queryResult fetchRowAsArray], 0),
- nil]];
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+
+ [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
+
+ [dateFormatter setDateStyle:NSDateFormatterShortStyle];
+ [dateFormatter setTimeStyle:NSDateFormatterNoStyle];
+
+ [string replaceOccurrencesOfString:@"host" withString:[tableDocumentInstance host]
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
+
+ [string replaceOccurrencesOfString:@"database" withString:[tableDocumentInstance database]
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
+
+ if (table) {
+ [string replaceOccurrencesOfString:@"table" withString:table
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
+ }
+ else {
+ [string replaceOccurrencesOfString:@"table" withString:@""
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
}
- [exportTableList reloadData];
-
- [exportPathField setStringValue:NSHomeDirectory()];
+ [string replaceOccurrencesOfString:@"date" withString:[dateFormatter stringFromDate:[NSDate date]]
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
- [NSApp beginSheet:exportWindow
- modalForWindow:[tableDocumentInstance parentWindow]
- modalDelegate:self
- didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
- contextInfo:nil];
+ [dateFormatter setDateStyle:NSDateFormatterNoStyle];
+ [dateFormatter setTimeStyle:NSDateFormatterShortStyle];
+
+ [string replaceOccurrencesOfString:@"time" withString:[dateFormatter stringFromDate:[NSDate date]]
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
+
+ // Strip comma separators
+ [string replaceOccurrencesOfString:@"," withString:@""
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
+
+ // Replace colons with hyphens
+ [string replaceOccurrencesOfString:@":" withString:@"-"
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [string length])];
+
+ [dateFormatter release];
+
+ return string;
}
/**
- * Closes the export dialog
+ * Closes the export dialog.
*/
- (IBAction)closeSheet:(id)sender
{
- [NSApp endSheet:exportWindow returnCode:[sender tag]];
- [exportWindow orderOut:self];
+ if ([sender window] == [self window]) {
+
+ // Close the advanced options view if it's open
+ [exportAdvancedOptionsView setHidden:YES];
+ [exportAdvancedOptionsViewButton setState:NSOffState];
+
+ [self _resizeWindowByHeightDelta:0];
+ }
+
+ [NSApp endSheet:[sender window] returnCode:[sender tag]];
+ [[sender window] orderOut:self];
}
/**
@@ -130,20 +277,70 @@
- (IBAction)switchTab:(id)sender
{
if ([sender isKindOfClass:[NSToolbarItem class]]) {
- [exportTabBar selectTabViewItemWithIdentifier:[[sender label] lowercaseString]];
+
+ currentToolbarItem = sender;
+
+ NSString *label = [[currentToolbarItem label] lowercaseString];
+
+ [exportTabBar selectTabViewItemWithIdentifier:label];
+
+ BOOL isSQL = [label isEqualToString:@"sql"];
+ BOOL isCSV = [label isEqualToString:@"csv"];
+ BOOL isXML = [label isEqualToString:@"xml"];
+ BOOL isHTML = [label isEqualToString:@"html"];
+ BOOL isPDF = [label isEqualToString:@"pdf"];
+ BOOL isDot = [label isEqualToString:@"dot"];
+
+ BOOL disable = (isCSV || isXML || isHTML || isPDF || isDot);
+
+ [exportFilePerTableCheck setHidden:(isSQL || isDot)];
+ [exportFilePerTableNote setHidden:(isSQL || isDot)];
+
+ [exportTableList setEnabled:(!isDot)];
+ [exportSelectAllTablesButton setEnabled:(!isDot)];
+ [exportDeselectAllTablesButton setEnabled:(!isDot)];
+ [exportRefreshTablesButton setEnabled:(!isDot)];
+
+ [[exportInputMatrix cellAtRow:2 column:0] setEnabled:(!isDot)];
+
+ if (isDot) {
+ // Disable all source checkboxes
+ [[exportInputMatrix cellAtRow:0 column:0] setEnabled:NO];
+ [[exportInputMatrix cellAtRow:1 column:0] setEnabled:NO];
+ }
+ else {
+ // Enable/disable the 'filtered result' and 'query result' options
+ [[exportInputMatrix cellAtRow:0 column:0] setEnabled:((disable) && ([[tableContentInstance currentResult] count] > 1))];
+ [[exportInputMatrix cellAtRow:1 column:0] setEnabled:((disable) && ([[customQueryInstance currentResult] count] > 1))];
+ }
- [exportFilePerTableCheck setHidden:[[sender label] isEqualToString:@"Excel"]];
- [exportFilePerTableNote setHidden:[[sender label] isEqualToString:@"Excel"]];
+ [[exportTableList tableColumnWithIdentifier:@"structure"] setHidden:disable];
+ [[exportTableList tableColumnWithIdentifier:@"drop"] setHidden:disable];
+
+ [[[exportTableList tableColumnWithIdentifier:@"content"] headerCell] setStringValue:(disable) ? @"" : @"C"];
+
+ [exportCSVNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]];
}
}
/**
- *
+ * Enables/disables and shows/hides various interface controls depending on the selected item.
*/
- (IBAction)switchInput:(id)sender
{
if ([sender isKindOfClass:[NSMatrix class]]) {
- [exportTableList setEnabled:([[sender selectedCell] tag] == 3)];
+
+ BOOL isSelectedTables = ([[sender selectedCell] tag] == SPTableExport);
+
+ [exportFilePerTableCheck setHidden:(!isSelectedTables)];
+ [exportFilePerTableNote setHidden:(!isSelectedTables)];
+
+ [exportTableList setEnabled:isSelectedTables];
+ [exportSelectAllTablesButton setEnabled:isSelectedTables];
+ [exportDeselectAllTablesButton setEnabled:isSelectedTables];
+ [exportRefreshTablesButton setEnabled:isSelectedTables];
+
+ availableFilenameTokens = ([[sender selectedCell] tag] == SPQueryExport) ? @"host,database,date,time" : @"host,database,table,date,time";
}
}
@@ -164,24 +361,161 @@
}
/**
- *
+ * Opens the open panel when user selects to change the output path.
*/
- (IBAction)changeExportOutputPath:(id)sender
{
+ [exportCustomFilenameTokenField setStringValue:@""];
+ [exportCustomFilenameTokensField setStringValue:availableFilenameTokens];
+
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setCanChooseFiles:NO];
[panel setCanChooseDirectories:YES];
[panel setCanCreateDirectories:YES];
+ [panel setAccessoryView:exportCustomFilenameView];
+
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
- [panel beginSheetForDirectory:NSHomeDirectory()
+ [panel beginSheetForDirectory:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory()
file:nil
- modalForWindow:exportWindow
+ modalForWindow:[self window]
modalDelegate:self
didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
+/**
+ * Refreshes the table list.
+ */
+- (IBAction)refreshTableList:(id)sender
+{
+ NSUInteger i;
+
+ [tables removeAllObjects];
+
+ // For all modes, retrieve table and view names
+ NSArray *tablesAndViews = [tablesListInstance allTableAndViewNames];
+
+ for (id itemName in tablesAndViews) {
+ [tables addObject:[NSMutableArray arrayWithObjects:
+ itemName,
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithInt:SPTableTypeTable],
+ nil]];
+ }
+
+ // For SQL only, add procedures and functions
+ if ([[[currentToolbarItem label] lowercaseString] isEqualToString:@"sql"]) {
+ NSArray *procedures = [tablesListInstance allProcedureNames];
+
+ for (id procName in procedures)
+ {
+ [tables addObject:[NSMutableArray arrayWithObjects:
+ procName,
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithInt:SPTableTypeProc],
+ nil]];
+ }
+
+ NSArray *functions = [tablesListInstance allFunctionNames];
+
+ for (id funcName in functions)
+ {
+ [tables addObject:[NSMutableArray arrayWithObjects:
+ funcName,
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithBool:YES],
+ [NSNumber numberWithInt:SPTableTypeFunc],
+ nil]];
+ }
+ }
+
+ [exportTableList reloadData];
+}
+
+/**
+ * Selects or de-selects all tables.
+ */
+- (IBAction)selectDeselectAllTables:(id)sender
+{
+ [self refreshTableList:self];
+
+ for (NSMutableArray *table in tables)
+ {
+ [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:[sender tag]]];
+ }
+
+ [exportTableList reloadData];
+
+ [self _toggleExportButton];
+}
+
+/**
+ * Toggles the state of the custom filename format token fields.
+ */
+- (IBAction)toggleCustomFilenameFormat:(id)sender
+{
+ [exportCustomFilenameTokenField setEnabled:[sender state]];
+ [exportCustomFilenameTokensField setEnabled:[sender state]];
+}
+
+/**
+ * Toggles the display of the advanced options box.
+ */
+- (IBAction)toggleAdvancedExportOptionsView:(id)sender
+{
+ showAdvancedView = !showAdvancedView;
+
+ if (showAdvancedView) {
+ [exportAdvancedOptionsViewButton setState:NSOnState];
+ [self _resizeWindowByHeightDelta:([exportAdvancedOptionsView frame].size.height + 10)];
+ [exportAdvancedOptionsView setHidden:NO];
+ }
+ else {
+ [exportAdvancedOptionsViewButton setState:NSOffState];
+ [self _resizeWindowByHeightDelta:0];
+ [exportAdvancedOptionsView setHidden:YES];
+ }
+}
+
+/**
+ * Toggles the export button when choosing to include or table structures in an SQL export.
+ */
+- (IBAction)toggleSQLIncludeStructure:(id)sender
+{
+ [[exportTableList tableColumnWithIdentifier:@"structure"] setHidden:(![sender state])];
+
+ [self _toggleExportButton];
+}
+
+/**
+ * Toggles the export button when choosing to include or exclude table contents in an SQL export.
+ */
+- (IBAction)toggleSQLIncludeContent:(id)sender
+{
+ [sender setTag:[sender state]];
+
+ [self selectDeselectAllTables:sender];
+
+ [self _toggleExportButton];
+}
+
+/**
+ * Toggles the export button when choosing to include or exclude table drop syntax in an SQL export.
+ */
+- (IBAction)toggleSQLIncludeDropSyntax:(id)sender
+{
+ [[exportTableList tableColumnWithIdentifier:@"drop"] setHidden:(![sender state])];
+
+ [self _toggleExportButton];
+}
+
#pragma mark -
#pragma mark Table view datasource methods
@@ -190,30 +524,32 @@
return [tables count];
}
-- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- return NSArrayObjectAtIndex([tables objectAtIndex:rowIndex], ([[aTableColumn identifier] isEqualToString:@"switch"]) ? 0 : 1);
+- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
+{
+ return NSArrayObjectAtIndex([tables objectAtIndex:rowIndex], [exportTableList columnWithIdentifier:[tableColumn identifier]]);
}
-- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:0 withObject:anObject];
+- (void)tableView:(NSTableView *)tableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
+{
+ [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:[exportTableList columnWithIdentifier:[tableColumn identifier]] withObject:anObject];
+
+ [self _toggleExportButton];
}
#pragma mark -
#pragma mark Table view delegate methods
-- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex
+- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex
{
- return (aTableView != exportTableList);
+ return (tableView != exportTableList);
}
-- (BOOL)tableView:(NSTableView *)aTableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
+- (BOOL)tableView:(NSTableView *)tableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
{
- return (aTableView == exportTableList);
+ return (tableView == exportTableList);
}
-- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
{
[aCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
}
@@ -223,7 +559,7 @@
- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
{
- NSMutableArray *items = [NSMutableArray arrayWithCapacity:6];
+ NSMutableArray *items = [NSMutableArray array];
for (NSToolbarItem *item in [toolbar items])
{
@@ -234,21 +570,16 @@
}
#pragma mark -
-#pragma mark SPExporterDataAccess protocol methods
+#pragma mark Text field delegate methods
-/**
- * This method is part of the SPExporterDataAccess protocol. It is called when an expoter complete it's data
- * conversion process and the operation is effectively complete. The resulting data can be accessed via
- * SPExporter's exportData method.
- */
-- (void)exporterDataConversionProcessComplete:(SPExporter *)exporter
-{
- // Do something with the data...
-
- // If there are no more operations in the queue, close the progress sheet
- if ([[operationQueue operations] count] == 0) {
- [NSApp endSheet:exportProgressWindow returnCode:0];
- [exportProgressWindow orderOut:self];
+- (void)controlTextDidChange:(NSNotification *)notification
+{
+ if ([notification object] == exportCustomFilenameTokenField) {
+
+ // Create the table name, but since this is only an example, use the first table in the list
+ NSString *filename = [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:[[tablesListInstance tables] objectAtIndex:1]];
+
+ [exportCustomFilenameExampleTextField setStringValue:[NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"Example", @"example label"), filename]];
}
}
@@ -256,7 +587,7 @@
#pragma mark Other
/**
- * Invoked when the user
+ * Invoked when the user dismissing the export dialog and starts the export process if required.
*/
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
@@ -264,12 +595,12 @@
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];
+ [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.
+ * Invoked when the user dismisses the save panel. Updates the selected directory if they clicked OK.
*/
- (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
@@ -286,247 +617,104 @@
- (void)dealloc
{
[tables release], tables = nil;
+ [exporters release], exporters = nil;
[operationQueue release], operationQueue = nil;
- for (id retainedObject in nibObjectsToRelease) [retainedObject release];
- [nibObjectsToRelease release], nibObjectsToRelease = nil;
+
+ if (sqlPreviousConnectionEncoding) [sqlPreviousConnectionEncoding release], sqlPreviousConnectionEncoding = nil;
[super dealloc];
}
-@end
-
-@implementation SPExportController (PrivateAPI)
+#pragma mark -
+#pragma mark Private API
/**
- *
+ * Enables or disables the export button based on the state of various interface controls.
*/
-- (void)_initializeExportUsingSelectedOptions
+- (void)_toggleExportButton
{
- // First determine what type of export the user selected
- SPExportType exportType = 0;
+ NSString *label = [[currentToolbarItem label] lowercaseString];
- 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
- 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]];
- }
+ BOOL isSQL = [label isEqualToString:@"sql"];
+ BOOL isCSV = [label isEqualToString:@"csv"];
+ BOOL isXML = [label isEqualToString:@"xml"];
+ BOOL isHTML = [label isEqualToString:@"html"];
+ BOOL isPDF = [label isEqualToString:@"pdf"];
+
+ if (isCSV || isXML || isHTML || isPDF) {
+ [exportButton setEnabled:NO];
+
+ // Only enable the button if at least one table is selected
+ for (NSArray *table in tables)
+ {
+ if ([NSArrayObjectAtIndex(table, 2) boolValue]) {
+ [exportButton setEnabled:YES];
+ break;
}
-
- break;
+ }
}
-
- // Begin the export based on the type
- switch (exportSource)
- {
- case SP_FILTERED_EXPORT:
-
- break;
- case SP_CUSTOM_QUERY_EXPORT:
-
- break;
- case SP_TABLE_EXPORT:
- [self _exportTables:exportTables asType:exportType toMultipleFiles:[exportFilePerTableCheck state]];
- break;
+ else if (isSQL) {
+ BOOL structureEnabled = [exportSQLIncludeStructureCheck state];
+ BOOL contentEnabled = [exportSQLIncludeContentCheck state];
+ BOOL dropEnabled = [exportSQLIncludeDropSyntaxCheck state];
+
+ // Disable if all are unchecked
+ if ((!contentEnabled) && (!structureEnabled) && (!dropEnabled)) {
+ [exportButton setEnabled:NO];
+ }
+ // Disable if structure is unchecked, but content and drop are as dropping a table then trying to insert
+ // into it is obviously an error
+ else if (contentEnabled && (!structureEnabled) && (dropEnabled)) {
+ [exportButton setEnabled:NO];
+ }
+ else {
+ [exportButton setEnabled:(contentEnabled || (structureEnabled || dropEnabled))];
+ }
}
}
/**
- * Exports the contents' of the supplied array of tables. Note that this method currently only supports
- * exporting in CSV and XML formats.
+ * Resizes the export window's height by the supplied delta, while retaining the position of
+ * all interface controls.
*/
-- (BOOL)_exportTables:(NSArray *)exportTables asType:(SPExportType)type toMultipleFiles:(BOOL)multipleFiles
+- (void)_resizeWindowByHeightDelta:(NSInteger)delta
{
- NSUInteger i;
+ NSUInteger scrollMask = [exportTablelistScrollView autoresizingMask];
+ NSUInteger buttonBarMask = [exportTableListButtonBar autoresizingMask];
+ NSUInteger tabBarMask = [exportTabBar autoresizingMask];
+ NSUInteger buttonMask = [exportAdvancedOptionsViewButton autoresizingMask];
+ NSUInteger textFieldMask = [exportAdvancedOptionsViewLabelButton autoresizingMask];
+ NSUInteger advancedViewMask = [exportAdvancedOptionsView autoresizingMask];
- NSMutableString *errors = [NSMutableString string];
+ NSRect frame = [[self window] frame];
- NSDictionary *tableDetails = nil;
- //NSStringEncoding encoding = [[self connection] encoding];
+ [exportTablelistScrollView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin];
+ [exportTableListButtonBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin];
+ [exportTabBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin];
+ [exportAdvancedOptionsViewButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin];
+ [exportAdvancedOptionsViewLabelButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin];
+ [exportAdvancedOptionsView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin];
- // 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];
+ NSInteger newMinHeight = (windowMinHeigth - heightOffset + delta < windowMinHeigth) ? windowMinHeigth : windowMinHeigth - heightOffset + delta;
- // Open the progress sheet
- [NSApp beginSheet:exportProgressWindow
- modalForWindow:[tableDocumentInstance parentWindow]
- 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])];
+ [[self window] setMinSize:NSMakeSize(windowMinWidth, newMinHeight)];
- [csvLineEnd replaceOccurrencesOfString:@"\\r" withString:@"\r"
- options:NSLiteralSearch
- range:NSMakeRange(0, [csvLineEnd length])];
+ frame.origin.y += heightOffset;
+ frame.size.height -= heightOffset;
- NSUInteger tableCount = [exportTables count];
+ heightOffset = delta;
- // If
- if ((type == SP_CSV_EXPORT) && (!multipleFiles) && (tableCount > 1)) {
-
- }
+ frame.origin.y -= heightOffset;
+ frame.size.height += heightOffset;
- /*if ([exportTables count] > 1) {
- [infoString setString:[NSString stringWithFormat:@"Host: %@ Database: %@ Generation Time: %@%@%@",
- [tableDocumentInstance host], [tableDocumentInstance database], [NSDate date], csvLineEnd, csvLineEnd]];
- }*/
-
- // Loop through the tables
- for (i = 0 ; i < tableCount; i++)
- {
- if ([self exportCancelled]) break;
-
- // Update the progress text and reset the progress bar to indeterminate status
- NSString *tableName = [exportTables objectAtIndex:i];
-
- [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %l (%@): fetching data...", @"text showing that app is fetching data for table dump"), (unsigned long)(i + 1), (unsigned long)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]]];
- [queryResult setReturnDataAsStrings:YES];
-
- 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
- NSMutableArray *tableColumnNumericStatus = [NSMutableArray array];
-
- 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"])]];
- }
-
- // Use low memory export?
- BOOL useLowMemoryBlockingStreaming = ([exportProcessLowMemory state] == NSOnState);
-
- // Make a streaming request for the data
- MCPStreamingResult *queryResultStreaming = [connection streamingQueryString:[NSString stringWithFormat:@"SELECT * FROM %@", [tableName backtickQuotedString]] useLowMemoryBlockingStreaming:useLowMemoryBlockingStreaming];
-
- // Note any errors during retrieval
- if ([connection queryErrored]) {
- [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]];
- }
-
- SPExporter *exporter = nil;
- SPCSVExporter *csvExporter = nil;
-
- // Based on the type of export create a new instance of the corresponding exporter and set it's specific options
- switch (type)
- {
- 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:SPNullValue]];
-
- [csvExporter setCsvTableColumnNumericStatus:tableColumnNumericStatus];
-
- // Assign the data to the exporter
- [csvExporter setCsvDataResult:queryResultStreaming];
-
- exporter = csvExporter;
-
- break;
- case SP_XML_EXPORT:
-
- break;
- case SP_PDF_EXPORT:
-
- break;
- case SP_HTML_EXPORT:
-
- break;
- case SP_EXCEL_EXPORT:
-
- break;
- }
-
- // Update the progress text and set the progress bar back to determinate
- [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing...", @"text showing that app is writing data for table export"), (unsigned long)(i + 1), (unsigned long)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];
-
- if (csvExporter) [csvExporter release];
-
- // Add a spacer to the file
- //[fileHandle writeData:[[NSString stringWithFormat:@"%@%@%@", csvLineEnd, csvLineEnd, csvLineEnd] dataUsingEncoding:encoding]];
- }
+ [[self window] setFrame:frame display:YES animate:YES];
- return YES;
+ [exportTablelistScrollView setAutoresizingMask:scrollMask];
+ [exportTableListButtonBar setAutoresizingMask:buttonBarMask];
+ [exportTabBar setAutoresizingMask:tabBarMask];
+ [exportAdvancedOptionsViewButton setAutoresizingMask:buttonMask];
+ [exportAdvancedOptionsViewLabelButton setAutoresizingMask:textFieldMask];
+ [exportAdvancedOptionsView setAutoresizingMask:advancedViewMask];
}
@end
diff --git a/Source/SPExportInitializer.h b/Source/SPExportInitializer.h
new file mode 100644
index 00000000..4d86f619
--- /dev/null
+++ b/Source/SPExportInitializer.h
@@ -0,0 +1,51 @@
+//
+// $Id$
+//
+// SPExportInitializer.h
+// 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 <Cocoa/Cocoa.h>
+
+#import "SPExportController.h"
+
+@class SPCSVExporter, SPSQLExporter, SPXMLExporter, SPFileHandle;
+
+/**
+ * @category SPExportInitializer SPExportInitializer.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * Export initializer category.
+ */
+@interface SPExportController (SPExportInitializer)
+
+- (void)initializeExportUsingSelectedOptions;
+
+- (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray;
+
+- (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray;
+- (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray;
+
+- (void)writeXMLHeaderToFileHandle:(SPFileHandle *)fileHandle;
+- (SPFileHandle *)getFileHandleForFilePath:(NSString *)filePath;
+
+@end
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
diff --git a/Source/SPExportUtilities.h b/Source/SPExportUtilities.h
new file mode 100644
index 00000000..2c7c1e09
--- /dev/null
+++ b/Source/SPExportUtilities.h
@@ -0,0 +1,30 @@
+//
+// $Id$
+//
+// SPExportUtilities.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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/>
+
+@interface SPExportUtilities : NSObject
+
+void SPExportDelegateConformsToProtocol(NSObject *delegate, Protocol *protocol);
+
+@end
diff --git a/Source/SPExportUtilities.m b/Source/SPExportUtilities.m
new file mode 100644
index 00000000..bf532bb2
--- /dev/null
+++ b/Source/SPExportUtilities.m
@@ -0,0 +1,40 @@
+//
+// $Id$
+//
+// SPExportUtilities.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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 "SPExportUtilities.h"
+
+@implementation SPExportUtilities
+
+void SPExportDelegateConformsToProtocol(NSObject *delegate, Protocol *protocol)
+{
+ // Check that the the supplied delegate conforms to the supplied protocol, if not throw an exception
+ if (![delegate conformsToProtocol:protocol]) {
+ @throw [NSException exceptionWithName:@"Protocol Conformance"
+ reason:[NSString stringWithFormat:@"The supplied delegate does not conform to the protocol '%@'.", NSStringFromProtocol(protocol)]
+ userInfo:nil];
+ }
+}
+
+@end
diff --git a/Source/SPExporter.h b/Source/SPExporter.h
index 1f52e5db..805173c9 100644
--- a/Source/SPExporter.h
+++ b/Source/SPExporter.h
@@ -25,9 +25,11 @@
#import <Cocoa/Cocoa.h>
-#import "SPExporterDataAccess.h"
-
/**
+ * @class SPExporter SPExporter.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
* This class is designed to be the base class of all data exporters and provide basic functionality
* common to each of them. Each data exporter (i.e. CSV, SQL, XML, etc.) should be implemented as a subclass
* of this class, with the end result being a modular export architecture separated by export type. All exporters
@@ -47,28 +49,35 @@
* once the exporter instance is placed on the operation queue once its ready to be run.
*/
+@class MCPConnection, SPFileHandle;
+
@interface SPExporter : NSOperation
-{
- id <SPExporterDataAccess> delegate;
- SEL didEndSelector;
+{
+ MCPConnection *connection;
double exportProgressValue;
BOOL exportProcessIsRunning;
+ BOOL exportUsingLowMemoryBlockingStreaming;
NSString *exportData;
+ SPFileHandle *exportOutputFileHandle;
NSStringEncoding exportOutputEncoding;
+
+ NSInteger exportMaxProgress;
}
-@property (readwrite, assign) id delegate;
-@property (readwrite, assign) SEL didEndSelector;
-@property (readwrite, assign) double exportProgressValue;
+@property(readwrite, retain) MCPConnection *connection;
+
+@property(readwrite, assign) double exportProgressValue;
-@property (readwrite, assign) BOOL exportProcessIsRunning;
+@property(readwrite, assign) BOOL exportProcessIsRunning;
+@property(readwrite, assign) BOOL exportUsingLowMemoryBlockingStreaming;
-@property (readwrite, retain) NSString *exportData;
-@property (readwrite, assign) NSStringEncoding exportOutputEncoding;
+@property(readwrite, retain) NSString *exportData;
+@property(readwrite, retain) SPFileHandle *exportOutputFileHandle;
+@property(readwrite, assign) NSStringEncoding exportOutputEncoding;
-- (id)initWithDelegate:(id)exportDelegate;
+@property(readwrite, assign) NSInteger exportMaxProgress;
@end
diff --git a/Source/SPExporter.m b/Source/SPExporter.m
index cf764b61..b7d9f242 100644
--- a/Source/SPExporter.m
+++ b/Source/SPExporter.m
@@ -27,21 +27,21 @@
@implementation SPExporter
-@synthesize delegate;
-@synthesize didEndSelector;
+@synthesize connection;
@synthesize exportProgressValue;
@synthesize exportProcessIsRunning;
+@synthesize exportUsingLowMemoryBlockingStreaming;
@synthesize exportData;
+@synthesize exportOutputFileHandle;
@synthesize exportOutputEncoding;
+@synthesize exportMaxProgress;
/**
- * Initialise an instance of SPCSVExporter using the supplied delegate and set some default values.
+ * Initialise an instance of SPExporter, while setting some default values.
*/
-- (id)initWithDelegate:(id)exportDelegate
+- (id)init
{
- if ((self = [super init])) {
- [self setDelegate:exportDelegate];
-
+ if ((self = [super init])) {
[self setExportProgressValue:0];
[self setExportProcessIsRunning:NO];
@@ -50,8 +50,6 @@
// Default the output encoding to UTF-8
[self setExportOutputEncoding:NSUTF8StringEncoding];
-
- [self setDidEndSelector:@selector(exporterDataConversionProcessComplete:)];
}
return self;
@@ -62,7 +60,9 @@
*/
- (void)main
{
- @throw [NSException exceptionWithName:@"NSOperation main() call" reason:@"Can't call NSOperation's main() method in SPExpoter, must be overriden in subclass." userInfo:nil];
+ @throw [NSException exceptionWithName:@"NSOperation main() Call"
+ reason:@"Can't call NSOperation's main() method in SPExpoter, must be overriden in subclass."
+ userInfo:nil];
}
/**
@@ -70,7 +70,9 @@
*/
- (void)dealloc
{
- [exportData release], exportData = nil;
+ if (exportData) [exportData release], exportData = nil;
+ if (connection) [connection release], connection = nil;
+ if (exportOutputFileHandle) [exportOutputFileHandle release], exportOutputFileHandle = nil;
[super dealloc];
}
diff --git a/Source/SPHTMLExporter.h b/Source/SPHTMLExporter.h
new file mode 100644
index 00000000..e58f8c80
--- /dev/null
+++ b/Source/SPHTMLExporter.h
@@ -0,0 +1,57 @@
+//
+// $Id$
+//
+// SPHTMLExporter.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010
+// Copyright (c) 2009 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 <Cocoa/Cocoa.h>
+
+#import "SPExporter.h"
+#import "SPHTMLExporterProtocol.h"
+
+/**
+ * @class SPHTMLExporter SPHTMLExporter.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * HTML exporter class.
+ */
+@interface SPHTMLExporter : SPExporter
+{
+ /**
+ * Exporter delegate
+ */
+ NSObject <SPHTMLExporterProtocol> *delegate;
+}
+
+@property(readwrite, assign) NSObject *delegate;
+
+/**
+ * Initialise an instance of SPHTMLExporter using the supplied delegate.
+ *
+ * @param exportDelegate The exporter delegate
+ *
+ * @return The initialised instance
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate;
+
+@end
diff --git a/Source/SPHTMLExporter.m b/Source/SPHTMLExporter.m
new file mode 100644
index 00000000..45f41c54
--- /dev/null
+++ b/Source/SPHTMLExporter.m
@@ -0,0 +1,62 @@
+//
+// $Id$
+//
+// SPHTMLExporter.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010
+// Copyright (c) 2009 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 "SPHTMLExporter.h"
+#import "SPExportUtilities.h"
+
+@implementation SPHTMLExporter
+
+@synthesize delegate;
+
+/**
+ * Initialise an instance of SPHTMLExporter using the supplied delegate.
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate
+{
+ if ((self = [super init])) {
+ SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPHTMLExporterProtocol));
+
+ [self setDelegate:exportDelegate];
+ }
+
+ return self;
+}
+
+/**
+ * Start the HTML 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
+{
+ @try {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ [pool release];
+
+ }
+ @catch (NSException *e) { }
+}
+
+@end
diff --git a/Source/SPHTMLExporterDelegate.h b/Source/SPHTMLExporterDelegate.h
new file mode 100644
index 00000000..5cace8af
--- /dev/null
+++ b/Source/SPHTMLExporterDelegate.h
@@ -0,0 +1,38 @@
+//
+// $Id$
+//
+// SPHTMLExporterDelegate.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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 "SPExportController.h"
+#import "SPHTMLExporterProtocol.h"
+
+/**
+ * @category SPHTMLExporterDelegate SPHTMLExporterDelegate.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * HTML exporter delegate category.
+ */
+@interface SPExportController (SPHTMLExporterDelegate) <SPHTMLExporterProtocol>
+
+@end
diff --git a/Source/SPHTMLExporterDelegate.m b/Source/SPHTMLExporterDelegate.m
new file mode 100644
index 00000000..e4f02180
--- /dev/null
+++ b/Source/SPHTMLExporterDelegate.m
@@ -0,0 +1,54 @@
+//
+// $Id$
+//
+// SPHTMLExporterDelegate.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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 "SPHTMLExporterDelegate.h"
+
+@implementation SPExportController (SPHTMLExporterDelegate)
+
+/**
+ *
+ */
+- (void)htmlExportProcessWillBegin:(SPHTMLExporter *)exporter
+{
+
+}
+
+/**
+ *
+ */
+- (void)htmlExportProcessComplete:(SPHTMLExporter *)exporter
+{
+
+}
+
+/**
+ *
+ */
+- (void)htmlExportProcessWillBeginWritingData:(SPHTMLExporter *)exporter
+{
+
+}
+
+@end
diff --git a/Source/SPHTMLExporterProtocol.h b/Source/SPHTMLExporterProtocol.h
new file mode 100644
index 00000000..f5c7328e
--- /dev/null
+++ b/Source/SPHTMLExporterProtocol.h
@@ -0,0 +1,60 @@
+//
+// $Id$
+//
+// SPHTMLExporterProtocol.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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 <Cocoa/Cocoa.h>
+
+@class SPHTMLExporter;
+
+/**
+ * @protocol SPHTMLExporterProtocol SPHTMLExporterProtocol.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * HTML exporter delegate protocol.
+ */
+@protocol SPHTMLExporterProtocol
+
+/**
+ * Called when the HTML export process is about to begin.
+ *
+ * @param SPHTMLExporter The expoter calling the method.
+ */
+- (void)htmlExportProcessWillBegin:(SPHTMLExporter *)exporter;
+
+/**
+ * Called when the HTML export process is complete.
+ *
+ * @param SPHTMLExporter The expoter calling the method.
+ */
+- (void)htmlExportProcessComplete:(SPHTMLExporter *)exporter;
+
+/**
+ * Called when the HTML export process is about to begin writing data to disk.
+ *
+ * @param SPHTMLExporter The expoter calling the method.
+ */
+- (void)htmlExportProcessWillBeginWritingData:(SPHTMLExporter *)exporter;
+
+@end
diff --git a/Source/SPPDFExporter.h b/Source/SPPDFExporter.h
new file mode 100644
index 00000000..62283c43
--- /dev/null
+++ b/Source/SPPDFExporter.h
@@ -0,0 +1,57 @@
+//
+// $Id$
+//
+// SPPDFExporter.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010
+// Copyright (c) 2009 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 <Cocoa/Cocoa.h>
+
+#import "SPExporter.h"
+#import "SPPDFExporterProtocol.h"
+
+/**
+ * @class SPPDFExporter SPPDFExporter.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * PDF exporter class.
+ */
+@interface SPPDFExporter : SPExporter
+{
+ /**
+ * Exporter delegate
+ */
+ NSObject <SPPDFExporterProtocol> *delegate;
+}
+
+@property(readwrite, assign) NSObject *delegate;
+
+/**
+ * Initialise an instance of SPPDFExporter using the supplied delegate.
+ *
+ * @param exportDelegate The exporter delegate
+ *
+ * @return The initialised instance
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate;
+
+@end
diff --git a/Source/SPPDFExporter.m b/Source/SPPDFExporter.m
new file mode 100644
index 00000000..a850e2bf
--- /dev/null
+++ b/Source/SPPDFExporter.m
@@ -0,0 +1,61 @@
+//
+// $Id$
+//
+// SPPDFExporter.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 2010
+// Copyright (c) 2009 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 "SPPDFExporter.h"
+#import "SPExportUtilities.h"
+
+@implementation SPPDFExporter
+
+@synthesize delegate;
+
+/**
+ * Initialise an instance of SPPDFExporter using the supplied delegate.
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate
+{
+ if ((self = [super init])) {
+ SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPPDFExporterProtocol));
+
+ [self setDelegate:exportDelegate];
+ }
+
+ return self;
+}
+
+/**
+ * Start the PDF 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
+{
+ @try {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ [pool release];
+ }
+ @catch (NSException *e) { }
+}
+
+@end
diff --git a/Source/SPPDFExporterDelegate.h b/Source/SPPDFExporterDelegate.h
new file mode 100644
index 00000000..ef17a741
--- /dev/null
+++ b/Source/SPPDFExporterDelegate.h
@@ -0,0 +1,38 @@
+//
+// $Id$
+//
+// SPPDFExporterDelegate.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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 "SPExportController.h"
+#import "SPPDFExporterProtocol.h"
+
+/**
+ * @category SPPDFExporterDelegate SPPDFExporterDelegate.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * PDF exporter delegate category.
+ */
+@interface SPExportController (SPPDFExporterDelegate) <SPPDFExporterProtocol>
+
+@end
diff --git a/Source/SPExporterDataAccess.h b/Source/SPPDFExporterDelegate.m
index ab44cd39..e838adee 100644
--- a/Source/SPExporterDataAccess.h
+++ b/Source/SPPDFExporterDelegate.m
@@ -1,11 +1,11 @@
//
// $Id$
//
-// SPExporterDataAccess.h
+// SPPDFExporterDelegate.m
// sequel-pro
//
-// Created by Stuart Connolly (stuconnolly.com) on October 6, 2009
-// Copyright (c) 2009 Stuart Connolly. All rights reserved.
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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
@@ -23,16 +23,32 @@
//
// More info at <http://code.google.com/p/sequel-pro/>
-#import <Cocoa/Cocoa.h>
+#import "SPPDFExporterDelegate.h"
-@class SPExporter;
+@implementation SPExportController (SPPDFExporterDelegate)
-@protocol SPExporterDataAccess
+/**
+ *
+ */
+- (void)pdfExportProcessWillBegin:(SPPDFExporter *)exporter
+{
+
+}
+
+/**
+ *
+ */
+- (void)pdfExportProcessComplete:(SPPDFExporter *)exporter
+{
+
+}
/**
- * This method called when an expoter complete it's data conversion process and the operation is effectively
- * complete. The resulting data can be accessed via SPExporter's exportData method.
+ *
*/
-- (void)exporterDataConversionProcessComplete:(SPExporter *)exporter;
+- (void)pdfExportProcessWillBeginWritingData:(SPPDFExporter *)exporter
+{
+
+}
@end
diff --git a/Source/SPPDFExporterProtocol.h b/Source/SPPDFExporterProtocol.h
new file mode 100644
index 00000000..4381451e
--- /dev/null
+++ b/Source/SPPDFExporterProtocol.h
@@ -0,0 +1,60 @@
+//
+// $Id$
+//
+// SPPDFExporterProtocol.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 24, 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 <Cocoa/Cocoa.h>
+
+@class SPPDFExporter;
+
+/**
+ * @protocol SPPDFExporterProtocol SPPDFExporterProtocol.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * PDF exporter delegate protocol.
+ */
+@protocol SPPDFExporterProtocol
+
+/**
+ * Called when the PDF export process is about to begin.
+ *
+ * @param SPHTMLExporter The expoter calling the method.
+ */
+- (void)pdfExportProcessWillBegin:(SPPDFExporter *)exporter;
+
+/**
+ * Called when the PDF export process is complete.
+ *
+ * @param SPHTMLExporter The expoter calling the method.
+ */
+- (void)pdfExportProcessComplete:(SPPDFExporter *)exporter;
+
+/**
+ * Called when the PDF export process is about to begin writing data to disk.
+ *
+ * @param SPHTMLExporter The expoter calling the method.
+ */
+- (void)pdfExportProcessWillBeginWritingData:(SPPDFExporter *)exporter;
+
+@end
diff --git a/Source/SPSQLExporter.h b/Source/SPSQLExporter.h
index 7a2e9006..6b4702ce 100644
--- a/Source/SPSQLExporter.h
+++ b/Source/SPSQLExporter.h
@@ -26,7 +26,107 @@
#import <Cocoa/Cocoa.h>
#import "SPExporter.h"
+#import "SPSQLExporterProtocol.h"
+/**
+ * @class SPSQLExporter SPSQLExporter.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * SQL exporter class.
+ */
@interface SPSQLExporter : SPExporter
+{
+ NSObject <SPSQLExporterProtocol> *delegate;
+
+ /**
+ * Tables
+ */
+ NSArray *sqlExportTables;
+
+ /**
+ * Database host
+ */
+ NSString *sqlDatabaseHost;
+
+ /**
+ * Database name
+ */
+ NSString *sqlDatabaseName;
+
+ /**
+ * Database version
+ */
+ NSString *sqlDatabaseVersion;
+
+ /**
+ * Current table
+ */
+ NSString *sqlExportCurrentTable;
+
+ /**
+ * Export errors
+ */
+ NSString *sqlExportErrors;
+
+ /**
+ * Include UTF-8 BOM
+ */
+ BOOL sqlOutputIncludeUTF8BOM;
+
+ /**
+ * Encode BLOB fields as Hex data
+ */
+ BOOL sqlOutputEncodeBLOBasHex;
+
+ /**
+ * Include export errors
+ */
+ BOOL sqlOutputIncludeErrors;
+
+ /**
+ * Compress output
+ */
+ BOOL sqlOutputCompressFile;
+
+ /**
+ * Table information
+ */
+ NSDictionary *sqlTableInformation;
+}
+
+@property(readwrite, assign) NSObject *delegate;
+
+@property(readwrite, retain) NSArray *sqlExportTables;
+
+@property(readwrite, retain) NSString *sqlDatabaseHost;
+@property(readwrite, retain) NSString *sqlDatabaseName;
+@property(readwrite, retain) NSString *sqlDatabaseVersion;
+
+@property(readwrite, retain) NSString *sqlExportCurrentTable;
+@property(readwrite, retain) NSString *sqlExportErrors;
+
+@property(readwrite, assign) BOOL sqlOutputIncludeUTF8BOM;
+@property(readwrite, assign) BOOL sqlOutputEncodeBLOBasHex;
+@property(readwrite, assign) BOOL sqlOutputIncludeErrors;
+@property(readwrite, assign) BOOL sqlOutputCompressFile;
+
+@property (readwrite, retain) NSDictionary *sqlTableInformation;
+
+/**
+ * Initialise an instance of SPSQLExporter using the supplied delegate.
+ *
+ * @param exportDelegate The exporter delegate
+ *
+ * @return The initialised instance
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate;
+
+/**
+ * Returns whether or not any export errors occurred.
+ *
+ * @return A BOOL indicating the occurrence of errors
+ */
+- (BOOL)didExportErrorsOccur;
@end
diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m
index db9e4532..9b7cbaf6 100644
--- a/Source/SPSQLExporter.m
+++ b/Source/SPSQLExporter.m
@@ -23,26 +23,794 @@
//
// More info at <http://code.google.com/p/sequel-pro/>
+#import <MCPKit/MCPKit.h>
+
#import "SPSQLExporter.h"
+#import "TablesList.h"
+#import "SPConstants.h"
+#import "SPArrayAdditions.h"
+#import "SPStringAdditions.h"
+#import "SPFileHandle.h"
+#import "SPExportUtilities.h"
+
+@interface SPSQLExporter (PrivateAPI)
+
+- (NSString *)_createViewPlaceholderSyntaxForView:(NSString *)viewName;
+
+@end
@implementation SPSQLExporter
+@synthesize delegate;
+@synthesize sqlExportTables;
+@synthesize sqlDatabaseHost;
+@synthesize sqlDatabaseName;
+@synthesize sqlDatabaseVersion;
+@synthesize sqlExportCurrentTable;
+@synthesize sqlExportErrors;
+@synthesize sqlOutputIncludeUTF8BOM;
+@synthesize sqlOutputEncodeBLOBasHex;
+@synthesize sqlOutputIncludeErrors;
+@synthesize sqlOutputCompressFile;
+@synthesize sqlTableInformation;
+
+/**
+ * Initialise an instance of SPSQLExporter using the supplied delegate.
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate
+{
+ if ((self = [super init])) {
+ SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPSQLExporterProtocol));
+
+ [self setDelegate:exportDelegate];
+ [self setSqlExportCurrentTable:nil];
+ }
+
+ return self;
+}
+
/**
- * Start the SQL data conversion process. This method is automatically called when an instance of this object
+ * Start the SQL 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
{
@try {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSAutoreleasePool *sqlExportPool = [[NSAutoreleasePool alloc] init];
+
+ MCPResult *queryResult;
+ MCPStreamingResult *streamingResult;
+
+ NSArray *row;
+ NSString *tableName;
+ NSDictionary *tableDetails;
+ NSMutableArray *tableColumnNumericStatus;
+ SPTableType tableType = SPTableTypeTable;
+
+ id createTableSyntax = nil;
+ NSUInteger i, j, t, s, rowCount, queryLength, lastProgressValue;
+
+ BOOL sqlOutputIncludeStructure;
+ BOOL sqlOutputIncludeContent;
+ BOOL sqlOutputIncludeDropSyntax;
+
+ NSMutableArray *tables = [NSMutableArray array];
+ NSMutableArray *procs = [NSMutableArray array];
+ NSMutableArray *funcs = [NSMutableArray array];
+
+ NSMutableString *metaString = [NSMutableString string];
+ NSMutableString *cellValue = [NSMutableString string];
+ NSMutableString *errors = [[NSMutableString alloc] init];
+ NSMutableString *sqlString = [[NSMutableString alloc] init];
+
+ NSMutableDictionary *viewSyntaxes = [NSMutableDictionary dictionary];
+
+ // Check that we have all the required info before starting the export
+ if ((![self sqlExportTables]) || ([[self sqlExportTables] count] == 0) ||
+ (![self sqlTableInformation]) || ([[self sqlTableInformation] count] == 0) ||
+ (![self sqlDatabaseHost]) || ([[self sqlDatabaseHost] isEqualToString:@""]) ||
+ (![self sqlDatabaseName]) || ([[self sqlDatabaseName] isEqualToString:@""]) ||
+ (![self sqlDatabaseVersion] || ([[self sqlDatabaseName] isEqualToString:@""])))
+ {
+ [pool release];
+ return;
+ }
+
+ // Inform the delegate that the export process is about to begin
+ [delegate performSelectorOnMainThread:@selector(sqlExportProcessWillBegin:) withObject:self waitUntilDone:NO];
+
+ // Mark the process as running
+ [self setExportProcessIsRunning:YES];
+
+ // Clear errors
+ [self setSqlExportErrors:@""];
+
+ // Copy over the selected item names into tables in preparation for iteration
+ NSMutableArray *targetArray;
+
+ for (NSArray *item in [self sqlExportTables])
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ switch ([NSArrayObjectAtIndex(item, 4) intValue]) {
+ case SPTableTypeProc:
+ targetArray = procs;
+ break;
+ case SPTableTypeFunc:
+ targetArray = funcs;
+ break;
+ default:
+ targetArray = tables;
+ break;
+ }
+
+ [targetArray addObject:item];
+ }
+
+ // If required write the UTF-8 Byte Order Mark
+ if ([self sqlOutputIncludeUTF8BOM]) {
+ [metaString setString:@"\xef\xbb\xbf"];
+ [metaString appendString:@"# ************************************************************\n"];
+ }
+ else {
+ [metaString setString:@"# ************************************************************\n"];
+ }
+
+ // If required set the file handle to compress it's output
+ [[self exportOutputFileHandle] setShouldWriteWithGzipCompression:[self sqlOutputCompressFile]];
+
+ // Add the dump header to the dump file
+ [metaString appendString:@"# Sequel Pro SQL dump\n"];
+ [metaString appendString:[NSString stringWithFormat:@"# Version %@\n#\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]];
+ [metaString appendString:[NSString stringWithFormat:@"# %@\n# %@\n#\n", SPHomePageURL, SPDevURL]];
+ [metaString appendString:[NSString stringWithFormat:@"# Host: %@ (MySQL %@)\n", [self sqlDatabaseHost], [self sqlDatabaseVersion]]];
+ [metaString appendString:[NSString stringWithFormat:@"# Database: %@\n", [self sqlDatabaseName]]];
+ [metaString appendString:[NSString stringWithFormat:@"# Generation Time: %@\n", [NSDate date]]];
+ [metaString appendString:@"# ************************************************************\n\n\n"];
+
+ // Add commands to store the client encodings used when importing and set to UTF8 to preserve data
+ [metaString appendString:@"/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n"];
+ [metaString appendString:@"/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n"];
+ [metaString appendString:@"/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n"];
+ [metaString appendString:@"/*!40101 SET NAMES utf8 */;\n"];
+
+ // Add commands to store and disable unique checks, foreign key checks, mode and notes where supported.
+ // Include trailing semicolons to ensure they're run individually. Use MySQL-version based comments.
+ //if (sqlOutputIncludeDropSyntax) {
+ //[metaString appendString:@"/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n"];
+ //}
+
+ [metaString appendString:@"/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n"];
+ [metaString appendString:@"/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n"];
+ [metaString appendString:@"/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\n\n"];
+
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:[self exportOutputEncoding]]];
+
+ // Loop through the selected tables
+ for (NSArray *table in [self sqlExportTables])
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ tableName = NSArrayObjectAtIndex(table, 0);
+
+ sqlOutputIncludeStructure = [NSArrayObjectAtIndex(table, 1) boolValue];
+ sqlOutputIncludeContent = [NSArrayObjectAtIndex(table, 2) boolValue];
+ sqlOutputIncludeDropSyntax = [NSArrayObjectAtIndex(table, 3) boolValue];
+
+ // Set the current table
+ [self setSqlExportCurrentTable:tableName];
+
+ // Inform the delegate that we are about to start fetcihing data for the current table
+ [delegate performSelectorOnMainThread:@selector(sqlExportProcessWillBeginFetchingData:) withObject:self waitUntilDone:NO];
+
+ lastProgressValue = 0;
+
+ // Add the name of table
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Dump of table %@\n# ------------------------------------------------------------\n\n", tableName] dataUsingEncoding:[self exportOutputEncoding]]];
+
+ // Determine whether this table is a table or a view via the CREATE TABLE command, and keep the create table syntax
+ queryResult = [connection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]];
+
+ [queryResult setReturnDataAsStrings:YES];
+
+ if ([queryResult numOfRows]) {
+ tableDetails = [[NSDictionary alloc] initWithDictionary:[queryResult fetchRowAsDictionary]];
+
+ if ([tableDetails objectForKey:@"Create View"]) {
+ [viewSyntaxes setValue:[[[[tableDetails objectForKey:@"Create View"] copy] autorelease] createViewSyntaxPrettifier] forKey:tableName];
+ createTableSyntax = [self _createViewPlaceholderSyntaxForView:tableName];
+ tableType = SPTableTypeView;
+ }
+ else {
+ createTableSyntax = [[[tableDetails objectForKey:@"Create Table"] copy] autorelease];
+ tableType = SPTableTypeTable;
+ }
+
+ [tableDetails release];
+ }
+
+ if ([connection queryErrored]) {
+ [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]];
+
+ if ([self sqlOutputIncludeErrors]) {
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ }
+
+ // Add a 'DROP TABLE' command if required
+ if (sqlOutputIncludeDropSyntax)
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"DROP %@ IF EXISTS %@;\n\n", ((tableType == SPTableTypeTable) ? @"TABLE" : @"VIEW"), [tableName backtickQuotedString]]
+ dataUsingEncoding:[self exportOutputEncoding]]];
+
+
+ // Add the create syntax for the table if specified in the export dialog
+ if (sqlOutputIncludeStructure && createTableSyntax) {
+
+ if ([createTableSyntax isKindOfClass:[NSData class]]) {
+ createTableSyntax = [[[NSString alloc] initWithData:createTableSyntax encoding:[self exportOutputEncoding]] autorelease];
+ }
+
+ [[self exportOutputFileHandle] writeData:[createTableSyntax dataUsingEncoding:NSUTF8StringEncoding]];
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithString:@";\n\n"] dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+
+ // Add the table content if required
+ if (sqlOutputIncludeContent && (tableType == SPTableTypeTable)) {
+
+ // Retrieve the table details via the data class, and use it to build an array containing column numeric status
+ tableDetails = [NSDictionary dictionaryWithDictionary:[[self sqlTableInformation] objectForKey:tableName]];
+
+ NSUInteger colCount = [[tableDetails objectForKey:@"columns"] count];
+
+ tableColumnNumericStatus = [NSMutableArray arrayWithCapacity:colCount];
+
+ for (j = 0; j < colCount; j++)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ NSString *tableColumnTypeGrouping = [NSArrayObjectAtIndex([tableDetails objectForKey:@"columns"], j) objectForKey:@"typegrouping"];
+
+ [tableColumnNumericStatus addObject:[NSNumber numberWithBool:([tableColumnTypeGrouping isEqualToString:@"bit"] || [tableColumnTypeGrouping isEqualToString:@"integer"] || [tableColumnTypeGrouping isEqualToString:@"float"])]];
+ }
+
+ // Retrieve the number of rows in the table for progress bar drawing
+ rowCount = [NSArrayObjectAtIndex([[connection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [tableName backtickQuotedString]]] fetchRowAsArray], 0) integerValue];
+
+ // Set up a result set in streaming mode
+ streamingResult = [[connection streamingQueryString:[NSString stringWithFormat:@"SELECT * FROM %@", [tableName backtickQuotedString]] useLowMemoryBlockingStreaming:([self exportUsingLowMemoryBlockingStreaming])] retain];
+
+ NSArray *fieldNames = [streamingResult fetchFieldNames];
+
+ // Inform the delegate that we are about to start writing data for the current table
+ [delegate performSelectorOnMainThread:@selector(sqlExportProcessWillBeginWritingData:) withObject:self waitUntilDone:NO];
+
+ if (rowCount) {
+ queryLength = 0;
+
+ // Lock the table for writing and disable keys if supported
+ [metaString setString:@""];
+ [metaString appendString:[NSString stringWithFormat:@"LOCK TABLES %@ WRITE;\n", [tableName backtickQuotedString]]];
+ [metaString appendString:[NSString stringWithFormat:@"/*!40000 ALTER TABLE %@ DISABLE KEYS */;\n\n", [tableName backtickQuotedString]]];
+
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:[self exportOutputEncoding]]];
+
+ // Construct the start of the insertion command
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"INSERT INTO %@ (%@)\nVALUES\n\t(",
+ [tableName backtickQuotedString], [fieldNames componentsJoinedAndBacktickQuoted]] dataUsingEncoding:NSUTF8StringEncoding]];
+
+ // Iterate through the rows to construct a VALUES group for each
+ j = 0;
+
+ sqlExportPool = [[NSAutoreleasePool alloc] init];
+
+ // Inform the delegate that we are about to start writing the data to disk
+ [delegate performSelectorOnMainThread:@selector(sqlExportProcessWillBeginWritingData:) withObject:self waitUntilDone:NO];
+
+ while (row = [streamingResult fetchNextRowAsArray])
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [connection cancelCurrentQuery];
+ [streamingResult cancelResultLoad];
+ [sqlExportPool release];
+ [pool release];
+
+ return;
+ }
+
+ j++;
+ [sqlString setString:@""];
+
+ // Update the progress
+ if ((j * ([self exportMaxProgress] / rowCount)) > lastProgressValue) {
+
+ NSInteger progress = (j * ([self exportMaxProgress] / rowCount));
+
+ [self setExportProgressValue:progress];
+
+ lastProgressValue = progress;
+
+ // Inform the delegate that the export's progress has been updated
+ [delegate performSelectorOnMainThread:@selector(sqlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO];
+ }
+
+ for (t = 0; t < colCount; t++)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [sqlExportPool release];
+ [pool release];
+
+ return;
+ }
+
+ // Add NULL values directly to the output row
+ if ([NSArrayObjectAtIndex(row, t) isMemberOfClass:[NSNull class]]) {
+ [sqlString appendString:@"NULL"];
+ }
+ // Add data types directly as hex data
+ else if ([NSArrayObjectAtIndex(row, t) isKindOfClass:[NSData class]]) {
+
+ if ([self sqlOutputEncodeBLOBasHex]) {
+ [sqlString appendString:@"X'"];
+ [sqlString appendString:[connection prepareBinaryData:NSArrayObjectAtIndex(row, t)]];
+ }
+ else {
+ [sqlString appendString:@"'"];
+
+ NSString *data = [[NSString alloc] initWithData:NSArrayObjectAtIndex(row, t) encoding:[self exportOutputEncoding]];
+
+ if (data == nil) {
+ data = [[NSString alloc] initWithData:NSArrayObjectAtIndex(row, t) encoding:NSASCIIStringEncoding];
+ }
+
+ [sqlString appendString:data];
+
+ [data release];
+ }
+
+ [sqlString appendString:@"'"];
+ }
+ else {
+ [cellValue setString:[NSArrayObjectAtIndex(row, t) description]];
+
+ // Add empty strings as a pair of quotes
+ if ([cellValue length] == 0) {
+ [sqlString appendString:@"''"];
+ }
+ else {
+ // If this is a numeric column type, add the number directly.
+ if ([NSArrayObjectAtIndex(tableColumnNumericStatus, t) boolValue]) {
+ [sqlString appendString:cellValue];
+ }
+ // Otherwise add a quoted string with special characters escaped
+ else {
+ [sqlString appendString:@"'"];
+ [sqlString appendString:[connection prepareString:cellValue]];
+ [sqlString appendString:@"'"];
+ }
+ }
+ }
+
+ // Add the field separator if this isn't the last cell in the row
+ if (t != ([row count] - 1)) [sqlString appendString:@","];
+ }
+
+ queryLength += [sqlString length];
+
+ // Close this VALUES group and set up the next one if appropriate
+ if (j != rowCount) {
+
+ // Add a new INSERT starter command every ~250k of data
+ if (queryLength > 250000) {
+ [sqlString appendString:[NSString stringWithFormat:@");\n\nINSERT INTO %@ (%@)\nVALUES\n\t(",
+ [tableName backtickQuotedString], [fieldNames componentsJoinedAndBacktickQuoted]]];
+ queryLength = 0;
+
+ // Use the opportunity to drain and reset the autorelease pool
+ [sqlExportPool release];
+ sqlExportPool = [[NSAutoreleasePool alloc] init];
+ }
+ else {
+ [sqlString appendString:@"),\n\t("];
+ }
+ }
+ else {
+ [sqlString appendString:@")"];
+ }
+
+ // Write this row to the file
+ [[self exportOutputFileHandle] writeData:[sqlString dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+
+ // Complete the command
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithString:@";\n\n"] dataUsingEncoding:NSUTF8StringEncoding]];
+
+ // Unlock the table and re-enable keys if supported
+ [metaString setString:@""];
+ [metaString appendString:[NSString stringWithFormat:@"/*!40000 ALTER TABLE %@ ENABLE KEYS */;\n", [tableName backtickQuotedString]]];
+ [metaString appendString:@"UNLOCK TABLES;\n"];
+
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+
+ // Drain the autorelease pool
+ [sqlExportPool release];
+ }
+
+ if ([connection queryErrored]) {
+ [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]];
+
+ if ([self sqlOutputIncludeErrors]) {
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection getLastErrorMessage]]
+ dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ }
+
+ // Release the result set
+ [streamingResult release];
+
+ queryResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW TRIGGERS WHERE `Table` = %@ */;", [tableName tickQuotedString]]];
+
+ [queryResult setReturnDataAsStrings:YES];
+
+ if ([queryResult numOfRows]) {
+
+ [metaString setString:@"\n"];
+ [metaString appendString:@"DELIMITER ;;\n"];
+
+ for (s = 0; s < [queryResult numOfRows]; s++)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ NSDictionary *triggers = [[NSDictionary alloc] initWithDictionary:[queryResult fetchRowAsDictionary]];
+
+ // Definer is user@host but we need to escape it to `user`@`host`
+ NSArray *triggersDefiner = [[triggers objectForKey:@"Definer"] componentsSeparatedByString:@"@"];
+
+ NSString *escapedDefiner = [NSString stringWithFormat:@"%@@%@",
+ [NSArrayObjectAtIndex(triggersDefiner, 0) backtickQuotedString],
+ [NSArrayObjectAtIndex(triggersDefiner, 1) backtickQuotedString]
+ ];
+
+ [metaString appendString:[NSString stringWithFormat:@"/*!50003 SET SESSION SQL_MODE=\"%@\" */;;\n", [triggers objectForKey:@"sql_mode"]]];
+ [metaString appendString:@"/*!50003 CREATE */ "];
+ [metaString appendString:[NSString stringWithFormat:@"/*!50017 DEFINER=%@ */ ", escapedDefiner]];
+ [metaString appendString:[NSString stringWithFormat:@"/*!50003 TRIGGER %@ %@ %@ ON %@ FOR EACH ROW %@ */;;\n",
+ [[triggers objectForKey:@"Trigger"] backtickQuotedString],
+ [triggers objectForKey:@"Timing"],
+ [triggers objectForKey:@"Event"],
+ [[triggers objectForKey:@"Table"] backtickQuotedString],
+ [triggers objectForKey:@"Statement"]
+ ]];
+
+ [triggers release];
+ }
+
+ [metaString appendString:@"DELIMITER ;\n"];
+ [metaString appendString:@"/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"];
+
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+
+ if ([connection queryErrored]) {
+ [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]];
+
+ if ([self sqlOutputIncludeErrors]) {
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection getLastErrorMessage]]
+ dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ }
+
+ }
+
+ // Add an additional separator between tables
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithString:@"\n\n"] dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ // Process any deferred views, adding commands to delete the placeholder tables and add the actual views
+ for (tableName in viewSyntaxes)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ [metaString setString:@"\n\n"];
+ [metaString appendFormat:@"DROP TABLE %@;\n", [tableName backtickQuotedString]];
+ [metaString appendFormat:@"%@;\n", [viewSyntaxes objectForKey:tableName]];
+
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ // Export procedures and functions
+ for (NSString *procedureType in [NSArray arrayWithObjects:@"PROCEDURE", @"FUNCTION", nil])
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ // Retrieve the array of selected procedures or functions, and skip export if not selected
+ NSMutableArray *items;
+
+ if ([procedureType isEqualToString:@"PROCEDURE"]) items = procs;
+ else items = funcs;
+
+ if ([items count] == 0) continue;
+
+ // Retrieve the definitions
+ queryResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW %@ STATUS WHERE `Db` = %@ */;", procedureType,
+ [[self sqlDatabaseName] tickQuotedString]]];
+
+ [queryResult setReturnDataAsStrings:YES];
+
+ if ([queryResult numOfRows]) {
+
+ [metaString setString:@"\n"];
+ [metaString appendString:@"--\n"];
+ [metaString appendString:[NSString stringWithFormat:@"-- Dumping routines (%@) for database %@\n", procedureType,
+ [[self sqlDatabaseName] tickQuotedString]]];
+
+ [metaString appendString:@"--\n"];
+ [metaString appendString:@"DELIMITER ;;\n"];
+
+ // Loop through the definitions, exporting if enabled
+ for (s = 0; s < [queryResult numOfRows]; s++)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ NSDictionary *proceduresList = [[NSDictionary alloc] initWithDictionary:[queryResult fetchRowAsDictionary]];
+ NSString *procedureName = [NSString stringWithFormat:@"%@", [proceduresList objectForKey:@"Name"]];
+
+ // Only proceed if the item was selected for export
+ if (![items containsObject:procedureName]) {
+ [proceduresList release];
+ continue;
+ }
+
+ // Only proceed if the item is in the list of items
+ for (NSArray *item in items)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ [pool release];
+ return;
+ }
+
+ if ([NSArrayObjectAtIndex(item, 0) isEqualToString:procedureName]) {
+ sqlOutputIncludeStructure = [NSArrayObjectAtIndex(item, 1) boolValue];
+ sqlOutputIncludeContent = [NSArrayObjectAtIndex(item, 2) boolValue];
+ sqlOutputIncludeDropSyntax = [NSArrayObjectAtIndex(item, 3) boolValue];
+ }
+ }
+
+ // Add the 'DROP' command if required
+ if (sqlOutputIncludeDropSyntax) {
+ [metaString appendString:[NSString stringWithFormat:@"/*!50003 DROP %@ IF EXISTS %@ */;;\n", procedureType,
+ [procedureName backtickQuotedString]]];
+ }
+
+ // Only continue if the 'CREATE SYNTAX' is required
+ if (sqlOutputIncludeStructure) {
+ [proceduresList release];
+ continue;
+ }
+
+ // Definer is user@host but we need to escape it to `user`@`host`
+ NSArray *procedureDefiner = [[proceduresList objectForKey:@"Definer"] componentsSeparatedByString:@"@"];
+
+ NSString *escapedDefiner = [NSString stringWithFormat:@"%@@%@",
+ [NSArrayObjectAtIndex(procedureDefiner, 0) backtickQuotedString],
+ [NSArrayObjectAtIndex(procedureDefiner, 1) backtickQuotedString]
+ ];
+
+ MCPResult *createProcedureResult = [connection queryString:[NSString stringWithFormat:@"/*!50003 SHOW CREATE %@ %@ */;;", procedureType,
+ [procedureName backtickQuotedString]]];
+
+ [createProcedureResult setReturnDataAsStrings:YES];
+
+ NSDictionary *procedureInfo = [[NSDictionary alloc] initWithDictionary:[createProcedureResult fetchRowAsDictionary]];
+
+ [metaString appendString:[NSString stringWithFormat:@"/*!50003 SET SESSION SQL_MODE=\"%@\"*/;;\n", [procedureInfo objectForKey:@"sql_mode"]]];
+
+ NSString *createProcedure = [procedureInfo objectForKey:[NSString stringWithFormat:@"Create %@", [procedureType capitalizedString]]];
+ NSRange procedureRange = [createProcedure rangeOfString:procedureType options:NSCaseInsensitiveSearch];
+ NSString *procedureBody = [createProcedure substringFromIndex:procedureRange.location];
+
+ // /*!50003 CREATE*/ /*!50020 DEFINER=`sequelpro`@`%`*/ /*!50003 PROCEDURE `p`()
+ // BEGIN
+ // /* This procedure does nothing */
+ // END */;;
+ //
+ // Build the CREATE PROCEDURE string to include MySQL Version limiters
+ [metaString appendString:[NSString stringWithFormat:@"/*!50003 CREATE*/ /*!50020 DEFINER=%@*/ /*!50003 %@ */;;\n", escapedDefiner, procedureBody]];
+
+ [procedureInfo release];
+ [proceduresList release];
+
+ [metaString appendString:@"/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;;\n"];
+ }
+
+ [metaString appendString:@"DELIMITER ;\n"];
+
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+
+ if ([connection queryErrored]) {
+ [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]];
+
+ if ([self sqlOutputIncludeErrors]) {
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+ }
+
+ }
+
+ // Restore unique checks, foreign key checks, and other settings saved at the start
+ [metaString setString:@"\n"];
+ [metaString appendString:@"/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n"];
+ [metaString appendString:@"/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n"];
+ [metaString appendString:@"/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n"];
+
+ //if (sqlOutputIncludeDropSyntax) {
+ //[metaString appendString:@"/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n"];
+ //}
+
+ // Restore the client encoding to the original encoding before import
+ [metaString appendString:@"/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n"];
+ [metaString appendString:@"/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n"];
+ [metaString appendString:@"/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"];
+
+ // Write footer-type information to the file
+ [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]];
+
+ // Set export errors
+ [self setSqlExportErrors:errors];
+
+ [errors release];
+ [sqlString release];
+
+ // Close the file
+ [[self exportOutputFileHandle] closeFile];
+
+ // Mark the process as not running
+ [self setExportProcessIsRunning:NO];
+
+ // Inform the delegate that the export process is complete
+ [delegate performSelectorOnMainThread:@selector(sqlExportProcessComplete:) withObject:self waitUntilDone:NO];
[pool release];
}
- @catch (NSException *e) {
+ @catch (NSException *e) {}
+}
+
+/**
+ * Returns whether or not any export errors occurred by examing the length of the errors string.
+ */
+- (BOOL)didExportErrorsOccur
+{
+ return [[self sqlExportErrors] length];
+}
+
+/**
+ * Retrieve information for a view and use that to construct a CREATE TABLE string for an equivalent basic
+ * table. Allows the construction of placeholder tables to resolve view interdependencies in dumps.
+ */
+- (NSString *)_createViewPlaceholderSyntaxForView:(NSString *)viewName
+{
+ NSInteger i, j;
+ NSMutableString *placeholderSyntax;
+
+ // Get structured information for the view via the SPTableData parsers
+ NSDictionary *viewInformation = [[self sqlTableInformation] objectForKey:viewName];
+
+ if (!viewInformation) return nil;
+
+ NSArray *viewColumns = [viewInformation objectForKey:@"columns"];
+
+ // Set up the start of the placeholder string and initialise an empty field string
+ placeholderSyntax = [[NSMutableString alloc] initWithFormat:@"CREATE TABLE %@ (\n", [viewName backtickQuotedString]];
+
+ NSMutableString *fieldString = [[NSMutableString alloc] init];
+
+ // Loop through the columns, creating an appropriate column definition for each and appending it to the syntax string
+ for (i = 0; i < [viewColumns count]; i++)
+ {
+ NSDictionary *column = NSArrayObjectAtIndex(viewColumns, i);
+
+ [fieldString setString:[[column objectForKey:@"name"] backtickQuotedString]];
+
+ // Add the type and length information as appropriate
+ if ([column objectForKey:@"length"]) {
+ [fieldString appendFormat:@" %@(%@)", [column objectForKey:@"type"], [column objectForKey:@"length"]];
+ }
+ else if ([column objectForKey:@"values"]) {
+ [fieldString appendFormat:@" %@(", [column objectForKey:@"type"]];
+
+ for (j = 0; j < [[column objectForKey:@"values"] count]; j++)
+ {
+ [fieldString appendFormat:@"'%@'%@", [connection prepareString:NSArrayObjectAtIndex([column objectForKey:@"values"], j)], ((j + 1) == [[column objectForKey:@"values"] count]) ? @"" : @","];
+ }
+
+ [fieldString appendString:@")"];
+ }
+ else {
+ [fieldString appendFormat:@" %@", [column objectForKey:@"type"]];
+ }
+
+ // Field specification details
+ if ([[column objectForKey:@"unsigned"] integerValue] == 1) [fieldString appendString:@" UNSIGNED"];
+ if ([[column objectForKey:@"zerofill"] integerValue] == 1) [fieldString appendString:@" ZEROFILL"];
+ if ([[column objectForKey:@"binary"] integerValue] == 1) [fieldString appendString:@" BINARY"];
+ if ([[column objectForKey:@"null"] integerValue] == 0) [fieldString appendString:@" NOT NULL"];
+ // Provide the field default if appropriate
+ if ([column objectForKey:@"default"]) {
+
+ // Some MySQL server versions show a default of NULL for NOT NULL columns - don't export those
+ if ([column objectForKey:@"default"] == [NSNull null]) {
+ if ([[column objectForKey:@"null"] integerValue]) {
+ [fieldString appendString:@" DEFAULT NULL"];
+ }
+ }
+ else if ([[column objectForKey:@"type"] isEqualToString:@"TIMESTAMP"] && [column objectForKey:@"default"] != [NSNull null] && [[[column objectForKey:@"default"] uppercaseString] isEqualToString:@"CURRENT_TIMESTAMP"]) {
+ [fieldString appendString:@" DEFAULT CURRENT_TIMESTAMP"];
+ }
+ else {
+ [fieldString appendFormat:@" DEFAULT '%@'", [connection prepareString:[column objectForKey:@"default"]]];
+ }
+ }
+
+ // Extras aren't required for the temp table
+ // Add the field string to the syntax string
+ [placeholderSyntax appendFormat:@" %@%@\n", fieldString, (i == [viewColumns count] - 1) ? @"" : @","];
}
+
+ // Append the remainder of the table string
+ [placeholderSyntax appendString:@") ENGINE=MyISAM;"];
+
+ // Clean up and return
+ [fieldString release];
+
+ return [placeholderSyntax autorelease];
+}
+
+/**
+ * Dealloc
+ */
+- (void)dealloc
+{
+ [sqlExportTables release], sqlExportTables = nil;
+ [sqlDatabaseHost release], sqlDatabaseHost = nil;
+ [sqlDatabaseName release], sqlDatabaseName = nil;
+ [sqlExportCurrentTable release], sqlExportCurrentTable = nil;
+ [sqlDatabaseVersion release], sqlDatabaseVersion = nil;
+ [sqlTableInformation release], sqlTableInformation = nil;
+
+ [super dealloc];
}
@end
diff --git a/Source/SPSQLExporterDelegate.h b/Source/SPSQLExporterDelegate.h
new file mode 100644
index 00000000..1a439cd2
--- /dev/null
+++ b/Source/SPSQLExporterDelegate.h
@@ -0,0 +1,38 @@
+//
+// $Id$
+//
+// SPSQLExporterDelegate.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on March 28, 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 "SPExportController.h"
+#import "SPSQLExporterProtocol.h"
+
+/**
+ * @category SPSQLExporterDelegate SPSQLExporterDelegate.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * SQL exporter delegate category.
+ */
+@interface SPExportController (SPSQLExporterDelegate) <SPSQLExporterProtocol>
+
+@end
diff --git a/Source/SPSQLExporterDelegate.m b/Source/SPSQLExporterDelegate.m
new file mode 100644
index 00000000..b9afe724
--- /dev/null
+++ b/Source/SPSQLExporterDelegate.m
@@ -0,0 +1,105 @@
+//
+// $Id$
+//
+// SPSQLExporterDelegate.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on March 28, 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 "SPSQLExporterDelegate.h"
+#import "SPSQLExporter.h"
+#import "TableDocument.h"
+#import "SPMainThreadTrampoline.h"
+
+@implementation SPExportController (SPSQLExporterDelegate)
+
+/**
+ *
+ */
+- (void)sqlExportProcessWillBegin:(SPSQLExporter *)exporter
+{
+ [[exportProgressTitle onMainThread] setStringValue:NSLocalizedString(@"Exporting SQL", @"text showing that the application is exporting SQL")];
+ [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Dumping...", @"text showing that app is writing dump")];
+
+ [[exportProgressTitle onMainThread] displayIfNeeded];
+ [[exportProgressText onMainThread] displayIfNeeded];
+}
+
+/**
+ *
+ */
+- (void)sqlExportProcessComplete:(SPSQLExporter *)exporter
+{
+ [NSApp endSheet:exportProgressWindow returnCode:0];
+ [exportProgressWindow orderOut:self];
+
+ [tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
+
+ // Restore the connection encoding to it's pre-export value
+ [tableDocumentInstance setConnectionEncoding:[NSString stringWithFormat:@"%@%@", sqlPreviousConnectionEncoding, (sqlPreviousConnectionEncodingViaLatin1) ? @"-" : @""] reloadingViews:NO];
+
+ // Display Growl notification
+ [self displayExportFinishedGrowlNotification];
+
+ // Check for errors and display the errors sheet if necessary
+ if ([exporter didExportErrorsOccur]) {
+ [self openExportErrorsSheetWithString:[exporter sqlExportErrors]];
+ }
+}
+
+/**
+ *
+ */
+- (void)sqlExportProcessProgressUpdated:(SPSQLExporter *)exporter
+{
+ //NSLog(@"updating: %f", [exporter exportProgressValue]);
+
+ [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]];
+}
+
+/**
+ *
+ */
+- (void)sqlExportProcessWillBeginFetchingData:(SPSQLExporter *)exporter
+{
+ // Update the current table export index
+ currentTableExportIndex = (exportTableCount - [exporters count]);
+
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter sqlExportCurrentTable]]];
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+
+ [[exportProgressIndicator onMainThread] stopAnimation:self];
+ [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:NO];
+ [[exportProgressIndicator onMainThread] setIndeterminate:NO];
+ [[exportProgressIndicator onMainThread] setDoubleValue:0];
+}
+
+/**
+ *
+ */
+- (void)sqlExportProcessWillBeginWritingData:(SPSQLExporter *)exporter
+{
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter sqlExportCurrentTable]]];
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+}
+
+@end
diff --git a/Source/SPSQLExporterProtocol.h b/Source/SPSQLExporterProtocol.h
new file mode 100644
index 00000000..2998e1c9
--- /dev/null
+++ b/Source/SPSQLExporterProtocol.h
@@ -0,0 +1,72 @@
+//
+// $Id$
+//
+// SPSQLExporterProtocol.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 15, 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/>
+
+@class SPSQLExporter;
+
+/**
+ * @protocol SPSQLExporterProtocol SPSQLExporterProtocol.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * SQL exporter delegate protocol.
+ */
+@protocol SPSQLExporterProtocol
+
+/**
+ * Called when the SQL export process is about to begin.
+ *
+ * @param SPSQLExporter The expoter calling the method.
+ */
+- (void)sqlExportProcessWillBegin:(SPSQLExporter *)exporter;
+
+/**
+ * Called when the SQL export process is complete.
+ *
+ * @param SPSQLExporter The expoter calling the method.
+ */
+- (void)sqlExportProcessComplete:(SPSQLExporter *)exporter;
+
+/**
+ * alled when the progress of the SQL export process is updated.
+ *
+ * @param SPSQLExporter The expoter calling the method.
+ */
+- (void)sqlExportProcessProgressUpdated:(SPSQLExporter *)exporter;
+
+/**
+ * Called when the SQL export process is about to begin fetching data from the database.
+ *
+ * @param SPSQLExporter The expoter calling the method.
+ */
+- (void)sqlExportProcessWillBeginFetchingData:(SPSQLExporter *)exporter;
+
+/**
+ * Called when the SQL export process is about to begin writing data to disk.
+ *
+ * @param SPSQLExporter The expoter calling the method.
+ */
+- (void)sqlExportProcessWillBeginWritingData:(SPSQLExporter *)exporter;
+
+@end
diff --git a/Source/SPStringAdditions.h b/Source/SPStringAdditions.h
index 3f7c9066..1bcf158f 100644
--- a/Source/SPStringAdditions.h
+++ b/Source/SPStringAdditions.h
@@ -59,14 +59,15 @@ static inline id NSMutableAttributedStringAttributeAtIndex (NSMutableAttributedS
+ (NSString *)stringForByteSize:(long long)byteSize;
+ (NSString *)stringForTimeInterval:(CGFloat)timeInterval;
+- (NSString *)HTMLEscapeString;
- (NSString *)backtickQuotedString;
- (NSString *)tickQuotedString;
- (NSString *)replaceUnderscoreWithSpace;
- (NSArray *)lineRangesForRange:(NSRange)aRange;
- (NSString *)createViewSyntaxPrettifier;
-- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*) charSet options:(NSUInteger) mask;
-- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*) charSet;
+- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)charSet options:(NSUInteger)mask;
+- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*)charSet;
- (CGFloat)levenshteinDistanceWithWord:(NSString *)stringB;
diff --git a/Source/SPStringAdditions.m b/Source/SPStringAdditions.m
index c36e55c1..de1fdf5e 100644
--- a/Source/SPStringAdditions.m
+++ b/Source/SPStringAdditions.m
@@ -82,12 +82,9 @@
return [numberFormatter stringFromNumber:[NSNumber numberWithDouble:size]];
}
-
-// -------------------------------------------------------------------------------
-// stringForTimeInterval:
-//
-// Returns a human readable version string of the supplied time interval.
-// -------------------------------------------------------------------------------
+/**
+ * Returns a human readable version string of the supplied time interval.
+ */
+ (NSString *)stringForTimeInterval:(CGFloat)timeInterval
{
NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
@@ -145,42 +142,64 @@
return [numberFormatter stringFromNumber:[NSNumber numberWithDouble:timeInterval]];
}
+/**
+ * Escapes HTML special characters.
+ */
+- (NSString *)HTMLEscapeString
+{
+ NSMutableString *mutableString = [NSMutableString stringWithString:self];
+
+ [mutableString replaceOccurrencesOfString:@"&" withString:@"&amp;"
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [mutableString length])];
+
+ [mutableString replaceOccurrencesOfString:@"<" withString:@"&lt;"
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [mutableString length])];
+
+ [mutableString replaceOccurrencesOfString:@">" withString:@"&gt;"
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [mutableString length])];
+
+ [mutableString replaceOccurrencesOfString:@"\"" withString:@"&quot;"
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [mutableString length])];
+
+ return [NSString stringWithString:mutableString];
+}
-// -------------------------------------------------------------------------------
-// backtickQuotedString
-//
-// Returns the string quoted with backticks as required for MySQL identifiers
-// eg.: tablename => `tablename`
-// my`table => `my``table`
-// -------------------------------------------------------------------------------
+/**
+ * Returns the string quoted with backticks as required for MySQL identifiers
+ * eg.: tablename => `tablename`
+ * my`table => `my``table`
+ */
- (NSString *)backtickQuotedString
{
return [NSString stringWithFormat: @"`%@`", [self stringByReplacingOccurrencesOfString:@"`" withString:@"``"]];
}
-// -------------------------------------------------------------------------------
-// tickQuotedString
-//
-// Returns the string quoted with ticks as required for MySQL identifiers
-// eg.: tablename => 'tablename'
-// my'table => 'my''table'
-// -------------------------------------------------------------------------------
+/**
+ * Returns the string quoted with ticks as required for MySQL identifiers
+ * eg.: tablename => 'tablename'
+ * my'table => 'my''table'
+ */
- (NSString *)tickQuotedString
{
return [NSString stringWithFormat: @"'%@'", [self stringByReplacingOccurrencesOfString:@"'" withString:@"''"]];
}
+/**
+ *
+ */
- (NSString *)replaceUnderscoreWithSpace
{
return [self stringByReplacingOccurrencesOfString:@"_" withString:@" "];
}
-// -------------------------------------------------------------------------------
-// createViewSyntaxPrettifier
-//
-// Returns a 'CREATE VIEW SYNTAX' string a bit more readable
-// If the string doesn't match it returns the unchanged string.
-// -------------------------------------------------------------------------------
+/**
+ * Returns a 'CREATE VIEW SYNTAX' string a bit more readable
+ * If the string doesn't match it returns the unchanged string.
+ */
- (NSString *)createViewSyntaxPrettifier
{
NSRange searchRange = NSMakeRange(0, [self length]);
@@ -219,16 +238,13 @@
return(tblSyntax);
}
-
-// -------------------------------------------------------------------------------
-// lineRangesForRange
-//
-// Returns an array of serialised NSRanges, each representing a line within the string
-// which is at least partially covered by the NSRange supplied.
-// Each line includes the line termination character(s) for the line. As per
-// lineRangeForRange, lines are split by CR, LF, CRLF, U+2028 (Unicode line separator),
-// or U+2029 (Unicode paragraph separator).
-// -------------------------------------------------------------------------------
+/**
+ * Returns an array of serialised NSRanges, each representing a line within the string
+ * which is at least partially covered by the NSRange supplied.
+ * Each line includes the line termination character(s) for the line. As per
+ * lineRangeForRange, lines are split by CR, LF, CRLF, U+2028 (Unicode line separator),
+ * or U+2029 (Unicode paragraph separator).
+ */
- (NSArray *)lineRangesForRange:(NSRange)aRange
{
NSMutableArray *lineRangesArray = [NSMutableArray array];
@@ -252,7 +268,6 @@
return lineRangesArray;
}
-
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
/*
* componentsSeparatedByCharactersInSet:
@@ -286,8 +301,10 @@
}
#endif
-
-- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*) charSet options:(NSUInteger) mask
+/**
+ *
+ */
+- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet options:(NSUInteger)mask
{
NSRange range;
NSMutableString* newString = [NSMutableString string];
@@ -316,13 +333,17 @@
return newString;
}
-
-- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet*) charSet
+/**
+ *
+ */
+- (NSString *)stringByRemovingCharactersInSet:(NSCharacterSet *)charSet
{
return [self stringByRemovingCharactersInSet:charSet options:0];
}
-// calculate the distance between two string case-insensitively
+/**
+ * Calculate the distance between two string case-insensitively
+ */
- (CGFloat)levenshteinDistanceWithWord:(NSString *)stringB
{
// normalize strings
@@ -368,10 +389,13 @@
return distance;
}
+
return 0.0;
}
-// return the minimum of a, b and c
+/**
+ * Returns the minimum of a, b and c
+ */
- (NSInteger)smallestOf:(NSInteger)a andOf:(NSInteger)b andOf:(NSInteger)c
{
NSInteger min = a;
diff --git a/Source/SPXMLExporter.h b/Source/SPXMLExporter.h
index dba81cba..a9e6c2dd 100644
--- a/Source/SPXMLExporter.h
+++ b/Source/SPXMLExporter.h
@@ -26,7 +26,45 @@
#import <Cocoa/Cocoa.h>
#import "SPExporter.h"
+#import "SPXMLExporterProtocol.h"
+/**
+ * @class SPXMLExporter SPXMLExporter.m
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * XML exporter class.
+ */
@interface SPXMLExporter : SPExporter
+{
+ /**
+ * Exporter delegate
+ */
+ NSObject <SPXMLExporterProtocol> *delegate;
+
+ /**
+ * Data array
+ */
+ NSArray *xmlDataArray;
+
+ /**
+ * Table name
+ */
+ NSString *xmlTableName;
+}
+
+@property(readwrite, assign) NSObject *delegate;
+
+@property(readwrite, retain) NSArray *xmlDataArray;
+@property(readwrite, retain) NSString *xmlTableName;
+
+/**
+ * Initialise an instance of SPXMLExporter using the supplied delegate.
+ *
+ * @param exportDelegate The exporter delegate
+ *
+ * @return The initialised instance
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate;
@end
diff --git a/Source/SPXMLExporter.m b/Source/SPXMLExporter.m
index dabfe263..94cfec05 100644
--- a/Source/SPXMLExporter.m
+++ b/Source/SPXMLExporter.m
@@ -23,26 +23,236 @@
//
// More info at <http://code.google.com/p/sequel-pro/>
+#import <MCPKit/MCPKit.h>
+
#import "SPXMLExporter.h"
+#import "SPArrayAdditions.h"
+#import "SPStringAdditions.h"
+#import "SPFileHandle.h"
+#import "SPConstants.h"
+#import "SPExportUtilities.h"
@implementation SPXMLExporter
+@synthesize delegate;
+@synthesize xmlDataArray;
+@synthesize xmlTableName;
+
+/**
+ * Initialise an instance of SPXMLExporter using the supplied delegate.
+ */
+- (id)initWithDelegate:(NSObject *)exportDelegate
+{
+ if ((self = [super init])) {
+ SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPXMLExporterProtocol));
+
+ [self setDelegate:exportDelegate];
+ }
+
+ return self;
+}
+
/**
- * Start the SQL data conversion process. This method is automatically called when an instance of this object
+ * Start the XML 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
{
@try {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSAutoreleasePool *xmlExportPool = [[NSAutoreleasePool alloc] init];
+ NSArray *xmlRow = nil;
+ NSString *dataConversionString = nil;
+ MCPStreamingResult *streamingResult = nil;
+ NSMutableArray *xmlTags = [NSMutableArray array];
+ NSMutableString *xmlString = [NSMutableString string];
+ NSMutableString *xmlItem = [NSMutableString string];
- [pool release];
- }
- @catch (NSException *e) {
+ NSUInteger xmlRowCount = 0;
+ NSUInteger i, totalRows, currentRowIndex, lastProgressValue, currentPoolDataLength;
+
+ // Check to see if we have at least a table name or data array
+ if ((![self xmlTableName]) && (![self xmlDataArray]) ||
+ ([[self xmlTableName] isEqualToString:@""]) && ([[self xmlDataArray] count] == 0))
+ {
+ [pool release];
+ return;
+ }
+
+ // Inform the delegate that the export process is about to begin
+ [delegate performSelectorOnMainThread:@selector(xmlExportProcessWillBegin:) 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 xmlDataArray]) && [self xmlTableName]) {
+ totalRows = [[[[connection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [[self xmlTableName] backtickQuotedString]]] fetchRowAsArray] objectAtIndex:0] integerValue];
+ streamingResult = [connection streamingQueryString:[NSString stringWithFormat:@"SELECT * FROM %@", [[self xmlTableName] backtickQuotedString]] useLowMemoryBlockingStreaming:[self exportUsingLowMemoryBlockingStreaming]];
+ }
+ else {
+ totalRows = [[self xmlDataArray] count];
+ }
+
+ // Set up an array of encoded field names as opening and closing tags
+ xmlRow = ([self xmlDataArray]) ? [[self xmlDataArray] objectAtIndex:0] : [streamingResult fetchFieldNames];
+
+ for (i = 0; i < [xmlRow count]; i++)
+ {
+ [xmlTags addObject:[NSMutableArray array]];
+
+ [[xmlTags objectAtIndex:i] addObject:[NSString stringWithFormat:@"\t\t<%@>", [[[xmlRow objectAtIndex:i] description] HTMLEscapeString]]];
+ [[xmlTags objectAtIndex:i] addObject:[NSString stringWithFormat:@"</%@>\n", [[[xmlRow objectAtIndex:i] description] HTMLEscapeString]]];
+ }
+ [[self exportOutputFileHandle] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]];
+
+ // Write an opening tag in the form of the table name
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"\t<%@>\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]];
+
+ // Set up the starting row, which is 0 for streaming result sets and
+ // 1 for supplied arrays which include the column headers as the first row.
+ currentRowIndex = 0;
+
+ if ([self xmlDataArray]) currentRowIndex++;
+
+ // Drop into the processing loop
+ xmlExportPool = [[NSAutoreleasePool alloc] init];
+
+ currentPoolDataLength = 0;
+
+ while (1)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ if (streamingResult) {
+ [connection cancelCurrentQuery];
+ [streamingResult cancelResultLoad];
+ }
+
+ [xmlExportPool release];
+ [pool release];
+
+ return;
+ }
+
+ // Retrieve the next row from the supplied data, either directly from the array...
+ if ([self xmlDataArray]) {
+ xmlRow = NSArrayObjectAtIndex([self xmlDataArray], currentRowIndex);
+ }
+ // Or by reading an appropriate row from the streaming result
+ else {
+ xmlRow = [streamingResult fetchNextRowAsArray];
+
+ if (!xmlRow) break;
+ }
+
+ // Get the cell count if we don't already have it stored
+ if (!xmlRowCount) xmlRowCount = [xmlRow count];
+
+ // Construct the row
+ [xmlString setString:@"\t<row>\n"];
+
+ for (i = 0; i < xmlRowCount; i++)
+ {
+ // Check for cancellation flag
+ if ([self isCancelled]) {
+ if (streamingResult) {
+ [connection cancelCurrentQuery];
+ [streamingResult cancelResultLoad];
+ }
+
+ [xmlExportPool release];
+ [pool release];
+
+ return;
+ }
+
+ // Retrieve the contents of this tag
+ if ([NSArrayObjectAtIndex(xmlRow, i) isKindOfClass:[NSData class]]) {
+ dataConversionString = [[NSString alloc] initWithData:NSArrayObjectAtIndex(xmlRow, i) encoding:[self exportOutputEncoding]];
+
+ if (dataConversionString == nil) {
+ dataConversionString = [[NSString alloc] initWithData:NSArrayObjectAtIndex(xmlRow, i) encoding:NSASCIIStringEncoding];
+ }
+
+ [xmlItem setString:[NSString stringWithString:dataConversionString]];
+ [dataConversionString release];
+ }
+ else {
+ [xmlItem setString:[NSArrayObjectAtIndex(xmlRow, i) description]];
+ }
+
+ // Add the opening and closing tag and the contents to the XML string
+ [xmlString appendString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(xmlTags, i), 0)];
+ [xmlString appendString:[xmlItem HTMLEscapeString]];
+ [xmlString appendString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(xmlTags, i), 1)];
+ }
+
+ [xmlString appendString:@"\t</row>\n"];
+
+ // Record the total length for use with pool flushing
+ currentPoolDataLength += [xmlString length];
+
+ // Write the row to the filehandle
+ [[self exportOutputFileHandle] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]];
+
+ // Update the progress counter and progress bar
+ currentRowIndex++;
+
+ // 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(xmlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO];
+
+ // If an array was supplied and we've processed all rows, break
+ if ([self xmlDataArray] && totalRows == currentRowIndex) break;
+
+ // Drain the autorelease pool as required to keep memory usage low
+ if (currentPoolDataLength > 250000) {
+ [xmlExportPool release];
+ xmlExportPool = [[NSAutoreleasePool alloc] init];
+ }
+ }
+
+ // Write the closing tag for the table
+ [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]];
+
+ // Write data to disk
+ [[self exportOutputFileHandle] synchronizeFile];
+
+ // Mark the process as not running
+ [self setExportProcessIsRunning:NO];
+
+ // Inform the delegate that the export process is complete
+ [delegate performSelectorOnMainThread:@selector(xmlExportProcessComplete:) withObject:self waitUntilDone:NO];
+
+ [pool release];
}
+ @catch (NSException *e) { }
+}
+
+/**
+ * Dealloc
+ */
+- (void)dealloc
+{
+ if (xmlDataArray) [xmlDataArray release], xmlDataArray = nil;
+ if (xmlTableName) [xmlTableName release], xmlTableName = nil;
+
+ [super dealloc];
}
@end
diff --git a/Source/SPXMLExporterDelegate.h b/Source/SPXMLExporterDelegate.h
new file mode 100644
index 00000000..720a8c9b
--- /dev/null
+++ b/Source/SPXMLExporterDelegate.h
@@ -0,0 +1,38 @@
+//
+// $Id$
+//
+// SPXMLExporterDelegate.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 6, 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 "SPExportController.h"
+#import "SPXMLExporterProtocol.h"
+
+/**
+ * @category SPXMLExporterDelegate SPXMLExporterDelegate.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * XML exporter delegate category.
+ */
+@interface SPExportController (SPXMLExporterDelegate) <SPXMLExporterProtocol>
+
+@end
diff --git a/Source/SPXMLExporterDelegate.m b/Source/SPXMLExporterDelegate.m
new file mode 100644
index 00000000..b8edaaf9
--- /dev/null
+++ b/Source/SPXMLExporterDelegate.m
@@ -0,0 +1,133 @@
+//
+// $Id$
+//
+// SPXMLExporterDelegate.m
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 6, 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 "SPXMLExporterDelegate.h"
+#import "SPXMLExporter.h"
+#import "SPMainThreadTrampoline.h"
+#import "TableDocument.h"
+#import "SPFileHandle.h"
+#import "SPStringAdditions.h"
+
+@implementation SPExportController (SPXMLExporterDelegate)
+
+/**
+ *
+ */
+- (void)xmlExportProcessWillBegin:(SPXMLExporter *)exporter
+{
+ [[exportProgressText onMainThread] displayIfNeeded];
+
+ [[exportProgressIndicator onMainThread] setIndeterminate:YES];
+ [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:YES];
+ [[exportProgressIndicator onMainThread] startAnimation:self];
+
+ // Only update the progress text if this is a table export
+ if (exportSource == SPTableExport) {
+ // Update the current table export index
+ currentTableExportIndex = (exportTableCount - [exporters count]);
+
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Fetching data...", @"export label showing that the app is fetching data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]];
+ }
+ else {
+ [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Fetching data...", @"export label showing that the app is fetching data")];
+ }
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+}
+
+/**
+ *
+ */
+- (void)xmlExportProcessComplete:(SPXMLExporter *)exporter
+{
+ NSUInteger exportCount = [exporters count];
+
+ // If required add the next exporter to the operation queue
+ if ((exportCount > 0) && (exportSource == SPTableExport)) {
+
+ // If we're exporting to multiple files then close the file handle of the exporter
+ // that just finished, ensuring its data is written to disk.
+ if (exportToMultipleFiles) {
+ [[exporter exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection encoding]]];
+
+ [[exporter exportOutputFileHandle] closeFile];
+ }
+
+ [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];
+ }
+ // Otherwise if the exporter list is empty, close the progress sheet
+ else {
+ if (exportSource == SPTableExport) {
+ [[exporter exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection encoding]]];
+ }
+
+ // Close the last exporter's file handle
+ [[exporter exportOutputFileHandle] closeFile];
+
+ [NSApp endSheet:exportProgressWindow returnCode:0];
+ [exportProgressWindow orderOut:self];
+
+ // Restore query mode
+ [tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
+
+ // Display Growl notification
+ [self displayExportFinishedGrowlNotification];
+ }
+}
+
+/**
+ *
+ */
+- (void)xmlExportProcessProgressUpdated:(SPXMLExporter *)exporter
+{
+ // Only update the progress text if this is a table export
+ if (exportSource == SPTableExport) {
+ [[exportProgressText onMainThread] setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing data...", @"export label showing app if writing data for a specific table"), currentTableExportIndex, exportTableCount, [exporter xmlTableName]]];
+ }
+ else {
+ [[exportProgressText onMainThread] setStringValue:NSLocalizedString(@"Writing data...", @"export label showing app is writing data")];
+ }
+
+ [[exportProgressText onMainThread] displayIfNeeded];
+
+ [[exportProgressIndicator onMainThread] stopAnimation:self];
+ [[exportProgressIndicator onMainThread] setUsesThreadedAnimation:NO];
+ [[exportProgressIndicator onMainThread] setIndeterminate:NO];
+ [[exportProgressIndicator onMainThread] setDoubleValue:0];
+}
+
+/**
+ *
+ */
+- (void)xmlExportProcessWillBeginWritingData:(SPXMLExporter *)exporter
+{
+ [exportProgressIndicator setDoubleValue:[exporter exportProgressValue]];
+}
+
+@end
diff --git a/Source/SPXMLExporterProtocol.h b/Source/SPXMLExporterProtocol.h
new file mode 100644
index 00000000..cac692c7
--- /dev/null
+++ b/Source/SPXMLExporterProtocol.h
@@ -0,0 +1,65 @@
+//
+// $Id$
+//
+// SPXMLExporterProtocol.h
+// sequel-pro
+//
+// Created by Stuart Connolly (stuconnolly.com) on April 15, 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/>
+
+@class SPXMLExporter;
+
+/**
+ * @protocol SPXMLExporterProtocol SPXMLExporterProtocol.h
+ *
+ * @author Stuart Connolly http://stuconnolly.com/
+ *
+ * SQL exporter delegate protocol.
+ */
+@protocol SPXMLExporterProtocol
+
+/**
+ * Called when the XML export process is about to begin.
+ *
+ * @param SPXMLExporter The expoter calling the method.
+ */
+- (void)xmlExportProcessWillBegin:(SPXMLExporter *)exporter;
+
+/**
+ * Called when the XML export process is complete.
+ *
+ * @param SPXMLExporter The expoter calling the method.
+ */
+- (void)xmlExportProcessComplete:(SPXMLExporter *)exporter;
+
+/**
+ * Called when the progress of the XML export process is updated.
+ *
+ * @param SPXMLExporter The expoter calling the method.
+ */
+- (void)xmlExportProcessProgressUpdated:(SPXMLExporter *)exporter;
+
+/**
+ * Called when the XML export process is about to begin writing data to disk.
+ *
+ * @param SPXMLExporter The expoter calling the method.
+ */
+- (void)xmlExportProcessWillBeginWritingData:(SPXMLExporter *)exporter;
+
+@end
diff --git a/Source/TableDocument.h b/Source/TableDocument.h
index b8478f98..5b6697a6 100644
--- a/Source/TableDocument.h
+++ b/Source/TableDocument.h
@@ -256,6 +256,7 @@
- (IBAction)copyCreateTableSyntaxFromSheet:(id)sender;
- (IBAction)focusOnTableContentFilter:(id)sender;
- (IBAction)focusOnTableListFilter:(id)sender;
+- (IBAction)exportSelectedTablesAs:(id)sender;
// Other methods
- (void) setQueryMode:(NSInteger)theQueryMode;
diff --git a/Source/TableDocument.m b/Source/TableDocument.m
index d0637eed..ce87a1ef 100644
--- a/Source/TableDocument.m
+++ b/Source/TableDocument.m
@@ -2283,6 +2283,14 @@
[tablesListInstance performSelector:@selector(makeTableListFilterHaveFocus) withObject:nil afterDelay:0.1];
}
+/**
+ * Exports the selected tables in the chosen file format.
+ */
+- (IBAction)exportSelectedTablesAs:(id)sender
+{
+ [exportControllerInstance exportTables:[tablesListInstance selectedTableItems] asFormat:[sender tag]];
+}
+
#pragma mark -
#pragma mark Other Methods
@@ -3159,9 +3167,18 @@
return YES;
}
}
+
+ // Data export
+ if ([menuItem action] == @selector(export:)) {
+ return (([self database] != nil) && ([[tablesListInstance tables] count] > 1));
+ }
+
+ // Selected tables data export
+ if ([menuItem action] == @selector(exportSelectedTablesAs:)) {
+ return (([self database] != nil) && ([[[tablesListInstance valueForKeyPath:@"tablesListView"] selectedRowIndexes] count]));
+ }
if ([menuItem action] == @selector(import:) ||
- [menuItem action] == @selector(export:) ||
[menuItem action] == @selector(exportMultipleTables:) ||
[menuItem action] == @selector(removeDatabase:) ||
[menuItem action] == @selector(copyDatabase:) ||