aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPExportFileUtilities.m
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2011-05-07 15:31:54 +0000
committerrowanbeentje <rowan@beent.je>2011-05-07 15:31:54 +0000
commit0c2a225a68ef1512e51ff3a48fef1fa13eacce60 (patch)
tree624b65144aac2f2a5d57172f8dd7625a5a8e236a /Source/SPExportFileUtilities.m
parent47a1c49e95403e6da6c9e0ae979144fd5c1dff08 (diff)
downloadsequelpro-0c2a225a68ef1512e51ff3a48fef1fa13eacce60.tar.gz
sequelpro-0c2a225a68ef1512e51ff3a48fef1fa13eacce60.tar.bz2
sequelpro-0c2a225a68ef1512e51ff3a48fef1fa13eacce60.zip
Implement a large number of minor fixes and improvements to export functionality, including an overhaul of filename NSTokenField functionality:
- Improve the NSTokenField used for export filenames: only tokenise reserved tokens, don't tokenise reserved words which are parts of other words, allow the comma to be used, update tokenisation during typing, and prevent whitespace triming. - Save the last selected export path, and make the path selection button open a dialog to the selected directory - Save the export filename and restore on future uses of the export dialog (only if the name contains placeholder tokens, so one-off export names aren't saved) - If the advanced options are collapsed, display a summary of the selected options next to the disclosure triangle - Display a small warning in the corner of the window if the export file cannot be imported into Sequel Pro, to warn those people attempting to back up their databases in XML - Clarify and improve the export warning dialog if files already exist or could not be created; make the simpler file-exists cases reflect OS-style dialogs, alter wording based on the number of files that failed and how they failed, and only show the "replace" or "skip" type buttons if it makes sense to do so. - Fix a mutation-during-enumeration error when skipping files - If "Cancel" is chosen in the export file creation replace/error dialog, redisplay the export sheet with the previous selection still active - Add support for year, month and day tokens in the filename token list - Don't allow blank custom filenames, before or after tokenisation, as this can cause problems - instead fall back to default filenames in those cases - Only append the extension if one hasn't been set - on all export formats, extending r3284 - If exporting to multiple files option is enabled but only one table is selected, supply that table name for filename table tokens - Update the progress bar to reflect update progress when exporting CSV data - Fix a bug causing exports to hang if the low-memory advanced option was set and content was selected to export and any empty tables were encountered - Save memory use and compression advanced export settings across sessions - Update localisable strings
Diffstat (limited to 'Source/SPExportFileUtilities.m')
-rw-r--r--Source/SPExportFileUtilities.m142
1 files changed, 112 insertions, 30 deletions
diff --git a/Source/SPExportFileUtilities.m b/Source/SPExportFileUtilities.m
index efa824f8..2a490307 100644
--- a/Source/SPExportFileUtilities.m
+++ b/Source/SPExportFileUtilities.m
@@ -30,6 +30,17 @@
#import "SPDatabaseDocument.h"
#import "SPCustomQuery.h"
+typedef enum
+{
+ SPExportErrorCancelExport = 0,
+ SPExportErrorReplaceFiles = 1,
+ SPExportErrorSkipErrorFiles = 2
+} SPExportErrorChoice;
+
+@interface SPExportController (SPExportFileUtilitiesPrivateAPI)
+ - (void)_reopenExportSheet;
+@end
+
@implementation SPExportController (SPExportFileUtilities)
/**
@@ -119,17 +130,18 @@
- (void)errorCreatingExportFileHandles:(NSArray *)files
{
// Get the number of files that already exists as well as couldn't be created because of other reasons
- NSUInteger i = 0;
+ NSUInteger filesAlreadyExisting = 0;
+ NSUInteger filesFailed = 0;
for (SPExportFile *file in files)
{
if ([file exportFileHandleStatus] == SPExportFileHandleExists) {
- i++;
- }
+ filesAlreadyExisting++;
+
// For file handles that we failed to create for some unknown reason, ignore them and remove any
// exporters that are associated with them.
- else if ([file exportFileHandleStatus] == SPExportFileHandleFailed) {
-
+ } else if ([file exportFileHandleStatus] == SPExportFileHandleFailed) {
+ filesFailed++;
for (SPExporter *exporter in exporters)
{
if ([[exporter exportOutputFile] isEqualTo:file]) {
@@ -139,27 +151,72 @@
}
}
- // If all the files failed, show a simplified export dialog
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSCriticalAlertStyle];
- // If only some of the files failed, show an export dialog with the option to ignore the failed files.
+ // If files failed because they already existed, show a OS-like dialog.
+ if (filesAlreadyExisting) {
- // For single files, show a dialog very close to the OS dialog
- if (i > 0) {
-
- NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error creating export files", @"export file handle creation error message")
- defaultButton:NSLocalizedString(@"Ignore", @"ignore button")
- alternateButton:NSLocalizedString(@"Overwrite", @"overwrite button")
- otherButton:NSLocalizedString(@"Cancel", @"cancel button")
- informativeTextWithFormat:NSLocalizedString(@"One or more errors occurred while attempting to create the export files. Those that failed to be created for unknown reasons will be ignored.\n\nHow would you like to proceed with the files that already exist at the location you have chosen to export to?", @"export file handle creation error informative message")];
-
- [alert setAlertStyle:NSCriticalAlertStyle];
-
- // Close the progress sheet
- [NSApp endSheet:exportProgressWindow returnCode:0];
- [exportProgressWindow orderOut:self];
+ // Set up a string for use if files had to be skipped.
+ NSString *additionalErrors = filesFailed ? NSLocalizedString(@"\n\n(In addition, one or more errors occurred while attempting to create the export files: %lu could not be created. These will be ignored.", @"Additional export file errors") : @"";
+
+ if (filesAlreadyExisting == 1) {
+ [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"“%@” already exists. Do you want to replace it?", @"Export file already exists message"), [[[files objectAtIndex:0] exportFilePath] lastPathComponent]]];
+ [alert setInformativeText:[NSString stringWithFormat:@"%@%@", NSLocalizedString(@"A file with the same name already exists in the target folder. Replacing it will overwrite its current contents.", @"Export file already exists explanatory text"), additionalErrors]];
+ } else if (filesAlreadyExisting == [exportFiles count]) {
+ [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"All the export files already exist. Do you want to replace them?", @"All export files already exist message")]];
+ [alert setInformativeText:[NSString stringWithFormat:@"%@%@", NSLocalizedString(@"Files with the same names already exist in the target folder. Replacing them will overwrite their current contents.", @"All export files already exist explanatory text"), additionalErrors]];
+ } else {
+ [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"%lu files already exist. Do you want to replace them?", @"Export file already exists message"), filesAlreadyExisting]];
+ [alert setInformativeText:[NSString stringWithFormat:@"%@%@", [NSString stringWithFormat:NSLocalizedString(@"%lu files with the same names already exist in the target folder. Replacing them will overwrite their current contents.", @"Some export files already exist explanatory text"), filesAlreadyExisting], additionalErrors]];
+ }
+
+ [alert addButtonWithTitle:NSLocalizedString(@"Replace", @"Replace button")];
+ [[[alert buttons] objectAtIndex:0] setTag:SPExportErrorReplaceFiles];
+ [[[alert buttons] objectAtIndex:0] setKeyEquivalent:@"r"];
+ [[[alert buttons] objectAtIndex:0] setKeyEquivalentModifierMask:NSCommandKeyMask];
+
+ [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")];
+ [[[alert buttons] objectAtIndex:1] setTag:SPExportErrorCancelExport];
+ [[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"\r"];
- [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:files];
+ if ((filesAlreadyExisting + filesFailed) != [exportFiles count]) {
+ [alert addButtonWithTitle:NSLocalizedString(@"Skip existing", @"skip existing button")];
+ [[[alert buttons] objectAtIndex:2] setTag:SPExportErrorSkipErrorFiles];
+ [[[alert buttons] objectAtIndex:2] setKeyEquivalent:@"s"];
+ [[[alert buttons] objectAtIndex:2] setKeyEquivalentModifierMask:NSCommandKeyMask];
+ }
+
+ // If one or multiple files failed, but only due to unhandled errors, show a short dialog
+ } else {
+ if (filesFailed == 1) {
+ [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"“%@” could not be created", @"Export file creation error title"), [[[files objectAtIndex:0] exportFilePath] lastPathComponent]]];
+ [alert setInformativeText:NSLocalizedString(@"An unhandled error occurred when attempting to create the export file. Please check the details and try again.", @"Export file creation error explanatory text")];
+ } else if (filesFailed == [exportFiles count]) {
+ [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"No files could be created", @"All export files creation error title")]];
+ [alert setInformativeText:NSLocalizedString(@"An unhandled error occurred when attempting to create each of the export files. Please check the details and try again.", @"All export files creation error explanatory text")];
+ } else {
+ [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"%lu files could not be created", @"Export files creation error title"), filesFailed]];
+ [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(@"An unhandled error occurred when attempting to create %lu of the export files. Please check the details and try again.", @"Export files creation error explanatory text"), filesFailed]];
+ }
+
+
+ [alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"cancel button")];
+ [[[alert buttons] objectAtIndex:0] setTag:SPExportErrorCancelExport];
+
+ if (filesFailed != [exportFiles count]) {
+ [alert addButtonWithTitle:NSLocalizedString(@"Skip problems", @"skip problems button")];
+ [[[alert buttons] objectAtIndex:1] setTag:SPExportErrorSkipErrorFiles];
+ [[[alert buttons] objectAtIndex:1] setKeyEquivalent:@"s"];
+ [[[alert buttons] objectAtIndex:1] setKeyEquivalentModifierMask:NSCommandKeyMask];
+ }
}
+
+ // Close the progress sheet
+ [NSApp endSheet:exportProgressWindow returnCode:0];
+ [exportProgressWindow orderOut:self];
+
+ [alert beginSheetModalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:files];
}
/**
@@ -170,14 +227,17 @@
NSArray *files = (NSArray *)contextInfo;
// Ignore the files that exist and remove the associated exporters
- if (returnCode == NSAlertDefaultReturn) {
+ if (returnCode == SPExportErrorSkipErrorFiles) {
- for (SPExportFile *file in files)
- {
- for (SPExporter *exporter in exporters)
- {
+ for (SPExportFile *file in files) {
+
+ // Use a numerically controlled loop to avoid mutating the collection while enumerating
+ NSUInteger i;
+ for (i = 0; i < [exporters count]; i++) {
+ SPExporter *exporter = [exporters objectAtIndex:i];
if ([[exporter exportOutputFile] isEqualTo:file]) {
- [exporters removeObject:exporter];
+ [exporters removeObjectAtIndex:i];
+ i--;
}
}
}
@@ -187,6 +247,9 @@
// If we're now left with no exporters, cancel the export operation
if ([exporters count] == 0) {
[exportFiles removeAllObjects];
+
+ // Trigger restoration of the export interface
+ [self performSelector:@selector(_reopenExportSheet) withObject:nil afterDelay:0.1];
}
else {
// Start the export after a short delay to give this sheet a chance to close
@@ -194,7 +257,7 @@
}
}
// Overwrite the files and continue
- else if (returnCode == NSAlertAlternateReturn) {
+ else if (returnCode == SPExportErrorReplaceFiles) {
for (SPExportFile *file in files)
{
@@ -220,7 +283,7 @@
}
// Cancel the entire export operation
- else if (returnCode == NSAlertOtherReturn) {
+ else if (returnCode == SPExportErrorCancelExport) {
// Loop the cached export files and remove those we've already created
for (SPExportFile *file in exportFiles)
@@ -233,7 +296,26 @@
// Finally get rid of all the exporters and files
[exportFiles removeAllObjects];
[exporters removeAllObjects];
+
+ // Trigger restoration of the export interface
+ [self performSelector:@selector(_reopenExportSheet) withObject:nil afterDelay:0.1];
}
}
@end
+
+@implementation SPExportController (SPExportFileUtilitiesPrivateAPI)
+
+/**
+ * Re-open the export sheet without resetting the interface - for use on error.
+ */
+- (void)_reopenExportSheet
+{
+ [NSApp beginSheet:[self window]
+ modalForWindow:[tableDocumentInstance parentWindow]
+ modalDelegate:self
+ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ contextInfo:nil];
+}
+
+@end