<commit_before>/*
 * Copyright © 2012 Canonical Ltd.
 *             By <NAME> <<EMAIL>>
 * Copyright © 2013 <NAME> <<EMAIL>>
 * Copyright © 2013 <NAME> <<EMAIL>>
 *
 * Based upon a C implementation (© 2010-2012 Canonical Ltd) by:
 *  <NAME> <<EMAIL>>
 *  <NAME> <<EMAIL>>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * Zeitgeist is an activity-logging framework to enable the desktop of
 * the future.
 *
 * Its main component is the Zeitgeist engine, a D-Bus service that logs
 * any events other applications send to it. An event may be anything like:
 *  - The user opened/created/modified/closed a file, or visited a website.
 *  - The user received an e-mail, a phone call or an IM notification.
 *  - Someone modified a remote (eg. Google Drive) document owned by the user.
 *
 * This information is then made available to other Zeitgeist-enabled
 * applications over a powerful querying and monitoring API, and can be used
 * and analyzed to create intelligent or adaptive interfaces.
 *
 * Zeitgeist also comes with a blacklist extension to make sure the user
 * always stays in control of what information is logged.
 */
namespace Zeitgeist
{

/**
 * Primary access point for talking to the Zeitgeist daemon
 *
 * {@link Log} encapsulates the low level access to the Zeitgeist daemon.
 * You can use it to manage the log by inserting and deleting entries as well
 * as do queries on the logged data.
 *
 * It's important to realize that the #ZeitgeistLog class does not expose
 * any API that does synchronous communications with the message bus -
 * everything is asynchronous. To ease development some of the methods have
 * variants that are "fire and forget" ignoring the normal return value, so
 * that callbacks does not have to be set up.
 */
public class Log : QueuedProxyWrapper
{
    [CCode (cheader_filename = "sys/sysinfo.h", cname = "get_nprocs_conf")]
    extern static int get_nprocs_conf ();

    class DbWorker
    {
        private unowned ThreadFunc<void*> func;

        public DbWorker (ThreadFunc<void*> func)
        {
            this.func = func;
        }

        public void run ()
        {
            this.func ();
        }
    }

    private static Log default_instance;

    private RemoteLog proxy;
    private Variant? engine_version;
    private HashTable<Monitor, uint> monitors;
    private DbReader dbreader;
    private ThreadPool<DbWorker> threads;
    private bool allow_direct_read;

    public Log ()
    {
        monitors = new HashTable<Monitor, uint> (direct_hash, direct_equal);
        MainLoop mainloop = new MainLoop();
        allow_direct_read = Utils.log_may_read_directly ();

        Bus.get_proxy.begin<RemoteLog> (BusType.SESSION, Utils.ENGINE_DBUS_NAME,
            Utils.ENGINE_DBUS_PATH, 0, null, (obj, res) =>
            {
                try
                {
                    proxy = Bus.get_proxy.end (res);
                    proxy_acquired (proxy);
                }
                catch (IOError err)
                {
                    critical ("Unable to connect to Zeitgeist: %s",
                        err.message);
                    proxy_unavailable (err);
                }
                mainloop.quit();
            });

        mainloop.run();
    }

    /**
     * Get a unique instance of #ZeitgeistLog, that you can share in your
     * application without caring about memory management.
     *
     * See zeitgeist_log_new() for more information.
     *
     * @return ZeitgeistLog.
     */
    public static Log get_default ()
    {
        if (default_instance == null)
            default_instance = new Log ();
        return default_instance;
    }

