Wednesday, July 23, 2008

Next on the Agenda...

Well, the client-side of things is mostly done for now, so it's time I turned my attention to the remaining items. The server side of things needs to be updated/fixed and the service client manager (SCM) needs updating. The server is fairly straightforward, but I have some concerns about the SCM.

  • Does the SCM record clients in its configuration when they are run? Probably not. How would it record the executable path, for example? We should find a way to make this more automatic.


    • Provide methods to read/write SCM configuration.

    • service_client --register #registers its configuration using above methods

    • The above command can be called by waf during build to eliminate user intervention. The clients are already registered and ready for use upon installation!


  • Can we rename the SCM's shutdown function to "kill" (and perhaps redefine its functionality) or something that can't be confused with xmmsc_service_shutdown?

  • Can we change the default value of the "monitor" option (to check the configuration directory for updates) to true? This seems to cooperate much better with user expectations.

  • Can we get rid of the "timeout" option (to wait before killing a service client after a shutdown request is received)? If we rename the method to "kill" its intentions will be a lot more obvious--take down the client now.

Sunday, July 20, 2008

Visions of Grandeur, Llamas, and API

I've been really bad about updating this blog, I just noticed. So here's some fresh, tasty meat to dangle in front of your snapping jaws. The past several weeks were met with real life issues and coding issues, but not a lot of real code. But very recently I started working on the new API, and it's looking exciting!

Here's the vision so far. A service client (SC) creates a new service, stored in a service_t. The SC then can add methods by giving it a name, description, and arguments, each with a name and type. There is a variadic version of this which lets one add a method at a time, and a more primitive version to add a method, then one argument at a time. This makes it easy for C programmers and binding authors alike.

Here's what the data structures look like:
service_t:
char *name;
char *desc;
uint32_t major;
uint32_t minor;
x_list_t *methods;

uint32_t ref;

service_method_t
char *name;
char *desc;
void *udata;
xmmsc_service_notifier_t func;
xmmsc_user_data_free_func_t ufree;
xmms_value_type_t rettype;
xmms_value_t *args;


The data is held in value_ts whenever possible because these are the new universal type being introduced with the result/value split code. A value_t can contain any of several basic types and can be serialized and deserialized automatically. Its representation and usage is effectively the same on the client and server. Because a service_method_t must contain function pointers and user data, it has to be a struct. (How could you send a function pointer over IPC?)

The next step is for the SC to call service_register and register its service with the server. Internally, service_register handles the conversion from service_t and service_method_t to representative value_ts, then sends them off to the server.

A client can request any information about the service from the server using service_describe. Since the data is already conveniently encapsulated in value_t form, it is just shipped off to the client. The client can then see everything except the actual function pointers, udata, and refcount (why would it want these anyway?).

Said client can use this data to figure out, say, the version of the service is within its own supported range. So the client queries a method. Let's make this more interesting with an example. We'll make a client LlamaGUI that implements support for counting llamas using the LlamaCount service.


void llamagui_llama_count_get (connection_t *xc)
{
value_t *farms = list_new("llama_farm_a", "llama_farm_b");
value_t *args = dict_new("farmlist", farms);

res = service_query(xc, "LlamaCount", "count_get", args);
service_notifier_set(xc, res, llamagui_llama_count_callback);
}


This request is sent to the server using the provided connection xc. The server simply looks up the "LlamaCount" service and shoves the request right along to the sc. Back in the sc, the request is checked for well-formedness. Are all the required arguments there? Are they the correct types? If not, a value_t containing an error is sent back through the server to the requesting client. If so, the notifier method is called, processing the request:


value_t *llamacount_get (connection_t *xc, service_method_t *method, value_t *args, void *udata)
{
int count = 0;
value_t *farmlist;

farmlist = value_dict_get (args, "farmlist");
for(; value_list_valid(farmlist); value_list_next(farmlist)) {
count += llama_farm_count (value_int_get (farmlist));
}

return value_new_int(count);
}


This starts the return trip through the server and back to the requesting client. There is one more check performed to see that the return type indeed matches the type given at registration. A service method may always send back a value_t containing an error. If the types do not match, however, an error will be returned instead of the original value_t.


void llamagui_llama_count_callback (value_t *ret)
{
if (value_type(ret) == VALUE_TYPE_ERROR) {
llamagui_error_set("Oh no! A llamacaust occurred. Error %s", value_error_get(ret));
return;
}

llamagui_llama_count_set(value_int_get(ret));
}


The LlamaGUI user gets her llama count right next to the XMMS2 playback controls and playlist of llama-friendly songs. Everyone is happy.