Monday, June 3, 2013

Pre-filling an Outlook Express mail in Windows XP

I want a Python3 script to open a pre-filled Outlook Express (OE) new-mail window. A human should read, edit, and approve the e-mail before sending. If I wanted a completely automatic e-mail, I would use Python's smtp module.

The problem is that OE has no COM server, and the Simple-MAPI support is in C++.




Command Line

There is an easy way from the Windows command line:

C:>"C:\Program Files\Outlook Express\msimn" /mailurl:mailto:"name%40example.com?cc=cc1%40example.com&bcc=bcc1@example.com&subject=The%20Subject%20Line&body=The%20first%20paragraph.%0A%0AThe%20second%20paragraph.%0A%0AThanks%2C%0AName

This launches OE with a single argument. That argument is a mailto: URL. The URL includes to:, cc:, bcc:, subject:, and the body.
  • "C:\Program Files\Outlook Express\msimn" launches OE
  • /mailurl:mailto:" defines the mailto: URL. The quote(") is required on the command line, though not in the python version.
  • name%40example.com? is the To: line. The To: line ends with the question-mark (?)
  • cc=cc1%40example.com& the rest of the fields are defined (cc=) and separated with the ampersand (&).
  • Don't use spaces or commas or returns - just text.
    space%20
    ,%2C
    return%0A
    @%40
  • I have not figured out a way to include more than one e-mail address on each line.
  • Files cannot be attached using this method.




Python

Since OE does not have any COM or other programmatic way to do it, we will fall back on subprocess, and have Python simply create a mailto: URL and use the command line.

This simple Python3 script creates a pre-filled Outlook Express window using subprocess:


import subprocess

# The header and body
to      = "name1%40example.com"
cc      = "cc1%40example.com"     # or leave blank: cc = ""
bcc     = "bcc1%40example.com"    # or leave blank: bcc = ""
subject = "The%20Subject"
body    = "The%20first%20paragraph.%0A%0AThe%20second%20paragraph.%0A%0AThanks%2C%0AName"

oe = "C:\Program Files\Outlook Express\msimn"
mailto_url = ('/mailurl:mailto:' + to      + '?' +   # No lone quote (") after colon (:)
              'cc='              + cc      + '&' +
              'bcc='             + bcc     + '&' +
              'subject='         + subject + '&' +
              'body='            + body )            # No ending ampersand (&) 

subprocess.call([oe, mailto_url])

Tuesday, May 7, 2013

Brainstorm Big 5 - May 2013

These are a few of the more interesting current ideas on Brainstorm. When you prepare for UDS, also do a quick Brainstorm search. It's a good way to see what users have thought about the topic in the past.

If you want to leave Developer Feedback on a Brainstorm Idea, but lack permissions, then please contact me. I can assign you permission, or simply add your response for you.  (ian dash weisser at ubuntu dot com)




The all-time top four:

The current top four all-time Brainstorm Ideas. They change from time to time as new ideas overtake old ideas, or as ideas get merged or implemented or closed.

You may see a common theme among them:

1) Restoring the bootloader by Ubuntu Live media
2) Graphical frontend to edit GRUB menu
3) Provide a simple interface for labeling partitions and external drives
4) Better Hardware Profile Manager

Here is what I see: None of these seem like features requested by unskilled users.

Instead, these seem more likely to be used by migrating power-users who have imprinted upon previous systems...and then discovered their first hurdle on the learning curve.

Now, I'm not proposing that we should implement any of these ideas. Instead, consider it a data point - here is one measure of how Ubuntu is perceived by rather skilled new users. Not what they actually need to be productive, but what they spend their time looking for fruitlessly.

And when they don't find it, some of them rant about Ubuntu. Goodness, just look at some of those comments.
  • Do we want these issues to be the first hurdles for this type of user?
  • Is there an easy alternative we can draw them into?
  • Is there a better message that Ubuntu, Launchpad, the forums, the Teams, etc. should be communicating to them?
  • Are these opportunities to begin their learning curve in a kinder, gentler way?
  • I wonder why those users, after overcoming the hurdle, did not implement a solution to help those who came after them?






