diff options
author | stuconnolly <stuart02@gmail.com> | 2010-08-12 12:56:20 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2010-08-12 12:56:20 +0000 |
commit | fc02f913371d522a025c47824fafeba8e3174da1 (patch) | |
tree | 086561fd418bf35139dda47d698d7923196a4f30 | |
parent | 50f15c41936be3fb62384834bb3629e09018e67b (diff) | |
download | sequelpro-fc02f913371d522a025c47824fafeba8e3174da1.tar.gz sequelpro-fc02f913371d522a025c47824fafeba8e3174da1.tar.bz2 sequelpro-fc02f913371d522a025c47824fafeba8e3174da1.zip |
Various export enhancements and fixes, including:
- A new SPExportFile class, providing an abstract interface to the handling and creation of export files.
- Enables the centralisation of all file/file handle creation logic as well as better support for handling situations where files fail to be created, including files that already exist at the export location.
- New SPExportFileHandleStatus constants to support the reporting of file handle creation.
- Update SPExporter to use the new file class instead of directly using an instance of SPFileHandle.
- Add the necessary logic to deal with files that already exist on disk, by providing the user with 3 options: cancel the export, ignore the files in question or overwrite them. We might want to enhance this to make new files sequential in name to prevent overwriting. Fixes issue #742.
- New SPExportFileUtilities category, which centralises all the logic relating to writing export type headers as well as dealing with problems occurred during file/file handle creation.
- Improve feedback given on the export progress sheet during export initialisation.
- Tidy up and improve comments.
30 files changed, 1203 insertions, 638 deletions
diff --git a/Source/SPCSVExporter.h b/Source/SPCSVExporter.h index f1148b7e..c01fda20 100644 --- a/Source/SPCSVExporter.h +++ b/Source/SPCSVExporter.h @@ -37,79 +37,72 @@ */ @interface SPCSVExporter : SPExporter { - /** - * Exporter delegate - */ NSObject <SPCSVExporterProtocol> *delegate; - /** - * Data array - */ NSArray *csvDataArray; - /** - * 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; - /** - * Table data - */ + BOOL csvOutputFieldNames; + SPTableData *csvTableData; } +/** + * @property delegate Exporter delegate + */ @property(readwrite, assign) NSObject <SPCSVExporterProtocol> *delegate; +/** + * @property csvDataArray Data array + */ @property(readwrite, retain) NSArray *csvDataArray; -@property(readwrite, retain) NSString *csvTableName; -@property(readwrite, assign) BOOL csvOutputFieldNames; +/** + * @property csvTableName Table name + */ +@property(readwrite, retain) NSString *csvTableName; +/** + * @property csvFieldSeparatorString CSV field separator string + */ @property(readwrite, retain) NSString *csvFieldSeparatorString; + +/** + * @property csvEnclosingCharacterString CSV enclosing character string + */ @property(readwrite, retain) NSString *csvEnclosingCharacterString; + +/** + * @property csvEscapeString CSV escape string + */ @property(readwrite, retain) NSString *csvEscapeString; + +/** + * @property csvLineEndingString CSV line ending string + */ @property(readwrite, retain) NSString *csvLineEndingString; + +/** + * @property csvNULLString CSV NULL string + */ @property(readwrite, retain) NSString *csvNULLString; -@property(readwrite, retain) SPTableData *csvTableData; +/** + * @property csvOutputFieldNames csvOutputFieldNames Output field names + */ +@property(readwrite, assign) BOOL csvOutputFieldNames; /** - * Initialise an instance of SPCSVExporter using the supplied delegate. - * - * @param exportDelegate The exporter delegate - * - * @return The initialised instance + * @property csvTableData Table data */ +@property(readwrite, retain) SPTableData *csvTableData; + - (id)initWithDelegate:(NSObject *)exportDelegate; @end diff --git a/Source/SPCSVExporter.m b/Source/SPCSVExporter.m index 40feadf9..cb0249ef 100644 --- a/Source/SPCSVExporter.m +++ b/Source/SPCSVExporter.m @@ -47,6 +47,10 @@ /** * Initialise an instance of SPCSVExporter using the supplied delegate. + * + * @param exportDelegate The exporter delegate + * + * @return The initialised instance */ - (id)initWithDelegate:(NSObject *)exportDelegate { @@ -361,7 +365,7 @@ currentPoolDataLength += [csvString length]; // Write it to the fileHandle - [[self exportOutputFileHandle] writeData:[csvString dataUsingEncoding:[self exportOutputEncoding]]]; + [[self exportOutputFile] writeData:[csvString dataUsingEncoding:[self exportOutputEncoding]]]; currentRowIndex++; @@ -389,7 +393,7 @@ } // Write data to disk - [[self exportOutputFileHandle] synchronizeFile]; + [[[self exportOutputFile] exportFileHandle] synchronizeFile]; // Mark the process as not running [self setExportProcessIsRunning:NO]; diff --git a/Source/SPCSVExporterDelegate.m b/Source/SPCSVExporterDelegate.m index 4da82285..6a0127c3 100644 --- a/Source/SPCSVExporterDelegate.m +++ b/Source/SPCSVExporterDelegate.m @@ -73,7 +73,7 @@ // 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 exportOutputFile] writeData:[[NSString stringWithFormat:@"%@%@%@ %@%@%@", [exporter csvLineEndingString], [exporter csvLineEndingString], NSLocalizedString(@"Table", @"csv export table heading"), @@ -84,7 +84,7 @@ // Otherwise close the file handle of the exporter that just finished // ensuring it's data is written to disk. else { - [[exporter exportOutputFileHandle] closeFile]; + [[exporter exportOutputFile] close]; } [operationQueue addOperation:[exporters objectAtIndex:0]]; @@ -96,7 +96,7 @@ // Otherwise if the exporter list is empty, close the progress sheet else { // Close the last exporter's file handle - [[exporter exportOutputFileHandle] closeFile]; + [[exporter exportOutputFile] close]; [NSApp endSheet:exportProgressWindow returnCode:0]; [exportProgressWindow orderOut:self]; diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 69efe326..89f35361 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -183,6 +183,14 @@ typedef enum SPBzip2Compression = 2 } SPFileCompressionFormat; +// Export file handle creation +typedef enum +{ + SPExportFileHandleCreated = 0, + SPExportFileHandleFailed = 1, + SPExportFileHandleExists = 2 +} SPExportFileHandleStatus; + // Predefined localisable URLs #define SPLOCALIZEDURL_HOMEPAGE NSLocalizedString(@"http://www.sequelpro.com/", @"Localized home page - do not localize if no translated webpage is available") #define SPLOCALIZEDURL_FAQ NSLocalizedString(@"http://www.sequelpro.com/docs/Frequently_Asked_Questions", @"Localized help page for Frequently Asked Questions - do not localize if no translated webpage is available") diff --git a/Source/SPDotExporter.h b/Source/SPDotExporter.h index 40954782..d54aa05b 100644 --- a/Source/SPDotExporter.h +++ b/Source/SPDotExporter.h @@ -37,58 +37,54 @@ */ @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 delegate Exporter delegate + */ @property(readwrite, assign) NSObject *delegate; + +/** + * @property dotExportTables Table information + */ @property(readwrite, retain) NSArray *dotExportTables; + +/** + * @property dotExportCurrentTable Current table + */ @property(readwrite, retain) NSString *dotExportCurrentTable; + +/** + * @property dotTableData Table data + */ @property(readwrite, retain) SPTableData *dotTableData; +/** + * @property dotDatabaseHost Database host + */ @property(readwrite, retain) NSString *dotDatabaseHost; + +/** + * @property dotDatabaseName Database name + */ @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 + * @property dotDatabaseVersion Database version */ +@property(readwrite, retain) NSString *dotDatabaseVersion; + - (id)initWithDelegate:(NSObject *)exportDelegate; @end diff --git a/Source/SPDotExporter.m b/Source/SPDotExporter.m index f10e1c97..a2db43af 100644 --- a/Source/SPDotExporter.m +++ b/Source/SPDotExporter.m @@ -42,6 +42,10 @@ /** * Initialise an instance of SPDotExporter using the supplied delegate. + * + * @param exportDelegate The exporter delegate + * + * @return The initialised instance */ - (id)initWithDelegate:(NSObject *)exportDelegate { @@ -97,7 +101,7 @@ [metaString appendString:@"\trankdir = LR;\n"]; // Write information to the file - [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; NSMutableArray *fkInfo = [[NSMutableArray alloc] init]; @@ -144,7 +148,7 @@ [metaString appendString:@"\t\t];\n"]; [metaString appendString:@"\t}\n"]; - [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; // see about relations columnInfo = [tableInfo objectForKey:@"constraints"]; @@ -192,10 +196,10 @@ [metaString appendString:@"}\n"]; // Write information to the file - [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; // Write data to disk - [[self exportOutputFileHandle] closeFile]; + [[self exportOutputFile] close]; // Mark the process as not running [self setExportProcessIsRunning:NO]; diff --git a/Source/SPExportController.h b/Source/SPExportController.h index 3e4d6aa7..07593e43 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -28,7 +28,7 @@ @class MCPConnection, BWAnchoredButtonBar; /** - * @class SPExportController SPExportController.m + * @class SPExportController SPExportController.h * * @author Stuart Connolly http://stuconnolly.com/ * @@ -170,9 +170,9 @@ NSMutableArray *exporters; /** - * Global export file handle + * Array of export files. */ - NSFileHandle *exportFileHandle; + NSMutableArray *exportFiles; /** * Export type @@ -208,11 +208,6 @@ * Previous connection encoding was via Latin1 */ BOOL sqlPreviousConnectionEncodingViaLatin1; - - /** - * Array of export file paths. - */ - NSMutableArray *exportFiles; NSInteger heightOffset1; NSInteger heightOffset2; diff --git a/Source/SPExportController.m b/Source/SPExportController.m index b7d26fef..11198472 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -34,6 +34,7 @@ #import "SPStringAdditions.h" #import "SPConstants.h" #import "SPGrowlController.h" +#import "SPExportFile.h" @interface SPExportController (PrivateAPI) @@ -56,7 +57,7 @@ #pragma mark Initialization /** - * Initializes an instance of SPExportController + * Initializes an instance of SPExportController. */ - (id)init { @@ -119,12 +120,6 @@ // 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()]; - - // Register to receive notifications of a change in selection of the CSV field separator - /*[[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(comboBoxSelectionDidChange:) - name:NSComboBoxSelectionDidChangeNotification - object:nil];*/ } #pragma mark - @@ -137,6 +132,8 @@ * either of these views are not active then the default source are the currently selected tables. If no * tables are currently selected then all tables are checked. Note that in this instance the default export * type is SQL where as in the case of filtered or query result export the default type is CSV. + * + * @param sender The caller (can be anything or nil as it is not currently used). */ - (IBAction)export:(id)sender { @@ -173,6 +170,10 @@ /** * Displays the export window with the supplied tables and export type/format selected. + * + * @param exportTables The array of table names to be exported + * @param format The export format to be used. See SPExportType constants. + * @param source The source of the export. See SPExportSource constants. */ - (void)exportTables:(NSArray *)exportTables asFormat:(SPExportType)format usingSource:(SPExportSource)source { @@ -184,7 +185,8 @@ [self refreshTableList:self]; - if ([exportFiles count] > 0) [exportFiles removeAllObjects]; + [exporters removeAllObjects]; + [exportFiles removeAllObjects]; // Select the 'selected tables' source option [exportInputPopUpButton selectItemAtIndex:source]; @@ -228,6 +230,8 @@ /** * Opens the errors sheet and displays the supplied errors string. + * + * @param errors The errors string to be displayed */ - (void)openExportErrorsSheetWithString:(NSString *)errors { @@ -378,13 +382,9 @@ [operationQueue cancelAllOperations]; // Loop the cached export file paths and remove them from disk if they exist - NSFileManager *fileManager = [NSFileManager defaultManager]; - - for (NSString *filePath in exportFiles) + for (SPExportFile *file in exportFiles) { - if ([fileManager fileExistsAtPath:filePath]) { - [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; - } + [file delete]; } // Close the progress sheet @@ -397,6 +397,10 @@ // Re-enable the cancel button for future exports [sender setEnabled:YES]; + + // Finally get rid of all the exporters and files + [exportFiles removeAllObjects]; + [exporters removeAllObjects]; } /** @@ -705,6 +709,8 @@ /** * Enables or disables the export button based on the state of various interface controls. + * + * @param uiStateDict A dictionary containing the state of various UI controls. */ - (void)_toggleExportButton:(id)uiStateDict { @@ -789,6 +795,8 @@ /** * Enables or disables the export button based on the supplied number (boolean). + * + * @param enable A boolean indicating the state. */ - (void)_toggleExportButtonWithBool:(NSNumber *)enable { @@ -798,6 +806,8 @@ /** * Resizes the export window's height by the supplied delta, while retaining the position of * all interface controls to accommodate the custom filename view. + * + * @param delta The height delta for which the height should be adjusted for. */ - (void)_resizeWindowForCustomFilenameViewByHeightDelta:(NSInteger)delta { @@ -855,6 +865,8 @@ /** * Resizes the export window's height by the supplied delta, while retaining the position of * all interface controls to accommodate the advanced options view. + * + * @param delta The height delta for which the height should be adjusted for. */ - (void)_resizeWindowForAdvancedOptionsViewByHeightDelta:(NSInteger)delta { diff --git a/Source/SPExportFile.h b/Source/SPExportFile.h new file mode 100644 index 00000000..fb61eef2 --- /dev/null +++ b/Source/SPExportFile.h @@ -0,0 +1,86 @@ +// +// $Id$ +// +// SPExportFile.h +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on July 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 "SPConstants.h" + +@class SPFileHandle; + +/** + * @class SPExportFile SPExportFile.h + * + * @author Stuart Connolly http://stuconnolly.com/ + * + * This class represents an abstract export file regardless of whether the file on disk actually exists on + * disk upon initialization. It provides multiple convenience methods for accessing the underlying file handle + * (SPFileHandle instance) as well as logic to deal with situations whether there are problems (file already + * exists, etc) creating the file and estabslishing a handle to it. + */ +@interface SPExportFile : NSObject +{ + NSString *exportFilePath; + + BOOL exportFileNeedsCSVHeader; + BOOL exportFileNeedsXMLHeader; + + SPFileHandle *exportFileHandle; + + SPExportFileHandleStatus exportFileHandleStatus; +} + +/** + * @property exportFilePath + */ +@property (readwrite, retain) NSString *exportFilePath; + +/** + * @property exportFileHandle + */ +@property (readonly) SPFileHandle *exportFileHandle; + +/** + * @property exportFileNeedsCSVHeader + */ +@property (readwrite, assign) BOOL exportFileNeedsCSVHeader; + +/** + * @property exportFileNeedsXMLHeader + */ +@property (readwrite, assign) BOOL exportFileNeedsXMLHeader; + +/** + * @property exportFileHandleStatus + */ +@property (readonly) SPExportFileHandleStatus exportFileHandleStatus; + ++ (SPExportFile *)exportFileAtPath:(NSString *)path; + +- (id)initWithFilePath:(NSString *)path; + +- (void)close; +- (BOOL)delete; +- (void)writeData:(NSData *)data; +- (SPExportFileHandleStatus)createExportFileHandle:(BOOL)overwrite; + +@end diff --git a/Source/SPExportFile.m b/Source/SPExportFile.m new file mode 100644 index 00000000..e102b28e --- /dev/null +++ b/Source/SPExportFile.m @@ -0,0 +1,210 @@ +// +// $Id$ +// +// SPExportFile.m +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on July 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 "SPExportFile.h" + +@interface SPExportFile (PrivateAPI) + +- (SPExportFileHandleStatus)_createFileHandle; + +@end + +@implementation SPExportFile + +@synthesize exportFilePath; +@synthesize exportFileHandle; +@synthesize exportFileNeedsCSVHeader; +@synthesize exportFileNeedsXMLHeader; +@synthesize exportFileHandleStatus; + +#pragma mark - +#pragma mark Initialization + +/** + * Initialise an autoreleased instance of SPExport using the supplied path. + * + * @param path The path of the export file + * + * @return The initialised instance + */ ++ (SPExportFile *)exportFileAtPath:(NSString *)path +{ + return [[[SPExportFile alloc] initWithFilePath:path] autorelease]; +} + +/** + * Initialise an instance of SPExportFile using the supplied path. + * + * @param path The path of the export file + * + * @return The initialised instance + */ +- (id)initWithFilePath:(NSString *)path +{ + if ((self = [super init])) { + + [self setExportFilePath:path]; + + exportFileHandleStatus = -1; + + [self setExportFileNeedsCSVHeader:NO]; + [self setExportFileNeedsXMLHeader:NO]; + } + + return self; +} + +#pragma mark - +#pragma mark General Methods + +/** + * Closes the export file to writing. + */ +- (void)close +{ + if (![self exportFileHandle]) return; + + [[self exportFileHandle] closeFile]; +} + +/** + * Deletes the export file on disk. + * + * @return A BOOL indicating the success of attempting to delete the file + */ +- (BOOL)delete +{ + if ((![self exportFilePath]) || (![self exportFileHandle]) || ([[self exportFilePath] length] == 0)) return NO; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:[self exportFilePath]]) { + return [[NSFileManager defaultManager] removeItemAtPath:[self exportFilePath] error:nil]; + } +} + +/** + * This is a convenience method provided in order to write the supplied data to the underlying filehandle + * without having to directly access it. Throws an exception if attempting to write data when no file + * handle exists. + * + * @param data The data to be written + */ +- (void)writeData:(NSData *)data +{ + if (![self exportFileHandle]) { + [NSException raise:NSInternalInconsistencyException format:@"Attempting to write to an uninitialized file handle."]; + + return; + } + + [[self exportFileHandle] writeData:data]; +} + +/** + * Creates the underlying export file handle and thus the actual file on disk. + * + * @param overwrite If true and a file already exists at this file's location, then it'll be overwritten. + * + * @return One of SPExportFileHandleStatus indicating the status of its creation. + */ +- (SPExportFileHandleStatus)createExportFileHandle:(BOOL)overwrite +{ + // The file path must be set before attempting to create the file handle + if ((![self exportFilePath]) || ([[self exportFilePath] length] == 0)) { + [NSException raise:NSInternalInconsistencyException + format:@"Attempting to create an export filehandle for a path that is either not set or has zero length: %@." + arguments:[self exportFilePath]]; + + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if ([fileManager fileExistsAtPath:[self exportFilePath]]) { + + // If specified attempt to overwrite the file + if (overwrite) { + // Check that it's writable first + if ([fileManager isWritableFileAtPath:[self exportFilePath]]) { + exportFileHandleStatus = [self _createFileHandle]; + } + // The file is not writable, so return that we failed. + else { + exportFileHandleStatus = SPExportFileHandleFailed; + } + } + else { + exportFileHandleStatus = SPExportFileHandleExists; + } + } + // Otherwise attempt to create a file + else { + exportFileHandleStatus = [self _createFileHandle]; + } + + return exportFileHandleStatus; +} + +#pragma mark - +#pragma mark Private API + +/** + * Creates the actual empty file handle and establishes a file handle to it. + * + * @return The status of the file handle's creation. See SPExportFileHandleStatus constants. + */ +- (SPExportFileHandleStatus)_createFileHandle +{ + NSFileManager *fileManager = [NSFileManager defaultManager]; + + if (![fileManager createFileAtPath:[self exportFilePath] contents:[NSData data] attributes:nil]) { + return SPExportFileHandleFailed; + } + + // Retrieve a filehandle for the file, attempting to delete it on failure. + exportFileHandle = [[SPFileHandle fileHandleForWritingAtPath:[self exportFilePath]] retain]; + + if (!exportFileHandle) { + [[NSFileManager defaultManager] removeFileAtPath:[self exportFilePath] handler:nil]; + + return SPExportFileHandleFailed; + } + + return SPExportFileHandleCreated; +} + +#pragma mark - +#pragma mark Other + +/** + * Dealloc. + */ +- (void)dealloc +{ + if (exportFileHandle) [exportFileHandle release], exportFileHandle = nil; +} + +@end diff --git a/Source/SPExportFileUtilities.h b/Source/SPExportFileUtilities.h new file mode 100644 index 00000000..f0a2c054 --- /dev/null +++ b/Source/SPExportFileUtilities.h @@ -0,0 +1,44 @@ +// +// $Id$ +// +// SPExportFileUtilities.h +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on July 30, 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" + +@class SPExportFile; + +/** + * @category SPExportFileUtilities SPExportFileUtilities.h + * + * @author Stuart Connolly http://stuconnolly.com/ + * + * Export file utilities category. + */ +@interface SPExportController (SPExportFileUtilities) + +- (void)writeCSVHeaderToExportFile:(SPExportFile *)file; +- (void)writeXMLHeaderToexportFile:(SPExportFile *)file; + +- (void)errorCreatingExportFileHandles:(NSArray *)files; + +@end diff --git a/Source/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m new file mode 100644 index 00000000..4cb39455 --- /dev/null +++ b/Source/SPExportFileUtilities.m @@ -0,0 +1,212 @@ +// +// $Id$ +// +// SPExportFileUtilities.m +// sequel-pro +// +// Created by Stuart Connolly (stuconnolly.com) on July 30, 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 "SPExportFileUtilities.h" +#import "SPExporter.h" +#import "SPAlertSheets.h" + +@implementation SPExportController (SPExportFileUtilities) + +/** + * Writes the CSV file header to the supplied export file. + * + * @param file The export file to write the header to. + */ +- (void)writeCSVHeaderToExportFile:(SPExportFile *)file +{ + 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 + [file 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"), + [[tables objectAtIndex:0] objectAtIndex:0], + lineEnding, + lineEnding] dataUsingEncoding:[connection encoding]]]; +} + +/** + * Writes the XML file header to the supplied export file. + * + * @param file The export file to write the header to. + */ +- (void)writeXMLHeaderToExportFile:(SPExportFile *)file +{ + 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", SPLOCALIZEDURL_HOMEPAGE, 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]]]; + } + + [file writeData:[header dataUsingEncoding:NSUTF8StringEncoding]]; +} + +/** + * Indicates that one or more errors occurred while attempting to create the export file handles. Asks the + * user how to proceed. + * + * @param files An array of export files (SPExportFile) that failed to be created. + */ +- (void)errorCreatingExportFileHandles:(NSArray *)files +{ + // Get the number of files that already exists as well as couldn't be created because of other reasons + NSUInteger i = 0; + + for (SPExportFile *file in files) + { + SPExportFileHandleStatus status = [file exportFileHandleStatus]; + + if (status == SPExportFileHandleExists) { + i++; + } + // For file handles that we failed to create for some unknown reason, ignore them and remove any + // exporters that are associated with them. + else if (status == SPExportFileHandleFailed) { + + for (SPExporter *exporter in exporters) + { + if ([[exporter exportOutputFile] isEqualTo:file]) { + [exporters removeObject:exporter]; + } + } + } + } + + + if (i > 0) { + + NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error creating export files", @"export file handle creation error message") + defaultButton:NSLocalizedString(@"Ignore", @"ignore button") + alternateButton:NSLocalizedString(@"Overwrite", @"overwrite button") + otherButton:NSLocalizedString(@"Cancel", @"cancel button") + informativeTextWithFormat:NSLocalizedString(@"One or more errors occurred while attempting to create the export files. Those that failed to be created for unknown reasons will be ignored.\n\nHow would you like to proceed with the files that already exist at the location you have chosen to export to?", @"export file handle creation error informative message")]; + + [alert setAlertStyle:NSCriticalAlertStyle]; + + // Close the progress sheet + [NSApp endSheet:exportProgressWindow returnCode:0]; + [exportProgressWindow orderOut:self]; + + [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:files]; + } +} + +/** + * NSAlert didEnd method. + */ +- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + NSArray *files = (NSArray *)contextInfo; + + // Ignore the files that exist and remove the associated exporters + if (returnCode == NSAlertDefaultReturn) { + + for (SPExportFile *file in files) + { + for (SPExporter *exporter in exporters) + { + if ([[exporter exportOutputFile] isEqualTo:file]) { + [exporters removeObject:exporter]; + } + } + } + + [files release]; + + // If we're now left with no exporters, cancel the export operation + if ([exporters count] == 0) { + [exportFiles removeAllObjects]; + } + else { + // Start the export after a short delay to give this sheet a chance to close + [self performSelector:@selector(startExport) withObject:nil afterDelay:0.1]; + } + } + // Overwrite the files and continue + else if (returnCode == NSAlertAlternateReturn) { + + for (SPExportFile *file in files) + { + if ([file exportFileHandleStatus] == SPExportFileHandleExists) { + + [file createExportFileHandle:YES]; + } + } + + [files release]; + + // Start the export after a short delay to give this sheet a chance to close + [self performSelector:@selector(startExport) withObject:nil afterDelay:0.1]; + + } + // Cancel the entire export operation + else if (returnCode == NSAlertOtherReturn) { + + // Loop the cached export files and remove those we've already created + for (SPExportFile *file in exportFiles) + { + [file delete]; + } + + [files release]; + + // Finally get rid of all the exporters and files + [exportFiles removeAllObjects]; + [exporters removeAllObjects]; + } +} + +@end diff --git a/Source/SPExportFilenameUtilities.m b/Source/SPExportFilenameUtilities.m index 64101b21..0695d29e 100644 --- a/Source/SPExportFilenameUtilities.m +++ b/Source/SPExportFilenameUtilities.m @@ -60,6 +60,8 @@ /** * Generates the default export filename based on the selected export options. + * + * @return The default filename. */ - (NSString *)generateDefaultExportFilename { @@ -85,6 +87,8 @@ /** * Returns the current default export file extension based on the selected export type. + * + * @return The default filename extension. */ - (NSString *)currentDefaultExportFileExtension { @@ -123,6 +127,11 @@ /** * Expands the custom filename format based on the selected tokens. + * + * @param format The filename format that is to be expanded. + * @param table A table name to be used within the expanded filename. + * + * @return The expanded filename. */ - (NSString *)expandCustomFilenameFormatFromString:(NSString *)format usingTableName:(NSString *)table { diff --git a/Source/SPExportInitializer.h b/Source/SPExportInitializer.h index cc80e15c..2b13f75d 100644 --- a/Source/SPExportInitializer.h +++ b/Source/SPExportInitializer.h @@ -25,7 +25,7 @@ #import "SPExportController.h" -@class SPCSVExporter, SPSQLExporter, SPXMLExporter, SPFileHandle; +@class SPCSVExporter, SPXMLExporter; /** * @category SPExportInitializer SPExportInitializer.h @@ -36,6 +36,7 @@ */ @interface SPExportController (SPExportInitializer) +- (void)startExport; - (void)initializeExportUsingSelectedOptions; - (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray; @@ -43,7 +44,4 @@ - (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray; - (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray; -- (void)writeXMLHeaderToFileHandle:(SPFileHandle *)fileHandle; -- (SPFileHandle *)fileHandleForFilePath:(NSString *)path; - @end diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index ee14b388..69bbcfce 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -34,7 +34,6 @@ #import "SPMainThreadTrampoline.h" #import "SPDatabaseDocument.h" #import "SPCustomQuery.h" -#import "SPFileHandle.h" #import "SPAlertSheets.h" #import "SPCSVExporter.h" @@ -45,6 +44,37 @@ @implementation SPExportController (SPExportInitializer) /** + * Starts the export process by placing the first exporter on the operation queue. Also opens the progress + * sheet if it's not already visible. + */ +- (void)startExport +{ + // Start progress indicator + [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; + [exportProgressText setStringValue:NSLocalizedString(@"Writing...", @"text showing that app is writing text file")]; + + [exportProgressIndicator setUsesThreadedAnimation:NO]; + [exportProgressIndicator setIndeterminate:NO]; + [exportProgressIndicator setDoubleValue:0]; + + // If it's not already displayed, open the progress sheet open the progress sheet. + if (![exportProgressWindow isVisible]) { + [NSApp beginSheet:exportProgressWindow + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; + } + + // 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]; +} + +/** * Initializes the export process by analysing the selected criteria. */ - (void)initializeExportUsingSelectedOptions @@ -78,12 +108,17 @@ if ([[table objectAtIndex:1] boolValue] || [[table objectAtIndex:2] boolValue] || [[table objectAtIndex:3] boolValue]) { // Check the overall export settings - if ([[table objectAtIndex:1] boolValue] && [exportSQLIncludeStructureCheck state] == NSOffState) + if ([[table objectAtIndex:1] boolValue] && (![exportSQLIncludeStructureCheck state])) { [table replaceObjectAtIndex:1 withObject:[NSNumber numberWithBool:NO]]; - if ([[table objectAtIndex:2] boolValue] && [exportSQLIncludeContentCheck state] == NSOffState) + } + + if ([[table objectAtIndex:2] boolValue] && (![exportSQLIncludeContentCheck state])) { [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:NO]]; - if ([[table objectAtIndex:3] boolValue] && [exportSQLIncludeDropSyntaxCheck state] == NSOffState) + } + + if ([[table objectAtIndex:3] boolValue] && (![exportSQLIncludeDropSyntaxCheck state])) { [table replaceObjectAtIndex:3 withObject:[NSNumber numberWithBool:NO]]; + } [exportTables addObject:table]; } @@ -133,12 +168,15 @@ /** * Exports the contents of the supplied array of tables or data array. + * + * @param exportTables An array of table/view names to be exported (can be nil). + * @param dataArray A MySQL result set array to be exported (can be nil). */ - (void)exportTables:(NSArray *)exportTables orDataArray:(NSArray *)dataArray { NSUInteger i; - SPFileHandle *singleFileHandle = nil; - BOOL singleFileHeaderHasBeenWritten = NO; + BOOL singleFileHandleSet = NO; + SPExportFile *singleExportFile = nil, *file = nil; // Change query logging mode [tableDocumentInstance setQueryMode:SPImportExportQueryMode]; @@ -146,10 +184,12 @@ // Start the notification timer to allow notifications to be shown even if frontmost for long queries [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Export Finished"]; - // Reset the progress interface - [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; - [exportProgressText setStringValue:NSLocalizedString(@"Writing...", @"text showing that app is writing text file")]; - [exportProgressIndicator setDoubleValue:0]; + // Setup the progress sheet + [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), exportTypeLabel]]; + [exportProgressText setStringValue:NSLocalizedString(@"Initializing...", @"initializing export label")]; + + [exportProgressIndicator setIndeterminate:YES]; + [exportProgressIndicator setUsesThreadedAnimation:YES]; // Open the progress sheet [NSApp beginSheet:exportProgressWindow @@ -169,7 +209,7 @@ [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil] : [self generateDefaultExportFilename]]; - singleFileHandle = [self fileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + singleExportFile = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; } // Start the export process depending on the data source @@ -185,43 +225,15 @@ // 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]]; + if (!singleFileHandleSet) { + [singleExportFile setExportFileNeedsCSVHeader:YES]; - // Escape tabs, line endings and carriage returns - [lineEnding replaceOccurrencesOfString:@"\\t" withString:@"\t" - options:NSLiteralSearch - range:NSMakeRange(0, [lineEnding length])]; + [exportFiles addObject:singleExportFile]; - - [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; + singleFileHandleSet = YES; } + + [csvExporter setExportOutputFile:singleExportFile]; } [exporters addObject:csvExporter]; @@ -230,7 +242,9 @@ else { csvExporter = [self initializeCSVExporterForTable:nil orDataArray:dataArray]; - [csvExporter setExportOutputFileHandle:singleFileHandle]; + [exportFiles addObject:singleExportFile]; + + [csvExporter setExportOutputFile:singleExportFile]; [exporters addObject:csvExporter]; } @@ -267,9 +281,11 @@ [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - SPFileHandle *fileHandle = [self fileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - - [sqlExporter setExportOutputFileHandle:fileHandle]; + file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + [exportFiles addObject:file]; + + [sqlExporter setExportOutputFile:file]; [exporters addObject:sqlExporter]; @@ -286,7 +302,7 @@ [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil] : [self generateDefaultExportFilename]]; - singleFileHandle = [self fileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + singleExportFile = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; } // Start the export process depending on the data source @@ -302,15 +318,11 @@ // If required create a single file handle for all XML exports if (![self exportToMultipleFiles]) { - [xmlExporter setExportOutputFileHandle:singleFileHandle]; + [singleExportFile setExportFileNeedsXMLHeader:YES]; - if (!singleFileHeaderHasBeenWritten) { - - // Write the file header - [self writeXMLHeaderToFileHandle:singleFileHandle]; - - singleFileHeaderHasBeenWritten = YES; - } + [exportFiles addObject:singleExportFile]; + + [xmlExporter setExportOutputFile:singleExportFile]; } [exporters addObject:xmlExporter]; @@ -319,10 +331,12 @@ else { xmlExporter = [self initializeXMLExporterForTable:nil orDataArray:dataArray]; - [xmlExporter setExportOutputFileHandle:singleFileHandle]; + [exportFiles addObject:singleExportFile]; + + [xmlExporter setExportOutputFile:singleExportFile]; [exporters addObject:xmlExporter]; - } + } } // Dot export else if (exportType == SPDotExport) { @@ -354,10 +368,12 @@ } [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - - SPFileHandle *fileHandle = [self fileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; - [dotExporter setExportOutputFileHandle:fileHandle]; + file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + [exportFiles addObject:file]; + + [dotExporter setExportOutputFile:file]; [exporters addObject:dotExporter]; @@ -375,21 +391,45 @@ [exporter setExportOutputCompressFile:([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression)]; } - // Add the first exporter to the operation queue - [operationQueue addOperation:[exporters objectAtIndex:0]]; + NSMutableArray *problemFiles = [[NSMutableArray alloc] init]; - // 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]; + // Create the actual file handles while dealing with errors (e.g. file already exists, etc) during creation + for (SPExportFile *exportFile in exportFiles) + { + SPExportFileHandleStatus status = [exportFile createExportFileHandle:NO]; + + if (status == SPExportFileHandleCreated) { + if ([exportFile exportFileNeedsCSVHeader]) { + [self writeCSVHeaderToExportFile:exportFile]; + } + else if ([exportFile exportFileNeedsXMLHeader]) { + [self writeXMLHeaderToExportFile:exportFile]; + } + } + else { + [problemFiles addObject:exportFile]; + } + } + + // Deal with any file handles that we failed to create for whatever reason + if ([problemFiles count] > 0) { + [self errorCreatingExportFileHandles:problemFiles]; + } + else { + [problemFiles release]; + + [self startExport]; + } } /** * Initialises a CSV exporter for the supplied table name or data array. + * + * @param table The table name for which the exporter should be cerated for (can be nil). + * @param dataArray The MySQL result data array for which the exporter should be created for (can be nil). */ - (SPCSVExporter *)initializeCSVExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray -{ - SPFileHandle *fileHandle = nil; - +{ SPCSVExporter *csvExporter = [[SPCSVExporter alloc] initWithDelegate:self]; // Depeding on the export source, set the table name or data array @@ -427,10 +467,12 @@ } [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; - - fileHandle = [self fileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + SPExportFile *file = [SPExportFile exportFileAtPath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; + + [exportFiles addObject:file]; - [csvExporter setExportOutputFileHandle:fileHandle]; + [csvExporter setExportOutputFile:file]; } return [csvExporter autorelease]; @@ -438,11 +480,12 @@ /** * Initialises a XML exporter for the supplied table name or data array. + * + * @param table The table name for which the exporter should be cerated for (can be nil). + * @param dataArray The MySQL result data array for which the exporter should be created for (can be nil). */ - (SPXMLExporter *)initializeXMLExporterForTable:(NSString *)table orDataArray:(NSArray *)dataArray -{ - SPFileHandle *fileHandle = nil; - +{ SPXMLExporter *xmlExporter = [[SPXMLExporter alloc] initWithDelegate:self]; // Depeding on the export source, set the table name or data array @@ -463,82 +506,17 @@ if ((exportSource == SPTableExport) && exportToMultipleFiles && (exportTableCount > 0)) { [exportFilename setString:[[exportPathField stringValue] stringByAppendingPathComponent:table]]; [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self currentDefaultExportFileExtension]]]; + + SPExportFile *file = [SPExportFile exportFileAtPath:exportFilename]; - fileHandle = [self fileHandleForFilePath:exportFilename]; - - // Write the file header - [self writeXMLHeaderToFileHandle:fileHandle]; + [file setExportFileNeedsXMLHeader:YES]; - [xmlExporter setExportOutputFileHandle:fileHandle]; - } - - 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", SPLOCALIZEDURL_HOMEPAGE, 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]]]; - } + [exportFiles addObject:file]; - [fileHandle writeData:[header dataUsingEncoding:NSUTF8StringEncoding]]; -} - -/** - * Returns a file handle for writing at the supplied path. - */ -- (SPFileHandle *)fileHandleForFilePath:(NSString *)path -{ - SPFileHandle *fileHandle = nil; - NSFileManager *fileManager = [NSFileManager defaultManager]; - - if ([fileManager fileExistsAtPath:path]) { - if ((![fileManager isWritableFileAtPath:path]) || (!(fileHandle = [SPFileHandle fileHandleForWritingAtPath:path]))) { - 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:path 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:path]; - - if (!fileHandle) { - [[NSFileManager defaultManager] removeFileAtPath:path 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; - } + [xmlExporter setExportOutputFile:file]; } - // Keep a record of the file handle's path in case the user cancels the export then we need to remove - // it from disk. - [exportFiles addObject:path]; - - return fileHandle; + return [xmlExporter autorelease]; } @end diff --git a/Source/SPExportUtilities.h b/Source/SPExportUtilities.h index 2c7c1e09..dfb81f54 100644 --- a/Source/SPExportUtilities.h +++ b/Source/SPExportUtilities.h @@ -23,6 +23,13 @@ // // More info at <http://code.google.com/p/sequel-pro/> +/** + * @class SPExportUtilities SPExportUtilities.h + * + * @author Stuart Connolly http://stuconnolly.com/ + * + * Export utility methods. + */ @interface SPExportUtilities : NSObject void SPExportDelegateConformsToProtocol(NSObject *delegate, Protocol *protocol); diff --git a/Source/SPExportUtilities.m b/Source/SPExportUtilities.m index bf532bb2..543beb67 100644 --- a/Source/SPExportUtilities.m +++ b/Source/SPExportUtilities.m @@ -27,13 +27,19 @@ @implementation SPExportUtilities +/** + * Tests whether the supplied delegate conforms to the supplied protocol. Throws an exception if it doesn't. + * + * @param delegate The delegate that is be tested. + * @param protocol The protocol that we are testing conformance for. + */ 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]; + [NSException raise:@"Protocol Conformance" + format:@"The supplied delegate does not conform to the protocol '%@'." + arguments:NSStringFromProtocol(protocol)]; } } diff --git a/Source/SPExporter.h b/Source/SPExporter.h index 22ca0cc3..5c5e68c1 100644 --- a/Source/SPExporter.h +++ b/Source/SPExporter.h @@ -50,88 +50,76 @@ #import "SPConstants.h" -@class MCPConnection, SPFileHandle; +@class MCPConnection, SPExportFile; @interface SPExporter : NSOperation { - /** - * The MySQL connection to use - */ MCPConnection *connection; - /** - * The exports current progress value - */ double exportProgressValue; - - /** - * The max progress value of the export operation - */ double exportMaxProgress; - /** - * Indicates whether or not the exporter is running - */ BOOL exportProcessIsRunning; - - /** - * Indicates whether or not low memory streaming is used - */ BOOL exportUsingLowMemoryBlockingStreaming; - - /** - * Compress output - */ BOOL exportOutputCompressFile; - /** - * Compression format - */ + SPFileCompressionFormat exportOutputCompressionFormat; - /** - * The resulting exported data as a string - */ NSString *exportData; - /** - * The output file handle of the exporter - */ - SPFileHandle *exportOutputFileHandle; - - /** - * Export output encoding - */ + SPExportFile *exportOutputFile; + NSStringEncoding exportOutputEncoding; } +/** + * @property connection The MySQL connection to use + */ @property(readwrite, retain) MCPConnection *connection; +/** + * @property exportProgressValue The export's current progress value + */ @property(readwrite, assign) double exportProgressValue; +/** + * @property exportMaxProgress The max progress value of the export operation + */ +@property(readwrite, assign) double exportMaxProgress; + +/** + * @property exportProcessIsRunning Indicates whether or not the exporter is running + */ @property(readwrite, assign) BOOL exportProcessIsRunning; + +/** + * @property exportUsingLowMemoryBlockingStreaming Indicates whether or not low memory streaming is used + */ @property(readwrite, assign) BOOL exportUsingLowMemoryBlockingStreaming; +/** + * @property exportOutputCompressionFormat Compression format + */ @property(readwrite, assign) SPFileCompressionFormat exportOutputCompressionFormat; +/** + * @property exportData The resulting exported data as a string + */ @property(readwrite, retain) NSString *exportData; -@property(readwrite, retain) SPFileHandle *exportOutputFileHandle; -@property(readwrite, assign) NSStringEncoding exportOutputEncoding; - -@property(readwrite, assign) double exportMaxProgress; /** - * Returns whether or not file compression is in use. - * - * @return A BOOL indicating the use of compression + * @property exportOutputFile The output file of the exporter */ -- (BOOL)exportOutputCompressFile; +@property(readwrite, retain) SPExportFile *exportOutputFile; /** - * Sets whether or not the resulting output of this exporter should be compressed. - * - * @param compress A BOOL indicating the use of compression + * @property exportOutputEncoding Export output encoding */ +@property(readwrite, assign) NSStringEncoding exportOutputEncoding; + +- (BOOL)exportOutputCompressFile; + - (void)setExportOutputCompressFile:(BOOL)compress; @end diff --git a/Source/SPExporter.m b/Source/SPExporter.m index cd45ec50..e0e862fa 100644 --- a/Source/SPExporter.m +++ b/Source/SPExporter.m @@ -33,7 +33,7 @@ @synthesize exportUsingLowMemoryBlockingStreaming; @synthesize exportOutputCompressionFormat; @synthesize exportData; -@synthesize exportOutputFileHandle; +@synthesize exportOutputFile; @synthesize exportOutputEncoding; @synthesize exportMaxProgress; @@ -63,13 +63,13 @@ */ - (void)main { - @throw [NSException exceptionWithName:@"NSOperation main() Call" - reason:@"Cannot call NSOperation's main() method in SPExpoter, must be overriden in a subclass. See SPExporter.h" - userInfo:nil]; + [NSException raise:NSInternalInconsistencyException format:@"Cannot call NSOperation's main() method in SPExpoter, must be overriden in a subclass. See SPExporter.h"]; } /** * Returns whether or not file compression is in use. + * + * @return A BOOL indicating the use of compression */ - (BOOL)exportOutputCompressFile { @@ -78,15 +78,17 @@ /** * Sets whether or not the resulting output of this exporter should be compressed. + * + * @param compress A BOOL indicating the use of compression */ - (void)setExportOutputCompressFile:(BOOL)compress { // If the export file handle is nil or a compression format has not yet been set don't proceed - if ((!exportOutputFileHandle) || ([self exportOutputCompressionFormat] == SPNoCompression)) return; + if ((![exportOutputFile exportFileHandle]) || ([self exportOutputCompressionFormat] == SPNoCompression)) return; exportOutputCompressFile = compress; - [[self exportOutputFileHandle] setShouldWriteWithCompressionFormat:(compress) ? [self exportOutputCompressionFormat] : SPNoCompression]; + [[[self exportOutputFile] exportFileHandle] setShouldWriteWithCompressionFormat:(compress) ? [self exportOutputCompressionFormat] : SPNoCompression]; } /** @@ -96,7 +98,7 @@ { if (exportData) [exportData release], exportData = nil; if (connection) [connection release], connection = nil; - if (exportOutputFileHandle) [exportOutputFileHandle release], exportOutputFileHandle = nil; + if (exportOutputFile) [exportOutputFile release], exportOutputFile = nil; [super dealloc]; } diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m index b71873ed..7e856c2a 100644 --- a/Source/SPFileHandle.m +++ b/Source/SPFileHandle.m @@ -95,7 +95,7 @@ // Set gzip buffer gzbuffer(gzfile, 131072); - // Get the first 3 bytes from the file + // Get the first 4 bytes from the file for (i = 0; (c = getc(wrappedFile)) != EOF && i < 4; bzbuf[i++] = c); rewind(wrappedFile); diff --git a/Source/SPHTMLExporter.h b/Source/SPHTMLExporter.h index fb0ce7f1..8fc7e54a 100644 --- a/Source/SPHTMLExporter.h +++ b/Source/SPHTMLExporter.h @@ -35,21 +35,14 @@ */ @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 + * @property delegate Exporter delegate */ +@property(readwrite, assign) NSObject *delegate; + - (id)initWithDelegate:(NSObject *)exportDelegate; @end diff --git a/Source/SPHTMLExporter.m b/Source/SPHTMLExporter.m index dbf649dc..4984aeb6 100644 --- a/Source/SPHTMLExporter.m +++ b/Source/SPHTMLExporter.m @@ -32,6 +32,10 @@ /** * Initialise an instance of SPHTMLExporter using the supplied delegate. + * + * @param exportDelegate The exporter delegate + * + * @return The initialised instance */ - (id)initWithDelegate:(NSObject *)exportDelegate { diff --git a/Source/SPPDFExporter.h b/Source/SPPDFExporter.h index 9071084f..2d00e8c1 100644 --- a/Source/SPPDFExporter.h +++ b/Source/SPPDFExporter.h @@ -35,21 +35,14 @@ */ @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 + * @property delegate Exporter delegate */ +@property(readwrite, assign) NSObject *delegate; + - (id)initWithDelegate:(NSObject *)exportDelegate; @end diff --git a/Source/SPPDFExporter.m b/Source/SPPDFExporter.m index 725b4173..19762739 100644 --- a/Source/SPPDFExporter.m +++ b/Source/SPPDFExporter.m @@ -32,6 +32,10 @@ /** * Initialise an instance of SPPDFExporter using the supplied delegate. + * + * @param exportDelegate The exporter delegate + * + * @return The initialised instance */ - (id)initWithDelegate:(NSObject *)exportDelegate { diff --git a/Source/SPSQLExporter.h b/Source/SPSQLExporter.h index 43ba0172..58203feb 100644 --- a/Source/SPSQLExporter.h +++ b/Source/SPSQLExporter.h @@ -40,106 +40,93 @@ { 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; - /** - * New INSERT statement divider - */ SPSQLExportInsertDivider sqlInsertDivider; - /** - * Number of tables processed by exporter - */ NSUInteger sqlCurrentTableExportIndex; - - /** - * The value after which a new INSERT statement should be created. - */ NSUInteger sqlInsertAfterNValue; - - /** - * Table information fetcher and parser - */ + SPTableData *sqlTableDataInstance; } +/** + * @property delegate Exporter delegate + */ @property(readwrite, assign) NSObject *delegate; +/** + * @property sqlExportTables Tables + */ @property(readwrite, retain) NSArray *sqlExportTables; +/** + * @property sqlDatabaseHost Database host + */ @property(readwrite, retain) NSString *sqlDatabaseHost; + +/** + * @property sqlDatabaseName Database name + */ @property(readwrite, retain) NSString *sqlDatabaseName; + +/** + * @property sqlDatabaseVersion Database version + */ @property(readwrite, retain) NSString *sqlDatabaseVersion; +/** + * @property sqlExportCurrentTable Current table + */ @property(readwrite, retain) NSString *sqlExportCurrentTable; + +/** + * @property sqlExportErrors Export errors + */ @property(readwrite, retain) NSString *sqlExportErrors; +/** + * @property sqlOutputIncludeUTF8BOM Include UTF-8 BOM + */ @property(readwrite, assign) BOOL sqlOutputIncludeUTF8BOM; + +/** + * @property sqlOutputEncodeBLOBasHex Encode BLOB fields as Hex data + */ @property(readwrite, assign) BOOL sqlOutputEncodeBLOBasHex; + +/** + * @property sqlOutputIncludeErrors Include export errors + */ @property(readwrite, assign) BOOL sqlOutputIncludeErrors; +/** + * @property sqlCurrentTableExportIndex Number of tables processed by exporter + */ @property(readwrite, assign) NSUInteger sqlCurrentTableExportIndex; -@property(readwrite, assign) NSUInteger sqlInsertAfterNValue; - -@property(readwrite, assign) SPSQLExportInsertDivider sqlInsertDivider; /** - * Initialise an instance of SPSQLExporter using the supplied delegate. - * - * @param exportDelegate The exporter delegate - * - * @return The initialised instance + * @property sqlInsertAfterNValue The value after which a new INSERT statement should be created */ -- (id)initWithDelegate:(NSObject *)exportDelegate; +@property(readwrite, assign) NSUInteger sqlInsertAfterNValue; /** - * Returns whether or not any export errors occurred. - * - * @return A BOOL indicating the occurrence of errors + * @property sqlInsertDivider New INSERT statement divider */ +@property(readwrite, assign) SPSQLExportInsertDivider sqlInsertDivider; + +- (id)initWithDelegate:(NSObject *)exportDelegate; + - (BOOL)didExportErrorsOccur; @end diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m index 83f10b46..c53a08ea 100644 --- a/Source/SPSQLExporter.m +++ b/Source/SPSQLExporter.m @@ -58,19 +58,23 @@ /** * Initialise an instance of SPSQLExporter using the supplied delegate. + * + * @param exportDelegate The exporter delegate + * + * @return The initialised instance */ - (id)initWithDelegate:(NSObject *)exportDelegate { if ((self = [super init])) { SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPSQLExporterProtocol)); - + [self setDelegate:exportDelegate]; [self setSqlExportCurrentTable:nil]; - + [self setSqlInsertDivider:SPSQLInsertEveryNDataBytes]; [self setSqlInsertAfterNValue:250000]; } - + return self; } @@ -83,34 +87,34 @@ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; sqlTableDataInstance = [[[SPTableData alloc] init] autorelease]; [sqlTableDataInstance setConnection:connection]; - + MCPResult *queryResult; MCPStreamingResult *streamingResult; - + NSArray *row; NSString *tableName; NSDictionary *tableDetails; NSMutableArray *tableColumnNumericStatus; SPTableType tableType = SPTableTypeTable; - + id createTableSyntax = nil; NSUInteger i, j, k, 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 sqlDatabaseHost]) || ([[self sqlDatabaseHost] isEqualToString:@""]) || @@ -122,20 +126,20 @@ [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]) + + for (NSArray *item in [self sqlExportTables]) { // Check for cancellation flag if ([self isCancelled]) { @@ -144,7 +148,7 @@ [pool release]; return; } - + switch ([NSArrayObjectAtIndex(item, 4) intValue]) { case SPTableTypeProc: targetArray = procs; @@ -156,10 +160,10 @@ targetArray = tables; break; } - + [targetArray addObject:item]; } - + // If required write the UTF-8 Byte Order Mark (BOM) if ([self sqlOutputIncludeUTF8BOM]) { [metaString setString:@"\xef\xbb\xbf"]; @@ -168,7 +172,7 @@ else { [metaString setString:@"# ************************************************************\n"]; } - + // 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"]]]; @@ -177,152 +181,157 @@ [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"]; - + [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]]]; - + + [[self exportOutputFile] writeData:[metaString dataUsingEncoding:[self exportOutputEncoding]]]; + // Loop through the selected tables - for (NSArray *table in tables) + for (NSArray *table in tables) { // Check for cancellation flag if ([self isCancelled]) { [pool release]; return; } - + [self setSqlCurrentTableExportIndex:[self sqlCurrentTableExportIndex]+1]; 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]]]; - + [[self exportOutputFile] 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]]]; - [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n\n\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n\n\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + continue; } - + // 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]] + [[self exportOutputFile] 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]]; + + [[self exportOutputFile] writeData:[createTableSyntax dataUsingEncoding:NSUTF8StringEncoding]]; + [[self exportOutputFile] 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:[sqlTableDataInstance informationForTable:tableName]]; - + NSUInteger colCount = [[tableDetails objectForKey:@"columns"] count]; - + tableColumnNumericStatus = [NSMutableArray arrayWithCapacity:colCount]; - - for (j = 0; j < colCount; j++) + + 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 NSArray *rowArray = [[connection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [tableName backtickQuotedString]]] fetchRowAsArray]; + if ([connection queryErrored] || ![rowArray count]) { [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]]; [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n\n\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + continue; } - + rowCount = [NSArrayObjectAtIndex(rowArray, 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]]]; - + + [[self exportOutputFile] 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]]; - + [[self exportOutputFile] 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, k = 0; - + NSAutoreleasePool *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]) + + while (row = [streamingResult fetchNextRowAsArray]) { // Check for cancellation flag if ([self isCancelled]) { @@ -330,42 +339,42 @@ [streamingResult cancelResultLoad]; [sqlExportPool release]; [pool release]; - + return; } - + j++; k++; - + [sqlString setString:@""]; - - // Update the progress + + // Update the progress NSUInteger progress = (j * ([self exportMaxProgress] / rowCount)); - + if (progress > lastProgressValue) { [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++) + + for (t = 0; t < colCount; t++) { // Check for cancellation flag if ([self isCancelled]) { [sqlExportPool release]; [pool release]; - + return; } - + id object = NSArrayObjectAtIndex(row, t); - + // Add NULL values directly to the output row if ([object isMemberOfClass:[NSNull class]]) { [sqlString appendString:@"NULL"]; - } + } // If the field is off type BIT, the values need a binary prefix of b'x'. else if ([[NSArrayObjectAtIndex([tableDetails objectForKey:@"columns"], t) objectForKey:@"type"] isEqualToString:@"BIT"]) { [sqlString appendString:@"b'"]; @@ -374,38 +383,38 @@ } // Add data types directly as hex data else if ([object isKindOfClass:[NSData class]]) { - + if ([self sqlOutputEncodeBLOBasHex]) { [sqlString appendString:@"X'"]; [sqlString appendString:[connection prepareBinaryData:object]]; } else { [sqlString appendString:@"'"]; - + NSString *data = [[NSString alloc] initWithData:object encoding:[self exportOutputEncoding]]; - + if (data == nil) { data = [[NSString alloc] initWithData:object encoding:NSASCIIStringEncoding]; } - + [sqlString appendString:data]; - + [data release]; } - + [sqlString appendString:@"'"]; - } + } else { [cellValue setString:[object description]]; - + // Add empty strings as a pair of quotes if ([cellValue length] == 0) { [sqlString appendString:@"''"]; - } - else { + } + else { if ([NSArrayObjectAtIndex(tableColumnNumericStatus, t) boolValue]) { [sqlString appendString:cellValue]; - } + } // Otherwise add a quoted string with special characters escaped else { [sqlString appendString:@"'"]; @@ -414,16 +423,16 @@ } } } - + // 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) { - + // If required start a new INSERT statment if ((([self sqlInsertDivider] == SPSQLInsertEveryNDataBytes) && (queryLength >= ([self sqlInsertAfterNValue] * 1024))) || (([self sqlInsertDivider] == SPSQLInsertEveryNRows) && (k == [self sqlInsertAfterNValue]))) @@ -433,79 +442,79 @@ [sqlString appendString:@" ("]; [sqlString appendString:[fieldNames componentsJoinedAndBacktickQuoted]]; [sqlString appendString:@")\nVALUES\n\t("]; - + queryLength = 0, k = 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]]; + [[self exportOutputFile] writeData:[sqlString dataUsingEncoding:NSUTF8StringEncoding]]; } - + // Complete the command - [[self exportOutputFileHandle] writeData:[[NSString stringWithString:@";\n\n"] dataUsingEncoding:NSUTF8StringEncoding]]; - + [[self exportOutputFile] 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]]; - + + [[self exportOutputFile] 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]] + [[self exportOutputFile] 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++) + + 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:@"%@@%@", + + 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]]; @@ -516,82 +525,82 @@ [[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]]; + + [[self exportOutputFile] 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]] + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; } } - + // Add an additional separator between tables - [[self exportOutputFileHandle] writeData:[[NSString stringWithString:@"\n\n"] dataUsingEncoding:NSUTF8StringEncoding]]; + [[self exportOutputFile] 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) + for (tableName in viewSyntaxes) { // Check for cancellation flag if ([self isCancelled]) { [pool release]; return; } - + [metaString setString:@"\n\n"]; // Add the name of table [metaString appendFormat:@"# Replace placeholder table for %@ with correct view syntax\n# ------------------------------------------------------------\n\n", tableName]; [metaString appendFormat:@"DROP TABLE %@;\n", [tableName backtickQuotedString]]; [metaString appendFormat:@"%@;\n", [viewSyntaxes objectForKey:tableName]]; - - [[self exportOutputFileHandle] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + + [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; } - + // Export procedures and functions - for (NSString *procedureType in [NSArray arrayWithObjects:@"PROCEDURE", @"FUNCTION", nil]) + 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\n"]; - + // Loop through the definitions, exporting if enabled - for (s = 0; s < [queryResult numOfRows]; s++) + for (s = 0; s < [queryResult numOfRows]; s++) { // Check for cancellation flag if ([self isCancelled]) { @@ -612,7 +621,7 @@ [pool release]; return; } - + if ([NSArrayObjectAtIndex(item, 0) isEqualToString:procedureName]) { itemFound = YES; sqlOutputIncludeStructure = [NSArrayObjectAtIndex(item, 1) boolValue]; @@ -634,46 +643,46 @@ [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:@"%@@%@", + + 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]; if ([connection queryErrored]) { [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]]; - + if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", [connection getLastErrorMessage]] dataUsingEncoding:NSUTF8StringEncoding]]; } [proceduresList release]; continue; } - + 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]]]; - + // A NULL result indicates a permission problem if ([createProcedure isNSNull]) { NSString *errorString = [NSString stringWithFormat:NSLocalizedString(@"Could not export the %@ '%@' because of a permisions error.\n", @"Procedure/function export permission error"), procedureType, procedureName]; [errors appendString:errorString]; if ([self sqlOutputIncludeErrors]) { - [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"# Error: %@\n", errorString] dataUsingEncoding:NSUTF8StringEncoding]]; + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"# Error: %@\n", errorString] dataUsingEncoding:NSUTF8StringEncoding]]; } [proceduresList release]; [procedureInfo release]; @@ -682,7 +691,7 @@ 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 */ @@ -690,62 +699,64 @@ // // Build the CREATE PROCEDURE string to include MySQL Version limiters [metaString appendString:[NSString stringWithFormat:@"/*!50003 CREATE*/ /*!50020 DEFINER=%@*/ /*!50003 %@ */;;\n\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]]; + + [[self exportOutputFile] 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]]; + [[self exportOutputFile] 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"]; - + // 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]]; - + [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; + // Set export errors [self setSqlExportErrors:errors]; - + [errors release]; [sqlString release]; - + // Close the file - [[self exportOutputFileHandle] closeFile]; - + [[self exportOutputFile] close]; + // 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]; } /** * Returns whether or not any export errors occurred by examing the length of the errors string. + * + * @return A BOOL indicating the occurrence of errors */ - (BOOL)didExportErrorsOccur { @@ -753,85 +764,89 @@ } /** - * 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. + * 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 within dumps. + * + * @param viewName The name of the view for which the placeholder is to be created for. + * + * @return The CREATE TABLE placeholder syntax */ - (NSString *)_createViewPlaceholderSyntaxForView:(NSString *)viewName { NSInteger i, j; NSMutableString *placeholderSyntax; - + // Get structured information for the view via the SPTableData parsers NSDictionary *viewInformation = [sqlTableDataInstance informationForView: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++) + 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++) + + 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]; } @@ -845,7 +860,7 @@ [sqlDatabaseName release], sqlDatabaseName = nil; [sqlExportCurrentTable release], sqlExportCurrentTable = nil; [sqlDatabaseVersion release], sqlDatabaseVersion = nil; - + [super dealloc]; } diff --git a/Source/SPXMLExporter.h b/Source/SPXMLExporter.h index b74c76bf..4e373e8d 100644 --- a/Source/SPXMLExporter.h +++ b/Source/SPXMLExporter.h @@ -35,41 +35,34 @@ */ @interface SPXMLExporter : SPExporter { - /** - * Exporter delegate - */ NSObject <SPXMLExporterProtocol> *delegate; - /** - * Data array - */ NSArray *xmlDataArray; - - /** - * Table name - */ + NSString *xmlTableName; - - /** - * XML NULL string - */ NSString *xmlNULLString; } +/** + * @property delegate Exporter delegate + */ @property(readwrite, assign) NSObject *delegate; +/** + * @property xmlDataArray Data array + */ @property(readwrite, retain) NSArray *xmlDataArray; -@property(readwrite, retain) NSString *xmlTableName; -@property(readwrite, retain) NSString *xmlNULLString; +/** + * @property xmlTableName Table name + */ +@property(readwrite, retain) NSString *xmlTableName; /** - * Initialise an instance of SPXMLExporter using the supplied delegate. - * - * @param exportDelegate The exporter delegate - * - * @return The initialised instance + * @property xmlNULLString XML NULL string */ +@property(readwrite, retain) NSString *xmlNULLString; + - (id)initWithDelegate:(NSObject *)exportDelegate; @end diff --git a/Source/SPXMLExporter.m b/Source/SPXMLExporter.m index eddfabbf..2da40220 100644 --- a/Source/SPXMLExporter.m +++ b/Source/SPXMLExporter.m @@ -41,6 +41,10 @@ /** * Initialise an instance of SPXMLExporter using the supplied delegate. + * + * @param exportDelegate The exporter delegate + * + * @return The initialised instance */ - (id)initWithDelegate:(NSObject *)exportDelegate { @@ -109,10 +113,10 @@ [[xmlTags objectAtIndex:i] addObject:[NSString stringWithFormat:@"</%@>\n", [[[xmlRow objectAtIndex:i] description] HTMLEscapeString]]]; } - [[self exportOutputFileHandle] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + [[self exportOutputFile] 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]]]; + [[self exportOutputFile] 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. @@ -204,7 +208,7 @@ currentPoolDataLength += [xmlString length]; // Write the row to the filehandle - [[self exportOutputFileHandle] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; // Update the progress counter and progress bar currentRowIndex++; @@ -233,10 +237,10 @@ } // Write the closing tag for the table - [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; // Write data to disk - [[self exportOutputFileHandle] synchronizeFile]; + [[[self exportOutputFile] exportFileHandle] synchronizeFile]; // Mark the process as not running [self setExportProcessIsRunning:NO]; diff --git a/Source/SPXMLExporterDelegate.m b/Source/SPXMLExporterDelegate.m index 44deb33c..5aa01ba8 100644 --- a/Source/SPXMLExporterDelegate.m +++ b/Source/SPXMLExporterDelegate.m @@ -71,9 +71,9 @@ // 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 exportOutputFile] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection encoding]]]; - [[exporter exportOutputFileHandle] closeFile]; + [[exporter exportOutputFile] close]; } [operationQueue addOperation:[exporters objectAtIndex:0]]; @@ -85,11 +85,11 @@ // 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]]]; + [[exporter exportOutputFile] writeData:[[NSString stringWithFormat:@"</%@>\n", [[tableDocumentInstance database] HTMLEscapeString]] dataUsingEncoding:[connection encoding]]]; } // Close the last exporter's file handle - [[exporter exportOutputFileHandle] closeFile]; + [[exporter exportOutputFile] close]; [NSApp endSheet:exportProgressWindow returnCode:0]; [exportProgressWindow orderOut:self]; diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 450a5c8b..79980eb9 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -135,6 +135,8 @@ 17F5B1541048C50D00FC794F /* SPExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F5B1531048C50D00FC794F /* SPExporter.m */; }; 17F5B39C1049B96A00FC794F /* SPSQLExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F5B39B1049B96A00FC794F /* SPSQLExporter.m */; }; 17F90E2C1210B34900274C98 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 17F90E2B1210B34900274C98 /* Credits.rtf */; }; + 17F90E481210B42700274C98 /* SPExportFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F90E471210B42700274C98 /* SPExportFile.m */; }; + 17F90E4B1210B43A00274C98 /* SPExportFileUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */; }; 296DC89F0F8FD336002A3258 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296DC89E0F8FD336002A3258 /* WebKit.framework */; }; 296DC8B60F909194002A3258 /* MGTemplateEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8A70F909194002A3258 /* MGTemplateEngine.m */; }; 296DC8B70F909194002A3258 /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 296DC8AB0F909194002A3258 /* RegexKitLite.m */; }; @@ -689,6 +691,10 @@ 17F5B1531048C50D00FC794F /* SPExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExporter.m; sourceTree = "<group>"; }; 17F5B39A1049B96A00FC794F /* SPSQLExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPSQLExporter.h; sourceTree = "<group>"; }; 17F5B39B1049B96A00FC794F /* SPSQLExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPSQLExporter.m; sourceTree = "<group>"; }; + 17F90E461210B42700274C98 /* SPExportFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportFile.h; sourceTree = "<group>"; }; + 17F90E471210B42700274C98 /* SPExportFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFile.m; sourceTree = "<group>"; }; + 17F90E491210B43A00274C98 /* SPExportFileUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportFileUtilities.h; sourceTree = "<group>"; }; + 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFileUtilities.m; sourceTree = "<group>"; }; 296DC89E0F8FD336002A3258 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = /System/Library/Frameworks/WebKit.framework; sourceTree = "<absolute>"; }; 296DC8A50F909194002A3258 /* MGTemplateMarker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGTemplateMarker.h; sourceTree = "<group>"; }; 296DC8A60F909194002A3258 /* MGTemplateFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGTemplateFilter.h; sourceTree = "<group>"; }; @@ -1832,8 +1838,11 @@ 173C836E11AAD26E00B8B084 /* SPExportInitializer.m */, 173C836F11AAD26E00B8B084 /* SPExportUtilities.h */, 173C837011AAD26E00B8B084 /* SPExportUtilities.m */, + 17F90E491210B43A00274C98 /* SPExportFileUtilities.h */, + 17F90E4A1210B43A00274C98 /* SPExportFileUtilities.m */, 17AF787911FC41C00073D043 /* SPExportFilenameUtilities.h */, 17AF787A11FC41C00073D043 /* SPExportFilenameUtilities.m */, + 17F90E451210B41100274C98 /* Model */, 173C836C11AAD24300B8B084 /* Exporters */, 173C837C11AAD2C500B8B084 /* Delegate Protocols */, 173C837D11AAD2D300B8B084 /* Delegate Categories */, @@ -1841,6 +1850,15 @@ name = "Data Export"; sourceTree = "<group>"; }; + 17F90E451210B41100274C98 /* Model */ = { + isa = PBXGroup; + children = ( + 17F90E461210B42700274C98 /* SPExportFile.h */, + 17F90E471210B42700274C98 /* SPExportFile.m */, + ); + name = Model; + sourceTree = "<group>"; + }; 19C28FB0FE9D524F11CA2CBB /* Products */ = { isa = PBXGroup; children = ( @@ -2723,6 +2741,8 @@ 17A7773411C52D8E001E27B4 /* SPIndexesController.m in Sources */, 589ED05B11E0ACD100C1DCEA /* DMLocalizedNib.m in Sources */, 17AF787B11FC41C00073D043 /* SPExportFilenameUtilities.m in Sources */, + 17F90E481210B42700274C98 /* SPExportFile.m in Sources */, + 17F90E4B1210B43A00274C98 /* SPExportFileUtilities.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; |