    protected override void on_connection_established ()
    {
        // Reinstate all active monitors
        foreach (unowned Monitor monitor in monitors.get_keys ())
        {
            reinstall_monitor.begin (monitor);
        }

        // Update our cached version property
        engine_version = proxy.version;
        warn_if_fail (engine_version.get_type_string () == "(iii)");

        try {
            threads = new ThreadPool<DbWorker>.with_owned_data ((worker) => {
                worker.run ();
            }, get_nprocs_conf (), true);
        } catch (ThreadError err) {
            warning ("%s", err.message);
            threads = null;
        }

        if (allow_direct_read && threads != null &&
            proxy.datapath != ":memory:" &&
            FileUtils.test (proxy.datapath, GLib.FileTest.EXISTS)) {
            Utils.set_database_file_path (proxy.datapath);
            try {
                dbreader = new DbReader ();
            } catch (EngineError err){
                warning ("%s", err.message);
                dbreader = null;
            }
        }
        else
        {
            dbreader = null;
        }
    }

    protected override void on_connection_lost ()
    {
        dbreader = null;
    }

    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    *
    * @param event A {@link Event}
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async Array<uint32> insert_event (Event event,
        Cancellable? cancellable=null) throws Error
    {
        var events = new GenericArray<Event> ();
        events.add (event);
        return yield insert_events (events, cancellable);
    }


    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    *
    * @param events An {@link GLib.GenericArray} of {@link Event}
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async Array<uint32> insert_events (GenericArray<Event> events,
        Cancellable? cancellable=null) throws Error
    {
        var events_cp = new GenericArray<Event> ();
        for (int i = 0; i < events.length; i++)
            events_cp.add (events.get (i));
        yield wait_for_proxy ();
        uint32[] ids = yield proxy.insert_events (Events.to_variant (events_cp), cancellable);
        var result = new Array<uint32> ();
        // Ideally we'd just place "(owned) ids" into the GArray, but .data isn't
        // in the Vala bindings...
        for (int i = 0; i < ids.length; ++i)
            result.append_val (ids[i]);
        return result;
    }

    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    * This method is &quot;fire and forget&quot; and the caller will never know
    * whether the events was successfully inserted or not.
    *
    * This method is exactly equivalent to calling zeitgeist_log_insert_event()
    * with NULL set as @cancellable, @callback, and @user_data.
    *
    * @param event A {@link Event}
    */
    public void insert_event_no_reply (Event event)
        throws Error
    {
        insert_event (event);
    }

    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    * This method is &quot;fire and forget&quot; and the caller will never know
    * whether the events was successfully inserted or not.
    *
    * This method is exactly equivalent to calling zeitgeist_log_insert_event()
    * with NULL set as @cancellable, @callback, and @user_data.
    *
    * @param events An {@link GLib.GenericArray} of {@link Event}
    */
    public void insert_events_no_reply (GenericArray<Event> events)
        throws Error
    {
        insert_events (events);
    }