Monetization by committee

To round out the Big 5 this month,

Alternative Sources of Income

The top Idea of the past six months, this is a crazy-quilt of monetization ideas.  I've been -among other things- a banker and a real-business-with-employees owner and a QA inspector, so I completely understand how unrealistic some of the money-handling-and-administration Solutions really are. But like the all-time top-four (above), the real message is the intent and the context.
  1. People are still really frustrated by [what they think are] bugs.
    Frustrated enough that some are willing to pay [small amounts] for bug bounties.
    Yet apparently not frustrated enough to actually get involved.

  2. There is also a lot of FUD still floating around about unity-lens-shopping, and some new users are still getting attracted to the tinfoil-hat crowd's message.
  • Is this an opportunity to recruit for the Bug Squad? LoCos? Teams? Upstreams?
  • How can we make our commitment to personal data privacy clearer?
  • How can we improve the message that Ubuntu users are participants, not customers?

Feel free to discuss in the appropriate Brainstorm Idea page, or in the comments section of this blog, or by e-mail...or anywhere else you like. Like at UDS.
Yeah, at UDS.
See you on IRC at UDS!

Saturday, May 4, 2013

Six ways to make a notification pop-up

UPDATED MAY 2013 to add Go and GObject methods to the existing Command Line and Python methods.
Originally published Feb 27, 2009.

