diff options
author | stuconnolly <stuart02@gmail.com> | 2010-07-24 21:34:05 +0000 |
---|---|---|
committer | stuconnolly <stuart02@gmail.com> | 2010-07-24 21:34:05 +0000 |
commit | bf1294bd1016672aa3062bb80b546bf2f8037fb3 (patch) | |
tree | 121768027638e59af7d5830855bd19b88de7e601 /Source | |
parent | eb29a57a6860961c0caa00787d779f4cb4117c90 (diff) | |
download | sequelpro-bf1294bd1016672aa3062bb80b546bf2f8037fb3.tar.gz sequelpro-bf1294bd1016672aa3062bb80b546bf2f8037fb3.tar.bz2 sequelpro-bf1294bd1016672aa3062bb80b546bf2f8037fb3.zip |
In addition to Gzip compression support when exporting SQL dumps add the ability to use Bzip2 compression.
Other changes include:
+ Enable the use of export compression (Gzip and Bzip2) for all export formats.
+ Move the compression options in the export dialog to the 'Advanced' export settings view.
+ Simplify the setting of common exporter properties (e.g. the connection, use of compression).
+ Fix a potential memory leak in the dot exporter.
+ Update the data importer to recognise Bzip2 compressed files.
+ Fix several display issues on export dialog.
+ Restore the default .csv file extension of CSV exports.
+ Correctly update the default export filename when selecting a output compression type.
The addition of Bzip2 compression support implements issue #688.
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPConstants.h | 8 | ||||
-rw-r--r-- | Source/SPDataImport.m | 26 | ||||
-rw-r--r-- | Source/SPDotExporter.m | 1 | ||||
-rw-r--r-- | Source/SPExportController.h | 3 | ||||
-rw-r--r-- | Source/SPExportController.m | 28 | ||||
-rw-r--r-- | Source/SPExportInitializer.m | 69 | ||||
-rw-r--r-- | Source/SPExporter.h | 72 | ||||
-rw-r--r-- | Source/SPExporter.m | 28 | ||||
-rw-r--r-- | Source/SPFileHandle.h | 46 | ||||
-rw-r--r-- | Source/SPFileHandle.m | 239 | ||||
-rw-r--r-- | Source/SPSQLExporter.h | 6 | ||||
-rw-r--r-- | Source/SPSQLExporter.m | 18 | ||||
-rw-r--r-- | Source/SPXMLExporterDelegate.m | 1 |
13 files changed, 369 insertions, 176 deletions
diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 3f69b268..3ba923ff 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -175,6 +175,14 @@ typedef enum SPEncodingEUCKRKorean = 180 } SPEncodingTypes; +// File compression formats +typedef enum +{ + SPNoCompression = 0, + SPGzipCompression = 1, + SPBzip2Compression = 2 +} SPFileCompressionFormat; + // 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/SPDataImport.m b/Source/SPDataImport.m index f78af011..b82bf1bb 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -596,7 +596,7 @@ - (void)importCSVFile:(NSString *)filename { NSAutoreleasePool *importPool; - NSFileHandle *csvFileHandle; + SPFileHandle *csvFileHandle; NSMutableData *csvDataBuffer; const unsigned char *csvDataBufferBytes; NSData *fileChunk; @@ -630,7 +630,8 @@ [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Import Finished"]; // Open a filehandle for the CSV file - csvFileHandle = [NSFileHandle fileHandleForReadingAtPath:filename]; + csvFileHandle = [SPFileHandle fileHandleForReadingAtPath:filename]; + if (!csvFileHandle) { SPBeginAlertSheet(NSLocalizedString(@"Import Error", @"Import Error title"), NSLocalizedString(@"OK", @"OK button"), @@ -1278,13 +1279,17 @@ if ( [selectedFilenames count] != 1 ) return; pathExtension = [[[selectedFilenames objectAtIndex:0] pathExtension] uppercaseString]; - // If a file has extension ".gz", indicating gzip, fetch the next extension - if ([pathExtension isEqualToString:@"GZ"]) { + // If the file has an extension '.gz' or '.bz2' indicating gzip or bzip2 compression, fetch the next extension + if ([pathExtension isEqualToString:@"GZ"] || [pathExtension isEqualToString:@"BZ2"]) { NSMutableString *pathString = [NSMutableString stringWithString:[selectedFilenames objectAtIndex:0]]; - [pathString deleteCharactersInRange:NSMakeRange([pathString length]-3, 3)]; - pathExtension = [[pathString pathExtension] uppercaseString]; + + BOOL isGzip = [pathExtension isEqualToString:@"GZ"]; + + [pathString deleteCharactersInRange:NSMakeRange([pathString length] - (isGzip ? 3 : 4), (isGzip ? 3 : 4))]; + + pathExtension = [[pathString pathExtension] uppercaseString]; } - + if ([pathExtension isEqualToString:@"SQL"]) { [importFormatPopup selectItemWithTitle:@"SQL"]; [self changeFormat:self]; @@ -1347,7 +1352,7 @@ } /** - * + * Selectable toolbar identifiers. */ - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar { @@ -1363,7 +1368,7 @@ } /** - * + * Displays the import error sheet with the supplied error message. */ - (void)showErrorSheetWithMessage:(NSString*)message { @@ -1381,9 +1386,6 @@ [errorsSheet makeKeyWindow]; } -/** - * - */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { [sheet orderOut:self]; diff --git a/Source/SPDotExporter.m b/Source/SPDotExporter.m index 88b859db..f10e1c97 100644 --- a/Source/SPDotExporter.m +++ b/Source/SPDotExporter.m @@ -153,6 +153,7 @@ { // Check for cancellation flag if ([self isCancelled]) { + [fkInfo release]; [pool release]; return; } diff --git a/Source/SPExportController.h b/Source/SPExportController.h index cedebdc3..b63f7c73 100644 --- a/Source/SPExportController.h +++ b/Source/SPExportController.h @@ -69,8 +69,8 @@ IBOutlet NSView *exportAdvancedOptionsView; IBOutlet NSButton *exportAdvancedOptionsViewLabelButton; IBOutlet NSButton *exportUseUTF8BOMButton; - IBOutlet NSButton *exportCompressOutputFile; IBOutlet NSButton *exportProcessLowMemoryButton; + IBOutlet NSPopUpButton *exportOutputCompressionFormatPopupButton; IBOutlet BWAnchoredButtonBar *exportTableListButtonBar; @@ -250,6 +250,7 @@ - (IBAction)changeExportOutputPath:(id)sender; - (IBAction)refreshTableList:(id)sender; - (IBAction)selectDeselectAllTables:(id)sender; +- (IBAction)changeExportCompressionFormat:(id)sender; - (IBAction)toggleCustomFilenameFormatView:(id)sender; - (IBAction)toggleAdvancedExportOptionsView:(id)sender; - (IBAction)exportCustomQueryResultAsFormat:(id)sender; diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 7ccd296d..89f4fdce 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -117,6 +117,9 @@ // Prevents the background colour from changing when clicked [[exportCustomFilenameViewLabelButton cell] setHighlightsBy:NSNoCellMask]; + // Set the progress indicator's max value + [exportProgressIndicator setMaxValue:(NSInteger)[exportProgressIndicator bounds].size.width]; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); // If found the set the default path to the user's desktop, otherwise use their home directory @@ -523,6 +526,14 @@ } /** + * Updates the default filename extenstion based on the selected output compression format. + */ +- (IBAction)changeExportCompressionFormat:(id)sender +{ + [self _updateDisplayedExportFilename]; +} + +/** * Toggles the state of the custom filename format token fields. */ - (IBAction)toggleCustomFilenameFormatView:(id)sender @@ -770,7 +781,10 @@ switch (exportType) { case SPSQLExport: - extension = ([exportCompressOutputFile state]) ? [NSString stringWithFormat:@"%@.gz", SPFileExtensionSQL] : SPFileExtensionSQL; + extension = SPFileExtensionSQL; + break; + case SPCSVExport: + extension = @"csv"; break; case SPXMLExport: extension = @"xml"; @@ -780,6 +794,18 @@ break; } + if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression) { + + SPFileCompressionFormat compressionFormat = [exportOutputCompressionFormatPopupButton indexOfSelectedItem]; + + if ([extension length] > 0) { + extension = [extension stringByAppendingPathExtension:(compressionFormat == SPGzipCompression) ? @"gz" : @"bz2"]; + } + else { + extension = (compressionFormat == SPGzipCompression) ? @"gz" : @"bz2"; + } + } + return extension; } diff --git a/Source/SPExportInitializer.m b/Source/SPExportInitializer.m index 2478b1b3..fab11da9 100644 --- a/Source/SPExportInitializer.m +++ b/Source/SPExportInitializer.m @@ -187,6 +187,8 @@ } } + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self _currentDefaultExportFileExtension]]]; + singleFileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; } @@ -267,17 +269,11 @@ [sqlExporter setSqlOutputIncludeUTF8BOM:[exportUseUTF8BOMButton state]]; [sqlExporter setSqlOutputEncodeBLOBasHex:[exportSQLBLOBFieldsAsHexCheck state]]; - [sqlExporter setSqlOutputCompressFile:[exportCompressOutputFile state]]; [sqlExporter setSqlOutputIncludeErrors:[exportSQLIncludeErrorsCheck state]]; [sqlExporter setSqlInsertAfterNValue:[exportSQLInsertNValueTextField integerValue]]; [sqlExporter setSqlInsertDivider:[exportSQLInsertDividerPopUpButton indexOfSelectedItem]]; - // Set generic properties - [sqlExporter setConnection:connection]; - [sqlExporter setExportOutputEncoding:[connection encoding]]; - [sqlExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; - // Cache the current connection encoding then change it to UTF-8 to allow SQL dumps to work sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[tableDocumentInstance connectionEncoding]]; sqlPreviousConnectionEncodingViaLatin1 = [tableDocumentInstance connectionEncodingViaLatin1:nil]; @@ -286,15 +282,10 @@ [sqlExporter setSqlExportTables:exportTables]; - // Set the exporter's max progress - [sqlExporter setExportMaxProgress:((NSInteger)[exportProgressIndicator bounds].size.width)]; - - // Set the progress bar's max value - [exportProgressIndicator setMaxValue:[sqlExporter exportMaxProgress]]; - // Create custom filename if required [exportFilename setString:(createCustomFilename) ? [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil] : [NSString stringWithFormat:@"%@_%@", [tableDocumentInstance database], [[NSDate date] descriptionWithCalendarFormat:@"%Y-%m-%d" timeZone:nil locale:nil]]]; - [exportFilename setString:[exportFilename stringByAppendingPathExtension:([exportCompressOutputFile state]) ? [NSString stringWithFormat:@"%@.gz", SPFileExtensionSQL] : SPFileExtensionSQL]]; + + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self _currentDefaultExportFileExtension]]]; SPFileHandle *fileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; @@ -333,7 +324,7 @@ } } - [exportFilename setString:[exportFilename stringByAppendingPathExtension:@"xml"]]; + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self _currentDefaultExportFileExtension]]]; singleFileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; } @@ -382,15 +373,9 @@ SPDotExporter *dotExporter = [[SPDotExporter alloc] initWithDelegate:self]; [dotExporter setDotTableData:tableDataInstance]; - [dotExporter setDotDatabaseHost:[tableDocumentInstance host]]; [dotExporter setDotDatabaseName:[tableDocumentInstance database]]; [dotExporter setDotDatabaseVersion:[tableDocumentInstance mySQLVersion]]; - - // Set generic properties - [dotExporter setConnection:connection]; - [dotExporter setExportOutputEncoding:[connection encoding]]; - [dotExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; // Cache the current connection encoding then change it to UTF-8 to allow SQL dumps to work sqlPreviousConnectionEncoding = [[NSString alloc] initWithString:[tableDocumentInstance connectionEncoding]]; @@ -400,12 +385,6 @@ [dotExporter setDotExportTables:exportTables]; - // Set the exporter's max progress - [dotExporter setExportMaxProgress:(NSInteger)[exportProgressIndicator bounds].size.width]; - - // Set the progress bar's max value - [exportProgressIndicator setMaxValue:[dotExporter exportMaxProgress]]; - // Create custom filename if required if (createCustomFilename) { [exportFilename setString:[self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:nil]]; @@ -414,7 +393,7 @@ [exportFilename setString:[tableDocumentInstance database]]; } - [exportFilename setString:[exportFilename stringByAppendingPathExtension:@"dot"]]; + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self _currentDefaultExportFileExtension]]]; SPFileHandle *fileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; @@ -424,6 +403,17 @@ [dotExporter release]; } + + // For each of the created exporters, set their generic properties + for (SPExporter *exporter in exporters) + { + [exporter setConnection:connection]; + [exporter setExportOutputEncoding:[connection encoding]]; + [exporter setExportMaxProgress:(NSInteger)[exportProgressIndicator bounds].size.width]; + [exporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; + [exporter setExportOutputCompressionFormat:[exportOutputCompressionFormatPopupButton indexOfSelectedItem]]; + [exporter setExportOutputCompressFile:([exportOutputCompressionFormatPopupButton indexOfSelectedItem] != SPNoCompression)]; + } // Add the first exporter to the operation queue [operationQueue addOperation:[exporters objectAtIndex:0]]; @@ -451,7 +441,6 @@ } [csvExporter setCsvTableData:tableDataInstance]; - [csvExporter setCsvOutputFieldNames:[exportCSVIncludeFieldNamesCheck state]]; [csvExporter setCsvFieldSeparatorString:[exportCSVFieldsTerminatedField stringValue]]; [csvExporter setCsvEnclosingCharacterString:[exportCSVFieldsWrappedField stringValue]]; @@ -476,21 +465,14 @@ else { [exportFilename setString:(dataArray) ? [tableDocumentInstance database] : table]; } - + + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self _currentDefaultExportFileExtension]]]; + fileHandle = [self getFileHandleForFilePath:[[exportPathField stringValue] stringByAppendingPathComponent:exportFilename]]; [csvExporter setExportOutputFileHandle:fileHandle]; } - // Set generic properties - [csvExporter setConnection:connection]; - [csvExporter setExportOutputEncoding:[connection encoding]]; - [csvExporter setExportMaxProgress:((NSInteger)[exportProgressIndicator bounds].size.width)]; - [csvExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; - - // Set the progress bar's max value - [exportProgressIndicator setMaxValue:[csvExporter exportMaxProgress]]; - return [csvExporter autorelease]; } @@ -520,7 +502,7 @@ // If required create separate files if ((exportSource == SPTableExport) && exportToMultipleFiles && (exportTableCount > 0)) { [exportFilename setString:[[exportPathField stringValue] stringByAppendingPathComponent:table]]; - [exportFilename setString:[exportFilename stringByAppendingPathExtension:@"xml"]]; + [exportFilename setString:[exportFilename stringByAppendingPathExtension:[self _currentDefaultExportFileExtension]]]; fileHandle = [self getFileHandleForFilePath:exportFilename]; @@ -530,15 +512,6 @@ [xmlExporter setExportOutputFileHandle:fileHandle]; } - // Set generic properties - [xmlExporter setConnection:connection]; - [xmlExporter setExportOutputEncoding:[connection encoding]]; - [xmlExporter setExportMaxProgress:((NSInteger)[exportProgressIndicator bounds].size.width)]; - [xmlExporter setExportUsingLowMemoryBlockingStreaming:[exportProcessLowMemoryButton state]]; - - // Set the progress bar's max value - [exportProgressIndicator setMaxValue:[xmlExporter exportMaxProgress]]; - return [xmlExporter autorelease]; } diff --git a/Source/SPExporter.h b/Source/SPExporter.h index f3c01077..d9dadb6c 100644 --- a/Source/SPExporter.h +++ b/Source/SPExporter.h @@ -33,38 +33,78 @@ * This class is designed to be the base class of all data exporters and provide basic functionality * common to each of them. Each data exporter (i.e. CSV, SQL, XML, etc.) should be implemented as a subclass * of this class, with the end result being a modular export architecture separated by export type. All exporters - * should also conform to the SPExporterAccess protocol to allow generic access to the exporter's state and common - * functionality. + * should also have an associated delegate protocol and delegate category (of SPExportController) which conforms + * to this protocol. * * All export functionality is initially controlled by SPExportController, which is the single point within the * architecture that controls the user interface and provides user feedback. When the user starts an export * operation after selecting the available options, SPExportController should create an instance of the appropriate - * exporter (e.g. SPCSVExporter for a CSV export) and begin the export process. Any available progress information - * (defined in SPExporter as is common to all exporters) of the export should be set by the exporter and made + * exporter (e.g. SPCSVExporter for a CSV export, etc) and begin the export process. Any available progress information + * (defined in SPExporter as it's common to all exporters) of the export should be set by the exporter and made * available to SPExportController via delegate methods in order to update the user interface. * * Note that all exporters are designed to be run concurrently and as such this base class is a subclass of * NSOperation. All the data format specific subclasses have to do is override NSOperation's main() method * and implement all processes which are to be run concurrently within it. This method is automatically called - * once the exporter instance is placed on the operation queue once its ready to be run. + * once the exporter instance is placed on the operation queue once its ready to be run. It should not be + * explicity called. */ +#import "SPConstants.h" + @class MCPConnection, SPFileHandle; @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 + */ NSStringEncoding exportOutputEncoding; - - double exportMaxProgress; } @property(readwrite, retain) MCPConnection *connection; @@ -74,10 +114,26 @@ @property(readwrite, assign) BOOL exportProcessIsRunning; @property(readwrite, assign) BOOL exportUsingLowMemoryBlockingStreaming; +@property(readwrite, assign) SPFileCompressionFormat exportOutputCompressionFormat; + @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 + */ +- (BOOL)exportOutputCompressFile; + +/** + * 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; + @end diff --git a/Source/SPExporter.m b/Source/SPExporter.m index b7d9f242..cd45ec50 100644 --- a/Source/SPExporter.m +++ b/Source/SPExporter.m @@ -31,6 +31,7 @@ @synthesize exportProgressValue; @synthesize exportProcessIsRunning; @synthesize exportUsingLowMemoryBlockingStreaming; +@synthesize exportOutputCompressionFormat; @synthesize exportData; @synthesize exportOutputFileHandle; @synthesize exportOutputEncoding; @@ -44,9 +45,11 @@ if ((self = [super init])) { [self setExportProgressValue:0]; [self setExportProcessIsRunning:NO]; + [self setExportOutputCompressFile:NO]; + [self setExportOutputCompressionFormat:SPNoCompression]; // Default the resulting data to an empty string - [self setExportData:@""]; + [self setExportData:[NSString string]]; // Default the output encoding to UTF-8 [self setExportOutputEncoding:NSUTF8StringEncoding]; @@ -61,11 +64,32 @@ - (void)main { @throw [NSException exceptionWithName:@"NSOperation main() Call" - reason:@"Can't call NSOperation's main() method in SPExpoter, must be overriden in subclass." + reason:@"Cannot call NSOperation's main() method in SPExpoter, must be overriden in a subclass. See SPExporter.h" userInfo:nil]; } /** + * Returns whether or not file compression is in use. + */ +- (BOOL)exportOutputCompressFile +{ + return exportOutputCompressFile; +} + +/** + * Sets whether or not the resulting output of this exporter should be compressed. + */ +- (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; + + exportOutputCompressFile = compress; + + [[self exportOutputFileHandle] setShouldWriteWithCompressionFormat:(compress) ? [self exportOutputCompressionFormat] : SPNoCompression]; +} + +/** * Get rid of the export data. */ - (void)dealloc diff --git a/Source/SPFileHandle.h b/Source/SPFileHandle.h index 05e05dd5..1bd2ef81 100644 --- a/Source/SPFileHandle.h +++ b/Source/SPFileHandle.h @@ -25,13 +25,16 @@ /** * Provides a class which aims to duplicate some of the most-used functionality - * of NSFileHandle, while also transparently supporting GZip-compressed content - * on reading; also supports GZip compression when writing. + * of NSFileHandle, while also transparently supporting gzip and bzip2 compressed content + * on reading; gzip and bzip2 compression is also supported on writing. */ #import <Cocoa/Cocoa.h> -@interface SPFileHandle : NSObject { +#import "SPConstants.h" + +@interface SPFileHandle : NSObject +{ void *wrappedFile; char *wrappedFilePath; @@ -46,57 +49,58 @@ BOOL dataWritten; BOOL allDataWritten; BOOL fileIsClosed; - BOOL useGzip; + BOOL useCompression; + + SPFileCompressionFormat compressionFormat; } - #pragma mark - #pragma mark Class methods -+ (id) fileHandleForReadingAtPath:(NSString *)path; -+ (id) fileHandleForWritingAtPath:(NSString *)path; -+ (id) fileHandleForPath:(NSString *)path mode:(int)mode; ++ (id)fileHandleForReadingAtPath:(NSString *)path; ++ (id)fileHandleForWritingAtPath:(NSString *)path; ++ (id)fileHandleForPath:(NSString *)path mode:(int)mode; #pragma mark - #pragma mark Initialisation // Returns a file handle initialised with a file -- (id) initWithFile:(void *)theFile fromPath:(const char *)path mode:(int)mode; - +- (id)initWithFile:(void *)theFile fromPath:(const char *)path mode:(int)mode; #pragma mark - #pragma mark Data reading // Reads data up to a specified number of bytes from the file -- (NSMutableData *) readDataOfLength:(NSUInteger)length; +- (NSMutableData *)readDataOfLength:(NSUInteger)length; // Returns the data to the end of the file -- (NSMutableData *) readDataToEndOfFile; +- (NSMutableData *)readDataToEndOfFile; // Returns the on-disk (raw) length of data read so far - can be used in progress bars -- (NSUInteger) realDataReadLength; +- (NSUInteger)realDataReadLength; #pragma mark - #pragma mark Data writing -// Set whether data should be written as gzipped data (defaults to NO on a fresh object) -- (void) setShouldWriteWithGzipCompression:(BOOL)useGzip; +// Set whether data should be written in the supplied compression format (defaults to NO on a fresh object) +- (void)setShouldWriteWithCompressionFormat:(SPFileCompressionFormat)useCompressionFormat; // Write the provided data to the file -- (void) writeData:(NSData *)data; +- (void)writeData:(NSData *)data; // Ensures any buffers are written to disk -- (void) synchronizeFile; +- (void)synchronizeFile; // Prevents further access to the file -- (void) closeFile; - +- (void)closeFile; #pragma mark - #pragma mark File information -// Returns whether gzip compression is enabled on the file -- (BOOL) isCompressed; +// Returns whether compression is enabled on the file +- (BOOL)isCompressed; +// Returns the compression format being used. Currently gzip or bzip2 only. +- (SPFileCompressionFormat)compressionFormat; @end diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m index bceae2aa..ac812ac7 100644 --- a/Source/SPFileHandle.m +++ b/Source/SPFileHandle.m @@ -25,6 +25,7 @@ #import "SPFileHandle.h" #import "zlib.1.2.4.h" +#import "bzlib.h" #import "pthread.h" // Define the maximum size of the background write buffer before the writing thread @@ -32,9 +33,10 @@ #define SPFH_MAX_WRITE_BUFFER_SIZE 1048576 @interface SPFileHandle (PrivateAPI) -- (void) _writeBufferToData; -@end +- (void)_writeBufferToData; + +@end @implementation SPFileHandle @@ -42,13 +44,16 @@ #pragma mark Setup and teardown /** - * Initialises and returns a SPFileHandle with a specified file (FILE or gzFile). + * Initialises and returns a SPFileHandle with a specified file (FILE, gzFile or BZFILE). * "mode" indicates the file interaction mode - currently only read-only * or write-only are supported. - * On reading, theFile should always be a gzFile; on writing, theFile is a FILE - * when compression is disabled, or a gzFile when enbled. + * + * On reading, theFile can either be one of FILE, gzFile or BZFILE depending on the attempt to + * determine whether or not the file is in a compressed format (gzip or bzip2). On writing, + * theFile is a FILE when compression is disabled, a gzFile when gzip compression is enabled + * or a BZFILE when bzip2 compression is enabled. */ -- (id) initWithFile:(void *)theFile fromPath:(const char *)path mode:(int)mode +- (id)initWithFile:(void *)theFile fromPath:(const char *)path mode:(int)mode { if (self = [super init]) { dataWritten = NO; @@ -61,26 +66,77 @@ // Check and set the mode fileMode = mode; + if (fileMode != O_RDONLY && fileMode != O_WRONLY) { [NSException raise:NSInvalidArgumentException format:@"SPFileHandle only supports read-only and write-only file modes"]; } // Instantiate the buffer pthread_mutex_init(&bufferLock, NULL); + buffer = [[NSMutableData alloc] init]; bufferDataLength = 0; bufferPosition = 0; endOfFile = NO; + + useCompression = NO; + compressionFormat = SPNoCompression; // If in read mode, set up the buffer if (fileMode == O_RDONLY) { - gzbuffer(wrappedFile, 131072); - useGzip = !gzdirect(wrappedFile); + + int i, c; + char *bzbuf = malloc(4); + const char *charFileMode = (fileMode == O_WRONLY) ? "wb" : "rb"; + + BZFILE *bzfile; + gzFile *gzfile = gzopen(path, charFileMode); + + // Set gzip buffer + gzbuffer(gzfile, 131072); + + // Get the first 3 bytes from the file + for (i = 0; (c = getc(wrappedFile)) != EOF && i < 4; bzbuf[i++] = c); + + // Test to see if the file is gzip compressed + BOOL isGzip = (!gzdirect(gzfile)); + + // Test to see if the first 2 bytes extracted from the file match the Bzip2 signature/magic number + // (BZ). The 3rd byte should be either 'h' (Huffman encoding) or 0 (Bzip1 - deprecated) to + // indicate the Bzip version. Finally, the 4th byte should be a number between 1 and 9 that indicates + // the block size used. + BOOL isBzip2 = ((bzbuf[0] == 'B') && (bzbuf[1] == 'Z') && + ((bzbuf[2] == 'h') || (bzbuf[2] == '0')) && + ((bzbuf[3] >= 0x31) && (bzbuf[3] <= 0x39))); + + free(bzbuf); + + if (isBzip2) bzfile = BZ2_bzopen(path, charFileMode); + + useCompression = (isGzip || isBzip2); + + if (useCompression) { + if (isGzip) { + compressionFormat = SPGzipCompression; + wrappedFile = gzfile; + } + else if (isBzip2) { + compressionFormat = SPBzip2Compression; + wrappedFile = bzfile; + gzclose(gzfile); + } + + fclose(theFile); + } + else { + gzclose(gzfile); + } + processingThread = nil; - + } // In write mode, set up a thread to handle writing in the background - } else if (fileMode == O_WRONLY) { - useGzip = NO; + else if (fileMode == O_WRONLY) { + useCompression = NO; processingThread = [[NSThread alloc] initWithTarget:self selector:@selector(_writeBufferToData) object:nil]; [processingThread start]; } @@ -89,7 +145,10 @@ return self; } -- (void) dealloc +/** + * Dealloc. + */ +- (void)dealloc { [self closeFile]; if (processingThread) [processingThread release]; @@ -106,7 +165,7 @@ * Retrieve and return a SPFileHandle for reading a file at the supplied * path. Returns nil if the file could not be found or opened. */ -+ (id) fileHandleForReadingAtPath:(NSString *)path ++ (id)fileHandleForReadingAtPath:(NSString *)path { return [self fileHandleForPath:path mode:O_RDONLY]; } @@ -115,57 +174,63 @@ * Retrieve and return a SPFileHandle for writing a file at the supplied * path. Returns nil if the file could not be found or opened. */ -+ (id) fileHandleForWritingAtPath:(NSString *)path ++ (id)fileHandleForWritingAtPath:(NSString *)path { return [self fileHandleForPath:path mode:O_WRONLY]; } /** * Retrieve and return a SPFileHandle for a file at the specified path, - * using the supplied file status flag. Returns nil if the file could + * using the supplied file status flag. Returns nil if the file could * not be found or opened. */ -+ (id) fileHandleForPath:(NSString *)path mode:(int)mode ++ (id)fileHandleForPath:(NSString *)path mode:(int)mode { - // Retrieves the path in a filesystem-appropriate format and encoding const char *pathRepresentation = [path fileSystemRepresentation]; if (!pathRepresentation) return nil; // Open the file to get a file descriptor, returning on failure - FILE *theFile; - const char *theMode; - if (mode == O_WRONLY) { - theMode = "wb"; - theFile = fopen(pathRepresentation, theMode); - } else { - theMode = "rb"; - theFile = gzopen(pathRepresentation, theMode); - } - if (theFile == NULL) return nil; + const char *theMode = (mode == O_WRONLY) ? "wb" : "rb"; + + FILE *file = fopen(pathRepresentation, theMode); + + if (file == NULL) return nil; // Return an autoreleased file handle - return [[[self alloc] initWithFile:theFile fromPath:pathRepresentation mode:mode] autorelease]; + return [[[self alloc] initWithFile:file fromPath:pathRepresentation mode:mode] autorelease]; } - #pragma mark - #pragma mark Data reading /** * Reads data up to a specified number of uncompressed bytes from the file. */ -- (NSMutableData *) readDataOfLength:(NSUInteger)length -{ +- (NSMutableData *)readDataOfLength:(NSUInteger)length +{ + long theDataLength; void *theData = malloc(length); - long theDataLength = gzread(wrappedFile, theData, length); + + if (useCompression) { + if (compressionFormat == SPGzipCompression) { + theDataLength = gzread(wrappedFile, theData, length); + } + else if (compressionFormat == SPBzip2Compression) { + theDataLength = BZ2_bzread(wrappedFile, theData, length); + } + } + else { + theDataLength = fread(theData, 1, length, wrappedFile); + } + return [NSMutableData dataWithBytesNoCopy:theData length:theDataLength freeWhenDone:YES]; } /** * Returns all the data to the end of the file. */ -- (NSMutableData *) readDataToEndOfFile +- (NSMutableData *)readDataToEndOfFile { return [self readDataOfLength:NSUIntegerMax]; } @@ -175,10 +240,16 @@ * This includes any compression headers within the data, and can be used * for progress bars when processing files. */ -- (NSUInteger) realDataReadLength +- (NSUInteger)realDataReadLength { - if (fileMode == O_WRONLY) return 0; - return gzoffset(wrappedFile); + if ((fileMode == O_WRONLY) || (compressionFormat == SPBzip2Compression)) return 0; + + if (useCompression && (compressionFormat == SPGzipCompression)) { + return gzoffset(wrappedFile); + } + else { + return ftell(wrappedFile); + } } #pragma mark - @@ -189,31 +260,49 @@ * to NO on a fresh object. If this is called after data has been * written, an exception is thrown. */ -- (void) setShouldWriteWithGzipCompression:(BOOL)shouldUseGzip +- (void)setShouldWriteWithCompressionFormat:(SPFileCompressionFormat)useCompressionFormat { - if (shouldUseGzip == useGzip) return; + if (compressionFormat == useCompressionFormat) return; - if (dataWritten) [NSException raise:NSInternalInconsistencyException format:@"Cannot change compression settings when data has already been written"]; - - if (shouldUseGzip) { + // Regardless of the supplied argument, close the current file according to how it was previously opened + if (useCompression) { + if (compressionFormat == SPGzipCompression) { + gzclose(wrappedFile); + } + else if (compressionFormat == SPBzip2Compression) { + BZ2_bzclose(wrappedFile); + } + } + else { fclose(wrappedFile); - wrappedFile = gzopen(wrappedFilePath, "wb"); - gzbuffer(wrappedFile, 131072); - } else { - gzclose(wrappedFile); + } + + if (dataWritten) [NSException raise:NSInternalInconsistencyException format:@"Cannot change compression settings when data has already been written."]; + + useCompression = ((useCompressionFormat == SPGzipCompression) || (useCompressionFormat == SPBzip2Compression)); + + compressionFormat = useCompressionFormat; + + if (useCompression) { + if (compressionFormat == SPGzipCompression) { + wrappedFile = gzopen(wrappedFilePath, "wb"); + gzbuffer(wrappedFile, 131072); + } + else if (compressionFormat == SPBzip2Compression) { + wrappedFile = BZ2_bzopen(wrappedFilePath, "wb"); + } + } + else { wrappedFile = fopen(wrappedFilePath, "wb"); } - useGzip = shouldUseGzip; } - /** * Write the supplied data to the file. The data may not be written to the * disk at once (see synchronizeFile). */ -- (void) writeData:(NSData *)data +- (void)writeData:(NSData *)data { - // Throw an exception if the file is closed if (fileIsClosed) [NSException raise:NSInternalInconsistencyException format:@"Cannot write to a file handle after it has been closed"]; @@ -237,7 +326,7 @@ /** * Blocks until all data has been written to disk. */ -- (void) synchronizeFile +- (void)synchronizeFile { pthread_mutex_lock(&bufferLock); while (!allDataWritten) { @@ -252,35 +341,51 @@ * Ensure all data is written out, close any file handles, and prevent any * more data from being written to the file. */ -- (void) closeFile +- (void)closeFile { if (!fileIsClosed) { [self synchronizeFile]; - if (useGzip || fileMode == O_RDONLY) { - gzclose(wrappedFile); - } else { + + if (useCompression) { + if (compressionFormat == SPGzipCompression) { + gzclose(wrappedFile); + } + else if (compressionFormat == SPBzip2Compression) { + BZ2_bzclose(wrappedFile); + } + } + else { fclose(wrappedFile); } + if (processingThread) { if ([processingThread isExecuting]) { [processingThread cancel]; while ([processingThread isExecuting]) usleep(100); } } + fileIsClosed = YES; } } - #pragma mark - #pragma mark File information /** - * Returns whether gzip compression is enabled on the file. + * Returns whether compression is enabled on the file. */ -- (BOOL) isCompressed +- (BOOL)isCompressed { - return useGzip; + return useCompression; +} + +/** + * Returns the compression format being used. Currently gzip or bzip2 only. + */ +- (SPFileCompressionFormat)compressionFormat +{ + return compressionFormat; } @end @@ -292,7 +397,7 @@ * up in a buffer and write to disk in chunks as the buffer fills. This allows * background compression of the data when using Gzip compression. */ -- (void) _writeBufferToData +- (void)_writeBufferToData { NSAutoreleasePool *writePool = [[NSAutoreleasePool alloc] init]; @@ -315,9 +420,19 @@ // Write out the data long bufferLengthWrittenOut; - if (useGzip) { - bufferLengthWrittenOut = gzwrite(wrappedFile, [dataToBeWritten bytes], [dataToBeWritten length]); - } else { + + if (useCompression) { + switch (compressionFormat) + { + case SPGzipCompression: + bufferLengthWrittenOut = gzwrite(wrappedFile, [dataToBeWritten bytes], [dataToBeWritten length]); + break; + case SPBzip2Compression: + bufferLengthWrittenOut = BZ2_bzwrite(wrappedFile, [dataToBeWritten bytes], [dataToBeWritten length]); + break; + } + } + else { bufferLengthWrittenOut = fwrite([dataToBeWritten bytes], 1, [dataToBeWritten length], wrappedFile); } diff --git a/Source/SPSQLExporter.h b/Source/SPSQLExporter.h index 1dcacf4b..6f42dd4c 100644 --- a/Source/SPSQLExporter.h +++ b/Source/SPSQLExporter.h @@ -88,11 +88,6 @@ BOOL sqlOutputIncludeErrors; /** - * Compress output - */ - BOOL sqlOutputCompressFile; - - /** * New INSERT statement divider */ SPSQLExportInsertDivider sqlInsertDivider; @@ -127,7 +122,6 @@ @property(readwrite, assign) BOOL sqlOutputIncludeUTF8BOM; @property(readwrite, assign) BOOL sqlOutputEncodeBLOBasHex; @property(readwrite, assign) BOOL sqlOutputIncludeErrors; -@property(readwrite, assign) BOOL sqlOutputCompressFile; @property(readwrite, assign) NSUInteger sqlCurrentTableExportIndex; @property(readwrite, assign) NSUInteger sqlInsertAfterNValue; diff --git a/Source/SPSQLExporter.m b/Source/SPSQLExporter.m index 3d36fce3..7249af1d 100644 --- a/Source/SPSQLExporter.m +++ b/Source/SPSQLExporter.m @@ -52,7 +52,6 @@ @synthesize sqlOutputIncludeUTF8BOM; @synthesize sqlOutputEncodeBLOBasHex; @synthesize sqlOutputIncludeErrors; -@synthesize sqlOutputCompressFile; @synthesize sqlCurrentTableExportIndex; @synthesize sqlInsertAfterNValue; @synthesize sqlInsertDivider; @@ -140,6 +139,8 @@ { // Check for cancellation flag if ([self isCancelled]) { + [errors release]; + [sqlString release]; [pool release]; return; } @@ -159,7 +160,7 @@ [targetArray addObject:item]; } - // If required write the UTF-8 Byte Order Mark + // If required write the UTF-8 Byte Order Mark (BOM) if ([self sqlOutputIncludeUTF8BOM]) { [metaString setString:@"\xef\xbb\xbf"]; [metaString appendString:@"# ************************************************************\n"]; @@ -167,9 +168,6 @@ else { [metaString setString:@"# ************************************************************\n"]; } - - // If required set the file handle to compress it's output - [[self exportOutputFileHandle] setShouldWriteWithGzipCompression:[self sqlOutputCompressFile]]; // Add the dump header to the dump file [metaString appendString:@"# Sequel Pro SQL dump\n"]; @@ -186,12 +184,6 @@ [metaString appendString:@"/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n"]; [metaString appendString:@"/*!40101 SET NAMES utf8 */;\n"]; - // Add commands to store and disable unique checks, foreign key checks, mode and notes where supported. - // Include trailing semicolons to ensure they're run individually. Use MySQL-version based comments. - //if (sqlOutputIncludeDropSyntax) { - //[metaString appendString:@"/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n"]; - //} - [metaString appendString:@"/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n"]; [metaString appendString:@"/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n"]; [metaString appendString:@"/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\n\n"]; @@ -722,10 +714,6 @@ [metaString appendString:@"/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n"]; [metaString appendString:@"/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n"]; - //if (sqlOutputIncludeDropSyntax) { - //[metaString appendString:@"/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n"]; - //} - // Restore the client encoding to the original encoding before import [metaString appendString:@"/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n"]; [metaString appendString:@"/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n"]; diff --git a/Source/SPXMLExporterDelegate.m b/Source/SPXMLExporterDelegate.m index ace3f0fa..44deb33c 100644 --- a/Source/SPXMLExporterDelegate.m +++ b/Source/SPXMLExporterDelegate.m @@ -45,6 +45,7 @@ // Only update the progress text if this is a table export if (exportSource == SPTableExport) { + // Update the current table export index currentTableExportIndex = (exportTableCount - [exporters count]); |