    /**
    * Send a query matching a collection of {@link Event} templates to the {@link Log}.
    * The query will match if an event matches any of the templates. If an event
    * template has more than one {@link Subject} the query will match if any one
    * of the {@link Subject}s templates match.
    *
    * The query will be done via an asynchronous DBus call and this method will
    * return immediately. The return value will be passed to callback as a list
    * of {@link Event}s. This list must be the sole argument for the callback.
    *
    * If you need to do a query yielding a large (or unpredictable) result set
    * and you only want to show some of the results at the same time (eg., by
    * paging them), consider using {@link find_event_ids}.
    *
    * In order to use this method there needs to be a mainloop runnning.
    * Both Qt and GLib mainloops are supported.
    *
    * @param time_range {@link TimeRange} A time range in which the events should be considered in
    * @param storage_state {@link StorageState} storage state
    * @param event_templates An {@link GLib.GenericArray} of {@link Event}
    * @param num_events int represteing the number of events that should be returned
    * @param result_type {@link ResultType} how the events should be grouped and sorted
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async ResultSet find_events (
        TimeRange time_range,
        GenericArray<Event> event_templates,
        StorageState storage_state,
        uint32 num_events,
        ResultType result_type,
        Cancellable? cancellable=null) throws Error
    {
        var event_templates_cp = new GenericArray<Event> ();
        for (int i = 0; i < event_templates.length; i++)
            event_templates_cp.add (event_templates.get (i));
    
        if (dbreader != null) {
            SimpleResultSet result_set = null;
            EngineError error = null;
            ThreadFunc<void*> run = () => {
                try {
                    var result = dbreader.find_events (time_range, event_templates_cp,
                        storage_state, num_events, result_type);
                    result_set = new SimpleResultSet (result);
                } catch (EngineError err) {
                    error = err;
                } finally {
                    Idle.add (find_events.callback);
                }
                return null;
            };

            threads.add (new DbWorker (run));
            yield;

            if (error != null)
                throw error;
            return result_set;
        }

        yield wait_for_proxy ();
        var result = yield proxy.find_events (time_range.to_variant (),
            Events.to_variant (event_templates_cp), storage_state,
            num_events, result_type, cancellable);
        return new SimpleResultSet (Events.from_variant (result));
    }

    /**
    * Send a query matching a collection of {@link Event} templates to the {@link Log}.
    * The query will match if an event matches any of the templates. If an event
    * template has more than one {@link Subject} the query will match if any one
    * of the {@link Subject}s templates match.
    *
    * The query will be done via an asynchronous DBus call and this method will
    * return immediately. The return value will be passed to callback as a list
    * of intergers represrting {@link Event} id's.
    * This list must be the sole argument for the callback.
    *
    * In order to use this method there needs to be a mainloop runnning.
    * Both Qt and GLib mainloops are supported.
    *
    * @param time_range {@link TimeRange} A time range in which the events should be considered in
    * @param storage_state {@link StorageState} storage state
    * @param event_templates An {@link GLib.GenericArray} of {@link Event}
    * @param num_events int represteing the number of events that should be returned
    * @param result_type {@link ResultType} how the events should be grouped and sorted
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async uint32[] find_event_ids (
        TimeRange time_range,
        GenericArray<Event> event_templates,
        StorageState storage_state,
        uint32 num_events,
        ResultType result_type,
        Cancellable? cancellable=null) throws Error
    {
        var event_templates_cp = new GenericArray<Event> ();
        for (int i = 0; i < event_templates.length; i++)
            event_templates_cp.add(event_templates.get (i));

        if (dbreader != null) {
            uint32[] ids = null;
            EngineError error = null;
            ThreadFunc<void*> run = () => {
                try {
                    ids = dbreader.find_event_ids (time_range, event_templates_cp,
                        storage_state, num_events, result_type);
                } catch (EngineError err) {
                    error = err;
                } finally {
                    Idle.add (find_event_ids.callback);
                }
                return null;
            };

            threads.add (new DbWorker (run));
            yield;

            if (error != null)
                throw error;
            return ids;
        }

        yield wait_for_proxy ();
        return yield proxy.find_event_ids (time_range.to_variant (),
            Events.to_variant (event_templates_cp), storage_state,
            num_events, result_type, cancellable);
    }

    /**
    * Look up a collection of {@link Event} in the {@link Log} given a collection
    * of event ids. This is useful for looking up the event data for events found
    * with the find_event_ids_* family of functions.
    *
    * Each {@link Event} which is not found in the {@link Log} is represented by
    * NULL in the resulting collection. The query will be done via an asynchronous
    * DBus call and this method will return immediately. The returned events will
    * be passed to callback as a list of {@link Event}s, which must be the only
    * argument of the function.
    *
    * In order to use this method there needs to be a mainloop runnning.
    *
    * @param event_ids a {@link GLib.Array} of {@link Event} ids
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async ResultSet get_events (
        Array<uint32> event_ids,
        Cancellable? cancellable=null) throws Error
    {
        uint32[] simple_event_ids = new uint32[event_ids.length];
        for (int i = 0; i < event_ids.length; i++)
            simple_event_ids[i] = event_ids.index (i);

        if (dbreader != null)
        {
            SimpleResultSet result_set = null;
            EngineError error = null;
            ThreadFunc<void*> run = () => {
                try {
                    var result = dbreader.get_events (simple_event_ids);
                    result_set = new SimpleResultSet (result);
                } catch (EngineError err) {
                    error = err;
                } finally {
                    Idle.add (get_events.callback);
                }
                return null;
            };

            threads.add (new DbWorker (run));
            yield;

            if (error != null)
                throw error;
            return result_set;
        }

        yield wait_for_proxy ();
        var result = yield proxy.get_events (simple_event_ids, cancellable);
        return new SimpleResultSet (Events.from_variant (result));
    }

    /**
    * Get a list of URIs of subjects which frequently occur together with events
    * matching event_templates. Possibly restricting to time_range or to URIs
    * that occur as subject of events matching result_event_templates.
    *
    * @param time_range {@link TimeRange} A time range in which the events should be considered in
    * @param storage_state {@link StorageState} storage state
    * @param event_templates An {@link GLib.GenericArray} of {@link Event} describing the events to relate to
    * @param result_event_templates An {@link GLib.GenericArray} of {@link Event} desrcibing the result to be returned
    * @param num_events int represteing the number of events that should be returned
    * @param result_type {@link ResultType} how the events should be grouped and sorted
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async string[] find_related_uris (
        TimeRange time_range,
        GenericArray<Event> event_templates,
        GenericArray<Event> result_event_templates,
        StorageState storage_state,
        uint32 num_events,
        ResultType result_type,
        Cancellable? cancellable=null) throws Error
    {<commit_msg>Fixed find_related_uris to take RelevantResultType as an argument instead of ResultType<commit_after>/*
 * Copyright © 2012 Canonical Ltd.
 *             By <NAME> <<EMAIL>>
 * Copyright © 2013 <NAME> <<EMAIL>>
 * Copyright © 2013 <NAME> <<EMAIL>>
 *
 * Based upon a C implementation (© 2010-2012 Canonical Ltd) by:
 *  <NAME> <<EMAIL>>
 *  <NAME> <<EMAIL>>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/**
 * Zeitgeist is an activity-logging framework to enable the desktop of
 * the future.
 *
 * Its main component is the Zeitgeist engine, a D-Bus service that logs
 * any events other applications send to it. An event may be anything like:
 *  - The user opened/created/modified/closed a file, or visited a website.
 *  - The user received an e-mail, a phone call or an IM notification.
 *  - Someone modified a remote (eg. Google Drive) document owned by the user.
 *
 * This information is then made available to other Zeitgeist-enabled
 * applications over a powerful querying and monitoring API, and can be used
 * and analyzed to create intelligent or adaptive interfaces.
 *
 * Zeitgeist also comes with a blacklist extension to make sure the user
 * always stays in control of what information is logged.
 */
namespace Zeitgeist
{

/**
 * Primary access point for talking to the Zeitgeist daemon
 *
 * {@link Log} encapsulates the low level access to the Zeitgeist daemon.
 * You can use it to manage the log by inserting and deleting entries as well
 * as do queries on the logged data.
 *
 * It's important to realize that the #ZeitgeistLog class does not expose
 * any API that does synchronous communications with the message bus -
 * everything is asynchronous. To ease development some of the methods have
 * variants that are "fire and forget" ignoring the normal return value, so
 * that callbacks does not have to be set up.
 */
public class Log : QueuedProxyWrapper
{
    [CCode (cheader_filename = "sys/sysinfo.h", cname = "get_nprocs_conf")]
    extern static int get_nprocs_conf ();