There are several ways to create notifications in Ubuntu. The notify-OSD daemon draws the boxes and creates the notifications, so all these ways simply contact it and tell it what to do.

  1. On the command line, use the notify-send command. This command is part of the libnotify-bin package. It's easy:
    $ notify-send -i /usr/share/icons/Tango/32x32/status/sunny.png \
                       "Notification Title" \
                       "This is the body"

  2. More command line: dbus-send command does NOT work for notification. It does not support nested message variables. If it did, it should be something really close to this:
    dbus-send --session --dest=org.freedesktop.Notifications \
                             --type=method_call --reply-timeout=10000 \
                             /org/freedesktop/Notifications org.freedesktop.Notifications \
                             string:'Test Application' uint32:0 \
                             string: string:'Notification Title' \
                             string:'This is the body' \
                             array:string: dict:string: int32:10000

  3. Using the Python2 or Python3 pynotify module:
    #!/usr/bin/env python
    """Python 2.5 script. Creates a Notification pop-up bubble"""
    import pynotify
    title = "Notification Title"
    text  = "This is the body"
    icon  = "/usr/share/icons/Tango/32x32/status/sunny.png"
    pynotify.init("Test Application")
    notification = pynotify.Notification(title, text, icon) 
    notification.set_urgency(pynotify.URGENCY_NORMAL)
    notification.show() 

  4. Using the Python2 or Python3 dbus module:
    #!/usr/bin/env python
    """Python 2.5 script. Creates a Notification pop-up bubble"""
    import dbus
    item              = "org.freedesktop.Notifications"
    path              = "/org/freedesktop/Notifications"
    interface         = "org.freedesktop.Notifications"
    app_name          = "Test Application"
    id_num_to_replace = 0
    icon              = "/usr/share/icons/Tango/32x32/status/sunny.png"
    title             = "Notification Title"
    text              = "This is the body"
    actions_list      = ''
    hint              = ''
    time              = 5000   # Use seconds x 1000
    
    bus = dbus.SessionBus()
    notif = bus.get_object(item, path)
    notify = dbus.Interface(notif, interface)
    notify.Notify(app_name, id_num_to_replace, icon, title, text, actions_list, hint, time)

  5. Using the Python3 GObject Introspection (gi) module:
    #!/usr/bin/env python3
    import gi.repository
    from gi.repository import Gio, GLib
    
    session_bus                = Gio.BusType.SESSION
    cancellable                = None
    proxy_property             = 0
    interface_properties_array = None
    destination                = "org.freedesktop.Notifications"
    path                       = "/org/freedesktop/Notifications"
    interface                  = destination
    method                     = "Notify"
    application_name           = "Test Application"
    title                      = "Notification Title"
    text                       = "This is the body"
    id_num_to_replace          = 0
    icon                       = "/usr/share/icons/Tango/32x32/status/sunny.png"
    actions_list               = []
    hints_dict                 = {}
    display_milliseconds       = 5000
    timeout                    = -1
    
    connection = Gio.bus_get_sync(session_bus, cancellable)
    notify = Gio.DBusProxy.new_sync( connection, proxy_property, interface_properties_array,
                                     destination, path, interface, cancellable)
    args = GLib.Variant('(susssasa{sv}i)', ( application_name, id_num_to_replace, icon, 
                        title, text, actions_list, hints_dict, display_milliseconds))
    result = notify.call_sync(method, args, proxy_property, timeout, cancellable)
    id = result.unpack()[0]
    print(id)

  6. Using the go programming language (golang):
    package main
    
    import "launchpad.net/~jamesh/go-dbus/trunk"
    import "fmt"
    
    func main() {
        // Set the variables
        var (
            err              error
            conn             *dbus.Connection
            destination      string = "org.freedesktop.Notifications"
            path             string = "/org/freedesktop/Notifications"
            dbus_interface   string = "org.freedesktop.Notifications"
            dbus_method      string = "Notify"
            application_name string = "dbus-test"
            id_num_to_repl   uint32
            icon_path        string = "/usr/share/icons/Tango/32x32/status/sunny.png"
            title            string = "Notification Title"
            text             string = "This is the body"
            actions_list     []string
            hint             map[string]dbus.Variant
            timeout          int32  = -1
        )
    
        // Connect to Session or System buses.
        if conn, err = dbus.Connect(dbus.SessionBus); err != nil {
            fmt.Println("Connection error:", err)
        }
    
        // Create an object proxy
        obj := conn.Object(destination, dbus.ObjectPath(path))
    
        // Call object methods.
        reply, err := obj.Call(
            dbus_interface,             dbus_method,
            application_name,           id_num_to_repl,
            icon_path,                  notif_box_title,
            notif_box_body,             actions_list,
            hint,                       timeout)
        if err != nil {
            fmt.Println("Notification error:", err)
        }
    
        // Parse the reply message
        var notification_id uint32
        if err := reply.Args(&notification_id); err != nil {
            fmt.Println(err)
        }
        fmt.Println("Notification id:", notification_id)
    


Zenity is an application, not a daemon. It creates a regular window that can be abused for notifications.
zenity --info --title "Big Title" --text "This is the\\nbody text"

Sunday, March 3, 2013

Creating libraries and linking to libraries in Vala

This is an example of how to create a library in vala, and how to access it using several different methods. This example includes:
  1. A shared library written in vala, including
    • shared object (.so), and how to install it
    • headers (.h), and
    • bindings (.vapi)
  2. A command-line tool that uses the library by direct-linking
  3. A dbus server that uses the library by direct linking
    • makes the library available to other dbus-aware applications
    • self-terminating process after use (not a daemon)
  4. A command-line tool that uses the library via dbus and the dbus-server.

The first half of the example is based on the official vala tutorial library example,
I have added dbus connectivity and a bit more explanation that I found helpful.





1) Create an empty directory. These steps create a lot of files!

    $ mkdir test_library
    $ cd test_library



2) Create the library. Save this file as libtest.vala:

// BEGIN
public class MyLib : Object {

    public string hello() {
      return "Hello World from MyLib";
   }

   public int sum(int x, int y) {
      return x + y;
   }
}
// END



