1
/* vi: set et sw=4 ts=4 cino=t0,(0: */
2
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3
/*
4
 * This file is part of libaccounts-glib
5
 *
6
 * Copyright (C) 2009-2010 Nokia Corporation.
7
 *
8
 * Contact: Alberto Mardegan <alberto.mardegan@nokia.com>
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public License
12
 * version 2.1 as published by the Free Software Foundation.
13
 *
14
 * This library is distributed in the hope that it will be useful, but
15
 * WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public
20
 * License along with this library; if not, write to the Free Software
21
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22
 * 02110-1301 USA
23
 */
24
25
/**
26
 * SECTION:ag-manager
27
 * @title: AgManager
28
 * @short_description: The account manager object
29
 *
30
 * The #AgManager is the main object in this library.
31
 */
32
33
#include "ag-manager.h"
34
35
#include "ag-errors.h"
36
#include "ag-internals.h"
37
#include "ag-util.h"
38
#include <dbus/dbus-glib-lowlevel.h>
39
#include <sched.h>
40
#include <sqlite3.h>
41
#include <string.h>
42
#include <unistd.h>
43
44
#ifndef DATABASE_DIR
45
#define DATABASE_DIR ".accounts"
46
#endif
47
48
enum
49
{
50
    PROP_0,
51
52
    PROP_SERVICE_TYPE,
53
};
54
55
enum
56
{
57
    ACCOUNT_CREATED,
58
    ACCOUNT_DELETED,
59
    ACCOUNT_ENABLED,
60
    ACCOUNT_UPDATED,
61
    LAST_SIGNAL
62
};
63
64
static guint signals[LAST_SIGNAL] = { 0 };
65
66
struct _AgManagerPrivate {
67
    sqlite3 *db;
68
69
    sqlite3_stmt *begin_stmt;
70
    sqlite3_stmt *commit_stmt;
71
    sqlite3_stmt *rollback_stmt;
72
73
    sqlite3_int64 last_service_id;
74
    sqlite3_int64 last_account_id;
75
76
    DBusConnection *dbus_conn;
77
78
    /* Cache for AgService */
79
    GHashTable *services;
80
81
    /* Weak references to loaded accounts */
82
    GHashTable *accounts;
83
84
    /* list of StoreCbData awaiting for exclusive locks */
85
    GList *locks;
86
87
    /* list of EmittedSignalData for the signals emitted by this instance */
88
    GList *emitted_signals;
89
90
    /* list of ProcessedSignalData, to avoid processing signals twice */
91
    GList *processed_signals;
92
93
    /* D-Bus object paths we are listening to */
94
    GPtrArray *object_paths;
95
96
    GError *last_error;
97
98
    guint db_timeout;
99
100
    guint abort_on_db_timeout : 1;
101
    guint is_disposed : 1;
102
103
    gchar *service_type;
104
};
105
106
typedef struct {
107
    AgManager *manager;
108
    AgAccount *account;
109
    gchar *sql;
110
    AgAccountChanges *changes;
111
    guint id;
112
    AgAccountStoreCb callback;
113
    gpointer user_data;
114
} StoreCbData;
115
116
typedef struct {
117
    struct timespec ts;
118
    gboolean must_process;
119
} EmittedSignalData;
120
121
typedef struct {
122
    struct timespec ts;
123
} ProcessedSignalData;
124
125
G_DEFINE_TYPE (AgManager, ag_manager, G_TYPE_OBJECT);
126
127
#define AG_MANAGER_PRIV(obj) (AG_MANAGER(obj)->priv)
128
129
static void store_cb_data_free (StoreCbData *sd);
130
static void account_weak_notify (gpointer userdata, GObject *dead_account);
131
132
static void
133
set_error_from_db (AgManager *manager)
134
{
135
    AgManagerPrivate *priv = manager->priv;
136
    AgError code;
137
    GError *error;
138
139
    switch (sqlite3_errcode (priv->db))
140
    {
141
    case SQLITE_DONE:
142
    case SQLITE_OK:
143
        _ag_manager_take_error (manager, NULL);
144
        return;
145
    case SQLITE_BUSY:
146
        code = AG_ERROR_DB_LOCKED;
147
        if (priv->abort_on_db_timeout)
148
            g_error ("Accounts DB timeout: causing application to abort.");
149
        break;
150
    default:
151
        code = AG_ERROR_DB;
152
        break;
153
    }
154
155
    error = g_error_new (AG_ERRORS, code, "SQLite error %d: %s",
156
                         sqlite3_errcode (priv->db),
157
                         sqlite3_errmsg (priv->db));
158
    _ag_manager_take_error (manager, error);
159
}
160
161
static gboolean
162
timed_unref_account (gpointer account)
163
{
164
    DEBUG_REFS ("Releasing temporary reference on account %u",
165
                AG_ACCOUNT (account)->id);
166
    g_object_unref (account);
167
    return FALSE;
168
}
169
170
static gboolean
171
parse_message_header (DBusMessageIter *iter,
172
                      struct timespec *ts, AgAccountId *id,
173
                      gboolean *created, gboolean *deleted,
174
                      const gchar **provider_name)
175
{
176
#define EXPECT_TYPE(t) \
177
    if (G_UNLIKELY (dbus_message_iter_get_arg_type (iter) != t)) return FALSE
178
179
    EXPECT_TYPE (DBUS_TYPE_UINT32);
180
    dbus_message_iter_get_basic (iter, &ts->tv_sec);
181
    dbus_message_iter_next (iter);
182
183
    EXPECT_TYPE (DBUS_TYPE_UINT32);
184
    dbus_message_iter_get_basic (iter, &ts->tv_nsec);
185
    dbus_message_iter_next (iter);
186
187
    EXPECT_TYPE (DBUS_TYPE_UINT32);
188
    dbus_message_iter_get_basic (iter, id);
189
    dbus_message_iter_next (iter);
190
191
    EXPECT_TYPE (DBUS_TYPE_BOOLEAN);
192
    dbus_message_iter_get_basic (iter, created);
193
    dbus_message_iter_next (iter);
194
195
    EXPECT_TYPE (DBUS_TYPE_BOOLEAN);
196
    dbus_message_iter_get_basic (iter, deleted);
197
    dbus_message_iter_next (iter);
198
199
    EXPECT_TYPE (DBUS_TYPE_STRING);
200
    dbus_message_iter_get_basic (iter, provider_name);
201
    dbus_message_iter_next (iter);
202
203
#undef EXPECT_TYPE
204
    return TRUE;
205
}
206
207
static gboolean
208
ag_manager_must_emit_updated (AgManager *manager, AgAccountChanges *changes)
209
{
210
    AgManagerPrivate *priv = manager->priv;
211
212
    /* Don't emit the "updated" signal along with "created" or "deleted" */
213
    if (changes->created || changes->deleted)
214
        return FALSE;
215
216
    /* The update-event is emitted whenever any value has been changed on
217
     * particular service of account.
218
     */
219
    return (priv->service_type != NULL) ?
220
        _ag_account_changes_have_service_type (changes, priv->service_type) : FALSE;
221
}
222
223
static gboolean
224
ag_manager_must_emit_enabled (AgManager *manager, AgAccountChanges *changes)
225
{
226
    AgManagerPrivate *priv = manager->priv;
227
228
    /* TODO: the enabled-event is emitted whenever enabled status has changed on
229
     * any service or account. This has some possibility for optimization.
230
     */
231
    return (priv->service_type != NULL) ?
232
        _ag_account_changes_have_enabled (changes) : FALSE;
233
}
234
235
static void
236
ag_manager_emit_signals (AgManager *manager, AgAccountId account_id,
237
                         gboolean updated,
238
                         gboolean enabled,
239
                         gboolean created,
240
                         gboolean deleted)
241
{
242
    if (updated)
243
        g_signal_emit_by_name (manager, "account-updated", account_id);
244
245
    if (enabled)
246
        g_signal_emit_by_name (manager, "enabled-event", account_id);
247
248
    if (deleted)
249
        g_signal_emit_by_name (manager, "account-deleted", account_id);
250
251
    if (created)
252
        g_signal_emit_by_name (manager, "account-created", account_id);
253
}
254
255
static gboolean
256
check_signal_processed (AgManagerPrivate *priv, struct timespec *ts)
257
{
258
    ProcessedSignalData *psd;
259
    GList *list;
260
261
    for (list = priv->processed_signals; list != NULL; list = list->next)
262
    {
263
        psd = list->data;
264
265
        if (psd->ts.tv_sec == ts->tv_sec &&
266
            psd->ts.tv_nsec == ts->tv_nsec)
267
        {
268
            DEBUG_INFO ("Signal already processed: %lu-%lu",
269
                        ts->tv_sec, ts->tv_nsec);
270
            g_slice_free (ProcessedSignalData, psd);
271
            priv->processed_signals =
272
                g_list_delete_link (priv->processed_signals, list);
273
            return TRUE;
274
        }
275
    }
276
277
    /* Add the signal to the list of processed ones; this is necessary if the
278
     * manager was created for a specific service type, because in that case
279
     * we are subscribing for DBus signals on two different object paths (the
280
     * one for our service type, and one for the global settings), so we might
281
     * get notified about the same signal twice.
282
     */
283
284
    /* Don't keep more than a very few elements in the list */
285
    for (list = g_list_nth (priv->processed_signals, 2);
286
         list != NULL;
287
         list = g_list_nth (priv->processed_signals, 2))
288
    {
289
        g_slice_free (ProcessedSignalData, list->data);
290
        priv->processed_signals = g_list_delete_link (priv->processed_signals,
291
                                                      list);
292
    }
293
294
    psd = g_slice_new (ProcessedSignalData);
295
    psd->ts = *ts;
296
    priv->processed_signals = g_list_prepend (priv->processed_signals, psd);
297
298
    return FALSE;
299
}
300
301
/**
302
 * checks whether the sender of the message is listed in the object_paths array
303
 */