    class DbWorker
    {
        private unowned ThreadFunc<void*> func;

        public DbWorker (ThreadFunc<void*> func)
        {
            this.func = func;
        }

        public void run ()
        {
            this.func ();
        }
    }

    private static Log default_instance;

    private RemoteLog proxy;
    private Variant? engine_version;
    private HashTable<Monitor, uint> monitors;
    private DbReader dbreader;
    private ThreadPool<DbWorker> threads;
    private bool allow_direct_read;

    public Log ()
    {
        monitors = new HashTable<Monitor, uint> (direct_hash, direct_equal);
        MainLoop mainloop = new MainLoop();
        allow_direct_read = Utils.log_may_read_directly ();

        Bus.get_proxy.begin<RemoteLog> (BusType.SESSION, Utils.ENGINE_DBUS_NAME,
            Utils.ENGINE_DBUS_PATH, 0, null, (obj, res) =>
            {
                try
                {
                    proxy = Bus.get_proxy.end (res);
                    proxy_acquired (proxy);
                }
                catch (IOError err)
                {
                    critical ("Unable to connect to Zeitgeist: %s",
                        err.message);
                    proxy_unavailable (err);
                }
                mainloop.quit();
            });

        mainloop.run();
    }

    /**
     * Get a unique instance of #ZeitgeistLog, that you can share in your
     * application without caring about memory management.
     *
     * See zeitgeist_log_new() for more information.
     *
     * @return ZeitgeistLog.
     */
    public static Log get_default ()
    {
        if (default_instance == null)
            default_instance = new Log ();
        return default_instance;
    }

