diff options
author | rowanbeentje <rowan@beent.je> | 2010-04-12 00:08:40 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2010-04-12 00:08:40 +0000 |
commit | ebfd8ca1dac81755451a22e364daa851992b386e (patch) | |
tree | ebc4ec3dbdbdb8e72f747d849a85a6a497bf6ea4 /Source | |
parent | cd9e9490ce6f073514cab18731cc0553f1c750d1 (diff) | |
download | sequelpro-ebfd8ca1dac81755451a22e364daa851992b386e.tar.gz sequelpro-ebfd8ca1dac81755451a22e364daa851992b386e.tar.bz2 sequelpro-ebfd8ca1dac81755451a22e364daa851992b386e.zip |
Add a new SPFileHandle class to support gzip compression and writing on a background thread, and integrate for SQL import:
- Implement streaming reading of gzip-compressed files for SQL import
- Support exporting SQL dumps into a gzip-compressed file
- SPFileHandle supports the most-used subset of NSFileHandle commands for easy integration
- Integrate zlib 1.2.4 for improved gzip streaming performance (and support for custom buffer sizes and file offset positions)
This implements Issue #571 .
Diffstat (limited to 'Source')
-rw-r--r-- | Source/SPConstants.h | 3 | ||||
-rw-r--r-- | Source/SPConstants.m | 3 | ||||
-rw-r--r-- | Source/SPFileHandle.h | 78 | ||||
-rw-r--r-- | Source/SPFileHandle.m | 287 | ||||
-rw-r--r-- | Source/TableDocument.m | 1 | ||||
-rw-r--r-- | Source/TableDump.h | 18 | ||||
-rw-r--r-- | Source/TableDump.m | 108 |
7 files changed, 452 insertions, 46 deletions
diff --git a/Source/SPConstants.h b/Source/SPConstants.h index e6574f31..4bda5116 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -223,7 +223,7 @@ extern NSString *SPFavoritesSortedInReverse; extern NSString *SPPrintWarningRowLimit; extern NSString *SPDisplayServerVersionInWindowTitle; -// Import +// Import and export extern NSString *SPCSVImportFieldTerminator; extern NSString *SPCSVImportLineTerminator; extern NSString *SPCSVImportFieldEnclosedBy; @@ -231,6 +231,7 @@ extern NSString *SPCSVImportFieldEscapeCharacter; extern NSString *SPCSVImportFirstLineIsHeader; extern NSString *SPCSVFieldImportMappingAlignment; extern NSString *SPImportClipboardTempFileNamePrefix; +extern NSString *SPSQLExportUseCompression; // Misc extern NSString *SPContentFilters; diff --git a/Source/SPConstants.m b/Source/SPConstants.m index f0d12e51..da9e6b10 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -137,7 +137,7 @@ NSString *SPFavoritesSortedInReverse = @"FavoritesSortedInReverse"; NSString *SPPrintWarningRowLimit = @"PrintWarningRowLimit"; NSString *SPDisplayServerVersionInWindowTitle = @"DisplayServerVersionInWindowTitle"; -// Import +// Import and export NSString *SPCSVImportFieldEnclosedBy = @"CSVImportFieldEnclosedBy"; NSString *SPCSVImportFieldEscapeCharacter = @"CSVImportFieldEscapeCharacter"; NSString *SPCSVImportFieldTerminator = @"CSVImportFieldTerminator"; @@ -145,6 +145,7 @@ NSString *SPCSVImportFirstLineIsHeader = @"CSVImportFirstLineIsHeader" NSString *SPCSVImportLineTerminator = @"CSVImportLineTerminator"; NSString *SPCSVFieldImportMappingAlignment = @"CSVFieldImportMappingAlignment"; NSString *SPImportClipboardTempFileNamePrefix = @"/tmp/_SP_ClipBoard_Import_File_"; +NSString *SPSQLExportUseCompression = @"SQLExportUseCompression"; // Misc NSString *SPContentFilters = @"ContentFilters"; diff --git a/Source/SPFileHandle.h b/Source/SPFileHandle.h new file mode 100644 index 00000000..f614d403 --- /dev/null +++ b/Source/SPFileHandle.h @@ -0,0 +1,78 @@ +// +// SPFileHandle.h +// sequel-pro +// +// Created by Rowan Beentje on 05/04/2010. +// Copyright 2010 Arboreal. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +@interface SPFileHandle : NSObject { + void *wrappedFile; + char *wrappedFilePath; + + NSMutableData *buffer; + NSUInteger bufferDataLength; + NSUInteger bufferPosition; + BOOL endOfFile; + pthread_mutex_t bufferLock; + NSThread *processingThread; + + int fileMode; + BOOL dataWritten; + BOOL fileIsClosed; + BOOL useGzip; +} + + +#pragma mark - +#pragma mark Class methods + ++ (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; + + +#pragma mark - +#pragma mark Data reading + +// Reads data up to a specified number of bytes from the file +- (NSMutableData *) readDataOfLength:(NSUInteger)length; + +// Returns the data to the end of the file +- (NSMutableData *) readDataToEndOfFile; + +// Returns the on-disk (raw) length of data read so far - can be used in progress bars +- (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; + +// Write the provided data to the file +- (void) writeData:(NSData *)data; + +// Ensures any buffers are written to disk +- (void) synchronizeFile; + +// Prevents further access to the file +- (void) closeFile; + + +#pragma mark - +#pragma mark File information + +// Returns whether gzip compression is enabled on the file +- (BOOL) isCompressed; + + +@end diff --git a/Source/SPFileHandle.m b/Source/SPFileHandle.m new file mode 100644 index 00000000..4b28d4d6 --- /dev/null +++ b/Source/SPFileHandle.m @@ -0,0 +1,287 @@ +// +// SPFileHandle.m +// sequel-pro +// +// Created by Rowan Beentje on 05/04/2010. +// Copyright 2010 Arboreal. All rights reserved. +// + +#import "SPFileHandle.h" +#import "zlib.1.2.4.h" + +// Define the size of the background read/write buffer. This affects speed and memory usage. +#define SPFH_BUFFER_SIZE 1048576 + +@interface SPFileHandle (PrivateAPI) +- (void) _writeBufferToData; +@end + + +@implementation SPFileHandle + +#pragma mark - +#pragma mark Setup and teardown + +/** + * Initialises and returns a SPFileHandle with a specified file (FILE or gzFile). + * "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. + */ +- (id) initWithFile:(void *)theFile fromPath:(const char *)path mode:(int)mode +{ + if (self = [super init]) { + fileIsClosed = NO; + + wrappedFile = theFile; + wrappedFilePath = malloc(strlen(path) + 1); + strcpy(wrappedFilePath, path); + + // 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; + + // If in read mode, set up the buffer + if (fileMode == O_RDONLY) { + gzbuffer(wrappedFile, 131072); + useGzip = !gzdirect(wrappedFile); + processingThread = nil; + + // In write mode, set up a thread to handle writing in the background + } else if (fileMode == O_WRONLY) { + useGzip = NO; + processingThread = [[NSThread alloc] initWithTarget:self selector:@selector(_writeBufferToData) object:nil]; + [processingThread start]; + } + } + + return self; +} + +- (void) dealloc +{ + [self closeFile]; + if (processingThread) [processingThread release]; + free(wrappedFilePath); + [buffer release]; + pthread_mutex_destroy(&bufferLock); + [super dealloc]; +} + +#pragma mark - +#pragma mark Class methods + +/** + * 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 +{ + return [self fileHandleForPath:path mode:O_RDONLY]; +} + +/** + * 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 +{ + 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 + * not be found or opened. + */ ++ (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; + + // Return an autoreleased file handle + return [[[self alloc] initWithFile:theFile fromPath:pathRepresentation mode:mode] autorelease]; +} + + +#pragma mark - +#pragma mark Data reading + +// Reads data up to a specified number of bytes from the file +- (NSMutableData *) readDataOfLength:(NSUInteger)length +{ + void *theData = malloc(length); + long theDataLength = gzread(wrappedFile, theData, length); + return [NSMutableData dataWithBytesNoCopy:theData length:theDataLength freeWhenDone:YES]; +} + +// Returns the data to the end of the file +- (NSMutableData *) readDataToEndOfFile +{ + return [self readDataOfLength:NSUIntegerMax]; +} + +// Returns the on-disk (raw) length of data read so far - can be used in progress bars +- (NSUInteger) realDataReadLength +{ + if (fileMode == O_WRONLY) return 0; + return gzoffset(wrappedFile); +} + +#pragma mark - +#pragma mark Data writing + +/** + * Set whether data should be written as gzipped data, defaulting + * to NO on a fresh object. If this is called after data has been + * written, an exception is thrown. + */ +- (void) setShouldWriteWithGzipCompression:(BOOL)shouldUseGzip +{ + if (shouldUseGzip == useGzip) return; + + if (dataWritten) [NSException raise:NSInternalInconsistencyException format:@"Cannot change compression settings when data has already been written"]; + + if (shouldUseGzip) { + fclose(wrappedFile); + wrappedFile = gzopen(wrappedFilePath, "wb"); + gzbuffer(wrappedFile, 131072); + } else { + gzclose(wrappedFile); + wrappedFile = fopen(wrappedFilePath, "wb"); + } + useGzip = shouldUseGzip; +} + + +// Write the provided data to the file +- (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"]; + + // Add the data to the buffer + pthread_mutex_lock(&bufferLock); + [buffer appendData:data]; + bufferDataLength += [data length]; + + // If the buffer is large, wait for some to be written out + while (bufferDataLength > SPFH_BUFFER_SIZE) { + pthread_mutex_unlock(&bufferLock); + usleep(100); + pthread_mutex_lock(&bufferLock); + } + pthread_mutex_unlock(&bufferLock); +} + +// Ensures any buffers are written to disk +- (void) synchronizeFile +{ + pthread_mutex_lock(&bufferLock); + while (bufferDataLength) { + pthread_mutex_unlock(&bufferLock); + usleep(100); + pthread_mutex_lock(&bufferLock); + } + pthread_mutex_unlock(&bufferLock); +} + +// Prevent further access to the file +- (void)closeFile +{ + if (!fileIsClosed) { + [self synchronizeFile]; + if (useGzip || fileMode == O_RDONLY) { + gzclose(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. + */ +- (BOOL) isCompressed +{ + return useGzip; +} + +@end + +@implementation SPFileHandle (PrivateAPI) + +/** + * A method to be called on a background thread, allowing write data to build + * 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 +{ + NSAutoreleasePool *writePool = [[NSAutoreleasePool alloc] init]; + + // Process the buffer in a loop into the file, until cancelled + while (![processingThread isCancelled]) { + + // Check whether any data in the buffer needs to be written out - using thread locks for safety + pthread_mutex_lock(&bufferLock); + if (!bufferDataLength) { + pthread_mutex_unlock(&bufferLock); + usleep(1000); + continue; + } + + // Write out the data + long bufferLengthWrittenOut; + if (useGzip) { + bufferLengthWrittenOut = gzwrite(wrappedFile, [buffer bytes], bufferDataLength); + } else { + bufferLengthWrittenOut = fwrite([buffer bytes], 1, bufferDataLength, wrappedFile); + } + + // Update the buffer + CFDataDeleteBytes((CFMutableDataRef)buffer, CFRangeMake(0, bufferLengthWrittenOut)); + bufferDataLength = 0; + pthread_mutex_unlock(&bufferLock); + } + + [writePool drain]; +} + +@end diff --git a/Source/TableDocument.m b/Source/TableDocument.m index 990f26a2..de23ba80 100644 --- a/Source/TableDocument.m +++ b/Source/TableDocument.m @@ -748,7 +748,6 @@ break; } } - } /** diff --git a/Source/TableDump.h b/Source/TableDump.h index af621f68..e506a6d2 100644 --- a/Source/TableDump.h +++ b/Source/TableDump.h @@ -27,7 +27,7 @@ #import <Cocoa/Cocoa.h> #import <MCPKit/MCPKit.h> -@class SPFieldMapperController; +@class SPFieldMapperController, SPFileHandle; @interface TableDump : NSObject { @@ -83,6 +83,7 @@ IBOutlet id addTableContentSwitch; IBOutlet id addErrorsSwitch; IBOutlet id sqlFullStreamingSwitch; + IBOutlet id sqlCompressionSwitch; IBOutlet id csvFullStreamingSwitch; IBOutlet id multiCSVFullStreamingSwitch; IBOutlet id multiXMLFullStreamingSwitch; @@ -121,6 +122,8 @@ NSUInteger exportMode; NSUserDefaults *prefs; BOOL progressCancelled; + + NSSavePanel *currentExportPanel; } // IBAction methods @@ -148,21 +151,21 @@ - (NSString *) mappedUpdateSetStatementStringForRowArray:(NSArray *)csvRowArray; // Export methods -- (BOOL)dumpSelectedTablesAsSqlToFileHandle:(NSFileHandle *)fileHandle; -- (BOOL)dumpSchemaAsDotToFileHandle:(NSFileHandle *)fileHandle; +- (BOOL)dumpSelectedTablesAsSqlToFileHandle:(SPFileHandle *)fileHandle; +- (BOOL)dumpSchemaAsDotToFileHandle:(SPFileHandle *)fileHandle; - (BOOL)writeCsvForArray:(NSArray *)array orStreamingResult:(MCPStreamingResult *)streamingResult - toFileHandle:(NSFileHandle *)fileHandle + toFileHandle:(SPFileHandle *)fileHandle outputFieldNames:(BOOL)firstLine terminatedBy:(NSString *)terminated enclosedBy:(NSString *)enclosed escapedBy:(NSString *)escaped lineEnds:(NSString *)lineEnds withNumericColumns:(NSArray *)tableColumnNumericStatus totalRows:(NSInteger)totalRows silently:(BOOL)silently; - (BOOL)writeXmlForArray:(NSArray *)array orStreamingResult:(MCPStreamingResult *)streamingResult - toFileHandle:(NSFileHandle *)fileHandle + toFileHandle:(SPFileHandle *)fileHandle tableName:(NSString *)table withHeader:(BOOL)header totalRows:(NSInteger)totalRows silently:(BOOL)silently; - (NSString *)htmlEscapeString:(NSString *)string; - (NSString *)createViewPlaceholderSyntaxForView:(NSString *)viewName; -- (BOOL)exportTables:(NSArray *)selectedTables toFileHandle:(NSFileHandle *)fileHandle usingFormat:(NSString *)type usingMulti:(BOOL)multi; -- (BOOL)exportSelectedTablesToFileHandle:(NSFileHandle *)fileHandle usingFormat:(NSString *)type; +- (BOOL)exportTables:(NSArray *)selectedTables toFileHandle:(SPFileHandle *)fileHandle usingFormat:(NSString *)type usingMulti:(BOOL)multi; +- (BOOL)exportSelectedTablesToFileHandle:(SPFileHandle *)fileHandle usingFormat:(NSString *)type; // New Export methods - (IBAction)switchTab:(id)sender; @@ -174,5 +177,6 @@ // Import/export delegate notifications - (void)panelSelectionDidChange:(id)sender; +- (IBAction)updateExportCompressionSetting:(id)sender; @end diff --git a/Source/TableDump.m b/Source/TableDump.m index 983d7b6d..8d56a1d7 100644 --- a/Source/TableDump.m +++ b/Source/TableDump.m @@ -41,6 +41,7 @@ #import "SPFieldMapperController.h" #import "SPMainThreadTrampoline.h" #import "SPNotLoaded.h" +#import "SPFileHandle.h" @implementation TableDump @@ -156,9 +157,9 @@ { NSString *file; NSString *contextInfo; - NSSavePanel *savePanel = [NSSavePanel savePanel]; - [savePanel setAllowsOtherFileTypes:YES]; - [savePanel setExtensionHidden:NO]; + currentExportPanel = [NSSavePanel savePanel]; + [currentExportPanel setAllowsOtherFileTypes:YES]; + [currentExportPanel setExtensionHidden:NO]; NSString *currentDate = [[NSDate date] descriptionWithCalendarFormat:@"%Y-%m-%d" timeZone:nil locale:nil]; switch ( tag ) { @@ -166,9 +167,14 @@ // export dump exportMode = SPExportingSQL; [self reloadTables:self]; - file = [NSString stringWithFormat:@"%@_%@.sql", [tableDocumentInstance database], currentDate]; - [savePanel setRequiredFileType:@"sql"]; - [savePanel setAccessoryView:exportDumpView]; + [sqlCompressionSwitch setState:[prefs boolForKey:SPSQLExportUseCompression]?NSOnState:NSOffState]; + if ([prefs boolForKey:SPSQLExportUseCompression]) { + [currentExportPanel setRequiredFileType:@"gz"]; + } else { + [currentExportPanel setRequiredFileType:@"sql"]; + } + file = [NSString stringWithFormat:@"%@_%@.%@", [tableDocumentInstance database], currentDate, [currentExportPanel requiredFileType]]; + [currentExportPanel setAccessoryView:exportDumpView]; contextInfo = @"exportDump"; break; @@ -176,7 +182,7 @@ case 6: exportMode = SPExportingCSV; file = [NSString stringWithFormat:@"%@.csv", [tableDocumentInstance table]]; - [savePanel setAccessoryView:exportCSVView]; + [currentExportPanel setAccessoryView:exportCSVView]; [csvFullStreamingSwitch setEnabled:YES]; contextInfo = @"exportTableContentAsCSV"; break; @@ -192,7 +198,7 @@ case 8: exportMode = SPExportingCSV; file = [NSString stringWithFormat:@"%@ view.csv", [tableDocumentInstance table]]; - [savePanel setAccessoryView:exportCSVView]; + [currentExportPanel setAccessoryView:exportCSVView]; [csvFullStreamingSwitch setEnabled:NO]; contextInfo = @"exportBrowseViewAsCSV"; break; @@ -208,7 +214,7 @@ case 10: exportMode = SPExportingCSV; file = @"customresult.csv"; - [savePanel setAccessoryView:exportCSVView]; + [currentExportPanel setAccessoryView:exportCSVView]; [csvFullStreamingSwitch setEnabled:NO]; contextInfo = @"exportCustomResultAsCSV"; break; @@ -225,7 +231,7 @@ exportMode = SPExportingCSV; [self reloadTables:self]; file = [NSString stringWithFormat:@"%@.csv", [tableDocumentInstance database]]; - [savePanel setAccessoryView:exportMultipleCSVView]; + [currentExportPanel setAccessoryView:exportMultipleCSVView]; contextInfo = @"exportMultipleTablesAsCSV"; break; @@ -234,7 +240,7 @@ exportMode = SPExportingXML; [self reloadTables:self]; file = [NSString stringWithFormat:@"%@.xml", [tableDocumentInstance database]]; - [savePanel setAccessoryView:exportMultipleXMLView]; + [currentExportPanel setAccessoryView:exportMultipleXMLView]; contextInfo = @"exportMultipleTablesAsXML"; break; @@ -243,7 +249,7 @@ exportMode = SPExportingDOT; [self reloadTables:self]; file = [NSString stringWithString:[tableDocumentInstance database]]; - [savePanel setRequiredFileType:@"dot"]; + [currentExportPanel setRequiredFileType:@"dot"]; contextInfo = @"exportDot"; break; @@ -253,12 +259,12 @@ break; } - [savePanel setDelegate:self]; + [currentExportPanel setDelegate:self]; // Open the savePanel - [savePanel beginSheetForDirectory:[prefs objectForKey:@"savePath"] - file:file modalForWindow:tableWindow modalDelegate:self - didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:contextInfo]; + [currentExportPanel beginSheetForDirectory:[prefs objectForKey:@"savePath"] + file:file modalForWindow:tableWindow modalDelegate:self + didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:contextInfo]; } /** @@ -293,7 +299,7 @@ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *exportActionName = [exportAction objectForKey:@"action"]; NSString *exportFile = [exportAction objectForKey:@"filename"]; - NSFileHandle *fileHandle = nil; + SPFileHandle *fileHandle = nil; BOOL success; // Start the notification timer to allow notifications to be shown even if frontmost for long queries @@ -305,17 +311,14 @@ // Error if the file already exists and is not writable, and get a fileHandle to it. if ( [[NSFileManager defaultManager] fileExistsAtPath:exportFile] ) { if ( ![[NSFileManager defaultManager] isWritableFileAtPath:exportFile] - || !(fileHandle = [NSFileHandle fileHandleForWritingAtPath:exportFile]) ) { + || !(fileHandle = [SPFileHandle fileHandleForWritingAtPath:exportFile]) ) { SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, NSLocalizedString(@"Couldn't replace the file. Be sure that you have the necessary privileges.", @"message of panel when file cannot be replaced")); [pool release]; return; } - // Truncate the file to zero bytes - [fileHandle truncateFileAtOffset:0]; - - // Otherwise attempt to create a file + // Otherwise attempt to create a file } else { if ( ![[NSFileManager defaultManager] createFileAtPath:exportFile contents:[NSData data] attributes:nil] ) { SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, @@ -325,7 +328,7 @@ } // Retrieve a filehandle for the file, attempting to delete it on failure. - fileHandle = [NSFileHandle fileHandleForWritingAtPath:exportFile]; + fileHandle = [SPFileHandle fileHandleForWritingAtPath:exportFile]; if ( !fileHandle ) { [[NSFileManager defaultManager] removeFileAtPath:exportFile handler:nil]; SPBeginAlertSheet(NSLocalizedString(@"Error", @"error"), NSLocalizedString(@"OK", @"OK button"), nil, nil, tableWindow, self, nil, nil, nil, @@ -337,6 +340,7 @@ // Export the tables selected in the MySQL export sheet to a file if ( [exportActionName isEqualToString:@"exportDump"] ) { + [fileHandle setShouldWriteWithGzipCompression:([sqlCompressionSwitch state] == NSOnState)]; success = [self dumpSelectedTablesAsSqlToFileHandle:fileHandle]; // Export the full resultset for the currently selected table to a file in CSV format @@ -578,7 +582,7 @@ - (void) importSQLFile:(NSString *)filename { NSAutoreleasePool *importPool; - NSFileHandle *sqlFileHandle; + SPFileHandle *sqlFileHandle; NSMutableData *sqlDataBuffer; const unsigned char *sqlDataBufferBytes; NSData *fileChunk; @@ -593,6 +597,7 @@ NSInteger dataBufferLength = 0; NSInteger dataBufferPosition = 0; NSInteger dataBufferLastQueryEndPosition = 0; + BOOL fileIsCompressed; BOOL importSQLAsUTF8 = YES; BOOL allDataRead = NO; NSStringEncoding sqlEncoding = NSUTF8StringEncoding; @@ -602,7 +607,7 @@ [[SPGrowlController sharedGrowlController] setVisibilityForNotificationName:@"Import Finished"]; // Open a filehandle for the SQL file - sqlFileHandle = [NSFileHandle fileHandleForReadingAtPath:filename]; + sqlFileHandle = [SPFileHandle fileHandleForReadingAtPath:filename]; if (!sqlFileHandle) { SPBeginAlertSheet(NSLocalizedString(@"Import Error title", @"Import Error"), NSLocalizedString(@"OK button label", @"OK button"), @@ -612,6 +617,7 @@ [[NSFileManager defaultManager] removeItemAtPath:filename error:nil]; return; } + fileIsCompressed = [sqlFileHandle isCompressed]; // Grab the file length fileTotalLength = [[[[NSFileManager defaultManager] attributesOfItemAtPath:filename error:NULL] objectForKey:NSFileSize] longLongValue]; @@ -757,9 +763,15 @@ queriesPerformed++; // Update the progress bar - [singleProgressBar setDoubleValue:fileProcessedLength]; - [singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"), - [NSString stringForByteSize:fileProcessedLength], [NSString stringForByteSize:fileTotalLength]]]; + if (fileIsCompressed) { + [singleProgressBar setDoubleValue:[sqlFileHandle realDataReadLength]]; + [singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of SQL", @"SQL import progress text where total size is unknown"), + [NSString stringForByteSize:fileProcessedLength]]]; + } else { + [singleProgressBar setDoubleValue:fileProcessedLength]; + [singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"), + [NSString stringForByteSize:fileProcessedLength], [NSString stringForByteSize:fileTotalLength]]]; + } } // If all the data has been read, break out of the processing loop @@ -1594,7 +1606,7 @@ /* Dump the selected tables to a file handle in SQL format. */ -- (BOOL)dumpSelectedTablesAsSqlToFileHandle:(NSFileHandle *)fileHandle +- (BOOL)dumpSelectedTablesAsSqlToFileHandle:(SPFileHandle *)fileHandle { NSInteger i,j,t,rowCount, colCount, lastProgressValue, queryLength; NSInteger progressBarWidth; @@ -2122,7 +2134,7 @@ See here for language syntax: http://www.graphviz.org/doc/info/lang.html (Not the easiest to decode) */ -- (BOOL)dumpSchemaAsDotToFileHandle:(NSFileHandle *)fileHandle +- (BOOL)dumpSchemaAsDotToFileHandle:(SPFileHandle *)fileHandle { NSMutableString *metaString = [NSMutableString string]; NSInteger progressBarWidth; @@ -2267,7 +2279,7 @@ /* * Takes an array, or a streaming result set, and writes the appropriate data - * in CSV format to the supplied NSFileHandle. + * in CSV format to the supplied SPFileHandle. * The field terminators, quotes and escape characters should all be supplied * together with the line terminators; if an array of numeric column types is * supplied, processing of rows is significantly sped up as each field does not @@ -2276,7 +2288,7 @@ * for arrays, this must be accurate, but for streaming result sets it is only * used for drawing the progress bar. */ -- (BOOL)writeCsvForArray:(NSArray *)array orStreamingResult:(MCPStreamingResult *)streamingResult toFileHandle:(NSFileHandle *)fileHandle +- (BOOL)writeCsvForArray:(NSArray *)array orStreamingResult:(MCPStreamingResult *)streamingResult toFileHandle:(SPFileHandle *)fileHandle outputFieldNames:(BOOL)outputFieldNames terminatedBy:(NSString *)fieldSeparatorString enclosedBy:(NSString *)enclosingString @@ -2521,14 +2533,14 @@ /* * Takes an array, or streaming result reference, and writes it in XML - * format to the supplied NSFileHandle. For output, also takes a table + * format to the supplied SPFileHandle. For output, also takes a table * name for tag construction, and a toggle to control whether the header * is output. * Also takes a totalRows parameter, which is used for drawing progress bars - * for arrays, this must be accurate, but for streaming result sets it is only * used for drawing the progress bar. */ -- (BOOL)writeXmlForArray:(NSArray *)array orStreamingResult:(MCPStreamingResult *)streamingResult toFileHandle:(NSFileHandle *)fileHandle tableName:(NSString *)table withHeader:(BOOL)header totalRows:(NSInteger)totalRows silently:(BOOL)silently +- (BOOL)writeXmlForArray:(NSArray *)array orStreamingResult:(MCPStreamingResult *)streamingResult toFileHandle:(SPFileHandle *)fileHandle tableName:(NSString *)table withHeader:(BOOL)header totalRows:(NSInteger)totalRows silently:(BOOL)silently { NSAutoreleasePool *xmlExportPool; NSStringEncoding tableEncoding = [MCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] UTF8String]]; @@ -2693,7 +2705,7 @@ Processes the selected tables within the multiple table export accessory view and passes them to be exported. */ -- (BOOL)exportSelectedTablesToFileHandle:(NSFileHandle *)fileHandle usingFormat:(NSString *)type +- (BOOL)exportSelectedTablesToFileHandle:(SPFileHandle *)fileHandle usingFormat:(NSString *)type { NSInteger i; NSMutableArray *selectedTables = [NSMutableArray array]; @@ -2712,7 +2724,7 @@ Walks through the selected tables and exports them to a file handle. The export type must be "csv" for CSV format, and "xml" for XML format. */ -- (BOOL)exportTables:(NSArray *)selectedTables toFileHandle:(NSFileHandle *)fileHandle usingFormat:(NSString *)type usingMulti:(BOOL)multi +- (BOOL)exportTables:(NSArray *)selectedTables toFileHandle:(SPFileHandle *)fileHandle usingFormat:(NSString *)type usingMulti:(BOOL)multi { NSInteger i, j; MCPResult *queryResult; @@ -3088,6 +3100,14 @@ // If a single file is selected and the extension is recognised, change the format dropdown automatically 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"]) { + NSMutableString *pathString = [NSMutableString stringWithString:[selectedFilenames objectAtIndex:0]]; + [pathString deleteCharactersInRange:NSMakeRange([pathString length]-3, 3)]; + pathExtension = [[pathString pathExtension] uppercaseString]; + } + if ([pathExtension isEqualToString:@"SQL"]) { [importFormatPopup selectItemWithTitle:@"SQL"]; [self changeFormat:self]; @@ -3123,6 +3143,22 @@ } } +/** + * When the compression setting on export is altered, update the filename + * and if appropriate the required extension. + */ +- (IBAction)updateExportCompressionSetting:(id)sender +{ + if (exportMode == SPExportingSQL) { + if ([sender state] == NSOnState) { + [currentExportPanel setAllowedFileTypes:[NSArray arrayWithObject:@"gz"]]; + } else { + [currentExportPanel setAllowedFileTypes:[NSArray arrayWithObject:@"sql"]]; + } + [prefs setBool:([sender state] == NSOnState) forKey:SPSQLExportUseCompression]; + } +} + #pragma mark - #pragma mark Other |