304
static gboolean
305
message_is_from_interesting_object (DBusMessage *msg, GPtrArray *object_paths)
306
{
307
    const gchar *msg_object_path;
308
    gint i;
309
310
    msg_object_path = dbus_message_get_path (msg);
311
    if (G_UNLIKELY (msg_object_path == NULL))
312
        return FALSE;
313
314
    for (i = 0; i < object_paths->len; i++)
315
    {
316
        const gchar *object_path = g_ptr_array_index (object_paths, i);
317
        if (strcmp (msg_object_path, object_path) == 0)
318
            return TRUE;
319
    }
320
    return FALSE;
321
}
322
323
static DBusHandlerResult
324
dbus_filter_callback (DBusConnection *dbus_conn, DBusMessage *msg,
325
                      void *user_data)
326
{
327
    AgManager *manager = AG_MANAGER (user_data);
328
    AgManagerPrivate *priv = manager->priv;
329
    const gchar *provider_name = NULL;
330
    AgAccountId account_id = 0;
331
    AgAccount *account;
332
    AgAccountChanges *changes;
333
    struct timespec ts;
334
    gboolean deleted, created;
335
    gboolean ret;
336
    gboolean ours = FALSE;
337
    gboolean updated = FALSE;
338
    gboolean enabled = FALSE;
339
    gboolean must_instantiate = TRUE;
340
    DBusMessageIter iter;
341
    GList *list, *node;
342
343
    if (!dbus_message_is_signal (msg, AG_DBUS_IFACE, AG_DBUS_SIG_CHANGED))
344
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
345
346
    if (!message_is_from_interesting_object(msg, priv->object_paths))
347
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
348
349
    dbus_message_iter_init (msg, &iter);
350
    ret = parse_message_header (&iter, &ts, &account_id,
351
                                &created, &deleted, &provider_name);
352
    if (G_UNLIKELY (!ret))
353
    {
354
        g_warning ("%s: error in parsing signal arguments", G_STRFUNC);
355
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
356
    }
357
358
    DEBUG_INFO ("path = %s, time = %lu-%lu (%p)",
359
                dbus_message_get_path (msg), ts.tv_sec, ts.tv_nsec,
360
                manager);
361
362
    /* Do not process the same signal more than once. */
363
    if (check_signal_processed (priv, &ts))
364
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
365
366
    list = priv->emitted_signals;
367
    while (list != NULL)
368
    {
369
        EmittedSignalData *esd = list->data;
370
        node = list;
371
        list = list->next;
372
373
        if (esd->ts.tv_sec == ts.tv_sec &&
374
            esd->ts.tv_nsec == ts.tv_nsec)
375
        {
376
            gboolean must_process = esd->must_process;
377
            /* message is ours: we can ignore it, as the changes
378
             * were already processed when the DB transaction succeeded. */
379
            ours = TRUE;
380
381
            DEBUG_INFO ("Signal is ours, must_process = %d", esd->must_process);
382
            g_slice_free (EmittedSignalData, esd);
383
            priv->emitted_signals = g_list_delete_link (
384
                                                    priv->emitted_signals,
385
                                                    node);
386
            if (!must_process)
387
                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
388
        }
389
    }
390
391
    /* we must mark our emitted signals for reprocessing, because the current
392
     * signal might modify some of the fields that were previously modified by
393
     * us.
394
     * This ensures that changes coming from different account manager
395
     * instances are processed in the right order. */
396
    for (list = priv->emitted_signals; list != NULL; list = list->next)
397
    {
398
        EmittedSignalData *esd = list->data;
399
        DEBUG_INFO ("Marking pending signal for processing");
400
        esd->must_process = TRUE;
401
    }
402
403
    changes = _ag_account_changes_from_dbus (manager, &iter, created, deleted);
404
405
    /* check if the account is loaded */
406
    account = g_hash_table_lookup (priv->accounts,
407
                                   GUINT_TO_POINTER (account_id));
408
409
    if (!account && !created && !deleted)
410
        must_instantiate = FALSE;
411
412
    if (ours && (deleted || created))
413
        must_instantiate = FALSE;
414
415
    if (!account && must_instantiate)
416
    {
417
        /* because of the checks above, this can happen if this is an account
418
         * created or deleted from another instance.
419
         * We must emit the signals, and cache the newly created account for a
420
         * while, because the application is likely to inspect it */
421
        account = g_object_new (AG_TYPE_ACCOUNT,
422
                                "manager", manager,
423
                                "provider", provider_name,
424
                                "id", account_id,
425
                                "foreign", created,
426
                                NULL);
427
        g_return_val_if_fail (AG_IS_ACCOUNT (account),
428
                              DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
429
430
        g_object_weak_ref (G_OBJECT (account), account_weak_notify, manager);
431
        g_hash_table_insert (priv->accounts, GUINT_TO_POINTER (account_id),
432
                             account);
433
        g_timeout_add_seconds (2, timed_unref_account, account);
434
    }
435
436
    if (changes)
437
    {
438
        updated = ag_manager_must_emit_updated (manager, changes);
439
        enabled = ag_manager_must_emit_enabled (manager, changes);
440
        if (account)
441
            _ag_account_done_changes (account, changes);
442
443
        _ag_account_changes_free (changes);
444
    }
445
446
    ag_manager_emit_signals (manager, account_id,
447
                             updated,
448
                             enabled,
449
                             created,
450
                             deleted);
451
452
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
453
}
454
455
static DBusMessage *
456
make_signal_for_service_type (DBusMessage *global_msg,
457
                              const gchar *service_type)
458
{
459
    gchar path[256];
460
    gchar *escaped_type;
461
    DBusMessage *msg;
462
463
    escaped_type = _ag_dbus_escape_as_identifier (service_type);
464
    g_snprintf (path, sizeof (path), "%s/%s",
465
                AG_DBUS_PATH_SERVICE, escaped_type);
466
    g_free (escaped_type);
467
468
    msg = dbus_message_copy (global_msg);
469
    DEBUG_INFO("Setting path to %s", path);
470
    if (!dbus_message_set_path (msg, path))
471
        g_warning("setting path failed!");
472
    return msg;
473
}
474
475
static void
476
signal_account_changes_on_service_types (AgManager *manager,
477
                                         AgAccountChanges *changes,
478
                                         DBusMessage *global_msg)
479
{
480
    GPtrArray *service_types;
481
    gint i;
482
483
    service_types = _ag_account_changes_get_service_types (changes);
484
    for (i = 0; i < service_types->len; i++)
485
    {
486
        const gchar *service_type;
487
        DBusMessage *msg;
488
        gboolean ret;
489
490
        service_type = g_ptr_array_index(service_types, i);
491
        msg = make_signal_for_service_type (global_msg, service_type);
492
493
        ret = dbus_connection_send (manager->priv->dbus_conn, msg, NULL);
494
        if (G_UNLIKELY (!ret))
495
            g_warning ("Emission of DBus signal failed");
496
        dbus_message_unref (msg);
497
    }
498
    g_ptr_array_free (service_types, TRUE);
499
}
500
501
static void
502
signal_account_changes (AgManager *manager, AgAccount *account,
503
                        AgAccountChanges *changes)
504
{
505
    AgManagerPrivate *priv = manager->priv;
506
    DBusMessage *msg;
507
    gboolean ret;
508
    EmittedSignalData eds;
509
510
    clock_gettime(CLOCK_MONOTONIC, &eds.ts);
511
512
    msg = _ag_account_build_signal (account, changes, &eds.ts);
513
    if (G_UNLIKELY (!msg))
514
    {
515
        g_warning ("Creation of D-Bus signal failed");
516
        return;
517
    }
518
519
    ret = dbus_connection_send (priv->dbus_conn, msg, NULL);
520
    if (G_UNLIKELY (!ret))
521
    {
522
        g_warning ("Emission of DBus signal failed");
523
        goto finish;
524
    }
525
526
    /* emit the signal on all service-types */
527
    signal_account_changes_on_service_types(manager, changes, msg);
528
529
    dbus_connection_flush (priv->dbus_conn);
530
    DEBUG_INFO ("Emitted signal, time: %lu-%lu", eds.ts.tv_sec, eds.ts.tv_nsec);
531
532
    eds.must_process = FALSE;
533
    priv->emitted_signals =
534
        g_list_prepend (priv->emitted_signals,
535
                        g_slice_dup (EmittedSignalData, &eds));
536
537
finish:
538
    dbus_message_unref (msg);
539
}
540
541
static gboolean
542
got_service (sqlite3_stmt *stmt, AgService **p_service)
543
{
544
    AgService *service;
545
546
    g_assert (p_service != NULL);
547
548
    service = _ag_service_new ();
549
    service->id = sqlite3_column_int (stmt, 0);
550
    service->display_name = g_strdup ((gchar *)sqlite3_column_text (stmt, 1));
551
    service->provider = g_strdup ((gchar *)sqlite3_column_text (stmt, 2));
552
    service->type = g_strdup ((gchar *)sqlite3_column_text (stmt, 3));
553
554
    *p_service = service;
555
    return TRUE;
556
}
557
558
static gboolean
559
got_service_id (sqlite3_stmt *stmt, AgService *service)
560
{
561
    g_assert (service != NULL);
562
563
    service->id = sqlite3_column_int (stmt, 0);
564
    return TRUE;
565
}
566
567
static gboolean
568
add_service_to_db (AgManager *manager, AgService *service)
569
{
570
    gchar *sql;
571
572
    /* Add the service to the DB */
573
    sql = sqlite3_mprintf ("INSERT INTO Services "
574
                           "(name, display, provider, type) "
575
                           "VALUES (%Q, %Q, %Q, %Q);",
576
                           service->name,
577
                           service->display_name,
578
                           service->provider,
579
                           service->type);
580
    _ag_manager_exec_query (manager, NULL, NULL, sql);
581
    sqlite3_free (sql);
582
583
    /* The insert statement above might fail in the unlikely case
584
     * that in the meantime the same service was inserted by some other
585
     * process; so, instead of calling sqlite3_last_insert_rowid(), we
586
     * just get the ID with another query. */
587
    sql = sqlite3_mprintf ("SELECT id FROM Services WHERE name = %Q",
588
                           service->name);
589
    _ag_manager_exec_query (manager, (AgQueryCallback)got_service_id,
590
                            service, sql);
591
    sqlite3_free (sql);
592
593
    return service->id != 0;
594
}
595
596
static gboolean
597
add_id_to_list (sqlite3_stmt *stmt, GList **plist)
598
{
599
    gint id;
600
601
    id = sqlite3_column_int (stmt, 0);
602
    *plist = g_list_prepend (*plist, GINT_TO_POINTER (id));
603
    return TRUE;
604
}
605
606
static void
607
account_weak_notify (gpointer userdata, GObject *dead_account)
608
{
609
    AgManagerPrivate *priv = AG_MANAGER_PRIV (userdata);
610
    GHashTableIter iter;
611
    GObject *account;
612
613
    DEBUG_REFS ("called for %p", dead_account);
614
    g_hash_table_iter_init (&iter, priv->accounts);
615
    while (g_hash_table_iter_next (&iter, NULL, (gpointer)&account))
616
    {
617
        if (account == dead_account)
618
        {
619
            g_hash_table_iter_steal (&iter);
620
            break;
621
        }
622
    }
623
}
624
625
static void
626
account_weak_unref (GObject *account)
627
{
628
    g_object_weak_unref (account, account_weak_notify,
629
                         ag_account_get_manager (AG_ACCOUNT (account)));
630
}
631
632
/*
633
 * exec_transaction:
634
 *
635
 * Executes a transaction, assuming that the exclusive lock has been obtained.
636
 */
637
static void
638
exec_transaction (AgManager *manager, AgAccount *account,
639
                  const gchar *sql, AgAccountChanges *changes,
640
                  GError **error)
641
{
642
    AgManagerPrivate *priv;
643
    gchar *err_msg = NULL;
644
    int ret;
645
    gboolean updated, enabled;
646
647
    DEBUG_LOCKS ("Accounts DB is now locked");
648
    DEBUG_QUERIES ("called: %s", sql);
649
    g_return_if_fail (AG_IS_MANAGER (manager));
650
    priv = manager->priv;
651
    g_return_if_fail (AG_IS_ACCOUNT (account));
652
    g_return_if_fail (sql != NULL);
653
    g_return_if_fail (priv->db != NULL);
654
655
    ret = sqlite3_exec (priv->db, sql, NULL, NULL, &err_msg);
656
    if (G_UNLIKELY (ret != SQLITE_OK))
657
    {
658
        *error = g_error_new (AG_ERRORS, AG_ERROR_DB, "%s", err_msg);
659
        if (err_msg)
660
            sqlite3_free (err_msg);
661
662
        ret = sqlite3_step (priv->rollback_stmt);
663
        if (G_UNLIKELY (ret != SQLITE_OK))
664
            g_warning ("Rollback failed");
665
        sqlite3_reset (priv->rollback_stmt);
666
        DEBUG_LOCKS ("Accounts DB is now unlocked");
667
        return;
668
    }
669
670
    ret = sqlite3_step (priv->commit_stmt);
671
    if (G_UNLIKELY (ret != SQLITE_DONE))
672
    {
673
        *error = g_error_new_literal (AG_ERRORS, AG_ERROR_DB,
674
                                      sqlite3_errmsg (priv->db));
675
        sqlite3_reset (priv->commit_stmt);
676
        return;
677
    }
678
    sqlite3_reset (priv->commit_stmt);
679
680
    DEBUG_LOCKS ("Accounts DB is now unlocked");
681
682
    /* everything went well; if this was a new account, we must update the
683
     * local data structure */
684
    if (account->id == 0)
685
    {
686
        account->id = priv->last_account_id;
687
688
        /* insert the account into our cache */
689
        g_object_weak_ref (G_OBJECT (account), account_weak_notify, manager);
690
        g_hash_table_insert (priv->accounts, GUINT_TO_POINTER (account->id),
691
                             account);
692
    }
693
694
    /* emit DBus signals to notify other processes */
695
    signal_account_changes (manager, account, changes);
696
697
    updated = ag_manager_must_emit_updated(manager, changes);
698
699
    enabled = ag_manager_must_emit_enabled(manager, changes);
700
    _ag_account_done_changes (account, changes);
701
702
    ag_manager_emit_signals (manager, account->id,
703
                             updated,
704
                             enabled,
705
                             changes->created,
706
                             changes->deleted);
707
}
708
709
static void
710
lost_weak_ref (gpointer data, GObject *dead)
711
{
712
    StoreCbData *sd = data;
713
    AgManagerPrivate *priv;
714
715
    GError error = { AG_ERRORS, AG_ERROR_DISPOSED, "Account disposed" };
716
717
    g_assert ((GObject *)sd->account == dead);
718
    _ag_account_store_completed (sd->account, sd->changes,
719
                                 sd->callback, &error, sd->user_data);
720
721
    priv = AG_MANAGER_PRIV (sd->manager);
722
    priv->locks = g_list_remove (priv->locks, sd);
723
    sd->account = NULL; /* so that the weak reference is not removed */
724
    store_cb_data_free (sd);
725
}
726
727
static void
728
store_cb_data_free (StoreCbData *sd)
729
{
730
    if (sd->account)
731
        g_object_weak_unref (G_OBJECT (sd->account), lost_weak_ref, sd);
732
    if (sd->id)
733
        g_source_remove (sd->id);
734
    g_free (sd->sql);
735
    g_slice_free (StoreCbData, sd);
736
}
737
738
static gboolean
739
exec_transaction_idle (StoreCbData *sd)
740
{
741
    AgManager *manager = sd->manager;
742
    AgAccount *account = sd->account;
743
    AgManagerPrivate *priv;
744
    GError *error = NULL;
745
    int ret;
746
747
    g_return_val_if_fail (AG_IS_MANAGER (manager), FALSE);
748
    priv = manager->priv;
749
750
    g_return_val_if_fail (priv->begin_stmt != NULL, FALSE);
751
    ret = sqlite3_step (priv->begin_stmt);
752
    if (ret == SQLITE_BUSY)
753
    {
754
        sched_yield ();
755
        return TRUE; /* call this callback again */
756
    }
757
758
    g_object_ref (manager);
759
    g_object_ref (account);
760
    if (ret == SQLITE_DONE)
761
    {
762
        exec_transaction (manager, account, sd->sql, sd->changes, &error);
763
    }
764
    else
765
    {
766
        error = g_error_new_literal (AG_ERRORS, AG_ERROR_DB, "Generic error");
767
    }
768
    _ag_account_store_completed (account, sd->changes,
769
                                 sd->callback, error, sd->user_data);
770
    if (error)
771
        g_error_free (error);
772
773
    priv->locks = g_list_remove (priv->locks, sd);
774
    sd->id = 0;
775
    store_cb_data_free (sd);
776
    g_object_unref (account);
777
    g_object_unref (manager);
778
    return FALSE;
779
}
780
781
static int
782
prepare_transaction_statements (AgManagerPrivate *priv)
783
{
784
    int ret;
785
786
    if (G_UNLIKELY (!priv->begin_stmt))
787
    {
788
        ret = sqlite3_prepare_v2 (priv->db, "BEGIN EXCLUSIVE;", -1,
789
                                  &priv->begin_stmt, NULL);
790
        if (ret != SQLITE_OK) return ret;
791
    }
792
    else
793
        sqlite3_reset (priv->begin_stmt);
794
795
    if (G_UNLIKELY (!priv->commit_stmt))
796
    {
797
        ret = sqlite3_prepare_v2 (priv->db, "COMMIT;", -1,
798
                                  &priv->commit_stmt, NULL);
799
        if (ret != SQLITE_OK) return ret;
800
    }
801
    else
802
        sqlite3_reset (priv->commit_stmt);
803
804
    if (G_UNLIKELY (!priv->rollback_stmt))
805
    {
806
        ret = sqlite3_prepare_v2 (priv->db, "ROLLBACK;", -1,
807
                                  &priv->rollback_stmt, NULL);
808
        if (ret != SQLITE_OK) return ret;
809
    }
810
    else
811
        sqlite3_reset (priv->rollback_stmt);
812
813
    return SQLITE_OK;
814
}
815
816
static void
817
set_last_rowid_as_account_id (sqlite3_context *ctx,
818
                              int argc, sqlite3_value **argv)
819
{
820
    AgManagerPrivate *priv;
821
822
    priv = sqlite3_user_data (ctx);
823
    priv->last_account_id = sqlite3_last_insert_rowid (priv->db);
824
    sqlite3_result_null (ctx);
825
}
826
827
static void
828
get_account_id (sqlite3_context *ctx, int argc, sqlite3_value **argv)
829
{
830
    AgManagerPrivate *priv;
831
832
    priv = sqlite3_user_data (ctx);
833
    sqlite3_result_int64 (ctx, priv->last_account_id);
834
}
835
836
static void
837
create_functions (AgManagerPrivate *priv)
838
{
839
    sqlite3_create_function (priv->db, "set_last_rowid_as_account_id", 0,
840
                             SQLITE_ANY, priv,
841
                             set_last_rowid_as_account_id, NULL, NULL);
842
    sqlite3_create_function (priv->db, "account_id", 0,
843
                             SQLITE_ANY, priv,
844
                             get_account_id, NULL, NULL);
845
}
846
847
static void
848
setup_db_options (sqlite3 *db)
849
{
850
    gchar *error;
851
    int ret;
852
853
    error = NULL;
854
    ret = sqlite3_exec (db, "PRAGMA synchronous = 1", NULL, NULL, &error);
855
    if (ret != SQLITE_OK)
856
    {
857
        g_warning ("%s: couldn't set synchronous mode (%s)",
858
                   G_STRFUNC, error);
859
        sqlite3_free (error);
860
    }
861
862
    error = NULL;
863
    ret = sqlite3_exec (db, "PRAGMA journal_mode = WAL", NULL, NULL, &error);
864
    if (ret != SQLITE_OK)
865
    {
866
        g_warning ("%s: couldn't set journal mode to WAL (%s)",
867
                   G_STRFUNC, error);
868
        sqlite3_free (error);
869
    }
870
}
871
872
static gint
873
get_db_version (sqlite3 *db)
874
{
875
    sqlite3_stmt *stmt;
876
    gint version = 0, ret;
877
878
    ret = sqlite3_prepare(db, "PRAGMA user_version", -1, &stmt, NULL);
879
    if (G_UNLIKELY(ret != SQLITE_OK)) return 0;
880
881
    ret = sqlite3_step(stmt);
882
    if (G_LIKELY(ret == SQLITE_ROW))
883
        version = sqlite3_column_int(stmt, 0);
884
885
    sqlite3_finalize(stmt);
886
    return version;
887
}
888
889
static gboolean
890
create_db (sqlite3 *db, guint timeout)
891
{
892
    const gchar *sql;
893
    gchar *error;
894
    int ret;
895
896
    sql = ""
897
        "CREATE TABLE IF NOT EXISTS Accounts ("
898
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
899
            "name TEXT,"
900
            "provider TEXT,"
901
            "enabled INTEGER);"
902
903
        "CREATE TABLE IF NOT EXISTS Services ("
904
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
905
            "name TEXT NOT NULL UNIQUE,"
906
            "display TEXT NOT NULL,"
907
            /* following fields are included for performance reasons */
908
            "provider TEXT,"
909
            "type TEXT);"
910
        "CREATE INDEX IF NOT EXISTS idx_service ON Services(name);"
911
912
        "CREATE TABLE IF NOT EXISTS Settings ("
913
            "account INTEGER NOT NULL,"
914
            "service INTEGER,"
915
            "key TEXT NOT NULL,"
916
            "type TEXT NOT NULL,"
917
            "value BLOB);"
918
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_setting ON Settings "
919
            "(account, service, key);"
920
921
        "CREATE TRIGGER IF NOT EXISTS tg_delete_account "
922
            "BEFORE DELETE ON Accounts FOR EACH ROW BEGIN "
923
                "DELETE FROM Settings WHERE account = OLD.id; "
924
            "END;"
925
926
        "CREATE TABLE IF NOT EXISTS Signatures ("
927
            "account INTEGER NOT NULL,"
928
            "service INTEGER,"
929
            "key TEXT NOT NULL,"
930
            "signature TEXT NOT NULL,"
931
            "token TEXT NOT NULL);"
932
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_signatures ON Signatures "
933
           "(account, service, key);"
934
935
        "PRAGMA user_version = 1;";
936
937
    error = NULL;
938
    ret = sqlite3_exec (db, sql, NULL, NULL, &error);
939
    if (ret == SQLITE_BUSY)
940
    {
941
        guint t;
942
        for (t = 5; t < MAX_SQLITE_BUSY_LOOP_TIME_MS; t *= 2)
943
        {
944
            DEBUG_LOCKS ("Database locked, retrying...");
945
            sched_yield ();
946
            g_assert(error != NULL);
947
            sqlite3_free (error);
948
            ret = sqlite3_exec (db, sql, NULL, NULL, &error);
949
            if (ret != SQLITE_BUSY) break;
950
            usleep(t * 1000);
951
        }
952
    }
953
954
    if (ret != SQLITE_OK)
955
    {
956
        g_warning ("Error initializing DB: %s", error);
957
        sqlite3_free (error);
958
        return FALSE;
959
    }
960
961
    return TRUE;
962
}
963
964
static gboolean
965
open_db (AgManager *manager)
966
{
967
    AgManagerPrivate *priv = manager->priv;
968
    const gchar *basedir;
969
    gchar *filename, *pathname;
970
    gint version;
971
    gboolean ok = TRUE;
972
    int ret;
973
974
    basedir = g_getenv ("ACCOUNTS");
975
    if (G_LIKELY (!basedir))
976
    {
977
        basedir = g_get_home_dir ();
978
        pathname = g_build_path (G_DIR_SEPARATOR_S, basedir,
979
            DATABASE_DIR, NULL);
980
        if (G_UNLIKELY (g_mkdir_with_parents(pathname, 0755)))
981
            g_warning ("Cannot create directory: %s", pathname);
982
        filename = g_build_filename (pathname, "accounts.db", NULL);
983
        g_free (pathname);
984
    }
985
    else
986
    {
987
        filename = g_build_filename (basedir, "accounts.db", NULL);
988
    }
989
    ret = sqlite3_open (filename, &priv->db);
990
    g_free (filename);
991
992
    if (ret != SQLITE_OK)
993
    {
994
        if (priv->db)
995
        {
996
            g_warning ("Error opening accounts DB: %s",
997
                       sqlite3_errmsg (priv->db));
998
            sqlite3_close (priv->db);
999
            priv->db = NULL;
1000
        }
1001
        return FALSE;
1002
    }
1003
1004
    /* TODO: busy handler */
1005
1006
    version = get_db_version(priv->db);
1007
    DEBUG_INFO ("DB version: %d", version);
1008
    if (version < 1)
1009
        ok = create_db(priv->db, priv->db_timeout);
1010
    /* insert here code to upgrade the DB from older versions... */
1011
1012
    if (G_UNLIKELY (!ok))
1013
    {
1014
        sqlite3_close (priv->db);
1015
        priv->db = NULL;
1016
        return FALSE;
1017
    }
1018
1019
    setup_db_options (priv->db);
1020
    create_functions (priv);
1021
1022
    return TRUE;
1023
}
1024
1025
static gboolean
1026
add_matches (AgManagerPrivate *priv)
1027
{
1028
    gchar match[DBUS_MAXIMUM_MATCH_RULE_LENGTH];
1029
    DBusError error;
1030
    gint i;
1031
1032
    dbus_error_init (&error);
1033
    for (i = 0; i < priv->object_paths->len; i++)
1034
    {
1035
        const gchar *path = g_ptr_array_index(priv->object_paths, i);
1036
1037
        g_snprintf (match, sizeof (match),
1038
                    "type='signal',interface='" AG_DBUS_IFACE "',path='%s'",
1039
                    path);
1040
        dbus_bus_add_match (priv->dbus_conn, match, &error);
1041
        if (G_UNLIKELY (dbus_error_is_set (&error)))
1042
        {
1043
            g_warning ("Failed to add dbus filter (%s)", error.message);
1044
            dbus_error_free (&error);
1045
            return FALSE;
1046
        }
1047
    }
1048
    return TRUE;
1049
}
1050
1051
static gboolean
1052
setup_dbus (AgManager *manager)
1053
{
1054
    AgManagerPrivate *priv = manager->priv;
1055
    DBusError error;
1056
    gboolean ret;
1057
1058
    dbus_error_init (&error);
1059
    priv->dbus_conn = dbus_bus_get (DBUS_BUS_SESSION, &error);
1060
    if (G_UNLIKELY (dbus_error_is_set (&error)))
1061
    {
1062
        g_warning ("Failed to get D-Bus connection (%s)", error.message);
1063
        dbus_error_free (&error);
1064
        return FALSE;
1065
    }
1066
1067
    ret = dbus_connection_add_filter (priv->dbus_conn,
1068
                                      dbus_filter_callback,
1069
                                      manager, NULL);
1070
    if (G_UNLIKELY (!ret))
1071
    {
1072
        g_warning ("Failed to add dbus filter");
1073
        return FALSE;
1074
    }
1075
1076
    if (priv->service_type == NULL)
1077
    {
1078
        /* listen to all changes */
1079
        g_ptr_array_add (priv->object_paths, g_strdup (AG_DBUS_PATH));
1080
    }
1081
    else
1082
    {
1083
        gchar *escaped_type, *path;
1084
1085
        /* listen for changes on our service type only */
1086
        escaped_type = _ag_dbus_escape_as_identifier (priv->service_type);
1087
        path = g_strdup_printf (AG_DBUS_PATH_SERVICE "/%s", escaped_type);
1088
        g_free (escaped_type);
1089
        g_ptr_array_add (priv->object_paths, path);
1090
1091
        /* add also the global service type */
1092
        g_ptr_array_add (priv->object_paths,
1093
                         g_strdup (AG_DBUS_PATH_SERVICE_GLOBAL));
1094
    }
1095
1096
    ret = add_matches(priv);
1097
    if (G_UNLIKELY (!ret)) return FALSE;
1098
1099
    dbus_connection_setup_with_g_main (priv->dbus_conn, NULL);
1100
    return TRUE;
1101
}
1102
1103
static void
1104
ag_manager_init (AgManager *manager)
1105
{
1106
    AgManagerPrivate *priv;
1107
1108
    manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, AG_TYPE_MANAGER,
1109
                                                 AgManagerPrivate);
1110
    priv = manager->priv;
1111
1112
    priv->services =
1113
        g_hash_table_new_full (g_str_hash, g_str_equal,
1114
                               NULL, (GDestroyNotify)ag_service_unref);
1115
    priv->accounts =
1116
        g_hash_table_new_full (NULL, NULL,
1117
                               NULL, (GDestroyNotify)account_weak_unref);
1118
1119
    priv->db_timeout = MAX_SQLITE_BUSY_LOOP_TIME_MS; /* 5 seconds */
1120
1121
    priv->object_paths = g_ptr_array_new_with_free_func (g_free);
1122
}
1123
1124
static GObject *
1125
ag_manager_constructor (GType type, guint n_params,
1126
                        GObjectConstructParam *params)