3) Create the .c, .h (C header), and .vapi (vala binding) files:
   -C --ccode   Output C code instead of compiled
   -H --header=file  Output C header file
      --library=name Assign name to library
      --vapi         Assign name to vapi file
   -b --basedir=dir  Use dir as the base source dir (For me, this is unnecessary)

    $ valac -C -H libtest.h --library libtest libtest.vala --basedir ./



4) Compile the library using:

   -shared       Create a .so shared object
   -fPIC         Position Independent Code (PIC) suitable for use in a shared library
   $(pkg-config --cflags gobject-2.0)
                 Output: -I/usr/include/glib-2.0
                         -I/usr/lib/i386-linux-gnu/glib-2.0/include
   -o filename   Output filename

     $ gcc -shared -fPIC -o libtest.so $(pkg-config --cflags --libs gobject-2.0) libtest.c



5) Create the command-line application that uses the library by linking.
   Save this file as hello.vala:

// BEGIN
void main() {
    var test = new MyLib();

    // MyLib hello()
    stdout.printf("%s\n", test.hello());

    // MyLib sum()
    int x = 4, y = 5;
    stdout.printf("The sum of %d and %d is %d\n", x, y, test.sum(x, y));
}
// END




6) Compile the command-line application.
Using vala, there need to be TWO links:
  • The vala link to a .vapi file (in this example, test.vapi)
  • The gcc link to a C library (in this example, -X -ltest to add libtest.so)

   - The vala link to a .vapi file (in this example, test.vapi)
   - The gcc link to a C library (in this example, -X -ltest to add libtest.so)

   -X --Xcc=-I.    Pass the -I. (include current directory) to gcc.
                   GCC will look for shared libraries in the current directory first
   -X --Xcc=-L.    Pass the -L. (add current directory to search path) to gcc.
   -X --Xcc=-ltest Link library "libtest" which we just created in the current directory. 

   If we had the libtest.so in the local directory, we could use:

    $ valac -X -I. -X -L. -X -ltest -o hello hello.vala libtest.vapi



7) Test the command-line application that uses the library:
The library is in the local directory (uninstalled):

     $ LD_LIBRARY_PATH=$PWD ./hello
     Hello World from MyLib
     The sum of 4 and 5 is 9



8) We cannot easily use the local library for dbus.
So install the library to the expected location.
Copy the library to /usr/lib using:

      $ sudo cp libtest.so /usr/lib/



9) Test the (installed) command-line application:

     $ ./hello
     Hello World from MyLib
     The sum of 4 and 5 is 9



10) Create the dbus server that uses the library.
A server listens for requests from other applications. This server acts as a gateway: It translates other application requests for library methods into a library call, and then sends the response back across dbus to the original requestor.

Save this file as dbus_server.vala:

// BEGIN
// Source: https://live.gnome.org/Vala/DBusServerSample#Server

[DBus (name = "org.example.Demo")]   // dbus interface name
public class DemoServer : Object {

    /* Functions that access the library on the Demo interface
     * Note the LACK of \n in the return strings.
     * Vala automatically mangles my_function_name into the
     *   standard Dbus camelcase MyFunctionName
     * So a function is "hello()" here, but "Hello" on Dbus 
     */
    public string sum () { 
        var test = new MyLib();
        int x = 4, y = 5;
        return "The sum of %d and %d is %d".printf( x, y, test.sum(x, y));
        // Note the LACK of \n in the return strings
    }

    public string hello () { 
        var test = new MyLib();
        return "%s".printf( test.hello());
    }

}

[DBus (name = "org.example.DemoError")]
public errordomain DemoError { SOME_ERROR }

/* Dbus functions */
void on_bus_aquired (DBusConnection conn) {
    try {
        string path = "/org/example/demo";
        conn.register_object (path, new DemoServer ());
    } catch (IOError e) { 
    stderr.printf ("Could not register service\n"); }
}

