aboutsummaryrefslogtreecommitdiffstats
path: root/Source
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2010-04-12 00:08:40 +0000
committerrowanbeentje <rowan@beent.je>2010-04-12 00:08:40 +0000
commitebfd8ca1dac81755451a22e364daa851992b386e (patch)
treeebc4ec3dbdbdb8e72f747d849a85a6a497bf6ea4 /Source
parentcd9e9490ce6f073514cab18731cc0553f1c750d1 (diff)
downloadsequelpro-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.h3
-rw-r--r--Source/SPConstants.m3
-rw-r--r--Source/SPFileHandle.h78
-rw-r--r--Source/SPFileHandle.m287
-rw-r--r--Source/TableDocument.m1
-rw-r--r--Source/TableDump.h18
-rw-r--r--Source/TableDump.m108
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