1127
{
1128
    GObjectClass *object_class = (GObjectClass *)ag_manager_parent_class;
1129
    AgManager *manager;
1130
    GObject *object;
1131
1132
    object = object_class->constructor (type, n_params, params);
1133
1134
    g_return_val_if_fail (object != NULL, NULL);
1135
1136
    manager = AG_MANAGER (object);
1137
    if (G_UNLIKELY (!open_db (manager) || !setup_dbus (manager)))
1138
    {
1139
        g_object_unref (object);
1140
        return NULL;
1141
    }
1142
1143
    return object;
1144
}
1145
1146
static void
1147
ag_manager_set_property (GObject *object, guint property_id,
1148
                         const GValue *value, GParamSpec *pspec)
1149
{
1150
    AgManager *manager = AG_MANAGER (object);
1151
    AgManagerPrivate *priv = manager->priv;
1152
1153
    switch (property_id)
1154
    {
1155
    case PROP_SERVICE_TYPE:
1156
        g_assert (priv->service_type == NULL);
1157
        priv->service_type = g_value_dup_string (value);
1158
        break;
1159
    default:
1160
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1161
        break;
1162
    }
1163
}
1164
1165
static void
1166
ag_manager_dispose (GObject *object)
1167
{
1168
    AgManagerPrivate *priv = AG_MANAGER_PRIV (object);
1169
1170
    if (priv->is_disposed) return;
1171
    priv->is_disposed = TRUE;
1172
1173
    while (priv->locks)
1174
    {
1175
        store_cb_data_free (priv->locks->data);
1176
        priv->locks = g_list_delete_link (priv->locks, priv->locks);
1177
    }
1178
1179
    G_OBJECT_CLASS (ag_manager_parent_class)->finalize (object);
1180
}
1181
1182
static void
1183
ag_manager_finalize (GObject *object)
1184
{
1185
    AgManagerPrivate *priv = AG_MANAGER_PRIV (object);
1186
1187
    if (priv->dbus_conn)
1188
    {
1189
        dbus_connection_remove_filter (priv->dbus_conn, dbus_filter_callback,
1190
                                       object);
1191
        dbus_connection_unref (priv->dbus_conn);
1192
    }
1193
1194
    g_ptr_array_free (priv->object_paths, TRUE);
1195
1196
    while (priv->emitted_signals)
1197
    {
1198
        g_slice_free (EmittedSignalData, priv->emitted_signals->data);
1199
        priv->emitted_signals = g_list_delete_link (priv->emitted_signals,
1200
                                                    priv->emitted_signals);
1201
    }
1202
1203
    while (priv->processed_signals)
1204
    {
1205
        g_slice_free (ProcessedSignalData, priv->processed_signals->data);
1206
        priv->processed_signals = g_list_delete_link (priv->processed_signals,
1207
                                                      priv->processed_signals);
1208
    }
1209
1210
    if (priv->begin_stmt)
1211
        sqlite3_finalize (priv->begin_stmt);
1212
    if (priv->commit_stmt)
1213
        sqlite3_finalize (priv->commit_stmt);
1214
    if (priv->rollback_stmt)
1215
        sqlite3_finalize (priv->rollback_stmt);
1216
1217
    if (priv->services)
1218
        g_hash_table_unref (priv->services);
1219
1220
    if (priv->accounts)
1221
        g_hash_table_unref (priv->accounts);
1222
1223
    if (priv->db)
1224
    {
1225
        if (sqlite3_close (priv->db) != SQLITE_OK)
1226
            g_warning ("Failed to close database: %s",
1227
                       sqlite3_errmsg (priv->db));
1228
        priv->db = NULL;
1229
    }
1230
    g_free (priv->service_type);
1231
1232
    if (priv->last_error)
1233
        g_error_free (priv->last_error);
1234
1235
    G_OBJECT_CLASS (ag_manager_parent_class)->finalize (object);
1236
}
1237
1238
static void
1239
ag_manager_account_deleted (AgManager *manager, AgAccountId id)
1240
{
1241
    g_return_if_fail (AG_IS_MANAGER (manager));
1242
1243
    /* The weak reference is removed automatically when the account is removed
1244
     * from the hash table */
1245
    g_hash_table_remove (manager->priv->accounts, GUINT_TO_POINTER (id));
1246
}
1247
1248
static void
1249
ag_manager_class_init (AgManagerClass *klass)
1250
{
1251
    GObjectClass* object_class = G_OBJECT_CLASS (klass);
1252
1253
    g_type_class_add_private (object_class, sizeof (AgManagerPrivate));
1254
1255
    klass->account_deleted = ag_manager_account_deleted;
1256
    object_class->constructor = ag_manager_constructor;
1257
    object_class->dispose = ag_manager_dispose;
1258
    object_class->set_property = ag_manager_set_property;
1259
    object_class->finalize = ag_manager_finalize;
1260
1261
    g_object_class_install_property
1262
        (object_class, PROP_SERVICE_TYPE,
1263
         g_param_spec_string ("service-type", "service type", "Set service type",
1264
                              NULL,
1265
                              G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
1266
1267
    /**
1268
     * AgManager::account-created:
1269
     * @manager: the #AgManager.
1270
     * @account_id: the #AgAccountId of the account that has been created.
1271
     *
1272
     * Emitted when a new account has been created; note that the account must
1273
     * have been stored in the database: the signal is not emitted just in
1274
     * response to ag_manager_create_account().
1275
     */
1276
    signals[ACCOUNT_CREATED] = g_signal_new ("account-created",
1277
        G_TYPE_FROM_CLASS (klass),
1278
        G_SIGNAL_RUN_LAST,
1279
        0,
1280
        NULL, NULL,
1281
        g_cclosure_marshal_VOID__UINT,
1282
        G_TYPE_NONE,
1283
        1, G_TYPE_UINT);
1284
1285
    /**
1286
     * AgManager::account-enabled:
1287
     * @manager: the #AgManager.
1288
     * @account_id: the #AgAccountId of the account that has been enabled.
1289
     *
1290
     * If the manager has been created with ag_manager_new_for_service_type(), this
1291
     * signal will be emitted when an account (identified by @account_id) has been
1292
     * modified in such a way that the application might be interested to start/stop
1293
     * using it: the "enabled" flag on the account or in some service supported by the
1294
     * account and matching the #AgManager:service-type have changed.
1295
     * In practice, this signal might be emitted more often than when strictly needed;
1296
     * applications must call ag_account_list_enabled_services() or
1297
     * ag_manager_list_enabled() to get the current state.
1298
     */
1299
    signals[ACCOUNT_ENABLED] = g_signal_new ("enabled-event",
1300
        G_TYPE_FROM_CLASS (klass),
1301
        G_SIGNAL_RUN_LAST,
1302
        0,
1303
        NULL, NULL,
1304
        g_cclosure_marshal_VOID__UINT,
1305
        G_TYPE_NONE,
1306
        1, G_TYPE_UINT);
1307
1308
    /**
1309
     * AgManager::account-deleted:
1310
     * @manager: the #AgManager.
1311
     * @account_id: the #AgAccountId of the account that has been deleted.
1312
     *
1313
     * Emitted when an account has been deleted.
1314
     * This signal is redundant with AgAccount::deleted, but it's convenient to
1315
     * provide full change notification to #AgManager.
1316
     */
1317
    signals[ACCOUNT_DELETED] = g_signal_new ("account-deleted",
1318
        G_TYPE_FROM_CLASS (klass),
1319
        G_SIGNAL_RUN_LAST,
1320
        G_STRUCT_OFFSET (AgManagerClass, account_deleted),
1321
        NULL, NULL,
1322
        g_cclosure_marshal_VOID__UINT,
1323
        G_TYPE_NONE,
1324
        1, G_TYPE_UINT);
1325
1326
    /**
1327
     * AgManager::account-updated:
1328
     * @manager: the #AgManager.
1329
     * @account_id: the #AgAccountId of the account that has been update.
1330
     *
1331
     * Emitted when particular service of an account has been updated.
1332
     * This signal is redundant with AgAccount::deleted, but it's convenient to
1333
     * provide full change notification to #AgManager.
1334
     */
1335
    signals[ACCOUNT_UPDATED] = g_signal_new ("account-updated",
1336
         G_TYPE_FROM_CLASS (klass),
1337
         G_SIGNAL_RUN_LAST,
1338
         0,
1339
         NULL, NULL,
1340
         g_cclosure_marshal_VOID__UINT,
1341
         G_TYPE_NONE,
1342
         1, G_TYPE_UINT);
1343
1344
    _ag_debug_init();
1345
}
1346
1347
/**
1348
 * ag_manager_new:
1349
 *
1350
 * Returns: an instance of an #AgManager.
1351
 */
1352
AgManager *
1353
ag_manager_new ()
1354
{
1355
    return g_object_new (AG_TYPE_MANAGER, NULL);
1356
}
1357
1358
GList *
1359
_ag_manager_list_all (AgManager *manager)
1360
{
1361
    GList *list = NULL;
1362
    const gchar *sql;
1363
1364
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1365
    sql = "SELECT id FROM Accounts;";
1366
    _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
1367
                            &list, sql);
1368
    return list;
1369
}
1370
1371
void
1372
_ag_manager_take_error (AgManager *manager, GError *error)
1373
{
1374
    AgManagerPrivate *priv;
1375
1376
    g_return_if_fail (AG_IS_MANAGER (manager));
1377
    priv = manager->priv;
1378
1379
    if (priv->last_error)
1380
        g_error_free (priv->last_error);
1381
    priv->last_error = error;
1382
}
1383
1384
const GError *
1385
_ag_manager_get_last_error (AgManager *manager)
1386
{
1387
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1388
1389
    return manager->priv->last_error;
1390
}
1391
1392
/**
1393
 * ag_manager_list:
1394
 * @manager: the #AgManager.
1395
 *
1396
 * Lists the accounts. If the #AgManager is created with specified service_type
1397
 * it will return only the accounts supporting this service_type.
1398
 *
1399
 * Returns: a #GList of #AgAccountId representing the accounts. Must
1400
 * be free'd with ag_manager_list_free().
1401
 */
1402
GList *
1403
ag_manager_list (AgManager *manager)
1404
{
1405
    AgManagerPrivate *priv;
1406
1407
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1408
    priv = manager->priv;
1409
1410
    if (priv->service_type)
1411
        return ag_manager_list_by_service_type (manager, priv->service_type);
1412
1413
    return _ag_manager_list_all (manager);
1414
}
1415
1416
/**
1417
 * ag_manager_list_by_service_type:
1418
 * @manager: the #AgManager.
1419
 *
1420
 * Lists the accounts supporting the given service type.
1421
 *
1422
 * Returns: a #GList of #AgAccountId representing the accounts. Must
1423
 * be free'd with ag_manager_list_free().
1424
 */
1425
GList *
1426
ag_manager_list_by_service_type (AgManager *manager,
1427
                                 const gchar *service_type)
1428
{
1429
    GList *list = NULL;
1430
    char sql[512];
1431
1432
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1433
    sqlite3_snprintf (sizeof (sql), sql,
1434
                      "SELECT DISTINCT account FROM Settings "
1435
                      "JOIN Services ON Settings.service = Services.id "
1436
                      "WHERE Services.type = %Q;",
1437
                      service_type);
1438
    _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
1439
                            &list, sql);
1440
    return list;
1441
}
1442
1443
/**
1444
 * ag_manager_list_enabled:
1445
 * @manager: the #AgManager.
1446
 *
1447
 * Lists the enabled accounts.
1448
 *
1449
 * Returns: a #GList of the enabled #AgAccountId representing the accounts. Must
1450
 * be free'd with ag_manager_list_free().
1451
 */