void main () {
    GLib.MainLoop loop           = new GLib.MainLoop ();
    GLib.TimeoutSource time      = new TimeoutSource(3000);   // 3 sec
    GLib.BusType bus             = BusType.SESSION;
    string destination           = "org.example.demo";
    GLib.BusNameOwnerFlags flags = BusNameOwnerFlags.NONE;

    Bus.own_name ( bus, destination, flags,
                  on_bus_aquired,
                  () => {},
                  () => stderr.printf ("Could not aquire name\n"));

    // Use timeout to quit the loop and exit the program
    time.set_callback( () => { loop.quit(); return false; });
    time.attach( loop.get_context() );  // Attach timeout to loop

    loop.run ();                // Listen for dbus connections

}
// END



11) Compile the dbus server:
   Dbus requires the gio package (--pkg gio-2.0)
   -X --Xcc=-I.    Pass the -I. (include current directory) to gcc.
                   GCC will look for shared libraries in the current directory first
   -X --Xcc=-L.    Pass the -L. (add current directory to search path) to gcc.
   -X --Xcc=-ltest Link library "libtest" which we just created in the current directory. 

   Since the library is installed, we can ignore those local directory flags:

      $ valac --pkg gio-2.0 -X -ltest dbus_server.vala libtest.vapi



12) Create a dbus .service file, so dbus knows how to find our new dbus_server.
Save this file as dbus.service. Your third line will vary - use the real path:

// BEGIN

[D-BUS Service]

Name=org.example.demo

Exec=/home/me/vala/library/dbus_server

// END



13) Install the dbus service file:

      $ sudo cp dbus.service /usr/share/dbus-1/services/org.example.demo.service



14) Test the dbus server using an existing command-line application, dbus-send.
This is a command-line application using the library via dbus, using dbus_server for it's intended purpose as a gatetay.

The sender is different each time because dbus-send creates a new process and connection each time it is used.
The destination is different because the server terminates after 3 seconds.

      $ dbus-send --session --type=method_call --print-reply \
        --dest="org.example.demo" /org/example/demo org.example.Demo.Hello
      method return sender=:1.3173 -> dest=:1.3172 reply_serial=2
      string "Hello World from MyLib"

      $ dbus-send --session --type=method_call --print-reply \
        --dest="org.example.demo" /org/example/demo org.example.Demo.Sum
      method return sender=:1.3175 -> dest=:1.3174 reply_serial=2
      string "The sum of 4 and 5 is 9"


15) Clean up:
Remove the dbus service file:
Remove the test library from /usr/lib

     $ sudo rm /usr/share/dbus-1/services/org.example.demo.service
     $ sudo rm /usr/lib/libtest.so

Thursday, February 21, 2013

Logging with Vala

How to send messages to syslog using Vala:

// log.vala

public static void main() {

    // Application Name (called 'domain' in Gspeak)
    string application_name = "MyTestApplication";

    // Log message
    string message = "This is a test message";

    // Log level - set to "Warning"
    GLib.LogLevelFlags glib_level       = GLib.LogLevelFlags.LEVEL_WARNING;
    int                posix_level      = Posix.LOG_WARNING; 

    // Print to stderr, not syslog or another log
    GLib.log( application_name, glib_level, message );

    // Log to syslog using the posix bindings
    // Posix.LOG_PID and Posix.LOG_USER are defined by Posix, not me.
    Posix.openlog(application_name, Posix.LOG_PID, Posix.LOG_USER);
    Posix.syslog(posix_level, message);

    return;
}

Compile using valac --pkg posix log.vala

String Arrays in Vala

String Arrays are simply bunches of strings grouped together

string[] apples = {"red delicious", "granny smith", "macintosh", "gala", "fuji"}

They look like Python lists, but they are not. They often don't work like lists (you cannot slice them), but they work *really* fast. They are useful if you have a data set that is (mostly) immutable...you can append to it, but I haven't found a way to delete strings from the array without copying everything else into a new array.

