GEPS 013: Gramps Webapp
Proposition of a server mode for GRAMPS. Now that the main graphical user interface (GUI) has been separated from the command-line interface (CLI), a server-mode would be the next logical step.
Web developers are in need of a method of accessing and creating functionality with their GRAMPS data on the web. Having a GRAMPS server-mode would allow a GRAMPS-based web project.
A server mode for GRAMPS is suggested to work as follows. A server program is started, and run in the background. It listens for requests from other processes (on that machine, or others), executes commands, and serves back data.
The server mode is vary similar to the CLI mode of GRAMPS. However, rather than just running a report, the program doesn't exit, but waits for requests. Clients need not keep track of any state. The server would host a single database, and allow multiple people to work on it simultaneously.
A GRAMPS server should allow clients to do most of what a user would want to do with the GRAMPS gtk application:
- browse data, including all of the primary objects
- have links that would take the user directly to view a particular object's data
- a method of adding and editing existing data
In order for such a client/server architecture to work, there needs to be a method of communication between them. There needs to be a format for making requests from clients to the server, and a for returning results from the server to the clients.
There are plenty of options for communication formats, including XML. There is also a library called Python Remote Objects for handing this kind of data. However, one possibility is to use Python's native pickle format, as that can be easily sent between connections. Likewise, all of the primary data's raw data can be pickled. Using pickled data requires that no database objects be pickled---only data. One would have to be careful about how you write web applications so as not to involve objects such as generators, and databases.
A prototype of a GRAMPS server mode has been checked into gramps/trunk (targeting version 3.2). It works as follows.
You start the server by selecting the database:
python src/gramps.py --server=50000 -O "My Family Tree"
This will start a socket listener on port 50000. If the database is locked, then, like usually, you can supply the --force-unlock flag:
python src/gramps.py --server=50000 -O "My Family Tree" --force-unlock
In fact, you can do any of the things you normally do with the CLI, before beginning serving.
After starting, the server produces output like:
$ python src/gramps.py --server=50000 -O "MyRelate" Opened successfully! GRAMPS server listening on port 50000. Use CONTROL+C to exit... -------------------------------------------------- Connection opened from 127.0.0.1:50114 Connection closed from 127.0.0.1:50114 Connection opened from 127.0.0.1:50115 Request: self.dbstate.db.surname_list.__getslice__(0, 2147483647) Connection closed from 127.0.0.1:50115
The server currently lists each connection start/end, and each request. The server is written in a fashion such that it can take many requests at once. It spawns off threads that handle the details of the request. TODO: does memory get reclaimed appropriately?
The server receives messages that it evaluates, pickles, and returns.
To make programming as natural as possible on the client side, a RemoteObject has been written which hides the gory details of the socket communication. Here is a sample client:
from cli.client import RemoteObject self = RemoteObject("localhost", 50000)
Here, you can use "self" very similarly to the use of self in the server. For example:
$ python Python 2.6 (r26:66714, Jun 8 2009, 16:07:26) [GCC 4.4.0 20090506 (Red Hat 4.4.0-4)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from cli.client import RemoteObject >>> self = RemoteObject("localhost", 50000) >>> self.dbstate <DbState.DbState object at 0x98e088c> >>> self.dbstate.db <gen.db.dbdir.GrampsDBDir object at 0x9dc4c0c>
Under the hood, the names "db" and "dbstate" are collected, sent over the socket to the server as the string "self.dbstate.db" which is evaluated, and the representation is sent back.
You can interactively explore the remote objects:
>>> self.dbstate.db.dir() ['_Callback__BLOCK_ALL_SIGNALS', '_Callback__LOG_ALL', '_Callback__block_instance_signals', ... 'source_prefix', 'surname_list', 'surnames', 'transaction_begin', 'transaction_commit', 'translist', 'txn', 'undo', 'undo_available', 'undo_callback', 'undo_data', 'undo_history_callback', 'undo_history_timestamp', 'undo_reference', 'undodb', 'undoindex', 'undolog', 'update_empty', 'update_real', 'update_reference_map', 'url_types', 'version_supported', 'write_version']
Notice that you can't wrap a "dir()" around a property, but you can tack a ".dir()" on the end to provide the same functionality.