    protected override void on_connection_established ()
    {
        // Reinstate all active monitors
        foreach (unowned Monitor monitor in monitors.get_keys ())
        {
            reinstall_monitor.begin (monitor);
        }

        // Update our cached version property
        engine_version = proxy.version;
        warn_if_fail (engine_version.get_type_string () == "(iii)");

        try {
            threads = new ThreadPool<DbWorker>.with_owned_data ((worker) => {
                worker.run ();
            }, get_nprocs_conf (), true);
        } catch (ThreadError err) {
            warning ("%s", err.message);
            threads = null;
        }

        if (allow_direct_read && threads != null &&
            proxy.datapath != ":memory:" &&
            FileUtils.test (proxy.datapath, GLib.FileTest.EXISTS)) {
            Utils.set_database_file_path (proxy.datapath);
            try {
                dbreader = new DbReader ();
            } catch (EngineError err){
                warning ("%s", err.message);
                dbreader = null;
            }
        }
        else
        {
            dbreader = null;
        }
    }

    protected override void on_connection_lost ()
    {
        dbreader = null;
    }

    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    *
    * @param event A {@link Event}
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async Array<uint32> insert_event (Event event,
        Cancellable? cancellable=null) throws Error
    {
        var events = new GenericArray<Event> ();
        events.add (event);
        return yield insert_events (events, cancellable);
    }


    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    *
    * @param events An {@link GLib.GenericArray} of {@link Event}
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async Array<uint32> insert_events (GenericArray<Event> events,
        Cancellable? cancellable=null) throws Error
    {
        var events_cp = new GenericArray<Event> ();
        for (int i = 0; i < events.length; i++)
            events_cp.add (events.get (i));
        yield wait_for_proxy ();
        uint32[] ids = yield proxy.insert_events (Events.to_variant (events_cp), cancellable);
        var result = new Array<uint32> ();
        // Ideally we'd just place "(owned) ids" into the GArray, but .data isn't
        // in the Vala bindings...
        for (int i = 0; i < ids.length; ++i)
            result.append_val (ids[i]);
        return result;
    }

    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    * This method is &quot;fire and forget&quot; and the caller will never know
    * whether the events was successfully inserted or not.
    *
    * This method is exactly equivalent to calling zeitgeist_log_insert_event()
    * with NULL set as @cancellable, @callback, and @user_data.
    *
    * @param event A {@link Event}
    */
    public void insert_event_no_reply (Event event)
        throws Error
    {
        insert_event (event);
    }