Here is a demo program showing how to
  • Create a string array
  • Append a string to an array
  • Match a string in an array
  • Determine the index of the matched string
  • Retrieve a string from the array (non-destructively)
  • Replace a string with another withing an array
  • Concatenate the array into a single string (for printing)

// string_array.vala

// If a in b
void if_a_in_b ( string a, string[] b, string b_name )  {
    if (a in b) { 
        stdout.printf("Found %s in %s\n", a, b_name); 
    }
    else {
        stdout.printf("%s not Found in %s\n", a, b_name);
    }
    return;
}


// One way to print an array
void print1 (string[] a, string a_name) {
    stdout.printf("Array %s: ", a_name);
    foreach (string item in a) {
        stdout.printf("%s, ", item);
    }
    stdout.printf("\n");
    return;
}

// Another way to print an array
void print2 (string[] a, string a_name) {
    string a_string = string.joinv(", ", a);
    stdout.printf("Array %s : %s\n", a_name, a_string);
    return;
}

// Index of an item in an array
void index_array (string[] a, string a_name, string match) {
    // Record the index of all matches
    int[] indexes = {};
    int i;
    for (i = 0; i < a.length; i++) {
        if (a[i] == match) {
            indexes += i;
        }
    }
    // Print the results
    if (indexes.length == 0) {
        stdout.printf("Indexing %s: %s not found\n", a_name, match);
    }
    else if (indexes.length == 1) {
        stdout.printf("Indexing %s: %s found at position %d\n", 
        a_name, match, indexes[0]);        
    }
    else if (indexes.length == 2) {
        stdout.printf("Indexing %s: %s found at positions %d and %d\n", 
        a_name, match, indexes[0], indexes[1]);        
    }
    else {
        stdout.printf("Indexing %s: %s found at positions ", 
        a_name, match);
        // Convert ints to a strings
        int j;
        for (j = 0; j < indexes.length; j++) {
            if ( j < (indexes.length - 1)) {
                stdout.printf("%d, ", indexes[j]);
            }
            else {
                stdout.printf("and %d.\n", indexes[j]);
            }
        }
    }
    return;
}



void arrays () {
    // Create two string arrays 
    string[] orange = { "fred", "joe", "allen", "steve" };
    string[] blue   = { "jane", "sam", "ellie", "terri" };

    // Test contents of each array
    if_a_in_b ("fred", orange, "Orange");
    if_a_in_b ("fred", blue, "Blue");

    // Length of an array
    stdout.printf("List length: %i\n", orange.length);

    // Item from an array
    stdout.printf("Orange item #2: %s\n", orange[1]);

    // Replace one string in an array
    blue[2] = "pamela";

    // Determine the index (location) of a string in an array
    index_array(blue, "blue", "pamela");

    // Append one string to an array 
    blue += "stacy";

    // Print an array
    print1(orange, "orange");
    print2(blue, "blue");

    return;
}


// Main
public static void main() {
    arrays ();
    stdout.printf("Hello, World\n");
    return;
    }


Compile with a simple valac string_array.vala



Thursday, February 14, 2013

Using custom C libraries with Vala

Let's try to use Vala with custom C libraries.

For this example, let's see if we can use Vala to manilulate a gdbm database.
Gdbm is a tiny db used in lots of applications.

In order for a C library to work in Vala, it needs a .vapi file to translate.
In this example, we will use the gdbm library (a small, common database in C)  using Vala bindings.

There are two issues for new users here:
  1. Linking to C libraries in the C compiler
  2. Linking to vapi files in the Vala interpreter
You'll see how to handle both.


1) Download the sources:
  • Create a working directory
  • Install the libgdbm-dev package, to get the c headers.
  • I discovered a vapi file for gdbm created by Andre Masella. Thank you, Andre!
  • Download and install the vapi file. Vapi files belong in /usr/share/vala/vapi/
  • Open a browser window to the Gnu GDBM documentation.