1452
GList *
1453
ag_manager_list_enabled (AgManager *manager)
1454
{
1455
    GList *list = NULL;
1456
    char sql[512];
1457
    AgManagerPrivate *priv;
1458
1459
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1460
    priv = manager->priv;
1461
    
1462
    if (priv->service_type == NULL)
1463
    {
1464
        sqlite3_snprintf (sizeof (sql), sql,
1465
                          "SELECT id FROM Accounts WHERE enabled=1;");
1466
        _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
1467
                                &list, sql);
1468
    }
1469
    else
1470
    {
1471
        list = ag_manager_list_enabled_by_service_type(manager, priv->service_type);
1472
    }
1473
    return list;
1474
}
1475
1476
/**
1477
 * ag_manager_list_enabled_by_service_type:
1478
 * @manager: the #AgManager.
1479
 *
1480
 * Lists the enabled accounts supporting the given service type.
1481
 *
1482
 * Returns: a #GList of the enabled #AgAccountId representing the accounts. Must
1483
 * be free'd with ag_manager_list_free().
1484
 */
1485
GList *
1486
ag_manager_list_enabled_by_service_type (AgManager *manager,
1487
                                         const gchar *service_type)
1488
{
1489
    GList *list = NULL;
1490
    char sql[512];
1491
1492
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1493
    g_return_val_if_fail (service_type != NULL, NULL);
1494
    sqlite3_snprintf (sizeof (sql), sql,
1495
                      "SELECT Settings.account FROM Settings "
1496
                      "INNER JOIN Services ON Settings.service = Services.id "
1497
                      "WHERE Settings.key='enabled' AND Settings.value='1' "
1498
                      "AND Services.type = %Q AND Settings.account IN "
1499
                      "(SELECT id FROM Accounts WHERE enabled=1);",
1500
                      service_type);
1501
    _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
1502
                            &list, sql);
