Async Vala Example

443 Views Asked by At

In the Book "Introduction to Vala" by Dr Michael Lauer, he has mentioned that the lib Soup async api is broken. I'm struggling to write a simple example using session.queue_message that query radio stations using the service from radio-browser. Here is my code. I would appreciate any help form experienced Programmers like "Al Thomas". Thank you.

public class Station : Object {

    // A globally unique identifier for the change of the station information
    public string changeuuid { get; set; default = ""; }

    // A globally unique identifier for the station
    public string stationuuid { get; set; default = ""; }

    // The name of the station
    public string name { get; set; default = ""; }

    // The stream URL provided by the user
    public string url { get; set; default = ""; }
    // and so on ... many properties
    public string to_string () {
        var builder = new StringBuilder ();
        builder.append_printf ("\nchangeuuid = %s\n", changeuuid);
        builder.append_printf ("stationuuid  = %s\n", stationuuid);
        builder.append_printf ("name = %s\n", name);
        builder.append_printf ("url = %s\n", url);
        return (owned) builder.str;
    }
}

public class RadioBrowser : Object {
    private static Soup.Session session;
    // private static MainLoop main_loop;
    public const string API_URL = "https://de1.api.radio-browser.info/json/stations";
    public const string USER_AGENT = "github.aeldemery.radiolibrary";

    public RadioBrowser (string user_agent = USER_AGENT, uint timeout = 50)
    requires (timeout > 0)
    {
        Intl.setlocale ();
        session = new Soup.Session ();
        session.timeout = timeout;
        session.user_agent = user_agent;
        session.use_thread_context = true;

        // main_loop = new MainLoop ();
    }

    private void check_response_status (Soup.Message msg) {
        if (msg.status_code != 200) {
            var str = "Error: Status message error %s.".printf (msg.reason_phrase);
            error (str);
        }
    }

    public Gee.ArrayList<Station> listStations () {
        var stations = new Gee.ArrayList<Station> ();
        var data_list = Datalist<string> ();
        data_list.set_data ("limit", "100");

        var parser = new Json.Parser ();
        parser.array_element.connect ((pars, array, index) => {
            var station = Json.gobject_deserialize (typeof (Station), array.get_element (index)) as Station;
            assert_nonnull (station);
            stations.add (station);
        });

        var msg = Soup.Form.request_new_from_datalist (
            "POST",
            API_URL,
            data_list
        );
        // send_message works but not queue_message
        // session.send_message (msg);

        session.queue_message (msg, (sess, mess) => {
            check_response_status (msg);
            try {
                parser.load_from_data ((string) msg.response_body.flatten ().data);
            } catch (Error e) {
                error ("Failed to parse data, error:" + e.message);
            }
        });

        return stations;
       }
    }

    int main (string[] args) {
        var radio_browser = new RadioBrowser ();
        var stations = radio_browser.listStations ();
        assert_nonnull (stations);

       foreach (var station in stations) {
           print (station.to_string ());
       }
       return 0;
    }
1

There are 1 best solutions below

2
On

While I'm not Al Thomas, I still might be able to help. ;)

For async calls to work in there needs to be a main loop running, and typically from the main program thread. Thus you want to create and execute the main loop from your main() function, rather than in your application code:

int main (string[] args) {
    var loop = new GLib.MainLoop ();
    var radio_browser = new RadioBrowser ();

    // set up async calls here

    // then set the main loop running as the last thing
    loop.run();
}

Also, if you want to wait for an async call to complete, you typically need to make the call using the yield keyword from another async function E.g:

    public async Gee.ArrayList<Station> listStations () {
        …

        // When this call is made, execution of listStations() will be
        // suspended until the soup response is received
        yield session.send_async(msg);

        // Execution then resumes normally
        check_response_status (msg);
        parser.load_from_data ((string) msg.response_body.flatten ().data);

        …

        return stations;
    }

You can then call this from the (non-async) main function using the listStations.begin(…) notation:

int main (string[] args) {
    var loop = new GLib.MainLoop ();
    var radio_browser = new RadioBrowser ();
    radio_browser.listStations.begin((obj, res) => {
        var stations = radio_browser.listStations.end(res);
        …
        loop.quit();
    });
    loop.run();
}

As further reading, I would recommend the async section of the Vala Tutorial, and the asyc examples on the wiki as well.