aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2010-07-24 21:34:05 +0000
committerstuconnolly <stuart02@gmail.com>2010-07-24 21:34:05 +0000
commitbf1294bd1016672aa3062bb80b546bf2f8037fb3 (patch)
tree121768027638e59af7d5830855bd19b88de7e601 /Source
parenteb29a57a6860961c0caa00787d779f4cb4117c90 (diff)
downloadsequelpro-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.h8
-rw-r--r--Source/SPDataImport.m26
-rw-r--r--Source/SPDotExporter.m1
-rw-r--r--Source/SPExportController.h3
-rw-r--r--Source/SPExportController.m28
-rw-r--r--Source/SPExportInitializer.m69
-rw-r--r--Source/SPExporter.h72
-rw-r--r--Source/SPExporter.m28
-rw-r--r--Source/SPFileHandle.h46
-rw-r--r--Source/SPFileHandle.m239
-rw-r--r--Source/SPSQLExporter.h6
-rw-r--r--Source/SPSQLExporter.m18
-rw-r--r--Source/SPXMLExporterDelegate.m1
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]);