// // $Id$ // // SPDotExporter.m // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on April 17, 2010. // Copyright (c) 2009 Stuart Connolly. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // More info at <http://code.google.com/p/sequel-pro/> #import "SPDotExporter.h" #import "SPFileHandle.h" #import "SPTableData.h" #import "SPExportUtilities.h" #import "SPExportFile.h" @implementation SPDotExporter @synthesize delegate; @synthesize dotExportTables; @synthesize dotExportCurrentTable; @synthesize dotForceLowerTableNames; @synthesize dotTableData; @synthesize dotDatabaseHost; @synthesize dotDatabaseName; @synthesize dotDatabaseVersion; /** * Initialise an instance of SPDotExporter using the supplied delegate. * * @param exportDelegate The exporter delegate * * @return The initialised instance */ - (id)initWithDelegate:(NSObject *)exportDelegate { if ((self = [super init])) { SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPDotExporterProtocol)); [self setDelegate:exportDelegate]; [self setDotExportCurrentTable:nil]; } return self; } /** * Start the Dot schema export process. This method is automatically called when an instance of this class * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. */ - (void)main { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableString *metaString = [NSMutableString string]; // Check that we have all the required info before starting the export if ((![self dotExportTables]) || (![self dotTableData]) || ([[self dotExportTables] count] == 0)) { [pool release]; return; } // Inform the delegate that the export process is about to begin [delegate performSelectorOnMainThread:@selector(dotExportProcessWillBegin:) withObject:self waitUntilDone:NO]; // Mark the process as running [self setExportProcessIsRunning:YES]; [metaString setString:@"// ************************************************************\n"]; [metaString appendString:@"// Generated by: Sequel Pro\n"]; [metaString appendFormat:@"// Version %@\n//\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; [metaString appendFormat:@"// %@\n// %@\n//\n", SPLOCALIZEDURL_HOMEPAGE, SPDevURL]; [metaString appendFormat:@"// Host: %@ (MySQL %@)\n", [self dotDatabaseHost], [self dotDatabaseVersion]]; [metaString appendFormat:@"// Database: %@\n", [self dotDatabaseName]]; [metaString appendFormat:@"// Generation Time: %@\n", [NSDate date]]; [metaString appendString:@"// ************************************************************\n\n"]; [metaString appendString:@"digraph \"Database Structure\" {\n"]; [metaString appendFormat:@"\tlabel = \"ER Diagram: %@\";\n", [self dotDatabaseName]]; [metaString appendString:@"\tlabelloc = t;\n"]; [metaString appendString:@"\tcompound = true;\n"]; [metaString appendString:@"\tnode [ shape = record ];\n"]; [metaString appendString:@"\tfontname = \"Helvetica\";\n"]; [metaString appendString:@"\tranksep = 1.25;\n"]; [metaString appendString:@"\tratio = 0.7;\n"]; [metaString appendString:@"\trankdir = LR;\n"]; // Write information to the file [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; NSMutableArray *fkInfo = [[NSMutableArray alloc] init]; // Process the tables for (NSUInteger i = 0; i < [[self dotExportTables] count]; i++) { // Check for cancellation flag if ([self isCancelled]) { [fkInfo release]; [pool release]; return; } NSString *tableName = NSArrayObjectAtIndex([self dotExportTables], i); NSString *tableLinkName = [self dotForceLowerTableNames] ? [tableName lowercaseString] : tableName; NSDictionary *tableInfo = [[self dotTableData] informationForTable:tableName]; // Set the current table [self setDotExportCurrentTable:tableName]; // Inform the delegate that we are about to start fetcihing data for the current table [[delegate onMainThread] dotExportProcessWillBeginFetchingData:self forTableWithIndex:i]; NSString *hdrColor = @"#DDDDDD"; if ([[tableInfo objectForKey:@"type"] isEqualToString:@"View"]) { hdrColor = @"#DDDDFF"; } [metaString setString:[NSString stringWithFormat:@"\tsubgraph \"table_%@\" {\n", tableName]]; [metaString appendString:@"\t\tnode [ shape = \"plaintext\" ];\n"]; [metaString appendFormat:@"\t\t\"%@\" [ label=<\n", tableLinkName]; [metaString appendString:@"\t\t\t<TABLE BORDER=\"0\" CELLSPACING=\"0\" CELLBORDER=\"1\">\n"]; [metaString appendFormat:@"\t\t\t<TR><TD COLSPAN=\"3\" BGCOLOR=\"%@\">%@</TD></TR>\n", hdrColor, tableName]; // Retrieve the column definitions for the current table NSArray *tableColumns = [tableInfo objectForKey:@"columns"]; for (NSDictionary *aColumn in tableColumns) { [metaString appendFormat:@"\t\t\t<TR><TD COLSPAN=\"3\" PORT=\"%@\">%@:<FONT FACE=\"Helvetica-Oblique\" POINT-SIZE=\"10\">%@</FONT></TD></TR>\n", [aColumn objectForKey:@"name"], [aColumn objectForKey:@"name"], [aColumn objectForKey:@"type"]]; } [metaString appendString:@"\t\t\t</TABLE>>\n"]; [metaString appendString:@"\t\t];\n"]; [metaString appendString:@"\t}\n"]; [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; // Check if any relations are available for the table NSArray *tableConstraints = [tableInfo objectForKey:@"constraints"]; if ([tableConstraints count]) { for (NSDictionary* constraint in tableConstraints) { // Check for cancellation flag if ([self isCancelled]) { [fkInfo release]; [pool release]; return; } // Get the column references. Currently the columns themselves are an array, // while reference columns and tables are comma separated if there are more than // one. Only use the first of each for the time being. NSArray *originColumns = [constraint objectForKey:@"columns"]; NSArray *referenceColumns = [[constraint objectForKey:@"ref_columns"] componentsSeparatedByString:@","]; NSString *extra = @""; if ([originColumns count] > 1) { extra = @" [ arrowhead=crow, arrowtail=odiamond ]"; } [fkInfo addObject:[NSString stringWithFormat:@"%@:%@ -> %@:%@ %@", tableLinkName, [originColumns objectAtIndex:0], [constraint objectForKey:@"ref_table"], [[referenceColumns objectAtIndex:0] lowercaseString], extra]]; } } // Update progress double progress = (i * ([self exportMaxProgress] / [[self dotExportTables] count])); [self setExportProgressValue:progress]; [delegate performSelectorOnMainThread:@selector(dotExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; } // Inform the delegate that we are about to start fetching relations data for the current table [delegate performSelectorOnMainThread:@selector(dotExportProcessWillBeginFetchingRelationsData:) withObject:self waitUntilDone:NO]; [metaString setString:@"edge [ arrowhead=inv, arrowtail=normal, style=dashed, color=\"#444444\" ];\n"]; // Get the relations for (id item in fkInfo) { [metaString appendFormat:@"%@;\n", item]; } [fkInfo release]; [metaString appendString:@"}\n"]; // Write information to the file [[self exportOutputFile] writeData:[metaString dataUsingEncoding:NSUTF8StringEncoding]]; // Write data to disk [[self exportOutputFile] close]; // Mark the process as not running [self setExportProcessIsRunning:NO]; // Inform the delegate that the export process is complete [delegate performSelectorOnMainThread:@selector(dotExportProcessComplete:) withObject:self waitUntilDone:NO]; [pool release]; } /** * Dealloc */ - (void)dealloc { delegate = nil; [dotExportTables release], dotExportTables = nil; [dotExportCurrentTable release], dotExportCurrentTable = nil; [dotTableData release], dotTableData = nil; [dotDatabaseHost release], dotDatabaseHost = nil; [dotDatabaseName release], dotDatabaseName = nil; [dotDatabaseVersion release], dotDatabaseVersion = nil; [super dealloc]; } @end