1503
    return list;
1504
}
1505
1506
/**
1507
 * ag_manager_list_free:
1508
 * @list: a #GList returned from some #AgManager method.
1509
 *
1510
 * Frees the memory taken by a #GList allocated by #AgManager.
1511
 */
1512
void
1513
ag_manager_list_free (GList *list)
1514
{
1515
    g_list_free (list);
1516
}
1517
1518
/**
1519
 * ag_manager_get_account:
1520
 * @manager: the #AgManager.
1521
 * @account_id: the #AgAccountId of the account.
1522
 *
1523
 * Instantiates the object representing the account identified by
1524
 * @account_id.
1525
 *
1526
 * Returns: an #AgAccount, on which the client must call g_object_unref()
1527
 * when it's done with it, or %NULL if an error occurs.
1528
 */
1529
AgAccount *
1530
ag_manager_get_account (AgManager *manager, AgAccountId account_id)
1531
{
1532
    return ag_manager_load_account (manager, account_id, NULL);
1533
}
1534
1535
/**
1536
 * ag_manager_load_account:
1537
 * @manager: the #AgManager.
1538
 * @account_id: the #AgAccountId of the account.
1539
 * @error: pointer to a #GError, or %NULL.
1540
 *
1541
 * Instantiates the object representing the account identified by
1542
 * @account_id.
1543
 *
1544
 * Returns: an #AgAccount, on which the client must call g_object_unref()
1545
 * when it's done with it, or %NULL if an error occurs.
1546
 */
