Skip to content
This repository has been archived by the owner on Jan 22, 2024. It is now read-only.

FoundationTools

David Thorpe edited this page Jan 9, 2015 · 5 revisions

Introduction

You can use PGClientKit and PGServerKit in Foundation tools, if you follow some basic principles. Firstly, when packaging, you'll need to place the frameworks in a folder called 'Frameworks' which is stored relative to the compiled binary. Secondly, your binary will need to start a run loop in the foreground, whilst performing any tasks in a separate background thread.

The source code under the src/Apps/Foundation demonstrate how to design command-line tools which use the frameworks. PGFoundationClient implements an interactive console for querying remote databases, and PGFoundationServer implements a server-side database.

PGFoundationApp

The PGFoundationApp class provides the foundation of the command-line applications. The class should be subclassed and some methods should implement application-specific code. In general the class provides the following:

  • Sets up a run loop on the main thread, in order to service asynchronous method calls, when the run method is called;
  • Calls the setup method (which can be overridden) in order to call any application-specific startup code;
  • Waits until it receives the stop method call from the application or from a process signal (SIGTERM or SIGINT). This can be overridden in the application in order to gracefully tear down any resources;
  • The application should then call stoppedWithReturnValue: in order to flag that it's safe for the run loop to end.
  • The run method will then end on the main thread, and return a value.

In order to subclass the class, your class should:

  • Subclass the setup method if there is any application-specific setup code that needs to be executed.
  • Subclass the stop method if there is any application-specific teardown code that needs to be executed.
  • Call the stoppedWithReturnValue: method once the stop method has been called and the teardown has been completed.

You should also provide a main method such as the following, assuming your application class is called MyFoundationApp:

int main (int argc, const char* argv[]) {
	int returnValue = 0;
	@autoreleasepool {
		returnValue = [(PGFoundationApp* )[MyFoundationApp sharedApp] run];
	}
    return returnValue;
}

The following two sections provide more implementation details for use of the PGClientKit and PGServerKit in your own command-line tools.

PGFoundationClient

The PGFoundationClient application demonstrates how to use the PGClientKit in order to provide a command-line interactive console. The setup and stop methods work as follows:

@implementation PGFoundationClient (SetupStop)

-(void)setup {
	[super setup];
	
	if([self url]==nil) {
		// missing URL argument
		[self stop];
	}
	if([self connect:[self url] inBackground:YES error:nil]==NO) {
		// bad connection
		[self stop];
	}	
	// set up a separate thread to deal with interactive input
	[NSThread detachNewThreadSelector:@selector(readlineThread:) toTarget:self withObject:nil];
}

-(void)stop {
	[super stop];

	if([[self db] status]==PGConnectionStatusConnected) {
		// disconnecting
		[[self db] disconnect];
	} else {
		// no tear-down to be performed, so indicate stopped
		[self stoppedWithReturnValue:0];
	}
}

@end

Here, the connection can be done either in the foreground (blocking) or background (asynchronous). If the setup fails, stop is called immediately in order to shutdown the application. On successful initiation of the connection, however, a new thread is created in order to receive input from the terminal. This can't be done on the main thread due to that one processing messages on the main run loop.

The stop method will only call the disconnect method if a connection is already made. If not, stoppedWithReturnValue: is called immediately since no disconnection needs to be performed. In the case of disconnection being required, a delegate is implemented to call the stoppedWithReturnValue: method instead:

@implementation PGFoundationClient (PGClientKitDelegate)

-(void)connection:(PGConnection* )connection statusChange:(PGConnectionStatus)status {
	if([super stopping] && status==PGConnectionStatusDisconnected) {
		// indicate server connection has been shutdown
		[self stoppedWithReturnValue:0];
	}
}

@end

The interactive command thread works quite simply. A class called Terminal implements an interface to the build-in readline library, which includes the ability to edit the command line, store history, etc:


@interface PGFoundationClient
@property (readonly) Terminal* term;
@end

@implementation PGFoundationClient (InteractiveConsole)