    /**
    * Asynchronously send a set of events to the Zeitgeist daemon, requesting they
    * be inserted into the log.
    * This method is &quot;fire and forget&quot; and the caller will never know
    * whether the events was successfully inserted or not.
    *
    * This method is exactly equivalent to calling zeitgeist_log_insert_event()
    * with NULL set as @cancellable, @callback, and @user_data.
    *
    * @param events An {@link GLib.GenericArray} of {@link Event}
    */
    public void insert_events_no_reply (GenericArray<Event> events)
        throws Error
    {
        insert_events (events);
    }

    /**
    * Send a query matching a collection of {@link Event} templates to the {@link Log}.
    * The query will match if an event matches any of the templates. If an event
    * template has more than one {@link Subject} the query will match if any one
    * of the {@link Subject}s templates match.
    *
    * The query will be done via an asynchronous DBus call and this method will
    * return immediately. The return value will be passed to callback as a list
    * of {@link Event}s. This list must be the sole argument for the callback.
    *
    * If you need to do a query yielding a large (or unpredictable) result set
    * and you only want to show some of the results at the same time (eg., by
    * paging them), consider using {@link find_event_ids}.
    *
    * In order to use this method there needs to be a mainloop runnning.
    * Both Qt and GLib mainloops are supported.
    *
    * @param time_range {@link TimeRange} A time range in which the events should be considered in
    * @param storage_state {@link StorageState} storage state
    * @param event_templates An {@link GLib.GenericArray} of {@link Event}
    * @param num_events int represteing the number of events that should be returned
    * @param result_type {@link ResultType} how the events should be grouped and sorted
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async ResultSet find_events (
        TimeRange time_range,
        GenericArray<Event> event_templates,
        StorageState storage_state,
        uint32 num_events,
        ResultType result_type,
        Cancellable? cancellable=null) throws Error
    {
        var event_templates_cp = new GenericArray<Event> ();
        for (int i = 0; i < event_templates.length; i++)
            event_templates_cp.add (event_templates.get (i));
    
        if (dbreader != null) {
            SimpleResultSet result_set = null;
            EngineError error = null;
            ThreadFunc<void*> run = () => {
                try {
                    var result = dbreader.find_events (time_range, event_templates_cp,
                        storage_state, num_events, result_type);
                    result_set = new SimpleResultSet (result);
                } catch (EngineError err) {
                    error = err;
                } finally {
                    Idle.add (find_events.callback);
                }
                return null;
            };

            threads.add (new DbWorker (run));
            yield;

            if (error != null)
                throw error;
            return result_set;
        }

        yield wait_for_proxy ();
        var result = yield proxy.find_events (time_range.to_variant (),
            Events.to_variant (event_templates_cp), storage_state,
            num_events, result_type, cancellable);
        return new SimpleResultSet (Events.from_variant (result));
    }

    /**
    * Send a query matching a collection of {@link Event} templates to the {@link Log}.
    * The query will match if an event matches any of the templates. If an event
    * template has more than one {@link Subject} the query will match if any one
    * of the {@link Subject}s templates match.
    *
    * The query will be done via an asynchronous DBus call and this method will
    * return immediately. The return value will be passed to callback as a list
    * of intergers represrting {@link Event} id's.
    * This list must be the sole argument for the callback.
    *
    * In order to use this method there needs to be a mainloop runnning.
    * Both Qt and GLib mainloops are supported.
    *
    * @param time_range {@link TimeRange} A time range in which the events should be considered in
    * @param storage_state {@link StorageState} storage state
    * @param event_templates An {@link GLib.GenericArray} of {@link Event}
    * @param num_events int represteing the number of events that should be returned
    * @param result_type {@link ResultType} how the events should be grouped and sorted
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async uint32[] find_event_ids (
        TimeRange time_range,
        GenericArray<Event> event_templates,
        StorageState storage_state,
        uint32 num_events,
        ResultType result_type,
        Cancellable? cancellable=null) throws Error
    {
        var event_templates_cp = new GenericArray<Event> ();
        for (int i = 0; i < event_templates.length; i++)
            event_templates_cp.add(event_templates.get (i));

        if (dbreader != null) {
            uint32[] ids = null;
            EngineError error = null;
            ThreadFunc<void*> run = () => {
                try {
                    ids = dbreader.find_event_ids (time_range, event_templates_cp,
                        storage_state, num_events, result_type);
                } catch (EngineError err) {
                    error = err;
                } finally {
                    Idle.add (find_event_ids.callback);
                }
                return null;
            };

            threads.add (new DbWorker (run));
            yield;

            if (error != null)
                throw error;
            return ids;
        }

        yield wait_for_proxy ();
        return yield proxy.find_event_ids (time_range.to_variant (),
            Events.to_variant (event_templates_cp), storage_state,
            num_events, result_type, cancellable);
    }

    /**
    * Look up a collection of {@link Event} in the {@link Log} given a collection
    * of event ids. This is useful for looking up the event data for events found
    * with the find_event_ids_* family of functions.
    *
    * Each {@link Event} which is not found in the {@link Log} is represented by
    * NULL in the resulting collection. The query will be done via an asynchronous
    * DBus call and this method will return immediately. The returned events will
    * be passed to callback as a list of {@link Event}s, which must be the only
    * argument of the function.
    *
    * In order to use this method there needs to be a mainloop runnning.
    *
    * @param event_ids a {@link GLib.Array} of {@link Event} ids
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async ResultSet get_events (
        Array<uint32> event_ids,
        Cancellable? cancellable=null) throws Error
    {
        uint32[] simple_event_ids = new uint32[event_ids.length];
        for (int i = 0; i < event_ids.length; i++)
            simple_event_ids[i] = event_ids.index (i);

        if (dbreader != null)
        {
            SimpleResultSet result_set = null;
            EngineError error = null;
            ThreadFunc<void*> run = () => {
                try {
                    var result = dbreader.get_events (simple_event_ids);
                    result_set = new SimpleResultSet (result);
                } catch (EngineError err) {
                    error = err;
                } finally {
                    Idle.add (get_events.callback);
                }
                return null;
            };

            threads.add (new DbWorker (run));
            yield;

            if (error != null)
                throw error;
            return result_set;
        }

        yield wait_for_proxy ();
        var result = yield proxy.get_events (simple_event_ids, cancellable);
        return new SimpleResultSet (Events.from_variant (result));
    }

    /**
    * Get a list of URIs of subjects which frequently occur together with events
    * matching event_templates. Possibly restricting to time_range or to URIs
    * that occur as subject of events matching result_event_templates.
    *
    * @param time_range {@link TimeRange} A time range in which the events should be considered in
    * @param storage_state {@link StorageState} storage state
    * @param event_templates An {@link GLib.GenericArray} of {@link Event} describing the events to relate to
    * @param result_event_templates An {@link GLib.GenericArray} of {@link Event} desrcibing the result to be returned
    * @param num_events int represteing the number of events that should be returned
    * @param result_type {@link ResultType} how the events should be grouped and sorted
    * @param cancellable a {@link GLib.Cancellable} to cancel the operation or %NULL
    */
    public async string[] find_related_uris (
        TimeRange time_range,
        GenericArray<Event> event_templates,
        GenericArray<Event> result_event_templates,
        StorageState storage_state,
        uint32 num_events,
        RelevantResultType result_type,
        Cancellable? cancellable=null) throws Error
    {
{'fraction_non_alphanumeric': 0.07835908239700375, 'fraction_numerical': 0.003043071161048689, 'mean_word_length': 2.249690976514215, 'pattern_counts': {'":': 2, '<': 93, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5964328', 'n_tokens_mistral': 9286, 'n_tokens_neox': 8923, 'n_words': 3514}