$ mkdir /home/me/vala/gdbm_vala
$ cd /home/me/vala/gdbm_vala
# sudo apt-get install libgdbm-dev
$ wget https://raw.github.com/apmasell/vapis/master/gdbm.vapi
# sudo ln /home/me/vala/gdbm_vala/gdbm.vapi /usr/share/vala/vapi/


2) Create a simple file in C to test the gdbm library:
This simple file (source) is /home/me/vala/gdbm_vala/gdbm_version1.c
It gets the gdbm_version string from the gdbm library.
#include <stdio.h>
#include <gdbm.h>
int main() {
    printf("VERSION (C): %s.\n", gdbm_version);
}
Let's compile it and run it to test the c headers we retrieved in the libgdbm-dev package.
  • The gcc -o flag specifies the output file name

$ gcc -o gdbm_version1 gdbm_version1.c
/tmp/cc31QKu0.o: In function `main':
gdbm_version1.c:(.text+0xa): undefined reference to `gdbm_version'
collect2: error: ld returned 1 exit status

Uh-oh. Fatal error.
The compiler did not understand the 'gdbm_version' variable because gdbm.h is not in it's standard library. I must tell the compiler to link to it using the -l flag.

Try again, linking to the gdbm library.

$ gcc -o gdbm_version1 gdbm_version1.c -l gdbm
$ ./gdbm_version1
VERSION (C): GDBM version 1.8.3. 10/15/2002 (built Jun 11 2012 00:27:26).

There should be no errors or warnings. Success!


3) First program:

Here's the relevant entry in the vapi file for the version number:

[CCode(cheader_filename = "gdbm.h")]
namespace GDBM {
    ...
    [CCode(cname = "gdbm_version")]
    public const string VERSION;
    ...
}

Namespace GDBM + string VERSION, so "string GDBM.VERSION" in Vala should translate to "string gdbm_version" in C. And we just tested that the C version works.

Let's try a simple Vala program that checks the version number:

private static void main () {
    stdout.printf("The gdbm version string: %s\n", GDBM.VERSION);
    }

And compile it:
  • --pkg gdbm tells valac to look for the gdbm vapi file.

