-
Notifications
You must be signed in to change notification settings - Fork 18
FoundationTools
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.
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 thestop
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.
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.
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
TODO