1547
AgAccount *
1548
ag_manager_load_account (AgManager *manager, AgAccountId account_id,
1549
                         GError **error)
1550
{
1551
    AgManagerPrivate *priv;
1552
    AgAccount *account;
1553
1554
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1555
    g_return_val_if_fail (account_id != 0, NULL);
1556
    priv = manager->priv;
1557
1558
    account = g_hash_table_lookup (priv->accounts,
1559
                                   GUINT_TO_POINTER (account_id));
1560
    if (account)
1561
        return g_object_ref (account);
1562
1563
    /* the account is not loaded; do it now */
1564
    account = g_object_new (AG_TYPE_ACCOUNT,
1565
                            "manager", manager,
1566
                            "id", account_id,
1567
                            NULL);
1568
    if (G_LIKELY (account))
1569
    {
1570
        g_object_weak_ref (G_OBJECT (account), account_weak_notify, manager);
1571
        g_hash_table_insert (priv->accounts, GUINT_TO_POINTER (account_id),
1572
                             account);
1573
    }
1574
    else if (priv->last_error != NULL)
1575
    {
1576
        g_set_error_literal (error,
1577
                             priv->last_error->domain,
1578
                             priv->last_error->code,
1579
                             priv->last_error->message);
1580
    }
1581
    return account;
1582
}
1583
1584
/**
1585
 * ag_manager_create_account:
1586
 * @manager: the #AgManager.
1587
 * @provider_name: name of the provider of the account.
1588
 *
1589
 * Create a new account. The account is not stored in the database until
1590
 * ag_account_store() has successfully returned; the @id field in the
1591
 * #AgAccount structure is also not meant to be valid till the account has been
1592
 * stored.
1593
 *
1594
 * Returns: a new #AgAccount.
1595
 */
