1 // Copyright (c) 2013, Peter Wood.
2 // See license.txt for licensing details.
3 module stalkd.connection;
4 
5 import std.socket;
6 import stalkd.exceptions;
7 import stalkd.server;
8 import stalkd.tube;
9 
10 /**
11  * The Connection class represents a connection to a single Beanstalkd server.
12  * A Connection instance should not be shared between multiple tubes.
13  * 
14  */
15 class Connection {
16    /**
17     * Constructor for the Connection class.
18     *
19     * Params:
20     *    server =  A reference to the Server the connection will attach to.
21     */
22    this(Server server) {
23       _server = server;
24    }
25 
26    /**
27     * Constructor for the Connection class.
28     */
29    this(string host, ushort port=Server.DEFAULT_BEANSTALKD_PORT) {
30       _server = Server(host, port);
31    }
32 
33    /**
34     * This function attempts to establish a connection to the server identified
35     * by a Connection objects settings.
36     */
37    void open() {
38       auto addresses = getAddress(_server.host, _server.port);
39 
40       if(addresses.length == 0) {
41          throw(new StalkdException("Unable to determine a network address for the server."));
42       }
43 
44       try {
45          _socket = new TcpSocket;
46          _socket.connect(addresses[0]);
47       } catch(Exception exception) {
48          throw(new StalkdException("Failed to open connection to server.", exception));
49       }
50    }
51 
52    /**
53     * This function closes a connection if it is open.
54     */
55    void close() {
56       if(_socket !is null) {
57          _socket.close();
58          _socket = null;
59       }
60    }
61 
62    /**
63     * This function is used to test whether a connection is open.
64     */
65    const @property bool isOpen() {
66       return(_socket !is null && _socket.isAlive);
67    }
68 
69    /**
70     * Getter for the server property.
71     */
72    @property Server server() {
73       return(_server);
74    }
75 
76    /**
77     * This function provides package level access to the Socket held within a
78     * Connection object. Asking for a socket before a Connection has been
79     * explicitly opened implies an implicit call to the open() function.
80     */
81    @property package Socket socket() {
82       if(_socket is null) {
83          this.open();
84       }
85       return(_socket);
86    }
87 
88    /**
89     * This function retrieves a Tube object from a Connection.
90     *
91     * Params:
92     *    name =  The name of the tube that the Tube object will use. Defaults
93     *            to Tube.DEFAULT_TUBE_NAME.
94     */
95    Tube getTube(string name=Tube.DEFAULT_TUBE_NAME) {
96       Tube tube = new Tube(this);
97 
98       if(name !is Tube.DEFAULT_TUBE_NAME) {
99          tube.use(name);
100       }
101 
102       return(tube);
103    }
104 
105    private Server _server;
106    private Socket _socket;
107 }
108 
109 //------------------------------------------------------------------------------
110 // Unit Tests
111 //------------------------------------------------------------------------------
112 /*
113  * NOTE: There is a limit to the amount of unit testing that can be performed
114  *       without an actual server connection. For this reason, the test below
115  *       check for the presence of an available test Beanstalkd instance via
116  *       the existence of the BEANSTALKD_TEST_HOST environment variable. If
117  *       this is set then an attempt will be made to connect to it to perform
118  *       an additional series of tests. You can specify the port for this test
119  *       server using the BEANSTALKD_TEST_PORT environment variable. As the
120  *       queues on this server will be added to, deleted from and cleared of
121  *       content as part of the tests this server should not be used for any
122  *       other purpose!
123  */
124 unittest {
125    import std.stdio;
126    import std.conv;
127    import std.process;
128    import std.exception;
129 
130    auto server     = Server("localhost");
131    auto connection = new Connection(server);
132 
133    assert(connection.server is server);
134    assert(connection.isOpen == false);
135 
136    auto tube = connection.getTube();
137    assert(tube !is null);
138    assert(tube.connection is connection);
139 
140    auto host = environment.get("BEANSTALKD_TEST_HOST");
141    if(host !is null) {
142       writeln("The BEANSTALKD_TEST_HOST environment variable is set, conducting advanced tests for the Connection class.");
143       ushort port = Server.DEFAULT_BEANSTALKD_PORT;
144       if(environment.get("BEANSTALKD_TEST_PORT") !is null) {
145          port = to!ushort(environment.get("BEANSTALKD_TEST_PORT"));
146       }
147 
148       connection = new Connection(host, port);
149 
150       void testOpen() {
151          connection.open();
152       }
153       assertNotThrown!StalkdException(testOpen);
154       assert(connection.isOpen == true);
155 
156       void testClose() {
157          connection.close();
158       }
159       assertNotThrown!StalkdException(testClose);
160       assert(connection.isOpen == false);
161    } else {
162       writeln("The BEANSTALKD_TEST_HOST environment variable is not set, advanced tests for the Connection class skipped.");
163    }
164 }
165 
166