-(void)readlineThread:(id)anObject {
	@autoreleasepool {
		BOOL isRunning = YES;
		while(isRunning) {
			if([[self db] status] != PGConnectionStatusConnected) {
				// wait for connection to be made
				[NSThread sleepForTimeInterval:0.1];
				continue;
			}
			// read command
			NSString* line = [[self term] readline];
			
			// deal with CTRL+D
			if(line==nil) {
				isRunning = NO;
				continue;
			}
			
			// execute a statement, display result
			NSString* result = [self execute:line];
			[[self term] printf:result];
		}
	}
	[self performSelectorOnMainThread:@selector(readlineThreadEnded:) withObject:nil waitUntilDone:YES];
}


-(NSString* )execute:(NSString* )statement {
	PGResult* r = [[self db] execute:statement error:nil];
	if(!r) return nil;
	if([r dataReturned]==NO) {
		return [NSString stringWithFormat:@"Affected Rows: %ld",[r affectedRows]];
	} else {
		return [r tableWithWidth:[[self term] columns]];
	}
}
@end

The tableWithWidth: method provides a nice ASCII table output. Further output methods for CSV, HTML and JSON should also be implemented, eventually.

PGFoundationServer

The PGFoundationServer application demonstrates how to use the PGServerKit in order to provide a postgresql server which can be controlled programmatically. The main application class PGFoundationServer inherits from PGFoundationApp as before. The setup and stop methods work as follows:

@interface PGFoundationServer
@property (retain) PGServer* server;
@property (readonly) NSString* dataPath;
@property (readonly) NSUInteger port;
@property (readonly) NSString* hostname;
@end

@implementation PGFoundationServer (SetupStop)

-(void)setup {
	// bind to server
	[self setServer:[PGServer serverWithDataPath:[self dataPath]]];
	[[self server] setDelegate:self];
	// start server
	[[self server] startWithNetworkBinding:[self hostname] port:[self port]];
}

-(void)stop {
	[super stop];
	[[self server] stop];
}

@end

The hostname and port properties come from the command-line arguments, and the dataPath is set the users' Application Support folder.

The delegate method which reacts to server state changes is used to indicate that the application should end:

@implementation PGFoundationServer (PGServerDelegate)

-(void)pgserver:(PGServer* )server stateChange:(PGServerState)state {
	switch(state) {
		case PGServerStateAlreadyRunning:
		case PGServerStateRunning:
			printf("Server is ready to accept connections\n\n");
			break;
		case PGServerStateError:
			// error occured stopping server, so program should quit with -1 return value
			printf("Server error, quitting\n");
			[self stop];
			[self stoppedWithReturnValue:-1];
			break;
		case PGServerStateStopped:
			// quit the application
			printf("Server stopped, ending application\n");
			[self stop];
			[self stoppedWithReturnValue:0];
			break;
	}
}

@end

To start the server, use the following command:

PGFoundationServer -hostname "*" -port 9001

If the server has not yet been initialized, the server will be setup first. Here is what the sample output appears like when the application is started:

host% PGFoundationServer -hostname "*" -port 9001

Server version: postgres (PostgreSQL) 9.1.5
Server state: PGServerStateStarting
Server state: PGServerStateInitialize
Server state: PGServerStateInitialize
The files belonging to this database system will be owned by user "djthorpe".
This user must also own the server process.
The database cluster will be initialized with locale C.
The default text search configuration will be set to "english".
fixing permissions on existing directory /Users/djthorpe/Library/Application Support/PostgreSQL ... ok
creating subdirectories ...
ok
...
Server state: PGServerStateStarting
LOG:  database system is ready to accept connections
Server state: PGServerStateRunning
LOG:  autovacuum launcher started
Server started with pid 25842
Server is ready to accept connections
  PID = 25842
  Port = 9001
  Hostname = *
  Socket path = /tmp
  Uptime = 1.215902 seconds

To stop the server, send a TERM signal to the running process, or click CTRL+C which sends this signal to the server.

host% kill 25842

LOG:  received smart shutdown request
LOG:  autovacuum launcher shutting down
LOG:  shutting down
LOG:  database system is shut down
Server has been stopped
Server state: PGServerStateStopping
Server stopped, ending application

Building and Packaging

TODO