1596
AgAccount *
1597
ag_manager_create_account (AgManager *manager, const gchar *provider_name)
1598
{
1599
    AgAccount *account;
1600
1601
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1602
1603
    account = g_object_new (AG_TYPE_ACCOUNT,
1604
                            "manager", manager,
1605
                            "provider", provider_name,
1606
                            NULL);
1607
    return account;
1608
}
1609
1610
/* This is called when creating AgService objects from inside the DBus
1611
 * handler: we don't want to access the Db from there */
1612
AgService *
1613
_ag_manager_get_service_lazy (AgManager *manager, const gchar *service_name,
1614
                              const gchar *service_type, const gint service_id)
1615
{
1616
    AgManagerPrivate *priv;
1617
    AgService *service;
1618
1619
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1620
    g_return_val_if_fail (service_name != NULL, NULL);
1621
    priv = manager->priv;
1622
1623
    service = g_hash_table_lookup (priv->services, service_name);
1624
    if (service)
1625
    {
1626
        if (service->id == 0)
1627
            service->id = service_id;
1628
        return ag_service_ref (service);
1629
    }
1630
1631
    service = _ag_service_new_from_memory (service_name, service_type, service_id);
1632
1633
    g_hash_table_insert (priv->services, service->name, service);
1634
    return ag_service_ref (service);
1635
}
1636
1637
/**
1638
 * ag_manager_get_service:
1639
 * @manager: the #AgManager.
1640
 * @service_name: the name of the service.
1641
 *
1642
 * Loads the service identified by @service_name.
1643
 *
1644
 * Returns: an #AgService, which must be then free'd with ag_service_unref().
1645
 */
1646
AgService *
1647
ag_manager_get_service (AgManager *manager, const gchar *service_name)
1648
{
1649
    AgManagerPrivate *priv;
1650
    AgService *service;
1651
    gchar *sql;
1652
    gint rows;
1653
1654
1655
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1656
    g_return_val_if_fail (service_name != NULL, NULL);
1657
    priv = manager->priv;
1658
1659
    service = g_hash_table_lookup (priv->services, service_name);
1660
    if (service)
1661
        return ag_service_ref (service);
1662
1663
    /* First, check if the service is in the DB */
1664
    sql = sqlite3_mprintf ("SELECT id, display, provider, type "
1665
                           "FROM Services WHERE name = %Q", service_name);
1666
    rows = _ag_manager_exec_query (manager, (AgQueryCallback)got_service,
1667
                                   &service, sql);
1668
    sqlite3_free (sql);
1669
1670
    if (service)
1671
    {
1672
        /* the basic server data have been loaded from the DB; the service name
1673
         * is still missing, though */
1674
        service->name = g_strdup (service_name);
1675
    }
1676
    else
1677
    {
1678
        /* The service is not in the DB: it must be loaded */
1679
        service = _ag_service_new_from_file (service_name);
1680
1681
        if (service && !add_service_to_db (manager, service))
1682
        {
1683
            g_warning ("Error in adding service %s to DB!", service_name);
1684
            ag_service_unref (service);
1685
            service = NULL;
1686
        }
1687
    }
1688
1689
    if (G_UNLIKELY (!service)) return NULL;
1690
1691
    g_hash_table_insert (priv->services, service->name, service);
1692
    return ag_service_ref (service);
1693
}
1694
1695
guint
1696
_ag_manager_get_service_id (AgManager *manager, AgService *service)
1697
{
1698
    g_return_val_if_fail (AG_IS_MANAGER (manager), 0);
1699
1700
    if (service == NULL) return 0; /* global service */
1701
1702
    if (service->id == 0)
1703
    {
1704
        gchar *sql;
1705
        gint rows;
1706
1707
        /* We got this service name from another process; load the id from the
1708
         * DB - it must already exist */
1709
        sql = sqlite3_mprintf ("SELECT id FROM Services WHERE name = %Q",
1710
                               service->name);
1711
        rows = _ag_manager_exec_query (manager, (AgQueryCallback)got_service_id,
1712
                                       service, sql);
1713
        sqlite3_free (sql);
1714
        if (G_UNLIKELY (rows != 1))
1715
        {
1716
            g_warning ("%s: got %d rows when asking for service %s",
1717
                       G_STRFUNC, rows, service->name);
1718
        }
1719
    }
1720
1721
    return service->id;
1722
}
1723
1724
/**
1725
 * ag_manager_list_services:
1726
 * @manager: the #AgManager.
1727
 *
1728
 * Gets a list of all the installed services.
1729
 * If the #AgManager is created with specified service_type
1730
 * it will return only the installed services supporting this service_type.
1731
 *
1732
 * Returns: a list of #AgService, which must be then free'd with
1733
 * ag_service_list_free().
1734
 */
1735
GList *
1736
ag_manager_list_services (AgManager *manager)
1737
{
1738
    AgManagerPrivate *priv;
1739
1740
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1741
    priv = manager->priv;
1742
1743
    if (priv->service_type)
1744
        return ag_manager_list_services_by_type (manager, priv->service_type);
1745
1746
    return _ag_services_list (manager);
1747
}
1748
1749
/**
1750
 * ag_manager_list_services_by_type:
1751
 * @manager: the #AgManager.
1752
 * @service_type: the type of the service.
1753
 *
1754
 * Gets a list of all the installed services of type @service_type.
1755
 *
1756
 * Returns: a list of #AgService, which must be then free'd with
1757
 * ag_service_list_free().
1758
 */
1759
GList *
1760
ag_manager_list_services_by_type (AgManager *manager, const gchar *service_type)
1761
{
1762
    GList *all_services, *list;
1763
    GList *services = NULL;
1764
1765
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1766
    g_return_val_if_fail (service_type != NULL, NULL);
1767
1768
    /* if we kept the DB Service table always up-to-date with all known
1769
     * services, then we could just run a query over it. But while we are not,
1770
     * it's simpler to implement the function by reusing the output from
1771
     * _ag_services_list(manager). */
1772
    all_services = _ag_services_list (manager);
1773
    for (list = all_services; list != NULL; list = list->next)
1774
    {
1775
        AgService *service = list->data;
1776
        const gchar *serviceType = ag_service_get_service_type (service);
1777
        if (serviceType && strcmp (serviceType, service_type) == 0)
1778
        {
1779
            services = g_list_prepend (services, service);
1780
        }
1781
        else
1782
            ag_service_unref (service);
1783
    }
1784
    g_list_free (all_services);
1785
1786
    return services;
1787
}
1788
1789
void
1790
_ag_manager_exec_transaction (AgManager *manager, const gchar *sql,
1791
                              AgAccountChanges *changes, AgAccount *account,
1792
                              AgAccountStoreCb callback, gpointer user_data)
1793
{
1794
    AgManagerPrivate *priv = manager->priv;
1795
    GError *error = NULL;
1796
    int ret;
1797
1798
    ret = prepare_transaction_statements (priv);
1799
    if (G_UNLIKELY (ret != SQLITE_OK))
1800
    {
1801
        error = g_error_new (AG_ERRORS, AG_ERROR_DB, "Got error: %s (%d)",
1802
                             sqlite3_errmsg (priv->db), ret);
1803
        goto finish;
1804
    }
1805
1806
    ret = sqlite3_step (priv->begin_stmt);
1807
    if (ret == SQLITE_BUSY)
1808
    {
1809
        if (callback)
1810
        {
1811
            StoreCbData *sd;
1812
1813
            sd = g_slice_new (StoreCbData);
1814
            sd->manager = manager;
1815
            sd->account = account;
1816
            sd->changes = changes;
1817
            sd->callback = callback;
1818
            sd->user_data = user_data;
1819
            sd->sql = g_strdup (sql);
1820
            sd->id = g_idle_add ((GSourceFunc)exec_transaction_idle, sd);
1821
            priv->locks = g_list_prepend (priv->locks, sd);
1822
            g_object_weak_ref (G_OBJECT (account), lost_weak_ref, sd);
1823
        }
1824
        return;
1825
    }
1826
1827
    if (ret != SQLITE_DONE)
1828
    {
1829
        error = g_error_new (AG_ERRORS, AG_ERROR_DB, "Got error: %s (%d)",
1830
                             sqlite3_errmsg (priv->db), ret);
1831
        goto finish;
1832
    }
1833
1834
    exec_transaction (manager, account, sql, changes, &error);
1835
1836
finish:
1837
    _ag_account_store_completed (account, changes,
1838
                                 callback, error, user_data);
1839
    if (error)
1840
        g_error_free (error);
1841
}
1842
1843
void
1844
_ag_manager_exec_transaction_blocking (AgManager *manager, const gchar *sql,
1845
                                       AgAccountChanges *changes,
1846
                                       AgAccount *account,
1847
                                       GError **error)