$ valac --verbose --pkg gdbm gdbm_version2.vala 
Loaded package `/usr/share/vala-0.18/vapi/glib-2.0.vapi'
Loaded package `/usr/share/vala-0.18/vapi/gobject-2.0.vapi'
Loaded package `/usr/share/vala/vapi/gdbm.vapi'
cc -o '/home/ian/vala/gdbm_vala/code/gdbm_version2' '/home/ian/vala/gdbm_vala/code/gdbm_version2.vala.c' -I/usr/include/glib-2.0 -I/usr/lib/i386-linux-gnu/glib-2.0/include  -lgobject-2.0 -lglib-2.0
/tmp/ccnYcsLH.o: In function `_vala_main':
gdbm_version2.vala.c:(.text+0xf): undefined reference to `gdbm_version'
collect2: error: ld returned 1 exit status
error: cc exited with status 256
Compilation failed: 1 error(s), 0 warning(s)

Uh-oh. Something went wrong.

When something goes wrong , the --verbose flag is your friend.
The output shows that the Vala was translated into C without any warnings or errors, but then something was wrong when the compiler tried to prcoess the C.

Wait a second...it's the SAME ERROR we had before in the C program!
We know how to fix that: Tell the compiler to link (-l flag) to the gdbm library.

Check the compiler command, and --sure enough-- link to the gdbm library is missing.

Two lessons here:
  • Vala's link to a pkg does not necessarily mean the compiler is linked to the library. valac --pkg foo may not translate into cc -lfoo
  • Use valac's -Xcc flag to pass links to the compiler.

Let's try compiling again, adding /usr/include/gdbm.h using an Xcc flag, and removing --verbose:
  • --pkg gdbm tells valac to use the gdbm vapi file
  • --Xcc=-gdbm tells the C compiler to link to the C gdbm header

$ valac --pkg gdbm --Xcc=-lgdbm gdbm_version2.vala 
$ ./gdbm_version2
The gdbm version is: GDBM version 1.8.3. 10/15/2002 (built Jun 11 2012 00:27:26)

No errors or warnings. Success!


4) Take a break

We are now past the a bunch of biggest beginner hurdles:
  1. How to figure out common compile errors.
  2. How to tell the compiler to link to libraries.
  3. How to tell valac to use vapi files.

 5) More complicated program

Here is a more complicated program that opens a test database (or create a new db if it doesn't exist), and look for a certain key. If the key is not in the database, it appends the key:value pair to the database.
  • It uses the gdbm.vapi bindings for open(), read/write/create permission, contains(), and save() [which really means append].

// File: /home/me/vala/gdbm_vala/code/gdbm_add
private static int main () {

    // Hard-coded values
    string filename = "/home/me/vala/gdbm_vala/code/sample_db.gdbm";
    string key_string = "spam";
    string value_string = "eggs";

    // Open the database
    // GDBM.OpenFlag.WRCREAT comes from the vapi file. It means read+write+create new db
    GDBM.Database db = GDBM.Database.open (filename, 0, GDBM.OpenFlag.WRCREAT);

    // Convert the string values to bytes. gdbm doesn't save strings.
    uint8[] key_int = key_string.data;
    uint8[] value_int = value_string.data;

    // If the key_int is already in the database, say so and exit.
    if (db.contains (key_int) == true) {
        stdout.printf("In db\n");
        return 0;
        }
    stdout.printf("Not in db\n");

    // If appending the key:value pair to the database is successful, say so and exit.
    if (db.save (key_int, value_int, false) == true) {
        stdout.printf("Good append saved\n");
        return 0;
        }

    // Problems
    stdout.printf("Failed to append\n");
    return 1;
    }

Let's compile it.

$ $ valac --pkg gdbm --Xcc=-lgdbm gdbm_add.vala 
/home/ian/vala/gdbm_vala/code/gdbm_add.vala.c: In function ‘_vala_main’:
/home/ian/vala/gdbm_vala/code/gdbm_add.vala.c:192:2: warning: passing argument 1 of ‘gdbm_open’ discards ‘const’ qualifier from pointer target type [enabled by default]
In file included from /home/ian/vala/gdbm_vala/code/gdbm_add.vala.c:9:0:
/usr/include/gdbm.h:85:18: note: expected ‘char *’ but argument is of type ‘const gchar *’

Oh, my.

Let's look more closely at these error messages. Each error takes two lines
  • The first warning is in the generated C file, line 192, warning: passing argument 1 of ‘gdbm_open’ discards ‘const’ qualifier from pointer target type [enabled by default].

    Line 192 of the C file looks like _tmp4_ = gdbm_open (_tmp3_, 0, GDBM_WRCREAT, 0644, NULL); which looks a lot like the original gdbm_open function again.

    Valac is moving the filename to _tmp3_, and that _tmp_ variable seems to be the wrong type. It's just a warning - the code still compiles. And it seems to be a bug in valac, not a problem with our code.
  • The second warning is in the generated C file, line 9. That's the #include line. The warning is expected ‘char *’ but argument is of type ‘const gchar *’

    This is an interesting warning, and I'm not sure my merely including the header would trigger it...but it's also just a warning, and the compiled binary works.
Finally, let's run the binary a few times. The first time, the database does not exist, so the program should create the database and populate it with one key:value pair. On subsequent runs, the database should already exist, and the program should successfully find the existing key.

$ ./gdbm_add 
Not in db
Good append saved
$ ./gdbm_add 
In db
$ ./gdbm_add 
In db

It works!

We've gotten past the namespace hurdle, the vapi hurdle, and have successfully manipulated a gdbm database using the original C library.