Permalink
Browse files

Work around some cases of „background thread manipulating ui“ during …

…CSV import (as reported by Xcode)
  • Loading branch information...
dmoagx committed Jan 20, 2018
1 parent d403528 commit 24ccb6bd602f4d362ab6d47f2dfe8edc2acebbcf
Showing with 107 additions and 77 deletions.
  1. +106 −76 Source/SPDataImport.m
  2. +1 −1 Source/SPMainThreadTrampoline.h
View
@@ -54,7 +54,8 @@
@interface SPDataImport ()
- (void)_importBackgroundProcess:(NSString *)filename;
- (void)_startBackgroundImportTaskForFilename:(NSString *)filename;
- (void)_importBackgroundProcess:(NSDictionary *)userInfo;
- (void)_resetFieldMappingGlobals;
@end
@@ -255,7 +256,7 @@ - (void)importFromClipboardSheetDidEnd:(id)sheet returnCode:(NSInteger)returnCod
if (importFileName == nil) return;
// Begin import process
[NSThread detachNewThreadWithName:SPCtxt(@"SPDataImport background import task",tableDocumentInstance) target:self selector:@selector(_importBackgroundProcess:) object:importFileName];
[self _startBackgroundImportTaskForFilename:importFileName];
}
@@ -324,7 +325,7 @@ - (void)importFile
if (importFileName == nil) return;
// Begin the import process
[NSThread detachNewThreadWithName:SPCtxt(@"SPDataImport background import task",tableDocumentInstance) target:self selector:@selector(_importBackgroundProcess:) object:importFileName];
[self _startBackgroundImportTaskForFilename:importFileName];
}];
}
@@ -334,7 +335,7 @@ - (void)importFile
- (void)startSQLImportProcessWithFile:(NSString *)filename
{
[importFormatPopup selectItemWithTitle:@"SQL"];
[NSThread detachNewThreadWithName:SPCtxt(@"SPDataImport background import task",tableDocumentInstance) target:self selector:@selector(_importBackgroundProcess:) object:filename];
[self _startBackgroundImportTaskForFilename:filename];
}
#pragma mark -
@@ -748,8 +749,7 @@ - (void)importCSVFile:(NSString *)filename
NSUInteger i;
BOOL allDataRead = NO;
BOOL insertBaseStringHasEntries;
NSStringEncoding csvEncoding = [mySQLConnection stringEncoding];
__block NSStringEncoding csvEncoding;
fieldMappingArray = nil;
fieldMappingGlobalValueArray = nil;
@@ -799,14 +799,19 @@ - (void)importCSVFile:(NSString *)filename
[tableDocumentInstance setQueryMode:SPImportExportQueryMode];
// Determine the file encoding. The first item in the encoding menu is "Autodetect"; if
// this is selected, attempt to detect the encoding of the file.
if (![importEncodingPopup indexOfSelectedItem]) {
SPMainQSync(^{
// Determine the file encoding. The first item in the encoding menu is "Autodetect";
if (![importEncodingPopup indexOfSelectedItem]) {
csvEncoding = 0;
}
// Otherwise, get the encoding to use from the menu
else {
csvEncoding = [importEncodingPopup selectedTag];
}
});
// if "Autodetect" is selected, attempt to detect the encoding of the file.
if (!csvEncoding) {
csvEncoding = [[NSFileManager defaultManager] detectEncodingforFileAtPath:filename];
// Otherwise, get the encoding to use from the menu
} else {
csvEncoding = [importEncodingPopup selectedTag];
}
// Read in the file in a loop. The loop actually needs to perform three tasks: read in
@@ -816,24 +821,26 @@ - (void)importCSVFile:(NSString *)filename
// other two must therefore be performed where possible.
csvParser = [[SPCSVParser alloc] init];
// Store settings in prefs
[prefs setObject:[importFieldsEnclosedField stringValue] forKey:SPCSVImportFieldEnclosedBy];
[prefs setObject:[importFieldsEscapedField stringValue] forKey:SPCSVImportFieldEscapeCharacter];
[prefs setObject:[importLinesTerminatedField stringValue] forKey:SPCSVImportLineTerminator];
[prefs setObject:[importFieldsTerminatedField stringValue] forKey:SPCSVImportFieldTerminator];
[prefs setBool:[importFieldNamesSwitch state] forKey:SPCSVImportFirstLineIsHeader];
// Take CSV import setting from accessory view
[csvParser setFieldTerminatorString:[importFieldsTerminatedField stringValue] convertDisplayStrings:YES];
[csvParser setLineTerminatorString:[importLinesTerminatedField stringValue] convertDisplayStrings:YES];
[csvParser setFieldQuoteString:[importFieldsEnclosedField stringValue] convertDisplayStrings:YES];
if ([[importFieldsEscapedField stringValue] isEqualToString:@"\\ or \""]) {
[csvParser setEscapeString:@"\\" convertDisplayStrings:NO];
} else {
[csvParser setEscapeString:[importFieldsEscapedField stringValue] convertDisplayStrings:YES];
[csvParser setEscapeStringsAreMatchedStrictly:YES];
}
[csvParser setNullReplacementString:[prefs objectForKey:SPNullValue]];
SPMainQSync(^{
// Store settings in prefs
[prefs setObject:[importFieldsEnclosedField stringValue] forKey:SPCSVImportFieldEnclosedBy];
[prefs setObject:[importFieldsEscapedField stringValue] forKey:SPCSVImportFieldEscapeCharacter];
[prefs setObject:[importLinesTerminatedField stringValue] forKey:SPCSVImportLineTerminator];
[prefs setObject:[importFieldsTerminatedField stringValue] forKey:SPCSVImportFieldTerminator];
[prefs setBool:[importFieldNamesSwitch state] forKey:SPCSVImportFirstLineIsHeader];
// Take CSV import setting from accessory view
[csvParser setFieldTerminatorString:[importFieldsTerminatedField stringValue] convertDisplayStrings:YES];
[csvParser setLineTerminatorString:[importLinesTerminatedField stringValue] convertDisplayStrings:YES];
[csvParser setFieldQuoteString:[importFieldsEnclosedField stringValue] convertDisplayStrings:YES];
if ([[importFieldsEscapedField stringValue] isEqualToString:@"\\ or \""]) {
[csvParser setEscapeString:@"\\" convertDisplayStrings:NO];
} else {
[csvParser setEscapeString:[importFieldsEscapedField stringValue] convertDisplayStrings:YES];
[csvParser setEscapeStringsAreMatchedStrictly:YES];
}
[csvParser setNullReplacementString:[prefs objectForKey:SPNullValue]];
});
csvDataBuffer = [[NSMutableData alloc] init];
importPool = [[NSAutoreleasePool alloc] init];
@@ -878,7 +885,7 @@ - (void)importCSVFile:(NSString *)filename
dataBufferLength = [csvDataBuffer length];
for ( ; dataBufferPosition < dataBufferLength || allDataRead; dataBufferPosition++) {
if (csvDataBufferBytes[dataBufferPosition] == 0x0A || csvDataBufferBytes[dataBufferPosition] == 0x0D || allDataRead) {
#warning This EOL detection logic will break for multibyte encodings (like UTF16)!
// Keep reading through any other line endings
while (dataBufferPosition + 1 < dataBufferLength
&& (csvDataBufferBytes[dataBufferPosition+1] == 0x0A
@@ -891,17 +898,19 @@ - (void)importCSVFile:(NSString *)filename
csvString = [[NSString alloc] initWithData:[csvDataBuffer subdataWithRange:NSMakeRange(dataBufferLastQueryEndPosition, dataBufferPosition - dataBufferLastQueryEndPosition)] encoding:csvEncoding];
if (!csvString) {
[self closeAndStopProgressSheet];
NSString *displayEncoding;
if (![importEncodingPopup indexOfSelectedItem]) {
displayEncoding = [NSString stringWithFormat:@"%@ - %@", [importEncodingPopup titleOfSelectedItem], [NSString localizedNameOfStringEncoding:csvEncoding]];
} else {
displayEncoding = [NSString localizedNameOfStringEncoding:csvEncoding];
}
SPOnewayAlertSheet(
SP_FILE_READ_ERROR_STRING,
[tableDocumentInstance parentWindow],
[NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file, as it could not be read using the encoding you selected (%@).\n\nOnly %ld rows were imported.", @"CSV encoding read error"), displayEncoding, (long)rowsImported]
);
SPMainQSync(^{
NSString *displayEncoding;
if (![importEncodingPopup indexOfSelectedItem]) {
displayEncoding = [NSString stringWithFormat:@"%@ - %@", [importEncodingPopup titleOfSelectedItem], [NSString localizedNameOfStringEncoding:csvEncoding]];
} else {
displayEncoding = [NSString localizedNameOfStringEncoding:csvEncoding];
}
SPOnewayAlertSheet(
SP_FILE_READ_ERROR_STRING,
[tableDocumentInstance parentWindow],
[NSString stringWithFormat:NSLocalizedString(@"An error occurred when reading the file, as it could not be read using the encoding you selected (%@).\n\nOnly %ld rows were imported.", @"CSV encoding read error"), displayEncoding, (long)rowsImported]
);
});
[csvParser release];
[csvDataBuffer release];
[parsedRows release];
@@ -939,7 +948,7 @@ - (void)importCSVFile:(NSString *)filename
// If valid, add the row array and length to local storage
if (csvRowArray) {
[parsedRows addObject:csvRowArray];
[parsePositions addObject:[NSNumber numberWithUnsignedInteger:[csvParser totalLengthParsed]]];
[parsePositions addObject:@([csvParser totalLengthParsed])];
}
// If we have no field mapping array, and either the first hundred rows or all
@@ -966,7 +975,7 @@ - (void)importCSVFile:(NSString *)filename
[singleProgressBar setMaxValue:fileTotalLength];
[singleProgressBar setIndeterminate:NO];
[singleProgressBar startAnimation:self];
[NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil];
[NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
[singleProgressSheet makeKeyWindow];
});
@@ -1011,7 +1020,7 @@ - (void)importCSVFile:(NSString *)filename
}
// Remove the header row from the data set if appropriate
if ([importFieldNamesSwitch state] == NSOnState) {
if ([[importFieldNamesSwitch onMainThread] state] == NSOnState) {
[parsedRows removeObjectAtIndex:0];
[parsePositions removeObjectAtIndex:0];
}
@@ -1110,15 +1119,16 @@ - (void)importCSVFile:(NSString *)filename
rowsImported++;
csvRowsThisQuery++;
if (fileIsCompressed) {
[singleProgressBar setDoubleValue:[csvFileHandle realDataReadLength]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of CSV data", @"CSV import progress text where total size is unknown"),
[NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]]]];
} else {
[singleProgressBar setDoubleValue:[[parsePositions objectAtIndex:i] doubleValue]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"),
[NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]], [NSString stringForByteSize:fileTotalLength]]];
}
#warning Updating the UI for every single row is likely a performance killer (even without synchronization).
SPMainQSync(^{
if (fileIsCompressed) {
[singleProgressBar setDoubleValue:[csvFileHandle realDataReadLength]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of CSV data", @"CSV import progress text where total size is unknown"), [NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]]]];
} else {
[singleProgressBar setDoubleValue:[[parsePositions objectAtIndex:i] doubleValue]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"CSV import progress text"), [NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]], [NSString stringForByteSize:fileTotalLength]]];
}
});
}
}
@@ -1142,28 +1152,30 @@ - (void)importCSVFile:(NSString *)filename
NSLocalizedString(@"[ERROR in row %ld] %@\n", @"error text when reading of csv file gave errors"),
(long)(rowsImported+1),[mySQLConnection lastErrorMessage]];
}
#warning duplicate code (see above)
rowsImported++;
SPMainQSync(^{
if (fileIsCompressed) {
[singleProgressBar setDoubleValue:[csvFileHandle realDataReadLength]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of CSV data", @"CSV import progress text where total size is unknown"), [NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]]]];
} else {
[singleProgressBar setDoubleValue:[[parsePositions objectAtIndex:i] doubleValue]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"), [NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]], [NSString stringForByteSize:fileTotalLength]]];
}
});
}
} else {
rowsImported += csvRowsThisQuery;
#warning duplicate code (see above)
SPMainQSync(^{
if (fileIsCompressed) {
[singleProgressBar setDoubleValue:[csvFileHandle realDataReadLength]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of CSV data", @"CSV import progress text where total size is unknown"),
[NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]]]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of CSV data", @"CSV import progress text where total size is unknown"), [NSString stringForByteSize:[[parsePositions objectAtIndex:csvRowsThisQuery-1] longValue]]]];
} else {
[singleProgressBar setDoubleValue:[[parsePositions objectAtIndex:i] doubleValue]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"),
[NSString stringForByteSize:[[parsePositions objectAtIndex:i] longValue]], [NSString stringForByteSize:fileTotalLength]]];
[singleProgressBar setDoubleValue:[[parsePositions objectAtIndex:csvRowsThisQuery-1] doubleValue]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"), [NSString stringForByteSize:[[parsePositions objectAtIndex:csvRowsThisQuery-1] longValue]], [NSString stringForByteSize:fileTotalLength]]];
}
}
} else {
rowsImported += csvRowsThisQuery;
if (fileIsCompressed) {
[singleProgressBar setDoubleValue:[csvFileHandle realDataReadLength]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of CSV data", @"CSV import progress text where total size is unknown"),
[NSString stringForByteSize:[[parsePositions objectAtIndex:csvRowsThisQuery-1] longValue]]]];
} else {
[singleProgressBar setDoubleValue:[[parsePositions objectAtIndex:csvRowsThisQuery-1] doubleValue]];
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"),
[NSString stringForByteSize:[[parsePositions objectAtIndex:csvRowsThisQuery-1] longValue]], [NSString stringForByteSize:fileTotalLength]]];
}
});
}
// Update the arrays
@@ -1200,7 +1212,7 @@ - (void)importCSVFile:(NSString *)filename
}
// Import finished Growl notification
[[SPGrowlController sharedGrowlController] notifyWithTitle:@"Import Finished"
[[SPGrowlController sharedGrowlController] notifyWithTitle:NSLocalizedString(@"Import Finished" , @"title for finished importing growl notification")
description:[NSString stringWithFormat:NSLocalizedString(@"Finished importing %@",@"description for finished importing growl notification"), [filename lastPathComponent]]
document:tableDocumentInstance
notificationName:@"Import Finished"];
@@ -1722,12 +1734,30 @@ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextIn
/**
* Starts the import process on a background thread.
*
* MUST BE CALLED ON THE UI THREAD!
*/
- (void)_startBackgroundImportTaskForFilename:(NSString *)filename
{
NSDictionary *userInfo = @{
@"filename": filename,
@"fileType": [[importFormatPopup selectedItem] title],
};
[NSThread detachNewThreadWithName:SPCtxt(@"SPDataImport background import task",tableDocumentInstance)
target:self
selector:@selector(_importBackgroundProcess:)
object:userInfo];
}
/**
* Background thread worker method for -_startBackgroundImportTaskForFilename:
*/
- (void)_importBackgroundProcess:(NSString *)filename
- (void)_importBackgroundProcess:(NSDictionary *)userInfo
{
[[NSThread currentThread] setName:@"Sequel Pro Background Importer"];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *fileType = [[importFormatPopup selectedItem] title];
NSString *filename = [userInfo objectForKey:@"filename"];
NSString *fileType = [userInfo objectForKey:@"fileType"];
// Use the appropriate processing function for the file type
if ([fileType isEqualToString:@"SQL"])
@@ -33,7 +33,7 @@
*/
@interface NSObject (SPMainThreadTrampoline)
- (id)onMainThread;
- (instancetype)onMainThread;
- (id)retainedOnMainThread;
@end

0 comments on commit 24ccb6b

Please sign in to comment.