1848
{
1849
    AgManagerPrivate *priv = manager->priv;
1850
    gint sleep_ms = 200;
1851
    int ret;
1852
1853
    ret = prepare_transaction_statements (priv);
1854
    if (G_UNLIKELY (ret != SQLITE_OK))
1855
    {
1856
        *error = g_error_new (AG_ERRORS, AG_ERROR_DB, "Got error: %s (%d)",
1857
                              sqlite3_errmsg (priv->db), ret);
1858
        return;
1859
    }
1860
1861
    ret = sqlite3_step (priv->begin_stmt);
1862
    while (ret == SQLITE_BUSY)
1863
    {
1864
        /* TODO: instead of this loop, use a semaphore or some other non
1865
         * polling mechanism */
1866
        if (sleep_ms > 30000)
1867
        {
1868
            DEBUG_LOCKS ("Database locked for more than 30 seconds; "
1869
                         "giving up!");
1870
            break;
1871
        }
1872
        DEBUG_LOCKS ("Database locked, sleeping for %ums", sleep_ms);
1873
        g_usleep (sleep_ms * 1000);
1874
        sleep_ms *= 2;
1875
        ret = sqlite3_step (priv->begin_stmt);
1876
    }
1877
1878
    if (ret != SQLITE_DONE)
1879
    {
1880
        *error = g_error_new (AG_ERRORS, AG_ERROR_DB, "Got error: %s (%d)",
1881
                              sqlite3_errmsg (priv->db), ret);
1882
        return;
1883
    }
1884
1885
    exec_transaction (manager, account, sql, changes, error);
1886
}
1887
1888
static guint
1889
timespec_diff_ms(struct timespec *ts1, struct timespec *ts0)
1890
{
1891
    return (ts1->tv_sec - ts0->tv_sec) * 1000 +
1892
        (ts1->tv_nsec - ts0->tv_nsec) / 1000000;
1893
}
1894
1895
/* Executes an SQL statement, and optionally calls
1896
 * the callback for every row of the result.
1897
 * Returns the number of rows fetched.
1898
 */
1899
gint
1900
_ag_manager_exec_query (AgManager *manager,
1901
                        AgQueryCallback callback, gpointer user_data,
1902
                        const gchar *sql)
1903
{
1904
    sqlite3 *db;
1905
    int ret;
1906
    sqlite3_stmt *stmt;
1907
    struct timespec ts0, ts1;
1908
    gint rows = 0;
1909
1910
    g_return_val_if_fail (AG_IS_MANAGER (manager), 0);
1911
    db = manager->priv->db;
1912
1913
    g_return_val_if_fail (db != NULL, 0);
1914
1915
    ret = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);
1916
    if (ret != SQLITE_OK)
1917
    {
1918
        g_warning ("%s: can't compile SQL statement \"%s\": %s", G_STRFUNC, sql,
1919
                   sqlite3_errmsg (db));
1920
        return 0;
1921
    }
1922
1923
    DEBUG_QUERIES ("about to run:\n%s", sql);
1924
1925
    /* get the current time, to abort the operation in case the DB is locked
1926
     * for longer than db_timeout. */
1927
    clock_gettime(CLOCK_MONOTONIC, &ts0);
1928
1929
    do
1930
    {
1931
        ret = sqlite3_step (stmt);
1932
1933
        switch (ret)
1934
        {
1935
            case SQLITE_DONE:
1936
                break;
1937
1938
            case SQLITE_ROW:
1939
                if (callback == NULL || callback (stmt, user_data))
1940
                {
1941
                    rows++;
1942
                }
1943
                break;
1944
1945
            case SQLITE_BUSY:
1946
                clock_gettime(CLOCK_MONOTONIC, &ts1);
1947
                if (timespec_diff_ms(&ts1, &ts0) < manager->priv->db_timeout)
1948
                {
1949
                    /* If timeout was specified and table is locked,
1950
                     * wait instead of executing default runtime
1951
                     * error action. Otherwise, fall through to it. */
1952
                    sched_yield ();
1953
                    break;
1954
                }
1955
1956
            default:
1957
                set_error_from_db (manager);
1958
                g_warning ("%s: runtime error while executing \"%s\": %s",
1959
                           G_STRFUNC, sql, sqlite3_errmsg (db));
1960
                sqlite3_finalize (stmt);
1961
                return rows;
1962
        }
1963
    } while (ret != SQLITE_DONE);
1964
1965
    sqlite3_finalize (stmt);
1966
1967
    return rows;
1968
}
1969
1970
/**
1971
 * ag_manager_get_provider:
1972
 * @manager: the #AgManager.
1973
 * @provider_name: the name of the provider.
1974
 *
1975
 * Loads the provider identified by @provider_name.
1976
 *
1977
 * Returns: an #AgProvider, which must be then free'd with ag_provider_unref().
1978
 */
1979
AgProvider *
1980
ag_manager_get_provider (AgManager *manager, const gchar *provider_name)
1981
{
1982
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
1983
    g_return_val_if_fail (provider_name != NULL, NULL);
1984
1985
    /* We don't implement any caching mechanism for AgProvider structures: they
1986
     * shouldn't be loaded that often. */
1987
    return _ag_provider_new_from_file (provider_name);
1988
}
1989
1990
/**
1991
 * ag_manager_list_providers:
1992
 * @manager: the #AgManager.
1993
 *
1994
 * Gets a list of all the installed providers.
1995
 *
1996
 * Returns: a list of #AgProvider, which must be then free'd with
1997
 * ag_provider_list_free().
1998
 */
1999
GList *
2000
ag_manager_list_providers (AgManager *manager)
2001
{
2002
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
2003
2004
    return _ag_providers_list (manager);
2005
}
2006
2007
/**
2008
 * ag_manager_new_for_service_type:
2009
 * @service_type: the name of a service type
2010
 *
2011
 * Returns: an instance of an #AgManager with specified service type.
2012
 */
2013
AgManager *
2014
ag_manager_new_for_service_type (const gchar *service_type)
2015
{
2016
    AgManager *manager;
2017
2018
    g_return_val_if_fail (service_type != NULL, NULL);
2019
2020
    manager = g_object_new (AG_TYPE_MANAGER, "service-type", service_type, NULL);
2021
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
2022
2023
    return manager;
2024
}
2025
2026
const gchar *
2027
ag_manager_get_service_type (AgManager *manager)
2028
{
2029
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
2030
2031
    return manager->priv->service_type;
2032
}
2033
2034
/**
2035
 * ag_manager_set_db_timeout:
2036
 * @manager: the #AgManager.
2037
 * @timeout_ms: the new timeout, in milliseconds.
2038
 *
2039
 * Sets the timeout for database operations. This tells the library how long
2040
 * it is allowed to block while waiting for a locked DB to become accessible.
2041
 * Higher values mean a higher chance of successful reads, but also mean that
2042
 * the execution might be blocked for a longer time.
2043
 * The default is 5 seconds.
2044
 */
2045
void
2046
ag_manager_set_db_timeout (AgManager *manager, guint timeout_ms)
2047
{
2048
    g_return_if_fail (AG_IS_MANAGER (manager));
2049
    manager->priv->db_timeout = timeout_ms;
2050
}
2051
2052
/**
2053
 * ag_manager_get_db_timeout:
2054
 * @manager: the #AgManager.
2055
 *
2056
 * Returns: the timeout (in milliseconds) for database operations.
2057
 */
2058
guint
2059
ag_manager_get_db_timeout (AgManager *manager)
2060
{
2061
    g_return_val_if_fail (AG_IS_MANAGER (manager), 0);
2062
    return manager->priv->db_timeout;
2063
}
2064
2065
/**
2066
 * ag_manager_set_abort_on_db_timeout:
2067
 * @manager: the #AgManager.
2068
 * @abort: whether to abort when a DB timeout occurs.
2069
 *
2070
 * Tells libaccounts whether it should make the client application abort when
2071
 * a timeout error occurs. The default is %FALSE.
2072
 */
2073
void
2074
ag_manager_set_abort_on_db_timeout (AgManager *manager, gboolean abort)
2075
{
2076
    g_return_if_fail (AG_IS_MANAGER (manager));
2077
    manager->priv->abort_on_db_timeout = abort;
2078
}
2079
2080
/**
2081
 * ag_manager_get_abort_on_db_timeout:
2082
 * @manager: the #AgManager.
2083
 *
2084
 * Returns: whether the library will abort when a timeout error occurs.
2085
 */
2086
gboolean
2087
ag_manager_get_abort_on_db_timeout (AgManager *manager)
2088
{
2089
    g_return_val_if_fail (AG_IS_MANAGER (manager), FALSE);
2090
    return manager->priv->abort_on_db_timeout;
2091
}
2092
2093
/**
2094
 * ag_manager_load_service_type:
2095
 * @manager: the #AgManager.
2096
 * @service_type: the name of the service type.
2097
 *
2098
 * Instantiate the service type @service_type.
2099
 *
2100
 * Returns: an #AgServiceType, which must be then free'd with
2101
 * ag_service_type_unref().
2102
 */
2103
AgServiceType *
2104
ag_manager_load_service_type (AgManager *manager, const gchar *service_type)
2105
{
2106
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
2107
2108
    /* Given the small size of the service type file, and the unlikely need to
2109
     * load them more than once, we don't cache them in the manager. But this
2110
     * might change in the future.
2111
     */
2112
    return _ag_service_type_new_from_file (service_type);
2113
}