File libaccounts-glib-1.24.obscpio of Package libaccounts-glib

07070100000000000081A4000003E800000064000000015BDC2B8B0000070E000000000000000000000000000000000000002500000000libaccounts-glib-1.24/.gitlab-ci.ymlimage: ubuntu:xenial

cache:
  key: apt-cache
  paths:
    - apt-cache/

before_script:
  - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
  - apt-get update -yq && apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -yq software-properties-common
  - add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ xenial-backports main restricted universe multiverse" -u -s
  - apt-get -o dir::cache::archives="$APT_CACHE_DIR" -t xenial-backports install -yq meson
  - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -yq gobject-introspection gtk-doc-tools libgirepository1.0-dev libglib2.0-dev libsqlite3-dev libxml2-dev libxml2-utils python3 python3-gi python-gi-dev valac
  - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -yq check dbus-test-runner lcov gcovr
  - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -yq sudo
  - useradd -m tester
  - adduser tester sudo
  - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y locales
  - echo "en_US UTF-8" > /etc/locale.gen
  - locale-gen en_US.UTF-8
  - export LANG=en_US.UTF-8
  - export LANGUAGE=en_US:en
  - export LC_ALL=en_US.UTF-8


build_amd64:
  stage: build
  script:
    - export PYTHON=python3
    - meson build -Db_coverage=true
    - cd build
    - ninja
  artifacts:
    paths:
      - ./

test_amd64:
  stage: test
  script:
    - cd build
    - chmod a+rw -R .
    - sudo -u tester ninja test
    - sudo -u tester ninja coverage-html
    - sudo ninja install
  dependencies:
    - build_amd64
  artifacts:
    paths:
      - ./

pages:
  stage: deploy
  script:
    - mkdir public
    - cp -a build/meson-logs/coveragereport public/coverage
    - cp -a build/docs/reference/html/* public/
  dependencies:
    - test_amd64
  artifacts:
    paths:
      - public
  only:
    - master
07070100000001000081A4000003E800000064000000015BDC2B8B00006742000000000000000000000000000000000000001E00000000libaccounts-glib-1.24/COPYING		  GNU LESSER GENERAL PUBLIC LICENSE
		       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

			    Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.

		  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".

  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.

  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)

  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.

  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.

  1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.

  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.

  2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) The modified work must itself be a software library.

    b) You must cause the files modified to carry prominent notices
    stating that you changed the files and the date of any change.

    c) You must cause the whole of the work to be licensed at no
    charge to all third parties under the terms of this License.

    d) If a facility in the modified Library refers to a function or a
    table of data to be supplied by an application program that uses
    the facility, other than as an argument passed when the facility
    is invoked, then you must make a good faith effort to ensure that,
    in the event an application does not supply such function or
    table, the facility still operates, and performs whatever part of
    its purpose remains meaningful.

    (For example, a function in a library to compute square roots has
    a purpose that is entirely well-defined independent of the
    application.  Therefore, Subsection 2d requires that any
    application-supplied function or table used by this function must
    be optional: if the application does not supply it, the square
    root function must still compute square roots.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.

In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.

  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.

  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.

  4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.

  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.

  5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.

  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.

  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.

  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)

  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.

  6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.

  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:

    a) Accompany the work with the complete corresponding
    machine-readable source code for the Library including whatever
    changes were used in the work (which must be distributed under
    Sections 1 and 2 above); and, if the work is an executable linked
    with the Library, with the complete machine-readable "work that
    uses the Library", as object code and/or source code, so that the
    user can modify the Library and then relink to produce a modified
    executable containing the modified Library.  (It is understood
    that the user who changes the contents of definitions files in the
    Library will not necessarily be able to recompile the application
    to use the modified definitions.)

    b) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) Accompany the work with a written offer, valid for at
    least three years, to give the same user the materials
    specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.

    d) If distribution of the work is made by offering access to copy
    from a designated place, offer equivalent access to copy the above
    specified materials from the same place.

    e) Verify that the user has already received a copy of these
    materials or that you have already sent this user a copy.

  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.

  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.

  7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:

    a) Accompany the combined library with a copy of the same work
    based on the Library, uncombined with any other library
    facilities.  This must be distributed under the terms of the
    Sections above.

    b) Give prominent notice with the combined library of the fact
    that part of it is a work based on the Library, and explaining
    where to find the accompanying uncombined form of the same work.

  8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License.  Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License.  However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

  9. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.

  10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.

  11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.

  13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.

  14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.

			    NO WARRANTY

  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

		     END OF TERMS AND CONDITIONS

           How to Apply These Terms to Your New Libraries

  If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change.  You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).

  To apply these terms, attach the following notices to the library.  It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This library 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 library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the
  library `Frob' (a library for tweaking knobs) written by James Random Hacker.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!


07070100000002000081A4000003E800000064000000015BDC2B8B00001ACE000000000000000000000000000000000000001B00000000libaccounts-glib-1.24/NEWSlibaccounts-glib NEWS

Version 1.23
------------

* Add a couple of methods related to .application files:
  ag_manager_list_services_by_application() and
  ag_application_supports_service().

Version 1.22
------------

* Lib: migrate to GTask
* Build: enable CI in gitlab
* Build: add support for lcov 1.12

Version 1.21
------------

* Fix installation of test data files

Version 1.20
------------

* Support desktop-specific overrides for service and providers files:
  desktops can define service and providers files in
    /usr/share/accounts/{providers,services}/$XDG_CURRENT_DESKTOP
  and these would override any files having the same name in the parent
  (default) directory.
* Fix endianness issues
  https://gitlab.com/accounts-sso/libaccounts-glib/issues/2

Version 1.19
------------

* Build: ignore deprecations of GSimpleAsyncResult
* Build: don't emit a build error on deprecations
* Coverage: allow lcov versions up to 1.11
* Build: fix handling of coverage flags
* Build: fix build errors when building with clang
* Update account ID after a remotely-executed store
* Vala: add allow-none and update VAPI file
  https://code.google.com/p/accounts-sso/issues/detail?id=241
* Build: multiarch fixes

Version 1.18
------------

* Python: fix Accounts.Manager() constructor, which got broken when version
  1.17 was released.

Version 1.17
------------

* Allow instantiation of AgManager with no D-Bus
* Tests: increase timeout in test_store_locked_cancel
* AgManager: expose DB timeout parameters as properties
* Fix build with -Werror

Version 1.16
------------

* Deliver store events to D-Bus Account Manager when DB is R/O
* Fix all memory leaks reported by valgrind
* Allow running the test suite in a wrapper program such as gdb and valgrind

Version 1.15
------------

* Fix SQL query for ag_manager_list_by_service_type()
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=208

Version 1.14
------------

* Add ag_provider_get_single_account
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=202
* Add coverage reporting using lcov
* Tests: increase test coverage
* Tests: increase tolerance on blocking time

Version 1.13
------------

* Allow disabling WAL journaling mode at configuration time; this is needed
  in order to support accessing the DB in read-only mode
* Tests: make test_signals_other_manager() more stable
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=200

Version 1.12
------------

* Allow opening the DB in read-only mode
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=199
* Application: do not require ".desktop" suffix
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=193
* Account: emit "enabled" signal also on non-selected services
* AgAccount: implement the GInitable interface
* Tests: revert "don't run tests in parallel", disable gtkdoc tests

Version 1.11
------------

* Tests: don't run tests in parallel

Version 1.10
------------

* Vala: rename .vapi and .deps files
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=176
* Tests: don't fail on critical warnings
* AgService: load the XML file before returning tags
* Removed deprecated g_type_init and bumped GObject dependency

Version 1.9
-----------

* Fix compilation with GCC 4.8
* Install tests
* Add installcheck target for installed tests

Version 1.8
-----------

* Account::enabled(): use NULL for global account
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=157
* Do never return a NULL AgAuthData pointer

Version 1.7
-----------

* Link with rt only when necessary.
* Remove AEGIS dead code.
* Update VAPI file.
* Allow creating AuthData for a global account.

Version 1.6
-----------

* Fix the check for pyoverridesdir for python3
* Also support Python 2.7

Version 1.5
-----------

* Allow provider XML files to specify which plugin manages the accounts of the
  provider
* Include ag-auth-data.h in accounts-glib.h
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=136

Version 1.4
-----------

* Port to GDBus; drop the dependencies on libdbus and libdbus-glib.
* Added support for the <template> element in .provider files. This allows to
  specify default settings for the global account in the XML file.
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=111
* Make some GObject properties readable.
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=125
* New method: ag_account_store_async(), which uses GAsyncResult and allows for
  a GCancellable option.
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=116
* Add a few GObject properties:
  AgAccount:
   - enabled
   - display-name
  AgAccountService:
   - enabled
* Generate and install man pages from documentation for ag-tool and ag-backup.
  Add a --disable-man flag so that packagers can disable the generation if
  necessary.
* Add GVariant-based APIs beside the GValue-based ones, which become
  deprecated.
* Enhance Python overrides to provide a more Pythonic API.
  Fixes: http://code.google.com/p/accounts-sso/issues/detail?id=109

Version 1.3
-----------

* Fix duplicate signal handling in AgManager
* Fix test for implicit ordering of the returned AgAccountService list
* Fix many compiler warnings

Version 1.2
-----------

* Add new AgAccountsError and deprecate AgError
* Distribute Vala bindings
* Set G_LOG_DOMAIN for namespaced logging messages
* Update D-Bus interface and object paths
* Emit "enabled" signal on AgAccountService deletion
* Remove an unnecessary D-Bus signal emission
* Fix reading of timepsec on 64-bit machines
* Move all typedefs to ag-types.h
* Fix linker error in tools
* Add description field to AgProvider, AgService and AgServiceType, and the
  corresponding XML data files
* Add Requires.private and application and service file path to pkg-config file
* Fix GValue direction in introspection data
* Add ag_provider_match_domain() for matching against the domain regex

Version 1.1
-----------

* Add domain match regular expression to AgProvider
* Document that AgAccountService can operate on global settings
* Do not require building with Python support
* Fix a couple of compiler warnings
* Make AgManager:service-type and AgAccount:id properties readable
* Add AgAuthData
* Add some short examples to the API reference
* Use dbus-test-runner if it is available
* Store the accounts database in $XDG_CONFIG_HOME
* Do not install test scripts
* Add tag support for AgServiceType and AgService
* Add AgApplication as a new boxed type
* Unify scode for scanning data files
* Several documentation fixes
* Test changes to run from the source tree
* Add several new test cases
* Add support for string arrays and doubles
* Remove AEGIS crypto support
* Add AgAccountSettingsIter for bindings
* Add PyGObject override file
* Use GObject-Introspection for bindings
07070100000003000081A4000003E800000064000000015BDC2B8B000002FE000000000000000000000000000000000000002000000000libaccounts-glib-1.24/README.mdAccounts management library for GLib applications
=================================================

This project is a library for managing accounts which can be used from GLib
applications. It is part of the [accounts-sso project][accounts-sso].


License
-------

See COPYING file.


Build instructions
------------------

The project depends on GLib (including GIO and GObject), libxml2, sqlite3 and
[check][].
To build it, run:
```
meson build --prefix=/usr
cd build
ninja
```

Resources
---------

[API reference documentation](http://accounts-sso.gitlab.io/libaccounts-glib/)

[Official source code repository](https://gitlab.com/accounts-sso/libaccounts-glib)

[accounts-sso]: https://gitlab.com/groups/accounts-sso
[check]: https://github.com/libcheck/check
07070100000004000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000001B00000000libaccounts-glib-1.24/data07070100000005000081A4000003E800000064000000015BDC2B8B000005E5000000000000000000000000000000000000003400000000libaccounts-glib-1.24/data/accounts-application.dtd<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD for libaccounts application files. -->

<!-- Root element for application files. -->
<!ELEMENT application (description?, desktop-entry?, translations?, services?,
                       service-types?)>

<!-- The ID is optional, and only used if it differs from the basename of
     the application file. -->
<!ATTLIST application
  id ID #IMPLIED>

<!-- Description of the application, service or service-type. -->
<!ELEMENT description (#PCDATA)>

<!-- Only required if the basename of the desktop file associated with the
     application is different to the basename of the application file. Used to
     lookup the application icon and name. -->
<!ELEMENT desktop-entry (#PCDATA)>

<!-- The gettext translation domain used for internationalization support in
     applications. -->
<!ELEMENT translations (#PCDATA)>

<!-- Container element for individual service descriptions. -->
<!ELEMENT services (service*)>

<!-- Container for a description of how the application uses a specified
     service. -->
<!ELEMENT service (description?)>
<!-- The ID must match a service identifier, defined in a separate .service
     file. -->
<!ATTLIST service
  id ID #REQUIRED>

<!-- Container element for individual service-type descriptions. -->
<!ELEMENT service-types (service-type*)>

<!-- Container for a description of how the application uses a specified
     service-type. -->
<!ELEMENT service-type (description?)>
<!ATTLIST service-type
  id ID #REQUIRED>
07070100000006000081A4000003E800000064000000015BDC2B8B00000337000000000000000000000000000000000000003400000000libaccounts-glib-1.24/data/accounts-application.its<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its"
           version="2.0">
  <its:translateRule selector="/application |
                               /application/desktop-entry |
                               /application/translations |
                               /application/services |
                               /application/services/service |
                               /application/service-types |
                               /application/service-types/service-type"
                     translate="no"/>
  <its:translateRule selector="/application/description |
                               /application/services/service/description \
                               /application/service-types/service-type/description"
                     translate="yes"/>
</its:rules>
07070100000007000081A4000003E800000064000000015BDC2B8B000000DC000000000000000000000000000000000000003400000000libaccounts-glib-1.24/data/accounts-application.loc<?xml version="1.0"?>
<locatingRules>
  <locatingRule name="Accounts Application" pattern="*.application">
    <documentRule localName="application" target="accounts-application.its"/>
  </locatingRule>
</locatingRules>
07070100000008000081A4000003E800000064000000015BDC2B8B000007AA000000000000000000000000000000000000003100000000libaccounts-glib-1.24/data/accounts-provider.dtd<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD for libaccounts provider files. -->

<!-- Root element for provider files. -->
<!ELEMENT provider (name?, description?, translations?, icon?, domains?,
                    plugin?, single-account?, template?)>

<!-- The ID is optional, and only used if it differs from the basename of
     the service file. -->
<!ATTLIST provider
  id ID #IMPLIED>

<!-- Human-readable name of the provider, for display to the user. -->
<!ELEMENT name (#PCDATA)>

<!-- Description of the provider, for display to the user. -->
<!ELEMENT description (#PCDATA)>

<!-- The gettext translation domain used for internationalization support in
     applications. -->
<!ELEMENT translations (#PCDATA)>

<!-- Name of a themed icon representing the provider. -->
<!ELEMENT icon (#PCDATA)>

<!-- A regular expression which should match all the domains where the provider
     may be used on the web. This can be used in combination with an Ubuntu
     Webapp to automatically create an account when the user logs in to an
     accepted domain. -->
<!ELEMENT domains (#PCDATA)>

<!-- Name of the account plugin which manages the accounts from this
     provider. -->
<!ELEMENT plugin (#PCDATA)>

<!-- Whether the provider supports creating only a single account. -->
<!ELEMENT single-account (#PCDATA)>

<!-- Optional template settings. Account settings have a higher priority
     than settings listed in the template. -->
<!ELEMENT template (group*, setting*)>

<!-- A named settings group. This could be used to group settings for
     an application or class of applications. -->
<!ELEMENT group (setting*, group*)>
<!ATTLIST group
  name CDATA #REQUIRED>

<!ELEMENT setting (#PCDATA)>
<!-- Settings are assumed to be of string type (hence the "s" GVariant type
     string as the default for type), but this can be overridden (for example,
     in the case of boolean types). -->
<!ATTLIST setting
  name ID #REQUIRED
  type CDATA "s">
07070100000009000081A4000003E800000064000000015BDC2B8B0000028A000000000000000000000000000000000000003100000000libaccounts-glib-1.24/data/accounts-provider.its<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its"
           version="2.0">
  <its:translateRule selector="/provider |
                               /provider/translations |
                               /provider/icon |
                               /provider/domains |
                               /provider/plugin |
                               /provider/single-account |
                               /provider/template"
                     translate="no"/>
  <its:translateRule selector="/provider/name |
                               /provider/description"
                     translate="yes"/>
</its:rules>
0707010000000A000081A4000003E800000064000000015BDC2B8B000000D0000000000000000000000000000000000000003100000000libaccounts-glib-1.24/data/accounts-provider.loc<?xml version="1.0"?>
<locatingRules>
  <locatingRule name="Accounts Provider" pattern="*.provider">
    <documentRule localName="provider" target="accounts-provider.its"/>
  </locatingRule>
</locatingRules>
0707010000000B000081A4000003E800000064000000015BDC2B8B000003AD000000000000000000000000000000000000003500000000libaccounts-glib-1.24/data/accounts-service-type.dtd<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD for libaccounts service-type files. -->

<!-- Root element for service-type files. -->
<!ELEMENT service-type (name?, description?, icon?, translations?, tags?)>

<!-- The ID is optional, and only used if it differs from the basename of
     the service file. -->
<!ATTLIST service-type
  id ID #IMPLIED>

<!-- Human-readable name of the service-type, for display to the user. -->
<!ELEMENT name (#PCDATA)>

<!-- Description of the service-type, for display to the user. -->
<!ELEMENT description (#PCDATA)>

<!-- Name of a themed icon representing the service-type. -->
<!ELEMENT icon (#PCDATA)>

<!-- The gettext translation domain used for internationalization support in
     applications. -->
<!ELEMENT translations (#PCDATA)>

<!-- Container element for tags to describe the service-type. -->
<!ELEMENT tags (tag*)>

<!-- A tag to describe the service-type. -->
<!ELEMENT tag (#PCDATA)>
0707010000000C000081A4000003E800000064000000015BDC2B8B00000237000000000000000000000000000000000000003500000000libaccounts-glib-1.24/data/accounts-service-type.its<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its"
           version="2.0">
  <its:translateRule selector="/service-type |
                               /service-type/icon |
                               /service-type/translations |
                               /service-type/tags"
                     translate="no"/>
  <its:translateRule selector="/service-type/name |
                               /service-type/description |
                               /service-type/tags/tag"
                     translate="yes"/>
</its:rules>
0707010000000D000081A4000003E800000064000000015BDC2B8B000000E0000000000000000000000000000000000000003500000000libaccounts-glib-1.24/data/accounts-service-type.loc<?xml version="1.0"?>
<locatingRules>
  <locatingRule name="Accounts Service Type" pattern="*.service-type">
    <documentRule localName="service-type" target="accounts-service-type.its"/>
  </locatingRule>
</locatingRules>
0707010000000E000081A4000003E800000064000000015BDC2B8B000006C9000000000000000000000000000000000000003000000000libaccounts-glib-1.24/data/accounts-service.dtd<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD for libaccounts service files. -->

<!-- Root element for service files. -->
<!ELEMENT service (type, name?, description?, icon?, provider, translations?,
                   tags?, template?)>

<!-- The ID is optional, and only used if it differs from the basename of
     the service file. -->
<!ATTLIST service
  id ID #IMPLIED>

<!-- Service-type identifier. -->
<!ELEMENT type (#PCDATA)>

<!-- Human-readable name of the service, for display to the user. -->
<!ELEMENT name (#PCDATA)>

<!-- Description of the service, for display to the user. -->
<!ELEMENT description (#PCDATA)>

<!-- Name of a themed icon representing the service. -->
<!ELEMENT icon (#PCDATA)>

<!-- The identifier of the provider which supports this service. -->
<!ELEMENT provider (#PCDATA)>

<!-- The gettext translation domain used for internationalization support in
     applications. -->
<!ELEMENT translations (#PCDATA)>

<!-- Container element for tags to describe the service. -->
<!ELEMENT tags (tag*)>

<!-- A tag to describe the service. -->
<!ELEMENT tag (#PCDATA)>

<!-- Optional template settings. Account settings have a higher priority
     than settings listed in the template. -->
<!ELEMENT template (group*, setting*)>

<!-- A named settings group. This could be used to group settings for
     an application or class of applications. -->
<!ELEMENT group (setting*, group*)>
<!ATTLIST group
  name ID #IMPLIED>

<!ELEMENT setting (#PCDATA)>
<!-- Settings are assumed to be of string type (hence the "s" GVariant type
     string as the default for type), but this can be overridden (for example,
     in the case of boolean types). -->
<!ATTLIST setting
  name ID #REQUIRED
  type CDATA "s">
0707010000000F000081A4000003E800000064000000015BDC2B8B0000031D000000000000000000000000000000000000003000000000libaccounts-glib-1.24/data/accounts-service.its<?xml version="1.0"?>
<its:rules xmlns:its="http://www.w3.org/2005/11/its"
           version="2.0">
  <its:translateRule selector="/service |
                               /service/type |
                               /service/icon |
                               /service/provider |
                               /service/translations |
                               /service/tags |
                               /service/template |
                               /service/template/group |
                               /service/template/setting"
                     translate="no"/>
  <its:translateRule selector="/service/name |
                               /service/description |
                               /service/tags/tag"
                     translate="yes"/>
</its:rules>
07070100000010000081A4000003E800000064000000015BDC2B8B000000CC000000000000000000000000000000000000003000000000libaccounts-glib-1.24/data/accounts-service.loc<?xml version="1.0"?>
<locatingRules>
  <locatingRule name="Accounts Service" pattern="*.service">
    <documentRule localName="service" target="accounts-service.its"/>
  </locatingRule>
</locatingRules>
07070100000011000081A4000003E800000064000000015BDC2B8B0000025F000000000000000000000000000000000000002700000000libaccounts-glib-1.24/data/meson.builddtd_files = [
    'accounts-application.dtd',
    'accounts-provider.dtd',
    'accounts-service.dtd',
    'accounts-service-type.dtd'
]

its_files = [
    'accounts-application.loc',
    'accounts-application.its',
    'accounts-provider.loc',
    'accounts-provider.its',
    'accounts-service.loc',
    'accounts-service.its',
    'accounts-service-type.loc',
    'accounts-service-type.its'
]

install_data(dtd_files,
    install_dir: join_paths(get_option('datadir'), 'xml', 'accounts', 'schema', 'dtd')
)

install_data(its_files,
    install_dir: join_paths(get_option('datadir'), 'gettext', 'its')
)
07070100000012000041ED000003E800000064000000035BDC2B8B00000000000000000000000000000000000000000000001B00000000libaccounts-glib-1.24/docs07070100000013000081A4000003E800000064000000015BDC2B8B00000014000000000000000000000000000000000000002700000000libaccounts-glib-1.24/docs/meson.buildsubdir('reference')
07070100000014000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000002500000000libaccounts-glib-1.24/docs/reference07070100000015000081A4000003E800000064000000015BDC2B8B000006F6000000000000000000000000000000000000003300000000libaccounts-glib-1.24/docs/reference/ag-backup.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
[<!ENTITY version SYSTEM "version.xml">]>

<refentry id="ag-backup">

<refmeta>
<refentrytitle>ag-backup</refentrytitle>
<manvolnum>1</manvolnum>
<refmiscinfo class="manual">User Commands</refmiscinfo>
<refmiscinfo class="source">libaccounts-glib</refmiscinfo>
<refmiscinfo class="version">&version;</refmiscinfo>
</refmeta>

<refnamediv>
<refname>ag-backup</refname>
<refpurpose>Backup the libaccounts database</refpurpose>
</refnamediv>

<refsynopsisdiv>
<cmdsynopsis>
<command>ag-backup</command>
<arg choice="opt">--help</arg>
</cmdsynopsis>
</refsynopsisdiv>

<refsect1>
<title>Description</title>
<para>
<command>ag-backup</command> is a simple tool to backup the accounts database.
</para>
</refsect1>

<refsect1>
<title>Invocation</title>
<para>
<command>ag-backup</command> accepts a single argument, to show usage notes. On
execution with no arguments, it will backup the accounts database from
<filename><varname>XDG_CONFIG_HOME</varname>/libaccounts-glib/accounts.db</filename>
to
<filename><varname>XDG_CONFIG_HOME</varname>/libaccounts-glib/accounts.db.bak</filename>.
</para>

<refsect2>
<title>Options</title>

<variablelist>
  <varlistentry>
    <term><option>--help</option></term>
    <listitem>
      <para>Show usage notes and exit.</para>
    </listitem>
  </varlistentry>
</variablelist>

</refsect2>

</refsect1>

<refsect1>
<title>Author</title>
<para>
<command>ag-backup</command> was written by Alberto Mardegan
<email>alberto.mardegan@canonical.com</email>.
</para>

<para>
This manual page was written by David King
<email>david.king@canonical.com</email>.
</para>
</refsect1>

</refentry>
07070100000016000081A4000003E800000064000000015BDC2B8B00001F52000000000000000000000000000000000000003100000000libaccounts-glib-1.24/docs/reference/ag-tool.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
[<!ENTITY version SYSTEM "version.xml">]>

<refentry id="ag-tool">

<refmeta>
<refentrytitle>ag-tool</refentrytitle>
<manvolnum>1</manvolnum>
<refmiscinfo class="manual">User Commands</refmiscinfo>
<refmiscinfo class="source">libaccounts-glib</refmiscinfo>
<refmiscinfo class="version">&version;</refmiscinfo>
</refmeta>

<refnamediv>
<refname>ag-tool</refname>
<refpurpose>Edit the libaccounts database</refpurpose>
</refnamediv>

<refsynopsisdiv>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">create-account</arg>
<arg choice="plain"><replaceable>PROVIDER-ID</replaceable></arg>
<arg>
  <arg choice="plain"><replaceable>DISPLAY-NAME</replaceable></arg>
  <group>
    <arg choice="plain">enable</arg>
    <arg choice="plain">disable</arg>
  </group>
</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">delete-account</arg>
<group choice="req">
  <arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
  <arg choice="plain">all</arg>
</group>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">disable-account</arg>
<arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">disable-service</arg>
<arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
<arg choice="plain"><replaceable>SERVICE-ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">enable-account</arg>
<arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">enable-service</arg>
<arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
<arg choice="plain"><replaceable>SERVICE-ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">get-account</arg>
<arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
<arg choice="req">
  <group choice="plain">
    <arg choice="plain">int</arg>
    <arg choice="plain">uint</arg>
    <arg choice="plain">bool</arg>
    <arg choice="plain">string</arg>
  </group>
  :
  <arg choice="plain"><replaceable>key</replaceable></arg>
</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">get-service</arg>
<arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
<arg choice="plain"><replaceable>SERVICE-ID</replaceable></arg>
<arg choice="req">
  <group choice="plain">
    <arg choice="plain">int</arg>
    <arg choice="plain">uint</arg>
    <arg choice="plain">bool</arg>
    <arg choice="plain">string</arg>
  </group>
  :
  <arg choice="plain"><replaceable>key</replaceable></arg>
</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">list-accounts</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">list-enabled</arg>
<arg choice="opt"><replaceable>ACCOUNT-ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">list-providers</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">list-services</arg>
<arg choice="opt"><replaceable>ACCOUNT-ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">list-settings</arg>
<arg choice="plain"><replaceable>ACCOUNT-ID</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ag-tool</command>
<arg choice="plain">--help</arg>
</cmdsynopsis>
</refsynopsisdiv>

<refsect1>
<title>Description</title>
<para>
<command>ag-tool</command> is a simple tool to modify and query the accounts
database.
</para>
</refsect1>

<refsect1>
<title>Invocation</title>
<para>
<command>ag-tool</command> takes a required <replaceable>action</replaceable>
as argument, followed by zero or more <replaceable>option</replaceable>s.
</para>

<refsect2>
<title>Actions and options</title>
<variablelist>
  <varlistentry>
    <term><option>delete-account</option></term>
    <listitem>
      <para>
      Delete the account specified by <replaceable>ACCOUNT-ID</replaceable>.
      Alternatively, use the special value <literal>all</literal> to delete all
      accounts in the database.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>disable-account</option></term>
    <listitem>
      <para>
      Disable the account specified by <replaceable>ACCOUNT-ID</replaceable>.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>disable-service</option></term>
    <listitem>
      <para>
      Disable the service specified by <replaceable>SERVICE-ID</replaceable> on
      the account given by <replaceable>ACCOUNT-ID</replaceable>.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>enable-account</option></term>
    <listitem>
      <para>
      Enable the account given by <replaceable>ACCOUNT-ID</replaceable>.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>enable-service</option></term>
    <listitem>
      <para>
      Enable the service specified by <replaceable>SERVICE-ID</replaceable> on
      the account given by <replaceable>ACCOUNT-ID</replaceable>.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>get-account</option></term>
    <listitem>
      <para>
      Gets the value of a settings key from the account given by
      <replaceable>ACCOUNT-ID</replaceable>, where the type of the setting is
      one of <literal>int</literal>, <literal>uint</literal>,
      <literal>bool</literal> or <literal>string</literal>.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>get-service</option></term>
    <listitem>
      <para>
      Gets the value of a settings key from the account given by
      <replaceable>ACCOUNT-ID</replaceable> with the service given by
      <replaceable>SERVICE-ID</replaceable>, where the type of the setting is
      one of <literal>int</literal>, <literal>uint</literal>,
      <literal>bool</literal> or <literal>string</literal>.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>list-accounts</option></term>
    <listitem>
      <para>List the accounts in the accounts database.</para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>list-enabled</option></term>
    <listitem>
      <para>
      If <replaceable>ACCOUNT-ID</replaceable> is specified, list services
      enabled on the given account. If <replaceable>ACCOUNT-ID</replaceable> is
      not given, list the enabled accounts.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>list-providers</option></term>
    <listitem>
      <para>List the available account providers.</para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>list-services</option></term>
    <listitem>
      <para>
      If <replaceable>ACCOUNT-ID</replaceable> is specified, list the services
      on the given account. If <replaceable>ACCOUNT-ID</replaceable> is not
      specified, list the available services.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>list-settings</option></term>
    <listitem>
      <para>
      List the settings associated with the account given by
      <replaceable>ACCOUNT-ID</replaceable>.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term><option>--help</option></term>
    <listitem>
      <para>Show usage notes and exit.</para>
    </listitem>
  </varlistentry>
</variablelist>
</refsect2>

</refsect1>

<refsect1>
<title>Author</title>
<para>
<command>ag-tool</command> was written by Aparna Nandyal
<email>aparna.nand@wipro.com</email>.
</para>

<para>
This manual page was written by David King
<email>david.king@canonical.com</email>.
</para>
</refsect1>

</refentry>
07070100000017000081A4000003E800000064000000015BDC2B8B00000E79000000000000000000000000000000000000004100000000libaccounts-glib-1.24/docs/reference/application-file-format.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/doxbook/xml/4.3/docbookx.dtd">

<section id="application-file-format" xreflabel="application manifest files">

<title>Application manifest file format</title>

<para>
Applications using libacounts-glib should supply a manifest: a simple XML file
describing the application and the services that it will use. An example is
shown below:
</para>

<example>
  <title>Application manifest for a gallery application</title>
  <programlisting>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;application id="Gallery"&gt;
  &lt;description&gt;Image gallery&lt;/description&gt;
  &lt;desktop-entry&gt;gallery&lt;/desktop-entry&gt;
  &lt;translations&gt;gallery&lt;/translations&gt;

  &lt;services&gt;
    &lt;service id="OtherService"&gt;
      &lt;description&gt;Publish images on OtherService&lt;/description&gt;
    &lt;/service&gt;
  &lt;/services&gt;

  &lt;service-types&gt;
    &lt;service-type id="sharing"&gt;
      &lt;description&gt;Share your images with your friends&lt;/description&gt;
    &lt;/service-type&gt;
  &lt;/service-types&gt;

&lt;/application&gt;</programlisting>
</example>

<para>
The example application manifest describes an application called
<literal>Gallery</literal>, indicated by the
<sgmltag class="attribute" role="xml">id</sgmltag> attribute on the
<sgmltag class="element" role="xml">application</sgmltag> element. The
<sgmltag class="element" role="xml">description</sgmltag> is a string that
describes the application in general, and is also used for specific services
and service types. The
<sgmltag class="element" role="xml">desktop-entry</sgmltag> element is only
required if the basename of the desktop file is different than the basename of
the application manifest, where the basename is the filename excluding any file
extension. The <sgmltag class="element" role="xml">translations</sgmltag>
element is used to indicate the gettext translation domain for the
<sgmltag class="element" role="xml">name</sgmltag> and
<sgmltag class="element" role="xml">description</sgmltag> elements, to be used
by applications when showing those elements in a UI. The
<sgmltag class="element" role="xml">services</sgmltag> element contains
individual <sgmltag class="element" role="xml">service</sgmltag> elements, with
an <sgmltag class="attribute" role="xml">id</sgmltag> attribute that
corresponds to an installed service. Finally, a
<sgmltag class="element" role="xml">service-types</sgmltag> element contains
individual <sgmltag class="element" role="xml">service-type</sgmltag> elements,
which act in the same way as for services.
  <note>
  <para>
  It is only useful to list services in the manifest if a separate description
  is desired for each service, such as if some special features are
  supported by the application, beyond those suggested by the general
  description and the service type.
  </para>
  </note>
</para>

  <section>

  <title>Installation</title>

  <para>
  Application manifest filenames should end in
  <filename class="extension">.application</filename> and be installed to
  <filename class="directory">${prefix}/share/accounts/applications</filename>,
  which normally expands to
  <filename class="directory">/usr/share/accounts/applications</filename>. The
  path can be queried with <command>pkg-config</command> by checking the
  <varname>applicationfilesdir</varname> variable of the libaccounts-glib
  pkg-config file, for example:
  </para>

  <informalexample>
  <programlisting>pkg-config --variable=applicationfilesdir libaccounts-glib</programlisting>
  </informalexample>

  </section>

</section>
07070100000018000081A4000003E800000064000000015BDC2B8B000015B8000000000000000000000000000000000000003B00000000libaccounts-glib-1.24/docs/reference/gettext-xml-files.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">

<section id="gettext-xml-files">

<title>Internationalizing XML data files using gettext</title>

<para>
Strings to be shown to the user which are found in the <acronym>XML</acronym>
data files supported by libaccounts-glib can be localized to the user's
language. For this purpose, a
<sgmltag class="element" role="xml">translations</sgmltag> element is present
to identify the gettext translation domain, which can then be used by
applications which consume those strings in order to request gettext to
localize the text. Thus, the burden of localization is split between the author
of the data file and the application developer. The following sections will
give brief explanations of how to simplify both tasks.
</para>

  <section id="intltool-xml-files">

  <title>
  Using <application>intltool</application> to extract translatable strings
  </title>

  <para>
  <command>intltool</command> is a helper tool to make internationalization
  easier for application developers. Using intltool, a developer can extract
  the translatable strings from XML data files with ease. There are several
  steps that must be taken to integrate intltool to extract translatable
  strings, as described below.
  </para>

  <procedure>
  <step>
    <para>
    Ensure that each XML data file has a
    <sgmltag class="element">translations</sgmltag> element, containing the
    gettext tranlslation domain for the application. This will generally be the
    name of the package, and is often set in <filename>configure.ac</filename>
    with:
    </para>
    <informalexample>
    <programlisting>AC_SUBST([GETTEXT_PACKAGE], [$PACKAGE_TARNAME])</programlisting>
    </informalexample>
  </step>
  <step>
    <para>
    Add a <filename class="extension">.in</filename> to the end of the name of
    the XML data file. For example, rename
    <filename><replaceable>my-application</replaceable>.application</filename>
    to
    <filename><replaceable>my-application</replaceable>.application.in</filename>.
    </para>
  </step>
  <step>
    <para>
    Translatable elements in an XML file must be marked for translation by
    adding an underscore at the beginning of the element name. For example,
    <literal>&lt;description&gt;</literal> would change to
    <literal>&lt;_description&gt;</literal>. An underscore prefix must also be
    added to the corresponding closing element.
    </para>
  </step>
  <step>
    <para>
    The strings that are marked for translation must be extracted by intltool.
    This simply creates a corresponding XML data file, without the
    <filename class="extension">.in</filename> extension, and places the
    marked strings in the intltool cache. The following
    <application>automake</application> snippet will extract the marked strings
    and distribute and install the resulting provider file:
    </para>
    <informalexample>
    <programlisting># Extract transatable strings from .provider file
my-provider.provider: my-provider.provider.in $(INTLTOOL_MERGE)
	$(INTLTOOL_V_MERGE) LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_MERGE_V_OPTIONS) --no-translations -x -u $&lt; $@

provider_in_file = \
	my-provider.provider.in

providerdir = $(datadir)/accounts/providers
provider_DATA = \
	$(providers_in_file:.provider.in=.provider)

dist_noinst_DATA = \
	$(provider_in_file)

CLEANFILES = \
	$(provider_DATA)</programlisting>
    </informalexample>
  </step>
  <step>
    <para>
    Add the <filename class="extension">.in</filename> to
    <filename>po/POTFILES.in</filename>, being sure to list the file type
    alongside it. For example, with a service file
    <filename><replaceable>my-service</replaceable>.service.in</filename> in
    the <filename class="directory">data</filename> directory in the source
    tree, the <filename>POTFILES.in</filename> addition would be:
    </para>
    <informalexample>
    <programlisting>[type: gettext/xml]data/my-service.service.in</programlisting>
    </informalexample>
  </step>
  </procedure>

  </section>

  <section id="gettext-xml-files-applications">

  <title>
  Using <application>gettext</application> to localize translatable strings
  </title>

  <para>
  <application>gettext</application> is used to show the localized versions of
  translatable strings that have been extracted and translated. As most use of
  gettext in a client application involves translatable strings only from that
  application, it is common practice to bind the translataion domain of the
  application as the default, which is normally done as follows:
  </para>

  <informalexample>
  <programlisting>bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALEDIR);</programlisting>
  </informalexample>

  <para>
  If the translation domain is bound in this way, then when requesting
  translation of text from another project, such as the XML data files used by
  libaccounts-glib, the domain must be specified explicitly. The
  <function>dgettext</function> function can be used for this purpose. As an
  example, for a hypothetical service <varname>foo_service</varname> calling
  dgettext could be done as follows:
  </para>

  <informalexample>
  <programlisting>dgettext (ag_service_get_i18n_domain (foo_service),
          ag_service_get_description (foo_service));</programlisting>
  </informalexample>

  <para>
  This returns the translated string, which can then be shown to the user.
  </para>

  </section>

</section>
07070100000019000081A4000003E800000064000000015BDC2B8B000007AC000000000000000000000000000000000000003F00000000libaccounts-glib-1.24/docs/reference/libaccounts-compiling.xml<?xml version="1.0"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/doxbook/xml/4.3/docbookx.dtd">

<chapter id="libaccounts-compiling">

<title>Compiling libaccounts-glib applications</title>

<para>
Building your application against libaccounts-glib is straightforward, and
this documentation give some hints for those new to the process.
</para>

  <section>

  <title>
  Using <application>pkg-config</application> to query compiler and linker
  flags
  </title>

  <para>
  To compile your own application which uses libaccounts-glib, you must give
  the compiler the location of the libaccounts-glib header files, and the
  linker the name of the library to link with. The easiest way to do this is to
  query those flags from the pkg-config file installed by libaccounts-glib. For
  example, to query both the compiler and linker flags, run:
  </para>

  <informalexample>
  <programlisting>pkg-config --cflags --libs libaccounts-glib</programlisting>
  </informalexample>

  <note>
    <para>
    In order for the pkg-config execution to succeed, you must have the
    development files for libaccounts-glib installed on your system. In most
    distributions, these are installed in a separate development package.
    Installing such packages is outside the scope of this documentation.
    </para>
  </note>

  <para>
  The <filename>libaccounts-glib.pc</filename> file installed by
  libaccounts-glib also contains several other useful variables, including the
  install locations for <xref linkend="application-file-format"/>,
  <xref linkend="provider-file-format"/>, <xref linkend="service-file-format"/>
  and <xref linkend="service-type-file-format"/>. Check the installed file for
  details, or query the list of variables with pkg-config:
  </para>

  <informalexample>
  <programlisting>pkg-config --print-variables libaccounts-glib</programlisting>
  </informalexample>

  </section>

</chapter>
0707010000001A000081A4000003E800000064000000015BDC2B8B00000F4B000000000000000000000000000000000000003F00000000libaccounts-glib-1.24/docs/reference/libaccounts-glib-docs.xml<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
<!ENTITY version SYSTEM "version.xml">
]>

<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
  <bookinfo>
    <title>libaccounts-glib Reference Manual</title>
    <releaseinfo>
      This document is for libaccounts-glib, version &version;. The latest
      version of this documentation can be built from the source code at
      <ulink role="online-location" url="https://gitlab.com/accounts-sso/libaccounts-glib">https://gitlab.com/accounts-sso/libaccounts-glib</ulink>.
    </releaseinfo>
  </bookinfo>

  <part id="libaccounts-glib-overview">
    <title>libaccounts-glib Overview</title>

    <partintro>
      <para>
      libaccounts-glib provides account management functionality for GLib
      applications.
      </para>
    </partintro>

    <xi:include href="libaccounts-overview.xml"/>
    <xi:include href="libaccounts-compiling.xml"/>
    <xi:include href="libaccounts-running.xml"/>
  </part>

  <part id="libaccounts-glib-objects">
    <title>libaccounts-glib Objects</title>

    <chapter id="account-management">
      <title>Account management</title>
      <xi:include href="xml/ag-manager.xml"/>
      <xi:include href="xml/ag-account.xml"/>
      <xi:include href="xml/ag-account-service.xml"/>
      <xi:include href="xml/ag-auth-data.xml"/>
      <xi:include href="xml/ag-application.xml"/>
      <xi:include href="xml/ag-provider.xml"/>
      <xi:include href="xml/ag-service.xml"/>
      <xi:include href="xml/ag-service-type.xml"/>
    </chapter>
  </part>

  <part id="xml-files">
    <title>libaccounts-glib data files</title>

    <partintro>
      <para>
      libaccounts-glib uses a number of different data files to enumerate
      information on applications, providers, services and service types. These
      files are all in an XML format, with DTDs available which describe the
      formats.
      </para>
    </partintro>

    <chapter id="xml-file-formats">
      <title>XML files</title>
      <xi:include href="application-file-format.xml"/>
      <xi:include href="provider-file-format.xml"/>
      <xi:include href="service-file-format.xml"/>
      <xi:include href="service-type-file-format.xml"/>
      <xi:include href="gettext-xml-files.xml"/>
      <xi:include href="validating-xml-files.xml"/>
    </chapter>
  </part>

  <part id="libaccounts-glib-tools">
    <title>libaccounts-glib tools</title>
    <xi:include href="ag-backup.xml"/>
    <xi:include href="ag-tool.xml"/>
  </part>

  <part id="appendices">
    <title>Appendices</title>
    <index id="api-index-full">
      <title>API Index</title>
      <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
    </index>

    <index id="api-index-deprecated" role="deprecated">
      <title>Index of deprecated symbols</title>
      <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
    </index>

    <index id="api-index-1-1" role="1.1">
      <title>Index of new symbols in 1.1</title>
      <xi:include href="xml/api-index-1.1.xml"><xi:fallback /></xi:include>
    </index>

    <index id="api-index-1-2" role="1.2">
      <title>Index of new symbols in 1.2</title>
      <xi:include href="xml/api-index-1.2.xml"><xi:fallback /></xi:include>
    </index>

    <index id="api-index-1-4" role="1.4">
      <title>Index of new symbols in 1.4</title>
      <xi:include href="xml/api-index-1.4.xml"><xi:fallback /></xi:include>
    </index>

    <index id="api-index-1-5" role="1.5">
      <title>Index of new symbols in 1.5</title>
      <xi:include href="xml/api-index-1.5.xml"><xi:fallback /></xi:include>
    </index>

    <xi:include href="libaccounts-glossary.xml"><xi:fallback /></xi:include>
    <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
  </part>
</book>
0707010000001B000081A4000003E800000064000000015BDC2B8B000003F3000000000000000000000000000000000000003E00000000libaccounts-glib-1.24/docs/reference/libaccounts-glossary.xml<?xml version="1.0"?>
<!DOCTYPE glossary PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
<!ENTITY version SYSTEM "version.xml">
]>

<glossary id="glossary">
  <title>Glossary</title>

  <glossentry id="DTD">
    <glossterm>DTD</glossterm>
    <glossdef>
      <para>
      A Document Type Definition, used to define an XML document format. XML
      documents can be validated against the DTD.
      </para>
    </glossdef>
  </glossentry>
  <glossentry id="SQL">
    <glossterm>SQL</glossterm>
    <glossdef>
      <para>
      Structured Query Language is a language for constructing queries against
      databases.
      </para>
    </glossdef>
  </glossentry>
  <glossentry id="XML">
    <glossterm>XML</glossterm>
    <glossdef>
      <para>
      The eXtensible Markup Language defines a set of rules for marking up
      documents that are both machine- and human-readable.
      </para>
    </glossdef>
  </glossentry>

</glossary>
0707010000001C000081A4000003E800000064000000015BDC2B8B0000066A000000000000000000000000000000000000003E00000000libaccounts-glib-1.24/docs/reference/libaccounts-overview.xml<?xml version="1.0"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/doxbook/xml/4.3/docbookx.dtd">

<chapter id="libaccounts-overview">

<title>Overview of libaccounts-glib terminology</title>

<para>
libaccounts-glib uses some straightforward terminology, that is nevertheless
important to outline before further discussion.
</para>

<variablelist>
  <varlistentry>
    <term>
    account
    </term>
    <listitem>
      <para>
      An account is given by a provider to a user, and allows access to
      services.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term>
    provider
    </term>
    <listitem>
      <para>
      A provider gives out an account to users, as well as allowing them to use
      the account with a variety of services. A user can have multiple accounts
      with the same provider, and each account may have multiple services.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term>
    service
    </term>
    <listitem>
      <para>
      A service is hosted by a provider, and allows the user to perform some
      task.
      </para>
    </listitem>
  </varlistentry>
  <varlistentry>
    <term>
    service type
    </term>
    <listitem>
      <para>
       A service type describes the main functionality provided by a service.
       Service types are a useful way to group services which support a
       certain functionality. Examples are <literal>calendar</literal>,
       <literal>e-mail</literal> and <literal>sharing</literal>.
      </para>
    </listitem>
  </varlistentry>
</variablelist>

</chapter>
0707010000001D000081A4000003E800000064000000015BDC2B8B00001155000000000000000000000000000000000000003D00000000libaccounts-glib-1.24/docs/reference/libaccounts-running.xml<?xml version="1.0"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/doxbook/xml/4.3/docbookx.dtd">

<chapter id="libaccounts-running">

<title>Running and debugging libaccounts-glib applications</title>

<para>
There are various debugging techniques available for applications which use
libaccounts-glib.
</para>

  <section>

  <title>Environment variables</title>

  <para>
  There are several environment variables which can aid in debugging
  libaccounts-glib applications by changing runtime behavior.
  </para>

  <variablelist>
  <title>Supported envionment variables</title>
    <varlistentry>
      <term>
      <varname>ACCOUNTS</varname>
      </term>
      <listitem>
        <para>
        Specifies the path to the accounts database. If this is unset, the
        database is stored in
        <filename class="directory">$XDG_CONFIG_HOME/libaccounts-glib</filename>,
        which normally expands to
        <filename class="directory">~/.config/libaccounts-glib</filename>.
        </para>
      </listitem>
    </varlistentry>
    <varlistentry>
      <term>
      <varname>AG_DEBUG</varname>
      </term>
      <listitem>
        <para>
        Can be set to a list of debug options, which causes libaccounts-glib to
        print different debug information.
        <variablelist>
        <title><varname>AG_DEBUG</varname> options</title>
          <varlistentry>
            <term>
            <literal>time</literal>
            </term>
            <listitem>
              <para>
              Print timing debug messages.
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term>
            <literal>refs</literal>
            </term>
            <listitem>
              <para>
              Print reference-counting debug messages.
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term>
            <literal>locks</literal>
            </term>
            <listitem>
              <para>
              Print locking debug messages.
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term>
            <literal>queries</literal>
            </term>
            <listitem>
              <para>
              Print <acronym>SQL</acronym> query debug messages.
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term>
            <literal>info</literal>
            </term>
            <listitem>
              <para>
              Print miscellaneous debug messages.
              </para>
            </listitem>
          </varlistentry>
        </variablelist>
        The special value <literal>all</literal> can be used to turn on all
        debug options.
        </para>
        <note>
          <para>
          In order for debug messages to be printed, libaccounts-glib must have
          been configured with the option <option>--enable-debug</option>.
          Additionally, GLib must be configured to print debug messages at the
          required debug level, for example with
          <varname>G_MESSAGES_DEBUG</varname> set to <literal>all</literal>.
          </para>
        </note>
      </listitem>
    </varlistentry>
    <varlistentry>
      <term>
      <varname>AG_APPLICATIONS</varname>
      </term>
      <listitem>
        <para>
        Specifies the path to search for
        <xref linkend="application-file-format"/>.
        </para>
      </listitem>
    </varlistentry>
    <varlistentry>
      <term>
      <varname>AG_PROVIDERS</varname>
      </term>
      <listitem>
        <para>
        Specifies the path to search for
        <xref linkend="provider-file-format"/>.
        </para>
      </listitem>
    </varlistentry>
    <varlistentry>
      <term>
      <varname>AG_SERVICES</varname>
      </term>
      <listitem>
        <para>
        Specifies the path to search for <xref linkend="service-file-format"/>.
        </para>
      </listitem>
    </varlistentry>
    <varlistentry>
      <term>
      <varname>AG_SERVICE_TYPES</varname>
      </term>
      <listitem>
        <para>
        Specifies the path to search for
        <xref linkend="service-type-file-format"/>.
        </para>
      </listitem>
    </varlistentry>
  </variablelist>

  </section>

</chapter>
0707010000001E000081A4000003E800000064000000015BDC2B8B000005D1000000000000000000000000000000000000003100000000libaccounts-glib-1.24/docs/reference/meson.buildglib_prefix = dependency('glib-2.0').get_pkgconfig_variable('prefix')
glib_docpath = join_paths(glib_prefix, 'share', 'gtk-doc', 'html')
docpath = join_paths(get_option('prefix'), get_option('datadir'), 'gtk-doc', 'html')

doc_configuration = configuration_data()
doc_configuration.set('VERSION', full_version)

configure_file(input : 'version.xml.in',
    output : 'version.xml',
    configuration : doc_configuration
)

private_headers = [
    'ag-debug.h',
    'ag-internals.h',
    'ag-util.h'
]

gnome.gtkdoc('libaccounts-glib',
    main_xml: 'libaccounts-glib-docs.xml',
    src_dir: [join_paths(meson.source_root (), 'libaccounts-glib')],
    dependencies : [glib_dep, gobject_dep, accounts_glib_dep],
    content_files: [
        'ag-backup.xml',
        'ag-tool.xml',
        'application-file-format.xml',
        'gettext-xml-files.xml',
        'libaccounts-compiling.xml',
        'libaccounts-glossary.xml',
        'libaccounts-overview.xml',
        'libaccounts-running.xml',
        'provider-file-format.xml',
        'service-file-format.xml',
        'service-type-file-format.xml',
        'validating-xml-files.xml'
    ],
    fixxref_args: [
        '--html-dir=' + docpath,
        '--extra-dir=' + join_paths(glib_docpath, 'glib'),
        '--extra-dir=' + join_paths(glib_docpath, 'gobject'),
        '--extra-dir=' + join_paths(glib_docpath, 'gio')
    ],
    scan_args: ['--rebuild-types','--ignore-headers=' + ' '.join(private_headers)],
    install: true
)
0707010000001F000081A4000003E800000064000000015BDC2B8B00000B8A000000000000000000000000000000000000003E00000000libaccounts-glib-1.24/docs/reference/provider-file-format.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/doxbook/xml/4.3/docbookx.dtd">

<section id="provider-file-format" xreflabel="provider description files">

<title>Provider description file format</title>

<para>
Providers for libaccounts-glib are described with a simple XML file. An example
for a hypothetical <literal>CoolProvider</literal> is shown below:
</para>

<example>
  <title>Provider description for CoolProvider</title>
  <programlisting>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;provider id="coolprovider"&gt;
  &lt;name&gt;CoolProvider&lt;/name&gt;
  &lt;description&gt;CoolProvider brings frost to your desktop&lt;/description&gt;
  &lt;translations&gt;coolprovider&lt;/translations&gt;
  &lt;icon&gt;coolprovider&lt;/icon&gt;
  &lt;domains&gt;.*coolprovider\.com&lt;/domains&gt;

&lt;/provider&gt;</programlisting>
</example>

<para>
The example provider description describes a provider called
<literal>coolprovider</literal>, indicated by the
<sgmltag class="attribute" role="xml">id</sgmltag> attribute on the
<sgmltag class="element" role="xml">provider</sgmltag> element. The
<sgmltag class="element" role="xml">name</sgmltag> element contains a
human-readable version of the provider name. The
<sgmltag class="element" role="xml">description</sgmltag> is a string that
describes the provider in general, and is especially useful if the provider
supports a wide range of services. The
<sgmltag class="element" role="xml">translations</sgmltag> element is used to
indicate the gettext translation domain for the
<sgmltag class="element" role="xml">name</sgmltag> and
<sgmltag class="element" role="xml">description</sgmltag> elements, to be used
by applications when showing those elements in a UI. The
<sgmltag class="element" role="xml">icon</sgmltag> element specifies a themed
icon to represent the provider. Finally, a
<sgmltag class="element" role="xml">domains</sgmltag> element is a regular
expression which should match all the web domains where the account with
<literal>coolprovider</literal> can be used. This can be used with an Ubuntu
Webapp to automatically create an account when the user logs in to an accepted
domain.
</para>

  <section>

  <title>Installation</title>

  <para>
  Provider description filenames should end in
  <filename class="extension">.provider</filename> and be installed to
  <filename class="directory">${prefix}/share/accounts/providers</filename>,
  which normally expands to
  <filename class="directory">/usr/share/accounts/providers</filename>. The
  path can be queried with <command>pkg-config</command> by checking the
  <varname>providerfilesdir</varname> variable of the libaccounts-glib
  pkg-config file, for example:
  </para>

  <informalexample>
  <programlisting>pkg-config --variable=providerfilesdir libaccounts-glib</programlisting>
  </informalexample>

  </section>

</section>
07070100000020000081A4000003E800000064000000015BDC2B8B00000FCC000000000000000000000000000000000000003D00000000libaccounts-glib-1.24/docs/reference/service-file-format.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/doxbook/xml/4.3/docbookx.dtd">

<section id="service-file-format" xreflabel="service descriptions files">

<title>Service description file format</title>

<para>
Services for libaccounts-glib providers are described with a simple XML file.
An example for a hypothetical <literal>CoolProvider</literal> chat service is
shown below:
</para>

<example>
  <title>Service description for CoolProvider chat service</title>
  <programlisting>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;service id="coolprovider-chat"&gt;
  &lt;type&gt;IM&lt;/type&gt;
  &lt;name&gt;CoolProvider Chat&lt;/name&gt;
  &lt;description&gt;Chat with your cool friends&lt;/description&gt;
  &lt;icon&gt;coolprovider&lt;/icon&gt;
  &lt;provider&gt;coolprovider&lt;/provider&gt;
  &lt;translations&gt;coolprovider&lt;/translations&gt;
  &lt;tags&gt;
    &lt;tag&gt;chat&lt;/tag&gt;
  &lt;/tags&gt;

  &lt;template&gt;
    &lt;group name="telepathy"&gt;
      &lt;setting name="manager"&gt;gabble&lt;/setting&gt;
      &lt;setting name="protocol"&gt;jabber&lt;/setting&gt;
    &lt;/group&gt;

    &lt;group name="auth"&gt;
      &lt;setting name="method"&gt;oauth2&lt;/setting&gt;
      &lt;setting name="mechanism"&gt;user_agent&lt;/setting&gt;
      &lt;group name="oauth2/user_agent"&gt;
        &lt;setting name="ClientId"&gt;ABCDEclient.ID&lt;/setting&gt;
      &lt;/group&gt;
    &lt;/group&gt;
  &lt;/template&gt;

&lt;/service&gt;</programlisting>
</example>

<para>
The example service description describes a service called
<literal>coolprovider-chat</literal>, indicated by the
<sgmltag class="attribute" role="xml">id</sgmltag> attribute on the
<sgmltag class="element" role="xml">service</sgmltag> element. The
<sgmltag class="element" role="xml">type</sgmltag> element corresponds to the
service type. The <sgmltag class="element" role="xml">name</sgmltag> element
contains a human-readable version of the service name. The
<sgmltag class="element" role="xml">description</sgmltag> is a string that
describes the service in general. The
<sgmltag class="element" role="xml">icon</sgmltag> element specifies a themed
icon to represent the service. The
<sgmltag class="element" role="provider">provider</sgmltag> element must point
to the identifier of the provider which supports this service. The
<sgmltag class="element" role="xml">translations</sgmltag> element is used to
indicate the gettext translation domain for the name and description elements,
to be used by applications when showing those elements in a UI. The
<sgmltag class="element" role="xml">tags</sgmltag> element is a container of
<sgmltag class="element" role="xml">tag</sgmltag> elements, which are used to
describe the service in abstract terms. Finally, a
<sgmltag class="element" role="xml">template</sgmltag> element is a container
for <sgmltag class="element" role="xml">group</sgmltag> elements, which
themselves are containers of
<sgmltag class="element" role="xml">setting</sgmltag> elements. Settings stored
within the template are default settings for the service, which the
applications using the account may need in order to function correctly. The
default settings can be overriden, typically at run time during the account
configuration phase.
</para>

  <section>

  <title>Installation</title>

  <para>
  Service description filenames should end in
  <filename class="extension">.service</filename> and be installed to
  <filename class="directory">${prefix}/share/accounts/services</filename>,
  which normally expands to
  <filename class="directory">/usr/share/accounts/services</filename>. The path
  can be queried with <command>pkg-config</command> by checking the
  <varname>servicefilesdir</varname> variable of the libaccounts-glib
  pkg-config file, for example:
  </para>

  <informalexample>
  <programlisting>pkg-config --variable=servicefilesdir libaccounts-glib</programlisting>
  </informalexample>

  </section>

</section>
07070100000021000081A4000003E800000064000000015BDC2B8B00000ADD000000000000000000000000000000000000004200000000libaccounts-glib-1.24/docs/reference/service-type-file-format.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/doxbook/xml/4.3/docbookx.dtd">

<section id="service-type-file-format" xreflabel="service type description files">

<title>Service type description file format</title>

<para>
Services types for libaccounts-glib providers are described with a simple XML
file. An example for a hypothetical <literal>CoolProvider</literal> chat
service is shown below:
</para>

<example>
  <title>Service type description for e-mail</title>
  <programlisting>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;service-type id="e-mail"&gt;
  &lt;name&gt;Electronic mail&lt;/name&gt;
  &lt;description&gt;Send mail without harming snails&lt;/description&gt;
  &lt;icon&gt;email&lt;/icon&gt;
  &lt;translations&gt;email&lt;/translations&gt;
  &lt;tags&gt;
    &lt;tag&gt;e-mail&lt;/tag&gt;
    &lt;tag&gt;messaging&lt;/tag&gt;
  &lt;/tags&gt;

&lt;/service-type&gt;</programlisting>
</example>

<para>
The example service type description describes a service type called
<literal>e-mail</literal>, indicated by the
<sgmltag class="attribute" role="xml">id</sgmltag> attribute on the
<sgmltag class="element" role="xml">service-type</sgmltag> root element. The
<sgmltag class="element" role="xml">name</sgmltag> element contains a
human-readable version of the service type name. The
<sgmltag class="element" role="xml">description</sgmltag> is a string that
describes the service type in general. The
<sgmltag class="element" role="xml">icon</sgmltag> element specifies a themed
icon to represent the service type. The
<sgmltag class="element" role="xml">translations</sgmltag> element is used to
indicate the gettext translation domain for the name and description elements,
to be used by applications when showing those elements in a UI. The
<sgmltag class="element" role="xml">tags</sgmltag> element is a container of
<sgmltag class="element" role="xml">tag</sgmltag> elements, which are used to
describe the service type in abstract terms.
</para>

  <section>

  <title>Installation</title>

  <para>
  Service type description filenames should end in
  <filename class="extension">.service-type</filename> and be installed to
  <filename class="directory">${prefix}/share/accounts/service_types</filename>,
  which normally expands to
  <filename class="directory">/usr/share/accounts/service_types</filename>. The
  path can be queried with <command>pkg-config</command> by checking the
  <varname>servicetypefilesdir</varname> variable of the libaccounts-glib
  pkg-config file, for example:
  </para>

  <informalexample>
  <programlisting>pkg-config --variable=servicetypefilesdir libaccounts-glib</programlisting>
  </informalexample>

  </section>

</section>
07070100000022000081A4000003E800000064000000015BDC2B8B00000676000000000000000000000000000000000000003E00000000libaccounts-glib-1.24/docs/reference/validating-xml-files.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
    "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">

<section id="validating-xml-files">

<title>Validating XML files against the DTDs</title>

<para>
<acronym>DTD</acronym>s exist for the <acronym>XML</acronym> file formats
supported by libaccounts-glib. Using these DTDs, it is possible to validate the
XML data files for errors. This is especially useful during the build or
testing process of applications which use libaccounts-glib, so that errors in
data files can be caught early.
</para>

  <section id="validating-with-xmllint">

  <title>Using <application>xmllint</application></title>

  <para>
  <command>xmllint</command> is part of <application>libxml2</application> and
  can be used to validate an XML document against a DTD. A sample command to
  validate the hypothetical XML file <filename>coolprovider.xml</filename> is:
  </para>

  <informalexample>
  <programlisting>xmllint --noout --dtdvalid /usr/share/xml/accounts/schema/dtd/accounts-provider.dtd coolprovider.xml</programlisting>
  </informalexample>

  <para>
  The <parameter class="option">--noout</parameter> argument suppresses
  printing of the XML document, and
  <parameter class="option">--dtdvalid</parameter> requests validation against
  a DTD, given by the following argument. By default, the libaccounts-glib DTDs
  are installed to
  <filename class="directory">$(datadir)/xml/accounts/schema/dtd</filename>,
  which usually expands to
  <filename class="directory">/usr/share/xml/accounts/schema/dtd</filename>.
  </para>

  </section>

</section>
07070100000023000081A4000003E800000064000000015BDC2B8B0000000A000000000000000000000000000000000000003400000000libaccounts-glib-1.24/docs/reference/version.xml.in@VERSION@
07070100000024000041ED000003E800000064000000035BDC2B8B00000000000000000000000000000000000000000000002700000000libaccounts-glib-1.24/libaccounts-glib07070100000025000081A4000003E800000064000000015BDC2B8B0000060A000000000000000000000000000000000000003700000000libaccounts-glib-1.24/libaccounts-glib/accounts-glib.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2011-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _ACCOUNTS_GLIB_H_
#define _ACCOUNTS_GLIB_H_

#warning "<accounts-glib.h> is deprecated, only <libaccounts-glib.h> should be included directly."

#define __ACCOUNTS_GLIB_H_INSIDE__

#include <libaccounts-glib/ag-account.h>
#include <libaccounts-glib/ag-account-service.h>
#include <libaccounts-glib/ag-application.h>
#include <libaccounts-glib/ag-auth-data.h>
#include <libaccounts-glib/ag-errors.h>
#include <libaccounts-glib/ag-manager.h>
#include <libaccounts-glib/ag-provider.h>
#include <libaccounts-glib/ag-service.h>
#include <libaccounts-glib/ag-service-type.h>

#endif /* _ACCOUNTS_GLIB_H_ */
07070100000026000081A4000003E800000064000000015BDC2B8B000058E1000000000000000000000000000000000000003C00000000libaccounts-glib-1.24/libaccounts-glib/ag-account-service.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2011 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-account-service
 * @short_description: Account settings for a specific service
 *
 * The #AgAccountService object provides access to the account settings for a
 * specific service type. It is meant to be easier to use than the #AgAccount
 * class because it hides the complexity of the account structure and gives
 * access to only the limited subset of account settings which are relevant to
 * a service.
 *
 * To get an #AgAccountService one can use the #AgManager methods
 * ag_manager_get_account_services() or
 * ag_manager_get_enabled_account_services(), which both return a #GList of
 * account services. Note that if the #AgManager was instantiated for a
 * specific service type, these lists will contain only those account services
 * matching that service type.
 * Another way to get an #AgAccountService is to instantiate one using
 * ag_account_service_new(): this is useful if one already has an #AgAccount
 * instance.
 *
 * This is intended to be a convenient wrapper over the accounts settings
 * specific for a service; as such, it doesn't offer all the editing
 * possibilities offered by the #AgAccount class, such as enabling the service
 * itself: these operations should ideally not be performed by consumer
 * applications, but by the account editing UI only.
 *
 * <example>
 * <title>Querying available e-mail services</title>
 *   <programlisting>
 * AgManager *manager;
 * GList *services, *list;
 *
 * // Instantiate an account manager interested in e-mail services only.
 * manager = ag_manager_new_for_service_type ("e-mail");
 *
 * // Get the list of enabled AgAccountService objects of type e-mail.
 * services = ag_manager_get_enabled_account_services (manager);
 *
 * // Loop through the account services and do something useful with them.
 * for (list = services; list != NULL; list = list->next)
 * {
 *     AgAccountService *service = AG_ACCOUNT_SERVICE (list->data);
 *     GVariant *v_server, *v_port, *v_username;
 *     gchar *server = NULL, *username = NULL;
 *     gint port;
 *     AgAccount *account;
 *
 *     v_server = ag_account_service_get_variant (service, "pop3/hostname", NULL);
 *     if (v_server != NULL)
 *         server = g_variant_dup_string (v_server, NULL);
 *
 *     v_port = ag_account_service_get_variant (service, "pop3/port", NULL);
 *     if (v_port != NULL)
 *         port = g_variant_get_int16 (&v_port);
 *
 *     // Suppose that the e-mail address is stored in the global account
 *     // settings; let's get it from there:
 *     account = ag_account_service_get_account (service);
 *     ag_account_select_service (NULL);
 *     v_username = ag_account_get_variant (account, "username", NULL);
 *     if (v_username != NULL)
 *         username = g_variant_dup_string (&v_username);
 *
 *     ...
 *
 *     g_free (username);
 *     g_free (server);
 * }
 *   </programlisting>
 * </example>
 *
 * <note>
 *   <para>
 * User applications (with the notable exception of the accounts editing
 * application) should never use account services which are not enabled, and
 * should stop using an account when the account service becomes disabled. The
 * latter can be done by connecting to the #AgAccountService::changed signal
 * and checking if ag_account_service_get_enabled() still returns %TRUE.
 * Note that if the account gets deleted, it will always get disabled first;
 * so, there is no need to connect to the #AgAccount::deleted signal; one can
 * just monitor the #AgAccountService::changed signal.
 *   </para>
 * </note>
 */

#define AG_DISABLE_DEPRECATION_WARNINGS

#include "ag-account-service.h"
#include "ag-errors.h"
#include "ag-internals.h"
#include "ag-manager.h"
#include "ag-service.h"
#include "ag-service-type.h"
#include "ag-util.h"

enum {
    PROP_0,

    PROP_ACCOUNT,
    PROP_SERVICE,
    PROP_ENABLED,
    N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES];

struct _AgAccountServicePrivate {
    AgAccount *account;
    AgService *service;
    gboolean enabled;
    AgAccountWatch watch;
    guint account_enabled_id;
};

enum
{
    CHANGED,
    ENABLED,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE (AgAccountService, ag_account_service, G_TYPE_OBJECT);

#define AG_ACCOUNT_SERVICE_PRIV(obj) (AG_ACCOUNT_SERVICE(obj)->priv)

static gboolean
check_enabled (AgAccountServicePrivate *priv)
{
    gboolean account_enabled;
    gboolean service_enabled;

    ag_account_select_service (priv->account, NULL);
    account_enabled = ag_account_get_enabled (priv->account);

    if (priv->service)
    {
        ag_account_select_service (priv->account, priv->service);
        service_enabled = ag_account_get_enabled (priv->account);
    }
    else
        service_enabled = TRUE;

    return service_enabled && account_enabled;
}

static void
account_watch_cb (G_GNUC_UNUSED AgAccount *account,
                  G_GNUC_UNUSED const gchar *key,
                  gpointer user_data)
{
    AgAccountService *self = (AgAccountService *)user_data;

    g_signal_emit (self, signals[CHANGED], 0);
}

static void
on_account_enabled (G_GNUC_UNUSED AgAccount *account,
                    const gchar *service_name,
                    gboolean service_enabled,
                    AgAccountService *self)
{
    AgAccountServicePrivate *priv = self->priv;
    gboolean enabled;

    DEBUG_INFO ("service: %s, enabled: %d", service_name, service_enabled);

    enabled = check_enabled (priv);
    if (enabled != priv->enabled)
    {
        priv->enabled = enabled;
        g_signal_emit (self, signals[ENABLED], 0, enabled);
        g_object_notify_by_pspec ((GObject *)self, properties[PROP_ENABLED]);
    }
}

static void
ag_account_service_get_property (GObject *object, guint property_id,
                                 GValue *value, GParamSpec *pspec)
{
    AgAccountService *self = AG_ACCOUNT_SERVICE (object);

    switch (property_id)
    {
    case PROP_ACCOUNT:
        g_value_set_object (value, self->priv->account);
        break;
    case PROP_SERVICE:
        g_value_set_boxed (value, self->priv->service);
        break;
    case PROP_ENABLED:
        g_value_set_boolean (value, self->priv->enabled);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
ag_account_service_set_property (GObject *object, guint property_id,
                                 const GValue *value, GParamSpec *pspec)
{
    AgAccountServicePrivate *priv = AG_ACCOUNT_SERVICE_PRIV (object);

    switch (property_id)
    {
    case PROP_ACCOUNT:
        g_assert (priv->account == NULL);
        priv->account = g_value_dup_object (value);
        break;
    case PROP_SERVICE:
        g_assert (priv->service == NULL);
        priv->service = g_value_dup_boxed (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }

}

static void
ag_account_service_constructed (GObject *object)
{
    AgAccountServicePrivate *priv = AG_ACCOUNT_SERVICE_PRIV (object);

    if (G_UNLIKELY (!priv->account))
    {
        g_warning ("AgAccountService constructed with no account!");
        return;
    }

    priv->account_enabled_id =
        g_signal_connect (priv->account, "enabled",
                          G_CALLBACK (on_account_enabled), object);

    ag_account_select_service (priv->account, priv->service);
    priv->watch = ag_account_watch_dir (priv->account, "",
                                        account_watch_cb, object);

    priv->enabled = check_enabled (priv);
}

static void
ag_account_service_dispose (GObject *object)
{
    AgAccountServicePrivate *priv = AG_ACCOUNT_SERVICE_PRIV (object);

    DEBUG_REFS ("Disposing account-service %p", object);

    if (priv->account)
    {
        ag_account_remove_watch (priv->account, priv->watch);
        g_signal_handler_disconnect (priv->account, priv->account_enabled_id);
        g_object_unref (priv->account);
        priv->account = NULL;
    }

    if (priv->service)
    {
        ag_service_unref (priv->service);
        priv->service = NULL;
    }

    G_OBJECT_CLASS (ag_account_service_parent_class)->dispose (object);
}

static void
ag_account_service_class_init(AgAccountServiceClass *klass)
{
    GObjectClass* object_class = G_OBJECT_CLASS (klass);

    g_type_class_add_private (object_class, sizeof (AgAccountServicePrivate));

    object_class->constructed = ag_account_service_constructed;
    object_class->dispose = ag_account_service_dispose;
    object_class->get_property = ag_account_service_get_property;
    object_class->set_property = ag_account_service_set_property;

    /**
     * AgAccountService:account:
     *
     * The #AgAccount used by the account service.
     *
     * Since: 1.4
     */
    properties[PROP_ACCOUNT] =
        g_param_spec_object ("account", "account", "account",
                             AG_TYPE_ACCOUNT,
                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

    /**
     * AgAccountService:service:
     *
     * The #AgService used by the account service.
     *
     * Since: 1.4
     */
    properties[PROP_SERVICE] =
        g_param_spec_boxed ("service", "service", "service",
                            ag_service_get_type(),
                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

    /**
     * AgAccountService:enabled:
     *
     * Whether the account service is currently enabled. The value of
     * this property is %TRUE if and only if the underlying #AgAccount
     * is enabled and the selected #AgService is enabled on it. If this
     * property is %FALSE, applications should not try to use this
     * object.
     *
     * Since: 1.4
     */
    properties[PROP_ENABLED] =
        g_param_spec_boolean ("enabled", "Enabled",
                              "Whether the account service is enabled",
                              FALSE,
                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    g_object_class_install_properties (object_class,
                                       N_PROPERTIES,
                                       properties);

    /**
     * AgAccountService::changed:
     * @self: the #AgAccountService.
     *
     * Emitted when some setting has changed on the account service. You can
     * use the ag_account_service_get_changed_fields() method to retrieve the
     * list of the settings which have changed.
     */
    signals[CHANGED] = g_signal_new ("changed",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);


    /**
     * AgAccountService::enabled:
     * @self: the #AgAccountService.
     * @enabled: whether the service is enabled.
     *
     * Emitted when the service enabled state changes.
     */
    signals[ENABLED] = g_signal_new ("enabled",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        g_cclosure_marshal_VOID__BOOLEAN,
        G_TYPE_NONE,
        1, G_TYPE_BOOLEAN);
}

static void
ag_account_service_init(AgAccountService *self)
{
    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, AG_TYPE_ACCOUNT_SERVICE,
                                              AgAccountServicePrivate);
}

/**
 * ag_account_service_new:
 * @account: (transfer full): an #AgAccount.
 * @service: (transfer full) (allow-none): an #AgService supported by @account.
 *
 * Constructor. If @service is %NULL, the returned object will operate on the
 * global account settings.
 *
 * Returns: a new #AgAccountService; call g_object_unref() when you don't need
 * this object anymore.
 */
AgAccountService *
ag_account_service_new(AgAccount *account, AgService *service)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);

    return g_object_new (AG_TYPE_ACCOUNT_SERVICE,
                         "account", account,
                         "service", service,
                         NULL);
}

/**
 * ag_account_service_get_account:
 * @self: the #AgAccountService.
 *
 * Get the #AgAccount associated with @self.
 *
 * Returns: (transfer none): the underlying #AgAccount. The reference count on
 * it is not incremented, so if you need to use it beyond the lifetime of
 * @self, you need to call g_object_ref() on it yourself.
 */
AgAccount *
ag_account_service_get_account (AgAccountService *self)
{
    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), NULL);

    return self->priv->account;
}

/**
 * ag_account_service_get_service:
 * @self: the #AgAccountService.
 *
 * Get the #AgService associated with @self.
 *
 * Returns: (transfer none): the underlying #AgService. The reference count on
 * it is not incremented, so if you need to use it beyond the lifetime of
 * @self, you need to call ag_service_ref() on it yourself.
 */
AgService *
ag_account_service_get_service (AgAccountService *self)
{
    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), NULL);

    return self->priv->service;
}

/**
 * ag_account_service_get_enabled:
 * @self: the #AgAccountService.
 *
 * Checks whether the underlying #AgAccount is enabled and the selected
 * #AgService is enabled on it. If this method returns %FALSE, applications
 * should not try to use this object.
 *
 * Returns: %TRUE if the service is enabled, %FALSE otherwise.
 */
gboolean
ag_account_service_get_enabled (AgAccountService *self)
{
    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), FALSE);

    return self->priv->enabled;
}

/**
 * ag_account_service_get_value:
 * @self: the #AgAccountService.
 * @key: the name of the setting to retrieve.
 * @value: (inout): an initialized #GValue to receive the setting's value.
 *
 * Gets the value of the configuration setting @key: @value must be a
 * #GValue initialized to the type of the setting.
 *
 * Returns: one of <type>#AgSettingSource</type>: %AG_SETTING_SOURCE_NONE if
 * the setting is not present, %AG_SETTING_SOURCE_ACCOUNT if the setting comes
 * from the account configuration, or %AG_SETTING_SOURCE_PROFILE if the value
 * comes as predefined in the profile.
 *
 * Deprecated: 1.4: Use ag_account_service_get_variant() instead.
 */
AgSettingSource
ag_account_service_get_value (AgAccountService *self, const gchar *key,
                              GValue *value)
{
    AgAccountServicePrivate *priv;

    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), AG_SETTING_SOURCE_NONE);
    priv = self->priv;

    ag_account_select_service (priv->account, priv->service);
    return ag_account_get_value (priv->account, key, value);
}

/**
 * ag_account_service_set_value:
 * @self: the #AgAccountService.
 * @key: the name of the setting to change.
 * @value: (allow-none): a #GValue holding the new setting's value.
 *
 * Sets the value of the configuration setting @key to the value @value.
 * If @value is %NULL, then the setting is unset.
 *
 * Deprecated: 1.4: Use ag_account_service_set_variant() instead.
 */
void
ag_account_service_set_value (AgAccountService *self, const gchar *key,
                              const GValue *value)
{
    AgAccountServicePrivate *priv;

    g_return_if_fail (AG_IS_ACCOUNT_SERVICE (self));
    priv = self->priv;

    ag_account_select_service (priv->account, priv->service);
    ag_account_set_value (priv->account, key, value);
}

/**
 * ag_account_service_get_variant:
 * @self: the #AgAccountService.
 * @key: the name of the setting to retrieve.
 * @source: (allow-none) (out): a pointer to an
 * #AgSettingSource variable which will tell whether the setting was
 * retrieved from the accounts DB or from a service template.
 *
 * Gets the value of the configuration setting @key.
 *
 * Returns: (transfer none): a #GVariant holding the setting value, or
 * %NULL. The returned #GVariant is owned by the account, and no guarantees
 * are made about its lifetime. If the client wishes to keep it, it should
 * call g_variant_ref() on it.
 *
 * Since: 1.4
 */
GVariant *
ag_account_service_get_variant (AgAccountService *self, const gchar *key,
                                AgSettingSource *source)
{
    AgAccountServicePrivate *priv;

    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), NULL);
    priv = self->priv;

    ag_account_select_service (priv->account, priv->service);
    return ag_account_get_variant (priv->account, key, source);
}

/**
 * ag_account_service_set_variant:
 * @self: the #AgAccountService.
 * @key: the name of the setting to change.
 * @value: (allow-none): a #GVariant holding the new setting's value.
 *
 * Sets the value of the configuration setting @key to the value @value.
 * If @value has a floating reference, the @account will take ownership
 * of it.
 * If @value is %NULL, then the setting is unset.
 *
 * Since: 1.4
 */
void
ag_account_service_set_variant (AgAccountService *self, const gchar *key,
                                GVariant *value)
{
    AgAccountServicePrivate *priv;

    g_return_if_fail (AG_IS_ACCOUNT_SERVICE (self));
    priv = self->priv;

    ag_account_select_service (priv->account, priv->service);
    ag_account_set_variant (priv->account, key, value);
}

/**
 * ag_account_service_settings_iter_init:
 * @self: the #AgAccountService.
 * @iter: an uninitialized #AgAccountSettingIter structure.
 * @key_prefix: (allow-none): enumerate only the settings whose key starts with
 * @key_prefix.
 *
 * Initializes @iter to iterate over the account settings. If @key_prefix is
 * not %NULL, only keys whose names start with @key_prefix will be iterated
 * over.
 * After calling this method, one would typically call
 * ag_account_settings_iter_get_next() to read the settings one by one.
 */
void
ag_account_service_settings_iter_init (AgAccountService *self,
                                       AgAccountSettingIter *iter,
                                       const gchar *key_prefix)
{
    AgAccountServicePrivate *priv;

    g_return_if_fail (AG_IS_ACCOUNT_SERVICE (self));
    priv = self->priv;

    ag_account_select_service (priv->account, priv->service);
    ag_account_settings_iter_init (priv->account, iter, key_prefix);
}

/**
 * ag_account_service_get_settings_iter:
 * @self: the #AgAccountService.
 * @key_prefix: (allow-none): enumerate only the settings whose key starts with
 * @key_prefix.
 *
 * Creates a new iterator. This method is useful for language bindings only.
 *
 * Returns: (transfer full): an #AgAccountSettingIter.
 */
AgAccountSettingIter *
ag_account_service_get_settings_iter (AgAccountService *self,
                                      const gchar *key_prefix)
{
    AgAccountSettingIter *iter;
    AgAccountServicePrivate *priv;

    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), NULL);
    priv = self->priv;

    ag_account_select_service (priv->account, priv->service);
    iter = g_slice_new (AgAccountSettingIter);
    _ag_account_settings_iter_init (priv->account, iter, key_prefix, TRUE);
    return iter;
}

/**
 * ag_account_service_settings_iter_next:
 * @iter: an initialized #AgAccountSettingIter structure.
 * @key: (out callee-allocates) (transfer none): a pointer to a string
 * receiving the key name.
 * @value: (out callee-allocates) (transfer none): a pointer to a pointer to a
 * #GValue, to receive the key value.
 *
 * Iterates over the account keys. @iter must be an iterator previously
 * initialized with ag_account_service_settings_iter_init().
 *
 * Returns: %TRUE if @key and @value have been set, %FALSE if we there are no
 * more account settings to iterate over.
 *
 * Deprecated: 1.4: Use ag_account_settings_iter_get_next() instead.
 */
gboolean
ag_account_service_settings_iter_next (AgAccountSettingIter *iter,
                                       const gchar **key,
                                       const GValue **value)
{
    return ag_account_settings_iter_next (iter, key, value);
}

/**
 * ag_account_service_get_auth_data:
 * @self: the #AgAccountService.
 *
 * Reads the authentication data stored in the account (merging the
 * service-specific settings with the global account settings) and returns an
 * #AgAuthData structure.
 * The method and mechanism are read from the "auth/method" and
 * "auth/mechanism" keys, respectively. The authentication parameters are
 * found under the "auth/&lt;method&gt;/&lt;mechanism&gt;/" group.
 *
 * Returns: (transfer full): a newly allocated #AgAuthData structure.
 */
AgAuthData *
ag_account_service_get_auth_data (AgAccountService *self)
{
    AgAccountServicePrivate *priv;

    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), NULL);
    priv = self->priv;

    return _ag_auth_data_new (priv->account, priv->service);
}

/**
 * ag_account_service_get_changed_fields:
 * @self: the #AgAccountService.
 *
 * This method should be called only in the context of a handler of the
 * #AgAccountService::changed signal, and can be used to retrieve the set of
 * changes.
 *
 * Returns: (transfer full): a newly allocated array of strings describing the
 * keys of the fields which have been altered. It must be free'd with
 * g_strfreev().
 */
gchar **
ag_account_service_get_changed_fields (AgAccountService *self)
{
    AgAccountServicePrivate *priv;
    GHashTable *settings;
    GList *keys, *list;
    gchar **fields;
    gint i;

    g_return_val_if_fail (AG_IS_ACCOUNT_SERVICE (self), NULL);
    priv = self->priv;

    settings = _ag_account_get_service_changes (priv->account, priv->service);
    keys = g_hash_table_get_keys (settings);
    fields = g_malloc ((g_hash_table_size (settings) + 1) *
                       sizeof(gchar *));

    i = 0;
    for (list = keys; list != NULL; list = list->next)
    {
        fields[i++] = g_strdup ((gchar *)(list->data));
    }
    fields[i] = NULL;
    g_list_free (keys);

    return fields;
}

07070100000027000081A4000003E800000064000000015BDC2B8B00001341000000000000000000000000000000000000003C00000000libaccounts-glib-1.24/libaccounts-glib/ag-account-service.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2011 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_ACCOUNT_SERVICE_H_
#define _AG_ACCOUNT_SERVICE_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib-object.h>
#include <libaccounts-glib/ag-account.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS

#define AG_TYPE_ACCOUNT_SERVICE (ag_account_service_get_type ())
#define AG_ACCOUNT_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
                                 AG_TYPE_ACCOUNT_SERVICE, AgAccountService))
#define AG_ACCOUNT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
                                         AG_TYPE_ACCOUNT_SERVICE, \
                                         AgAccountServiceClass))
#define AG_IS_ACCOUNT_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
                                    AG_TYPE_ACCOUNT_SERVICE))
#define AG_IS_ACCOUNT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
                                            AG_TYPE_ACCOUNT_SERVICE))
#define AG_ACCOUNT_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
                                           AG_TYPE_ACCOUNT_SERVICE, \
                                           AgAccountServiceClass))

typedef struct _AgAccountServiceClass AgAccountServiceClass;
typedef struct _AgAccountServicePrivate AgAccountServicePrivate;

/**
 * AgAccountServiceClass:
 *
 * Use the accessor functions below.
 */
struct _AgAccountServiceClass
{
    GObjectClass parent_class;
    void (*_ag_reserved1) (void);
    void (*_ag_reserved2) (void);
    void (*_ag_reserved3) (void);
    void (*_ag_reserved4) (void);
    void (*_ag_reserved5) (void);
    void (*_ag_reserved6) (void);
    void (*_ag_reserved7) (void);
};

struct _AgAccountService
{
    GObject parent_instance;
    AgAccountServicePrivate *priv;
};

GType ag_account_service_get_type (void) G_GNUC_CONST;

AgAccountService *ag_account_service_new (AgAccount *account,
                                          AgService *service);

AgAccount *ag_account_service_get_account (AgAccountService *self);

AgService *ag_account_service_get_service (AgAccountService *self);

gboolean ag_account_service_get_enabled (AgAccountService *self);

#ifndef AG_DISABLE_DEPRECATED
AG_DEPRECATED_FOR(ag_account_service_get_variant)
AgSettingSource ag_account_service_get_value (AgAccountService *self,
                                              const gchar *key,
                                              GValue *value);

AG_DEPRECATED_FOR(ag_account_service_set_variant)
void ag_account_service_set_value (AgAccountService *self, const gchar *key,
                                   const GValue *value);
#endif
GVariant *ag_account_service_get_variant (AgAccountService *self,
                                          const gchar *key,
                                          AgSettingSource *source);
void ag_account_service_set_variant (AgAccountService *self,
                                     const gchar *key,
                                     GVariant *value);

AgAccountSettingIter *
ag_account_service_get_settings_iter (AgAccountService *self,
                                      const gchar *key_prefix);

void ag_account_service_settings_iter_init (AgAccountService *self,
                                            AgAccountSettingIter *iter,
                                            const gchar *key_prefix);

#ifndef AG_DISABLE_DEPRECATED
AG_DEPRECATED_FOR(ag_account_settings_iter_get_next)
gboolean ag_account_service_settings_iter_next (AgAccountSettingIter *iter,
                                                const gchar **key,
                                                const GValue **value);
#endif

AgAuthData *ag_account_service_get_auth_data (AgAccountService *self);

gchar **ag_account_service_get_changed_fields (AgAccountService *self);

G_END_DECLS

#endif /* _AG_ACCOUNT_SERVICE_H_ */
07070100000028000081A4000003E800000064000000015BDC2B8B00012F49000000000000000000000000000000000000003400000000libaccounts-glib-1.24/libaccounts-glib/ag-account.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-account
 * @short_description: A representation of an account.
 *
 * An #AgAccount is an object which represents an account. It provides a
 * method for enabling/disabling the account and methods for editing the
 * account settings.
 *
 * Accounts are created by #AgManager with ag_manager_create_account(), and
 * deleted by #AgAccount with ag_account_delete(). These operations, and any
 * other operations which modify the account settings, must be followed by
 * ag_account_store() before the changes are committed to the database.
 * <example id="example-create-new-AgAccount">
 * <title>Creating a new <structname>AgAccount</structname></title>
 * <programlisting>
 * GMainLoop *main_loop = NULL;
 *
 * gboolean account_cleanup_idle (gpointer user_data)
 * {
 *     AgManager *manager;
 *     AgAccount *account = AG_ACCOUNT (user_data);
 *     manager = ag_account_get_manager (account);
 *
 *     g_object_unref (account);
 *     g_object_unref (manager);
 *
 *     g_main_loop_quit (main_loop);
 *
 *     return FALSE;
 * }
 *
 * void account_stored_cb (AgAccount *account,
 *                         const GError *error,
 *                         gpointer user_data)
 * {
 *     AgManager *manager = AG_MANAGER (user_data);
 *
 *     if (error != NULL)
 *     {
 *         g_warning ("Account with ID '%u' failed to store, with error: %s",
 *                    account->id,
 *                    error->message);
 *     }
 *     else
 *     {
 *         g_print ("Account stored with ID: %u", account->id);
 *     }
 *
 *     /&ast; Clean up in an idle callback. &ast;/
 *     g_idle_add (account_cleanup_idle, account);
 *     g_main_loop_run (main_loop);
 * }
 *
 * void store_account (void)
 * {
 *     AgManager *manager;
 *     GList *providers;
 *     const gchar *provider_name;
 *     AgAccount *account;
 *
 *     main_loop = g_main_loop_new (NULL, FALSE);
 *     manager = ag_manager_new ();
 *     providers = ag_manager_list_providers (manager);
 *     g_assert (providers != NULL);
 *     provider_name = ag_provider_get_name ((AgProvider *) providers->data);
 *     account = ag_manager_create_account (manager, provider_name);
 *
 *     ag_provider_list_free (providers);
 *
 *     /&ast; The account is not valid until it has been stored. &ast;/
 *     ag_account_store (account, account_stored_cb, (gpointer) manager);
 * }
 * </programlisting>
 * </example>
 */

#include "ag-manager.h"
#include "ag-account.h"
#include "ag-errors.h"

#include "ag-internals.h"
#include "ag-marshal.h"
#include "ag-provider.h"
#include "ag-service.h"
#include "ag-util.h"

#include <string.h>

#define SERVICE_GLOBAL "global"

enum
{
    PROP_0,

    PROP_ID,
    PROP_MANAGER,
    PROP_PROVIDER,
    PROP_FOREIGN,
    PROP_ENABLED,
    PROP_DISPLAY_NAME,
    N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES];

enum
{
    ENABLED,
    DISPLAY_NAME_CHANGED,
    DELETED,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

typedef struct _AgServiceChanges {
    AgService *service; /* this is set only if the change came from this
                           instance */
    gchar *service_type;

    GHashTable *settings;
    GHashTable *signatures;
} AgServiceChanges;

typedef struct _AgServiceSettings {
    AgService *service;
    GHashTable *settings;
} AgServiceSettings;

struct _AgAccountPrivate {
    AgManager *manager;

    /* selected service */
    AgService *service;

    AgProvider *provider;
    gchar *provider_name;
    gchar *display_name;

    /* cached settings: keys are service names, values are AgServiceSettings
     * structures.
     * It may be that not all services are loaded in this hash table. But if a
     * service is present, then all of its settings are.
     */
    GHashTable *services;

    AgAccountChanges *changes;

    /* Watches: it's a GHashTable whose keys are pointers to AgService
     * elements, and values are GHashTables whose keys and values are
     * AgAccountWatch-es. */
    GHashTable *watches;

    /* Temporary pointer to the services table of the AgAccountChanges
     * structure, to be used while invoking the watches in case some handlers
     * want to retrieve the list of changes (AgAccountService does this).
     */
    GHashTable *changes_for_watches;

    /* GTask for the ag_account_store_async operation. */
    GTask *store_task;

    /* The "foreign" flag means that the account has been created by another
     * instance and we got informed about it from D-Bus. In this case, all the
     * informations that we get via D-Bus will be cached in the
     * AgServiceSetting structures. */
    guint foreign : 1;
    guint enabled : 1;
    guint deleted : 1;
};

struct _AgAccountWatch {
    AgService *service;
    gchar *key;
    gchar *prefix;
    AgAccountNotifyCb callback;
    gpointer user_data;
};

/* Same size and member types as AgAccountSettingIter */
typedef struct {
    AgAccount *account;
    GHashTableIter iter;
    gchar *key_prefix;
    /* The next field is used by ag_account_settings_iter_next() only */
    GValue *last_gvalue;
    gint stage;
    gint must_free_prefix;
} RealIter;

typedef struct _AgSignature {
    gchar *signature;
    gchar *token;
} AgSignature;

typedef struct {
    AgAccountStoreCb callback;
    gpointer user_data;
} AsyncReadyCbWrapperData;

#define AG_ITER_STAGE_UNSET     0
#define AG_ITER_STAGE_ACCOUNT   1
#define AG_ITER_STAGE_SERVICE   2

static void ag_account_initable_iface_init(gpointer g_iface,
                                           gpointer iface_data);

G_DEFINE_TYPE_WITH_CODE (AgAccount, ag_account, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                            ag_account_initable_iface_init));

#define AG_ACCOUNT_PRIV(obj) (AG_ACCOUNT(obj)->priv)

static inline gboolean
ensure_has_provider (AgAccountPrivate *priv)
{
    if (priv->provider == NULL &&
        priv->provider_name != NULL)
    {
        priv->provider = ag_manager_get_provider (priv->manager,
                                                  priv->provider_name);
    }

    return priv->provider != NULL;
}

static void
async_ready_cb_wrapper (GObject *object, GAsyncResult *res,
                        gpointer user_data)
{
    AsyncReadyCbWrapperData *cb_data = user_data;
    AgAccount *account = AG_ACCOUNT (object);
    GError *error = NULL;

    ag_account_store_finish (account, res, &error);
    if (cb_data->callback != NULL)
    {
        cb_data->callback (account, error, cb_data->user_data);
    }

    g_clear_error (&error);
    g_slice_free (AsyncReadyCbWrapperData, cb_data);
}

static void
ag_variant_safe_unref (gpointer variant)
{
    if (variant != NULL)
        g_variant_unref ((GVariant *)variant);
}

GVariant *
_ag_account_build_dbus_changes (AgAccount *account, AgAccountChanges *changes,
                                const struct timespec *ts)
{
    GVariantBuilder builder;
    const gchar *provider_name;

    g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);

    provider_name = account->priv->provider_name;
    if (!provider_name) provider_name = "";

    if (ts)
    {
        guint32 sec = ts->tv_sec;
        guint32 nsec = ts->tv_nsec;
        g_variant_builder_add (&builder, "u", sec);
        g_variant_builder_add (&builder, "u", nsec);
    }
    g_variant_builder_add (&builder, "u", account->id);
    g_variant_builder_add (&builder, "b", changes->created);
    g_variant_builder_add (&builder, "b", changes->deleted);
    g_variant_builder_add (&builder, "s", provider_name);

    /* Append the settings */
    g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(ssua{sv}as)"));
    if (changes->services)
    {
        GHashTableIter iter;
        AgServiceChanges *sc;
        gchar *service_name;

        g_hash_table_iter_init (&iter, changes->services);
        while (g_hash_table_iter_next (&iter,
                                       (gpointer)&service_name, (gpointer)&sc))
        {
            GSList *removed_keys = NULL;
            GHashTableIter si;
            gchar *key;
            GVariant *value;
            guint service_id;

            g_variant_builder_open (&builder,
                                    G_VARIANT_TYPE ("(ssua{sv}as)"));

            /* Append the service name */
            g_variant_builder_add (&builder, "s", service_name);
            /* Append the service type */
            g_variant_builder_add (&builder, "s", sc->service_type);
            /* Append the service id */
            if (sc->service == NULL)
                service_id = 0;
            else
                service_id = sc->service->id;

            g_variant_builder_add (&builder, "u", service_id);
            /* Append the dictionary of service settings */
            g_variant_builder_open (&builder, G_VARIANT_TYPE_VARDICT);

            g_hash_table_iter_init (&si, sc->settings);
            while (g_hash_table_iter_next (&si,
                                           (gpointer)&key, (gpointer)&value))
            {
                if (value)
                    g_variant_builder_add (&builder, "{sv}", key, value);
                else
                    removed_keys = g_slist_prepend (removed_keys, key);
            }
            g_variant_builder_close (&builder);

            /* append the list of removed keys */
            g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY);
            while (removed_keys)
            {
                g_variant_builder_add (&builder, "s", removed_keys->data);
                removed_keys = g_slist_delete_link (removed_keys, removed_keys);
            }
            g_variant_builder_close (&builder);

            /* Close the service entry builder */
            g_variant_builder_close (&builder);
        }
    }
    g_variant_builder_close (&builder);
    return g_variant_builder_end (&builder);
}

static void
ag_account_watch_free (AgAccountWatch watch)
{
    g_return_if_fail (watch != NULL);
    g_free (watch->key);
    g_free (watch->prefix);
    g_slice_free (struct _AgAccountWatch, watch);
}

static AgService *
ag_service_ref_null (AgService *service)
{
    if (service)
        ag_service_ref (service);
    return service;
}

static void
ag_service_unref_null (AgService *service)
{
    if (service)
        ag_service_unref (service);
}

static AgAccountWatch
ag_account_watch_int (AgAccount *account, gchar *key, gchar *prefix,
                      AgAccountNotifyCb callback, gpointer user_data)
{
    AgAccountPrivate *priv = account->priv;
    AgAccountWatch watch;
    GHashTable *service_watches;

    if (!priv->watches)
    {
        priv->watches =
            g_hash_table_new_full (g_direct_hash, g_direct_equal,
                                   (GDestroyNotify)ag_service_unref_null,
                                   (GDestroyNotify)g_hash_table_destroy);
    }

    service_watches = g_hash_table_lookup (priv->watches, priv->service);
    if (!service_watches)
    {
        service_watches =
            g_hash_table_new_full (g_direct_hash, g_direct_equal,
                                   NULL,
                                   (GDestroyNotify)ag_account_watch_free);
        g_hash_table_insert (priv->watches,
                             ag_service_ref_null (priv->service),
                             service_watches);
    }

    watch = g_slice_new (struct _AgAccountWatch);
    watch->service = priv->service;
    watch->key = key;
    watch->prefix = prefix;
    watch->callback = callback;
    watch->user_data = user_data;

    g_hash_table_insert (service_watches, watch, watch);

    return watch;
}

static gboolean
got_account_setting (sqlite3_stmt *stmt, GHashTable *settings)
{
    gchar *key;
    GVariant *value;

    key = g_strdup ((gchar *)sqlite3_column_text (stmt, 0));
    g_return_val_if_fail (key != NULL, FALSE);

    value = _ag_value_from_db (stmt, 1, 2);

    g_hash_table_insert (settings, key, value);
    return TRUE;
}

static void
ag_service_settings_free (AgServiceSettings *ss)
{
    if (ss->service)
        ag_service_unref (ss->service);
    g_hash_table_unref (ss->settings);
    g_slice_free (AgServiceSettings, ss);
}

static AgServiceSettings *
get_service_settings (AgAccountPrivate *priv, AgService *service,
                      gboolean create)
{
    AgServiceSettings *ss;
    const gchar *service_name;

    if (G_UNLIKELY (!priv->services))
    {
        priv->services = g_hash_table_new_full
            (g_str_hash, g_str_equal,
             NULL, (GDestroyNotify) ag_service_settings_free);
    }

    service_name = service ? service->name : SERVICE_GLOBAL;
    ss = g_hash_table_lookup (priv->services, service_name);
    if (!ss && create)
    {
        ss = g_slice_new (AgServiceSettings);
        ss->service = service ? ag_service_ref (service) : NULL;
        ss->settings = g_hash_table_new_full
            (g_str_hash, g_str_equal,
             g_free, ag_variant_safe_unref);
        g_hash_table_insert (priv->services, (gchar *)service_name, ss);
    }

    return ss;
}

static gboolean
ag_account_changes_get_enabled (AgAccountChanges *changes, gboolean *enabled)
{
    AgServiceChanges *sc;
    GVariant *value;

    sc = g_hash_table_lookup (changes->services, SERVICE_GLOBAL);
    if (sc)
    {
        value = g_hash_table_lookup (sc->settings, "enabled");
        if (value)
        {
            *enabled = g_variant_get_boolean (value);
            return TRUE;
        }
    }
    *enabled = FALSE;
    return FALSE;
}

static gboolean
ag_account_changes_get_display_name (AgAccountChanges *changes,
                                     const gchar **display_name)
{
    AgServiceChanges *sc;
    GVariant *value;

    sc = g_hash_table_lookup (changes->services, SERVICE_GLOBAL);
    if (sc)
    {
        value = g_hash_table_lookup (sc->settings, "name");
        if (value)
        {
            *display_name = g_variant_get_string (value, NULL);
            return TRUE;
        }
    }
    *display_name = NULL;
    return FALSE;
}

static void
ag_service_changes_free (AgServiceChanges *sc)
{
    g_free (sc->service_type);

    if (sc->service)
        ag_service_unref (sc->service);

    if (sc->settings)
        g_hash_table_unref (sc->settings);

    if (sc->signatures)
        g_hash_table_unref (sc->signatures);

    g_slice_free (AgServiceChanges, sc);
}

void
_ag_account_changes_free (AgAccountChanges *changes)
{
    if (G_LIKELY (changes))
    {
        g_hash_table_unref (changes->services);
        g_slice_free (AgAccountChanges, changes);
    }
}

static GList *
match_watch_with_key (GHashTable *watches, const gchar *key, GList *watch_list)
{
    GHashTableIter iter;
    AgAccountWatch watch;

    g_hash_table_iter_init (&iter, watches);
    while (g_hash_table_iter_next (&iter, NULL, (gpointer)&watch))
    {
        if (watch->key)
        {
            if (strcmp (key, watch->key) == 0)
            {
                watch_list = g_list_prepend (watch_list, watch);
            }
        }
        else /* match on the prefix */
        {
            if (g_str_has_prefix (key, watch->prefix))
            {
                /* before addind the watch to the list, make sure it's not
                 * already there */
                if (!g_list_find (watch_list, watch))
                    watch_list = g_list_prepend (watch_list, watch);
            }
        }
    }
    return watch_list;
}

static void
update_settings (AgAccount *account, GHashTable *services)
{
    AgAccountPrivate *priv = account->priv;
    GHashTableIter iter;
    AgServiceChanges *sc;
    gchar *service_name;
    GList *watch_list = NULL;

    g_hash_table_iter_init (&iter, services);
    while (g_hash_table_iter_next (&iter,
                                   (gpointer)&service_name, (gpointer)&sc))
    {
        AgServiceSettings *ss;
        GHashTableIter si;
        gchar *key;
        GVariant *value;
        GHashTable *watches = NULL;

        if (priv->foreign)
        {
            /* If the account has been created from another instance
             * (which might be in another process), the "changes" structure
             * contains all the account settings for all services.
             *
             * Instead of discarding this precious information, we store all
             * the settings in memory, to minimize future disk accesses.
             */
            ss = get_service_settings (priv, sc->service, TRUE);
        }
        else
        {
            /* if the changed service doesn't have a AgServiceSettings entry it
             * means that the service was never selected on this account, so we
             * don't need to update its settings. */
            if (!priv->services) continue;
            ss = g_hash_table_lookup (priv->services, service_name);
        }

        /* get the watches associated to this service */
        if (ss != NULL && priv->watches != NULL)
            watches = g_hash_table_lookup (priv->watches, ss->service);

        g_hash_table_iter_init (&si, sc->settings);
        while (g_hash_table_iter_next (&si,
                                       (gpointer)&key, (gpointer)&value))
        {
            if (ss != NULL)
            {
                if (ss->service == NULL)
                {
                    if (strcmp (key, "name") == 0)
                    {
                        g_free (priv->display_name);
                        priv->display_name =
                            value ? g_variant_dup_string (value, NULL) : NULL;
                        g_signal_emit (account, signals[DISPLAY_NAME_CHANGED], 0);
                        g_object_notify_by_pspec ((GObject *)account,
                                                  properties[PROP_DISPLAY_NAME]);
                        continue;
                    }
                    else if (strcmp (key, "enabled") == 0)
                    {
                        priv->enabled =
                            value ? g_variant_get_boolean (value) : FALSE;
                        g_signal_emit (account, signals[ENABLED], 0,
                                       NULL, priv->enabled);
                        g_object_notify_by_pspec ((GObject *)account,
                                                  properties[PROP_ENABLED]);
                        continue;
                    }
                }

                if (value)
                    g_hash_table_replace (ss->settings,
                                          g_strdup (key),
                                          g_variant_ref (value));
                else
                    g_hash_table_remove (ss->settings, key);

                /* check for installed watches to be invoked */
                if (watches)
                    watch_list = match_watch_with_key (watches, key, watch_list);
            }

            if (strcmp (key, "enabled") == 0)
            {
                gboolean enabled =
                    value ? g_variant_get_boolean (value) : FALSE;
                g_signal_emit (account, signals[ENABLED], 0,
                               service_name, enabled);
            }
        }
    }

    /* Invoke all watches
     * While whatches are running, let the receivers retrieve the changes
     * table with _ag_account_get_service_changes(): set it into the
     * changes_for_watches field. */
    priv->changes_for_watches = services;
    while (watch_list)
    {
        AgAccountWatch watch = watch_list->data;

        if (watch->key)
            watch->callback (account, watch->key, watch->user_data);
        else
            watch->callback (account, watch->prefix, watch->user_data);
        watch_list = g_list_delete_link (watch_list, watch_list);
    }
    priv->changes_for_watches = NULL;
}

void
_ag_account_store_completed (AgAccount *account, AgAccountChanges *changes)
{
    AgAccountPrivate *priv = account->priv;

    g_clear_object (&priv->store_task);

    _ag_account_changes_free (changes);
}

/*
 * _ag_account_done_changes:
 *
 * This function is called after a successful execution of a transaction, and
 * must update the account data as with the contents of the AgAccountChanges
 * structure.
 */
void
_ag_account_done_changes (AgAccount *account, AgAccountChanges *changes)
{
    AgAccountPrivate *priv = account->priv;

    g_return_if_fail (changes != NULL);

    if (changes->services)
        update_settings (account, changes->services);

    if (changes->deleted)
    {
        priv->deleted = TRUE;
        priv->enabled = FALSE;
        g_signal_emit (account, signals[ENABLED], 0, NULL, FALSE);
        g_object_notify_by_pspec ((GObject *)account,
                                  properties[PROP_ENABLED]);
        g_signal_emit (account, signals[DELETED], 0);
    }
}

static AgAccountChanges *
account_changes_get (AgAccountPrivate *priv)
{
    if (!priv->changes)
    {
        priv->changes = g_slice_new0 (AgAccountChanges);
        priv->changes->services =
            g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
                                   (GDestroyNotify)ag_service_changes_free);
    }

    return priv->changes;
}

static void
_ag_signatures_slice_free (AgSignature *sgn)
{
    g_free (sgn->signature);
    g_free (sgn->token);
    g_slice_free (AgSignature, sgn);
}

static AgServiceChanges*
account_service_changes_get (AgAccountPrivate *priv, AgService *service,
                             gboolean create_signatures)
{
    AgAccountChanges *changes;
    AgServiceChanges *sc;
    gchar *service_name;
    gchar *service_type;

    changes = account_changes_get (priv);

    service_name = service ? service->name : SERVICE_GLOBAL;
    service_type = service ? service->type : SERVICE_GLOBAL_TYPE;

    sc = g_hash_table_lookup (changes->services, service_name);
    if (!sc)
    {
        sc = g_slice_new0 (AgServiceChanges);
        sc->service = service ? ag_service_ref (service) : NULL;
        sc->service_type = g_strdup (service_type);

        sc->settings = g_hash_table_new_full
            (g_str_hash, g_str_equal,
            g_free, ag_variant_safe_unref);
        g_hash_table_insert (changes->services, service_name, sc);
    }

    if (create_signatures && !sc->signatures)
        sc->signatures = g_hash_table_new_full
            (g_str_hash, g_str_equal,
             g_free, (GDestroyNotify)_ag_signatures_slice_free);

    return sc;
}

GHashTable *
_ag_account_get_service_changes (AgAccount *account, AgService *service)
{
    GHashTable *services;
    AgServiceChanges *sc;

    services = account->priv->changes_for_watches;
    if (!services) return NULL;

    sc = g_hash_table_lookup (services,
                              service ? service->name : SERVICE_GLOBAL);
    if (!sc) return NULL;
    return sc->settings;
}

static void
change_service_value (AgAccountPrivate *priv, AgService *service,
                      const gchar *key, GVariant *value)
{
    AgServiceChanges *sc;
    sc = account_service_changes_get (priv, service, FALSE);
    g_hash_table_insert (sc->settings,
                         g_strdup (key),
                         value ? g_variant_ref_sink (value) : NULL);
}

static inline void
change_selected_service_value (AgAccountPrivate *priv,
                               const gchar *key, GVariant *value)
{
    change_service_value(priv, priv->service, key, value);
}

static void
ag_account_init (AgAccount *account)
{
    account->priv = G_TYPE_INSTANCE_GET_PRIVATE (account, AG_TYPE_ACCOUNT,
                                                 AgAccountPrivate);
}

static gboolean
got_account (sqlite3_stmt *stmt, AgAccountPrivate *priv)
{
    g_assert (priv->display_name == NULL);
    g_assert (priv->provider_name == NULL);
    priv->display_name = g_strdup ((gchar *)sqlite3_column_text (stmt, 0));
    priv->provider_name = g_strdup ((gchar *)sqlite3_column_text (stmt, 1));
    priv->enabled = sqlite3_column_int (stmt, 2);
    return TRUE;
}

static gboolean
ag_account_load (AgAccount *account, GError **error)
{
    AgAccountPrivate *priv = account->priv;
    gchar sql[128];
    gint rows;

    g_snprintf (sql, sizeof (sql),
                "SELECT name, provider, enabled "
                "FROM Accounts WHERE id = %u", account->id);
    rows = _ag_manager_exec_query (priv->manager,
                                   (AgQueryCallback)got_account, priv, sql);
    /* if the query succeeded but we didn't get a row, we must set the
     * NOT_FOUND error */
    if (rows != 1)
    {
        g_set_error (error,
                     AG_ACCOUNTS_ERROR,
                     AG_ACCOUNTS_ERROR_ACCOUNT_NOT_FOUND,
                     "Account %u not found in DB", account->id);
    }

    return rows == 1;
}

static gboolean
ag_account_initable_init (GInitable *initable,
                          G_GNUC_UNUSED GCancellable *cancellable,
                          GError **error)
{
    AgAccount *account = AG_ACCOUNT (initable);

    if (account->id)
    {
        if (account->priv->changes && account->priv->changes->created)
        {
            /* this is a new account and we should not load it */
            _ag_account_changes_free (account->priv->changes);
            account->priv->changes = NULL;
        }
        else if (!ag_account_load (account, error))
        {
            g_warning ("Unable to load account %u", account->id);
            return FALSE;
        }
    }

    if (!account->priv->foreign)
        ag_account_select_service (account, NULL);

    return TRUE;
}

static void
ag_account_initable_iface_init (gpointer g_iface,
                                G_GNUC_UNUSED gpointer iface_data)
{
    GInitableIface *iface = (GInitableIface *)g_iface;
    iface->init = ag_account_initable_init;
}

static void
ag_account_get_property (GObject *object, guint property_id,
                         GValue *value, GParamSpec *pspec)
{
    AgAccount *account = AG_ACCOUNT (object);

    switch (property_id)
    {
    case PROP_ID:
        g_value_set_uint (value, account->id);
        break;
    case PROP_MANAGER:
        g_value_set_object (value, account->priv->manager);
        break;
    case PROP_PROVIDER:
        g_value_set_string (value, account->priv->provider_name);
        break;
    case PROP_ENABLED:
        g_value_set_boolean (value, account->priv->enabled);
        break;
    case PROP_DISPLAY_NAME:
        g_value_set_string (value, account->priv->display_name);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
ag_account_set_property (GObject *object, guint property_id,
                         const GValue *value, GParamSpec *pspec)
{
    AgAccount *account = AG_ACCOUNT (object);
    AgAccountPrivate *priv = account->priv;

    switch (property_id)
    {
    case PROP_ID:
        g_assert (account->id == 0);
        account->id = g_value_get_uint (value);
        break;
    case PROP_MANAGER:
        g_assert (priv->manager == NULL);
        priv->manager = g_value_dup_object (value);
        break;
    case PROP_PROVIDER:
        g_assert (priv->provider_name == NULL);
        priv->provider_name = g_value_dup_string (value);
        /* if this property is given, it means we are creating a new account */
        if (priv->provider_name)
        {
            AgAccountChanges *changes = account_changes_get (priv);
            changes->created = TRUE;
        }
        break;
    case PROP_FOREIGN:
        priv->foreign = g_value_get_boolean (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
ag_account_dispose (GObject *object)
{
    AgAccount *account = AG_ACCOUNT (object);
    AgAccountPrivate *priv = account->priv;

    DEBUG_REFS ("Disposing account %p", object);

    if (priv->watches)
    {
        g_hash_table_destroy (priv->watches);
        priv->watches = NULL;
    }

    if (priv->provider)
    {
        ag_provider_unref (priv->provider);
        priv->provider = NULL;
    }

    if (priv->manager)
    {
        g_object_unref (priv->manager);
        priv->manager = NULL;
    }

    G_OBJECT_CLASS (ag_account_parent_class)->dispose (object);
}

static void
ag_account_finalize (GObject *object)
{
    AgAccountPrivate *priv = AG_ACCOUNT_PRIV (object);

    g_free (priv->provider_name);
    g_free (priv->display_name);

    if (priv->services)
        g_hash_table_unref (priv->services);

    if (priv->changes)
    {
        DEBUG_INFO ("Finalizing account with uncommitted changes!");
        _ag_account_changes_free (priv->changes);
    }

    G_OBJECT_CLASS (ag_account_parent_class)->finalize (object);
}

static void
ag_account_class_init (AgAccountClass *klass)
{
    GObjectClass* object_class = G_OBJECT_CLASS (klass);

    g_type_class_add_private (object_class, sizeof (AgAccountPrivate));

    object_class->get_property = ag_account_get_property;
    object_class->set_property = ag_account_set_property;
    object_class->dispose = ag_account_dispose;
    object_class->finalize = ag_account_finalize;

    /**
     * AgAccount:id:
     *
     * The AgAccountId for the account.
     */
    properties[PROP_ID] =
        g_param_spec_uint ("id", "Account ID",
                           "The AgAccountId of the account",
                           0, G_MAXUINT, 0,
                           G_PARAM_STATIC_STRINGS |
                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

    /**
     * AgAccount:manager:
     *
     * The #AgManager from which the account was instantiated.
     *
     * Since: 1.4
     */
    properties[PROP_MANAGER] =
        g_param_spec_object ("manager", "manager", "manager",
                             AG_TYPE_MANAGER,
                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

    /**
     * AgAccount:provider:
     *
     * The ID of the provider for the account.
     *
     * Since: 1.4
     */
    properties[PROP_PROVIDER] =
        g_param_spec_string ("provider", "provider", "provider",
                             NULL,
                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                             G_PARAM_STATIC_STRINGS);

    properties[PROP_FOREIGN] =
        g_param_spec_boolean ("foreign", "foreign", "foreign",
                              FALSE,
                              G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
                              G_PARAM_STATIC_STRINGS);

    /**
     * AgAccount:enabled:
     *
     * Whether the account is currently enabled.
     *
     * Since: 1.4
     */
    properties[PROP_ENABLED] =
        g_param_spec_boolean ("enabled", "Enabled",
                              "Whether the account is enabled",
                              FALSE,
                              G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    /**
     * AgAccount:display-name:
     *
     * The display name of the account.
     *
     * Since: 1.4
     */
    properties[PROP_DISPLAY_NAME] =
        g_param_spec_string ("display-name", "Display name",
                             "The display name of the account",
                             NULL,
                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

    g_object_class_install_properties (object_class,
                                       N_PROPERTIES,
                                       properties);

    /**
     * AgAccount::enabled:
     * @account: the #AgAccount.
     * @service: the service which was enabled/disabled, or %NULL if the global
     * enabledness of the account changed.
     * @enabled: the new state of the @account.
     *
     * Emitted when the account "enabled" status was changed for one of its
     * services, or for the account globally.
     */
    signals[ENABLED] = g_signal_new ("enabled",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        ag_marshal_VOID__STRING_BOOLEAN,
        G_TYPE_NONE,
        2, G_TYPE_STRING, G_TYPE_BOOLEAN);

    /**
     * AgAccount::display-name-changed:
     * @account: the #AgAccount.
     *
     * Emitted when the account display name has changed.
     */
    signals[DISPLAY_NAME_CHANGED] = g_signal_new ("display-name-changed",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE,
        0);

    /**
     * AgAccount::deleted:
     * @account: the #AgAccount.
     *
     * Emitted when the account has been deleted.
     */
    signals[DELETED] = g_signal_new ("deleted",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
}

AgAccountChanges *
_ag_account_changes_from_dbus (AgManager *manager, GVariant *v_services,
                               gboolean created, gboolean deleted)
{
    AgAccountChanges *changes;
    AgServiceChanges *sc;
    GVariantIter i_serv, i_dict, i_list;
    GVariant *changed_keys, *removed_keys;
    gchar *service_name;
    gchar *service_type;
    gint service_id;

    changes = g_slice_new0 (AgAccountChanges);
    changes->created = created;
    changes->deleted = deleted;
    changes->services =
        g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
                               (GDestroyNotify)ag_service_changes_free);

    /* parse the settings */
    g_variant_iter_init (&i_serv, v_services);

    /* iterate the array, each element holds one service */
    while (g_variant_iter_next (&i_serv, "(ssu@a{sv}@as)",
                                &service_name,
                                &service_type,
                                &service_id,
                                &changed_keys,
                                &removed_keys))
    {
        GVariant *variant;
        gchar *key;

        sc = g_slice_new0 (AgServiceChanges);
        if (service_name != NULL && strcmp (service_name, SERVICE_GLOBAL) == 0)
            sc->service = NULL;
        else
            sc->service = _ag_manager_get_service_lazy (manager, service_name,
                                                        service_type,
                                                        service_id);
        sc->service_type = service_type;

        sc->settings = g_hash_table_new_full
            (g_str_hash, g_str_equal,
             g_free, ag_variant_safe_unref);
        g_hash_table_insert (changes->services, service_name, sc);

        /* iterate the "a{sv}" of settings */
        g_variant_iter_init (&i_dict, changed_keys);
        while (g_variant_iter_next (&i_dict, "{sv}", &key, &variant))
        {
            g_hash_table_insert (sc->settings, key, variant);
        }
        g_variant_unref (changed_keys);

        /* iterate the "as" of removed settings */
        g_variant_iter_init (&i_list, removed_keys);
        while (g_variant_iter_next (&i_list, "s", &key))
        {
            g_hash_table_insert (sc->settings, key, NULL);
        }

        g_variant_unref (removed_keys);
    }

    return changes;
}

AgAccountChanges *
_ag_account_steal_changes (AgAccount *account)
{
    AgAccountChanges *changes;

    changes = account->priv->changes;
    account->priv->changes = NULL;
    return changes;
}

static void
add_service_type (GPtrArray *types, const gchar *service_type)
{
    gboolean found = FALSE;
    guint i;

    /* if the service type is not yet in the list, add it */
    for (i = 0; i < types->len; i++)
    {
        if (strcmp (service_type, g_ptr_array_index (types, i)) == 0)
        {
            found = TRUE;
            break;
        }
    }

    if (!found)
        g_ptr_array_add (types, (gchar *)service_type);
}

/**
 * _ag_account_changes_get_service_types:
 * @changes: the #AgAccountChanges structure.
 *
 * Gets the list of service types involved in the change. The list does not
 * contain duplicates.
 *
 * Returns: a newly-allocated GPtrArray (this must be freed, but not the
 * strings it holds!).
 */
GPtrArray *
_ag_account_changes_get_service_types (AgAccountChanges *changes)
{
    GPtrArray *ret = g_ptr_array_sized_new (8);

    if (changes->services)
    {
        GHashTableIter iter;
        AgServiceChanges *sc;

        g_hash_table_iter_init (&iter, changes->services);
        while (g_hash_table_iter_next (&iter, NULL, (gpointer)&sc))
        {
            if (!sc->service_type) continue;

            add_service_type (ret, sc->service_type);
        }
    }

    /* if the account has been created or deleted, make sure that the global
     * service type is in the list */
    if (changes->created || changes->deleted)
        add_service_type (ret, SERVICE_GLOBAL_TYPE);

    return ret;
}

gboolean
_ag_account_changes_have_service_type (AgAccountChanges *changes, gchar *service_type)
{
    if (changes->services)
    {
        GHashTableIter iter;
        AgServiceChanges *sc;

        g_hash_table_iter_init (&iter, changes->services);
        while (g_hash_table_iter_next (&iter,
                                       NULL, (gpointer)&sc))
        {
            if (g_strcmp0(sc->service_type, service_type) == 0)
                return TRUE;
        }
    }

    return FALSE;
}

gboolean
_ag_account_changes_have_enabled (AgAccountChanges *changes)
{
    if (changes->services)
    {
        GHashTableIter iter;
        AgServiceChanges *sc;

        g_hash_table_iter_init (&iter, changes->services);
        while (g_hash_table_iter_next (&iter,
                                       NULL, (gpointer)&sc))
        {
            const gchar *key = "enabled";

            if (g_hash_table_lookup (sc->settings, (gpointer)key))
                return TRUE;
        }
    }

    return FALSE;
}

static void
ag_account_store_signature (AgAccount *account, AgServiceChanges *sc, GString *sql)
{
    AgAccountId account_id;
    GHashTableIter i_signatures;
    gint service_id;
    gpointer ht_key, ht_value;

    account_id = account->id;
    service_id = (sc->service != NULL) ? sc->service->id : 0;

    g_hash_table_iter_init (&i_signatures, sc->signatures);
    while (g_hash_table_iter_next (&i_signatures, &ht_key, &ht_value))
    {
        const gchar *key = ht_key;
        AgSignature *sgn = ht_value;

        if (sgn)
        {
            _ag_string_append_printf
                (sql,
                 "INSERT OR REPLACE INTO Signatures"
                 "(account, service, key, signature, token)"
                 "VALUES (%d, %d, %Q, %Q, %Q);",
                 account_id, service_id, key, sgn->signature, sgn->token);
        }
    }
}

gchar *
_ag_account_get_store_sql (AgAccount *account, GError **error)
{
    AgAccountPrivate *priv;
    AgAccountChanges *changes;
    GString *sql;
    gchar account_id_buffer[16];
    const gchar *account_id_str;

    priv = account->priv;

    if (G_UNLIKELY (priv->deleted))
    {
        *error = g_error_new (AG_ACCOUNTS_ERROR, AG_ACCOUNTS_ERROR_DELETED,
                              "Account %s (id = %d) has been deleted",
                              priv->display_name, account->id);
        return NULL;
    }

    changes = priv->changes;

    if (G_UNLIKELY (!changes))
    {
        /* Nothing to do: return no SQL, and no error */
        return NULL;
    }

    sql = g_string_sized_new (512);
    if (changes->deleted)
    {
        if (account->id != 0)
        {
            _ag_string_append_printf
                (sql, "DELETE FROM Accounts WHERE id = %d;", account->id);
            _ag_string_append_printf
                (sql, "DELETE FROM Settings WHERE account = %d;", account->id);
        }
        account_id_str = NULL; /* make the compiler happy */
    }
    else if (account->id == 0)
    {
        gboolean enabled;
        const gchar *display_name;

        ag_account_changes_get_enabled (changes, &enabled);
        ag_account_changes_get_display_name (changes, &display_name);
        _ag_string_append_printf
            (sql,
             "INSERT INTO Accounts (name, provider, enabled) "
             "VALUES (%Q, %Q, %d);",
             display_name,
             priv->provider_name,
             enabled);

        g_string_append (sql, "SELECT set_last_rowid_as_account_id();");
        account_id_str = "account_id()";
    }
    else
    {
        gboolean enabled, enabled_changed, display_name_changed;
        const gchar *display_name;

        g_snprintf (account_id_buffer, sizeof (account_id_buffer),
                    "%u", account->id);
        account_id_str = account_id_buffer;

        enabled_changed = ag_account_changes_get_enabled (changes, &enabled);
        display_name_changed =
            ag_account_changes_get_display_name (changes, &display_name);

        if (display_name_changed || enabled_changed)
        {
            gboolean comma = FALSE;
            g_string_append (sql, "UPDATE Accounts SET ");
            if (display_name_changed)
            {
                _ag_string_append_printf
                    (sql, "name = %Q", display_name);
                comma = TRUE;
            }

            if (enabled_changed)
            {
                _ag_string_append_printf
                    (sql, "%cenabled = %d",
                     comma ? ',' : ' ', enabled);
            }

            _ag_string_append_printf (sql, " WHERE id = %d;", account->id);
        }
    }

    if (!changes->deleted)
    {
        GHashTableIter i_services;
        gpointer ht_key, ht_value;

        g_hash_table_iter_init (&i_services, changes->services);
        while (g_hash_table_iter_next (&i_services, &ht_key, &ht_value))
        {
            AgServiceChanges *sc = ht_value;
            GHashTableIter i_settings;
            const gchar *service_id_str;
            gchar service_id_buffer[16];

            if (sc->service)
            {
                g_snprintf (service_id_buffer, sizeof (service_id_buffer),
                            "%d", sc->service->id);
                service_id_str = service_id_buffer;
            }
            else
                service_id_str = "0";

            g_hash_table_iter_init (&i_settings, sc->settings);
            while (g_hash_table_iter_next (&i_settings, &ht_key, &ht_value))
            {
                const gchar *key = ht_key;
                GVariant *value = ht_value;

                if (value)
                {
                    const GVariantType *type_str;
                    gchar *value_str;

                    value_str = _ag_value_to_db (value, FALSE);
                    type_str = g_variant_get_type (value);
                    _ag_string_append_printf
                        (sql,
                         "INSERT OR REPLACE INTO Settings (account, service,"
                                                          "key, type, value) "
                         "VALUES (%s, %s, %Q, %Q, %Q);",
                         account_id_str, service_id_str, key,
                         (const gchar *)type_str, value_str);
                    g_free (value_str);
                }
                else if (account->id != 0)
                {
                    _ag_string_append_printf
                        (sql,
                         "DELETE FROM Settings WHERE "
                         "account = %d AND "
                         "service = %Q AND "
                         "key = %Q;",
                         account->id, service_id_str, key);
                }
            }

            if (sc->signatures)
                ag_account_store_signature (account, sc, sql);
        }
    }

    return g_string_free (sql, FALSE);
}

/**
 * ag_account_supports_service:
 * @account: the #AgAccount.
 * @service_type: the name of the service type to check for
 *
 * Get whether @service_type is supported on @account.
 *
 * Returns: %TRUE if @account supports the service type @service_type, %FALSE
 * otherwise.
 */
gboolean
ag_account_supports_service (AgAccount *account, const gchar *service_type)
{
    GList *services;
    gboolean ret = FALSE;

    services = ag_account_list_services_by_type (account, service_type);
    if (services)
    {
        ag_service_list_free (services);
        ret = TRUE;
    }
    return ret;
}

/**
 * ag_account_list_services:
 * @account: the #AgAccount.
 *
 * Get the list of services for @account. If the #AgManager was created with
 * specified service_type this will return only services with this service_type.
 *
 * Returns: (transfer full) (element-type AgService): a #GList of #AgService
 * items representing all the services supported by this account. Must be
 * free'd with ag_service_list_free().
 */
GList *
ag_account_list_services (AgAccount *account)
{
    AgAccountPrivate *priv;
    GList *all_services, *list;
    GList *services = NULL;

    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    priv = account->priv;

    if (!priv->provider_name)
        return NULL;

    all_services = ag_manager_list_services (priv->manager);
    for (list = all_services; list != NULL; list = list->next)
    {
        AgService *service = list->data;

        const gchar *provider = ag_service_get_provider (service);
        if (provider &&
            strcmp (provider, priv->provider_name) == 0)
        {
            services = g_list_prepend (services, service);
        }
        else
            ag_service_unref (service);
    }
    g_list_free (all_services);
    return services;
}

/**
 * ag_account_list_services_by_type:
 * @account: the #AgAccount.
 * @service_type: the service type which all the returned services should
 * provide.
 *
 * Get the list of services supported by @account, filtered by @service_type.
 *
 * Returns: (transfer full) (element-type AgService): a #GList of #AgService
 * items representing all the services supported by this account which provide
 * @service_type. Must be free'd with ag_service_list_free().
 */
GList *
ag_account_list_services_by_type (AgAccount *account,
                                  const gchar *service_type)
{
    AgAccountPrivate *priv;
    GList *all_services, *list;
    GList *services = NULL;

    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    g_return_val_if_fail (service_type != NULL, NULL);
    priv = account->priv;

    if (!priv->provider_name)
        return NULL;

    all_services = ag_manager_list_services_by_type (priv->manager, service_type);
    for (list = all_services; list != NULL; list = list->next)
    {
        AgService *service = list->data;
        const gchar *provider = ag_service_get_provider (service);
        if (provider &&
            strcmp (provider, priv->provider_name) == 0)
        {
            services = g_list_prepend (services, service);
        }
        else
            ag_service_unref (service);
    }
    g_list_free (all_services);
    return services;
}

static gboolean
add_name_to_list (sqlite3_stmt *stmt, GList **plist)
{
    gchar *name;
    name = g_strdup ((gchar *)sqlite3_column_text (stmt, 0));

    *plist = g_list_prepend(*plist, name);

    return TRUE;
}

static inline GList *
list_enabled_services_from_memory (AgAccountPrivate *priv,
                                   const gchar *service_type)
{
    GHashTableIter iter;
    AgServiceSettings *ss;
    GList *list = NULL;

    g_hash_table_iter_init (&iter, priv->services);
    while (g_hash_table_iter_next (&iter, NULL, (gpointer)&ss))
    {
        GVariant *value;

        if (ss->service == NULL) continue;

        if (service_type != NULL &&
            g_strcmp0 (ag_service_get_service_type (ss->service), service_type) != 0)
                continue;

        value = g_hash_table_lookup (ss->settings, "enabled");
        if (value != NULL && g_variant_get_boolean (value))
            list = g_list_prepend (list, ag_service_ref(ss->service));
    }
    return list;
}

static AgAccountSettingIter *
ag_account_settings_iter_copy(const AgAccountSettingIter *orig)
{
    RealIter *copy;

    copy = (RealIter *)g_slice_dup (AgAccountSettingIter, orig);
    copy->last_gvalue = NULL;
    return (AgAccountSettingIter *)copy;
}

G_DEFINE_BOXED_TYPE (AgAccountSettingIter, ag_account_settings_iter,
                     (GBoxedCopyFunc)ag_account_settings_iter_copy,
                     (GBoxedFreeFunc)ag_account_settings_iter_free);

void
_ag_account_settings_iter_init (AgAccount *account,
                                AgAccountSettingIter *iter,
                                const gchar *key_prefix,
                                gboolean copy_string)
{
    AgAccountPrivate *priv;
    AgServiceSettings *ss;
    RealIter *ri = (RealIter *)iter;

    g_return_if_fail (AG_IS_ACCOUNT (account));
    g_return_if_fail (iter != NULL);
    priv = account->priv;

    ri->account = account;
    if (copy_string)
    {
        ri->key_prefix = g_strdup (key_prefix);
        ri->must_free_prefix = TRUE;
    }
    else
    {
        ri->key_prefix = (gchar *)key_prefix;
        ri->must_free_prefix = FALSE;
    }
    ri->stage = AG_ITER_STAGE_UNSET;

    ss = get_service_settings (priv, priv->service, FALSE);
    if (ss)
    {
        g_hash_table_iter_init (&ri->iter, ss->settings);
        ri->stage = AG_ITER_STAGE_ACCOUNT;
    }

    ri->last_gvalue = NULL;
}

/**
 * ag_account_list_enabled_services:
 * @account: the #AgAccount.
 *
 * Gets a list of services that are enabled for @account.
 *
 * Returns: (transfer full) (element-type AgService): a #GList of #AgService
 * items representing all the services which are enabled. Must be free'd with
 * ag_service_list_free().
 */
GList *
ag_account_list_enabled_services (AgAccount *account)
{
    AgAccountPrivate *priv;
    GList *list = NULL;
    GList *iter;
    GList *services = NULL;
    const gchar *service_type;
    char sql[512];

    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    priv = account->priv;

    service_type = ag_manager_get_service_type (priv->manager);

    /* avoid accessing the DB, if possible */
    if (priv->foreign)
        return list_enabled_services_from_memory (priv, service_type);

    if (service_type != NULL)
        sqlite3_snprintf (sizeof (sql), sql,
                          "SELECT DISTINCT Services.name FROM Services "
                          "JOIN Settings ON Settings.service = Services.id "
                          "WHERE Settings.key='enabled' "
                          "AND Settings.value='true' "
                          "AND Settings.account='%d' "
                          "AND Services.type = '%s';",
                           account->id,
                           service_type);
    else
        sqlite3_snprintf (sizeof (sql), sql,
                          "SELECT DISTINCT Services.name FROM Services "
                          "JOIN Settings ON Settings.service = Services.id "
                          "WHERE Settings.key='enabled' "
                          "AND Settings.value='true' "
                          "AND Settings.account='%d';",
                           account->id);

    _ag_manager_exec_query (priv->manager, (AgQueryCallback)add_name_to_list,
                            &list, sql);

    for (iter = list; iter != NULL; iter = iter->next)
    {
        gchar *service_name;
        AgService *service;

        service_name = (gchar*)iter->data;
        service = ag_manager_get_service (priv->manager, service_name);

        services = g_list_prepend (services, service);
        g_free (service_name);
    }

    g_list_free (list);

    return services;
}

/**
 * ag_account_get_manager:
 * @account: the #AgAccount.
 *
 * Get the #AgManager for @account.
 *
 * Returns: (transfer none): the #AgManager.
 */
AgManager *
ag_account_get_manager (AgAccount *account)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    return account->priv->manager;
}

/**
 * ag_account_get_provider_name:
 * @account: the #AgAccount.
 *
 * Get the name of the provider of @account.
 *
 * Returns: the name of the provider.
 */
const gchar *
ag_account_get_provider_name (AgAccount *account)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    return account->priv->provider_name;
}

/**
 * ag_account_get_display_name:
 * @account: the #AgAccount.
 *
 * Get the display name of @account.
 *
 * Returns: the display name.
 */
const gchar *
ag_account_get_display_name (AgAccount *account)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    return account->priv->display_name;
}

/**
 * ag_account_set_display_name:
 * @account: the #AgAccount.
 * @display_name: the display name to set.
 *
 * Changes the display name for @account to @display_name.
 */
void
ag_account_set_display_name (AgAccount *account, const gchar *display_name)
{
    g_return_if_fail (AG_IS_ACCOUNT (account));

    change_service_value (account->priv, NULL, "name",
                          g_variant_new_string (display_name));
}

/**
 * ag_account_select_service:
 * @account: the #AgAccount.
 * @service: (allow-none): the #AgService to select.
 *
 * Selects the configuration of service @service: from now on, all the
 * subsequent calls on the #AgAccount configuration will act on the @service.
 * If @service is %NULL, the global account configuration is selected.
 *
 * Note that if @account is being shared with other code one must take special
 * care to make sure the desired service is always selected.
 */
void
ag_account_select_service (AgAccount *account, AgService *service)
{
    AgAccountPrivate *priv;
    gboolean load_settings = FALSE;
    AgServiceSettings *ss;

    g_return_if_fail (AG_IS_ACCOUNT (account));
    priv = account->priv;

    priv->service = service;

    if (account->id != 0 &&
        !get_service_settings (priv, service, FALSE))
    {
        /* the settings for this service are not yet loaded: do it now */
        load_settings = TRUE;
    }

    ss = get_service_settings (priv, service, TRUE);

    if (load_settings)
    {
        guint service_id;
        gchar sql[128];

        service_id = _ag_manager_get_service_id (priv->manager, service);
        g_snprintf (sql, sizeof (sql),
                    "SELECT key, type, value FROM Settings "
                    "WHERE account = %u AND service = %u",
                    account->id, service_id);
        _ag_manager_exec_query (priv->manager,
                                (AgQueryCallback)got_account_setting,
                                ss->settings, sql);
    }
}

/**
 * ag_account_get_selected_service:
 * @account: the #AgAccount.
 *
 * Gets the selected #AgService for @account.
 *
 * Returns: the selected service, or %NULL if no service is selected.
 */
AgService *
ag_account_get_selected_service (AgAccount *account)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    return account->priv->service;
}

/**
 * ag_account_get_enabled:
 * @account: the #AgAccount.
 *
 * Gets whether the selected service is enabled for @account.
 *
 * Returns: %TRUE if the selected service for @account is enabled, %FALSE
 * otherwise.
 */
gboolean
ag_account_get_enabled (AgAccount *account)
{
    AgAccountPrivate *priv;
    gboolean ret = FALSE;
    AgServiceSettings *ss;
    GVariant *val;

    g_return_val_if_fail (AG_IS_ACCOUNT (account), FALSE);
    priv = account->priv;

    if (priv->service == NULL)
    {
        ret = priv->enabled;
    }
    else
    {
        ss = get_service_settings (priv, priv->service, FALSE);
        if (ss)
        {
            val = g_hash_table_lookup (ss->settings, "enabled");
            ret = val ? g_variant_get_boolean (val) : FALSE;
        }
    }
    return ret;
}

/**
 * ag_account_set_enabled:
 * @account: the #AgAccount.
 * @enabled: whether @account should be enabled.
 *
 * Sets the "enabled" flag on the selected service for @account.
 */
void
ag_account_set_enabled (AgAccount *account, gboolean enabled)
{
    g_return_if_fail (AG_IS_ACCOUNT (account));

    change_selected_service_value (account->priv, "enabled",
                                   g_variant_new_boolean (enabled));
}

/**
 * ag_account_delete:
 * @account: the #AgAccount.
 *
 * Deletes the account. Call ag_account_store() in order to record the change
 * in the storage.
 */
void
ag_account_delete (AgAccount *account)
{
    AgAccountChanges *changes;

    g_return_if_fail (AG_IS_ACCOUNT (account));

    changes = account_changes_get (account->priv);
    changes->deleted = TRUE;
}

/**
 * ag_account_get_value:
 * @account: the #AgAccount.
 * @key: the name of the setting to retrieve.
 * @value: (inout): an initialized #GValue to receive the setting's value.
 *
 * Gets the value of the configuration setting @key: @value must be a
 * #GValue initialized to the type of the setting.
 *
 * Returns: one of #AgSettingSource: %AG_SETTING_SOURCE_NONE if the setting is
 * not present, %AG_SETTING_SOURCE_ACCOUNT if the setting comes from the
 * account configuration, or %AG_SETTING_SOURCE_PROFILE if the value comes as
 * predefined in the profile.
 *
 * Deprecated: 1.4: Use ag_account_get_variant() instead.
 */
AgSettingSource
ag_account_get_value (AgAccount *account, const gchar *key,
                      GValue *value)
{
    AgSettingSource source;
    GValue val = G_VALUE_INIT;
    GVariant *variant;

    g_return_val_if_fail (AG_IS_ACCOUNT (account), AG_SETTING_SOURCE_NONE);

    variant = ag_account_get_variant (account, key, &source);

    if (variant != NULL)
    {
        _ag_value_from_variant (&val, variant);
        if (G_VALUE_TYPE (&val) == G_VALUE_TYPE (value))
            g_value_copy (&val, value);
        else
            g_value_transform (&val, value);
        g_value_unset (&val);
        return source;
    }

    return AG_SETTING_SOURCE_NONE;
}

/**
 * ag_account_set_value:
 * @account: the #AgAccount.
 * @key: the name of the setting to change.
 * @value: (allow-none): a #GValue holding the new setting's value.
 *
 * Sets the value of the configuration setting @key to the value @value.
 * If @value is %NULL, then the setting is unset.
 *
 * Deprecated: 1.4: Use ag_account_set_variant() instead.
 */
void
ag_account_set_value (AgAccount *account, const gchar *key,
                      const GValue *value)
{
    AgAccountPrivate *priv;
    GVariant *variant;

    g_return_if_fail (AG_IS_ACCOUNT (account));
    priv = account->priv;

    if (value != NULL)
    {
        variant = _ag_value_to_variant (value);
        g_return_if_fail (variant != NULL);
    }
    else
    {
        variant = NULL;
    }

    change_selected_service_value (priv, key, variant);
    if (variant != NULL)
    {
        g_variant_unref (variant);
    }
}

/**
 * ag_account_get_variant:
 * @account: the #AgAccount.
 * @key: the name of the setting to retrieve.
 * @source: (allow-none) (out): a pointer to an
 * #AgSettingSource variable which will tell whether the setting was
 * retrieved from the accounts DB or from a service template.
 *
 * Gets the value of the configuration setting @key.
 *
 * Returns: (transfer none): a #GVariant holding the setting value, or
 * %NULL. The returned #GVariant is owned by the account, and no guarantees
 * are made about its lifetime. If the client wishes to keep it, it should
 * call g_variant_ref() on it.
 *
 * Since: 1.4
 */
GVariant *
ag_account_get_variant (AgAccount *account, const gchar *key,
                        AgSettingSource *source)
{
    AgAccountPrivate *priv;
    AgServiceSettings *ss;
    GVariant *value = NULL;

    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    priv = account->priv;

    ss = get_service_settings (priv, priv->service, FALSE);
    if (ss)
    {
        value = g_hash_table_lookup (ss->settings, key);
        if (value != NULL)
        {
            if (source) *source = AG_SETTING_SOURCE_ACCOUNT;
            return value;
        }
    }

    if (priv->service)
    {
        value = _ag_service_get_default_setting (priv->service, key);
    }
    else if (ensure_has_provider (priv))
    {
        value = _ag_provider_get_default_setting (priv->provider, key);
    }

    if (value != NULL)
    {
        if (source) *source = AG_SETTING_SOURCE_PROFILE;
        return value;
    }

    if (source) *source = AG_SETTING_SOURCE_NONE;
    return NULL;
}

/**
 * ag_account_set_variant:
 * @account: the #AgAccount.
 * @key: the name of the setting to change.
 * @value: (allow-none): a #GVariant holding the new setting's value.
 *
 * Sets the value of the configuration setting @key to the value @value.
 * If @value has a floating reference, the @account will take ownership
 * of it.
 * If @value is %NULL, then the setting is unset.
 *
 * Since: 1.4
 */
void
ag_account_set_variant (AgAccount *account, const gchar *key,
                        GVariant *value)
{
    AgAccountPrivate *priv;

    g_return_if_fail (AG_IS_ACCOUNT (account));
    priv = account->priv;

    change_selected_service_value (priv, key, value);
}

/**
 * ag_account_get_settings_iter:
 * @account: the #AgAccount.
 * @key_prefix: (allow-none): enumerate only the settings whose key starts with
 * @key_prefix.
 *
 * Creates a new iterator. This method is useful for language bindings only.
 *
 * Returns: (transfer full): an #AgAccountSettingIter.
 */
AgAccountSettingIter *
ag_account_get_settings_iter (AgAccount *account,
                              const gchar *key_prefix)
{
    AgAccountSettingIter *iter = g_slice_new (AgAccountSettingIter);
    _ag_account_settings_iter_init (account, iter, key_prefix, TRUE);
    return iter;
}

/**
 * ag_account_settings_iter_free:
 * @iter: a #AgAccountSettingIter.
 *
 * Frees the memory associated with an #AgAccountSettingIter.
 */
void
ag_account_settings_iter_free (AgAccountSettingIter *iter)
{
    if (iter == NULL) return;

    RealIter *ri = (RealIter *)iter;
    if (ri->must_free_prefix)
        g_free (ri->key_prefix);
    if (ri->last_gvalue != NULL)
        _ag_value_slice_free (ri->last_gvalue);
    g_slice_free (AgAccountSettingIter, iter);
}

/**
 * ag_account_settings_iter_init:
 * @account: the #AgAccount.
 * @iter: an uninitialized #AgAccountSettingIter structure.
 * @key_prefix: (allow-none): enumerate only the settings whose key starts with
 * @key_prefix.
 *
 * Initializes @iter to iterate over the account settings. If @key_prefix is
 * not %NULL, only keys whose names start with @key_prefix will be iterated
 * over.
 */
void
ag_account_settings_iter_init (AgAccount *account,
                               AgAccountSettingIter *iter,
                               const gchar *key_prefix)
{
    _ag_account_settings_iter_init (account, iter, key_prefix, FALSE);
}

/**
 * ag_account_settings_iter_next:
 * @iter: an initialized #AgAccountSettingIter structure.
 * @key: (out callee-allocates) (transfer none): a pointer to a string
 * receiving the key name.
 * @value: (out callee-allocates) (transfer none): a pointer to a pointer to a
 * #GValue, to receive the key value.
 *
 * Iterates over the account keys. @iter must be an iterator previously
 * initialized with ag_account_settings_iter_init().
 *
 * Returns: %TRUE if @key and @value have been set, %FALSE if we there are no
 * more account settings to iterate over.
 *
 * Deprecated: 1.4: Use ag_account_settings_iter_get_next() instead.
 */
gboolean
ag_account_settings_iter_next (AgAccountSettingIter *iter,
                               const gchar **key, const GValue **value)
{
    RealIter *ri = (RealIter *)iter;
    GVariant *variant;
    GValue *val;
    gboolean ok;

    /* Since AgAccount internally operates with GVariants, we need to
     * allocate a new GValue. The client, however, won't free it, so we
     * free it ourselves the next time that this function is called, or
     * when the iterator is freed.
     * NOTE: It's still possible that the GValue is leaked if the
     * AgAccountSettingIter was allocated on the stack and the loop was
     * interrupted before ag_account_settings_iter_next() returned
     * FALSE; however, this is not common (and we hope that clients
     * will soon migrate to the new GVariant API. */
    if (ri->last_gvalue != NULL)
    {
        _ag_value_slice_free (ri->last_gvalue);
        ri->last_gvalue = NULL;
    }

    ok = ag_account_settings_iter_get_next (iter, key, &variant);
    if (!ok)
    {
        *value = NULL;
        return FALSE;
    }

    val = g_slice_new0 (GValue);
    _ag_value_from_variant (val, variant);
    ri->last_gvalue = val;
    *value = val;
    return TRUE;
}

/**
 * ag_account_settings_iter_get_next:
 * @iter: an initialized #AgAccountSettingIter structure.
 * @key: (out callee-allocates) (transfer none): a pointer to a string
 * receiving the key name.
 * @value: (out callee-allocates) (transfer none): a pointer to a pointer to a
 * #GVariant, to receive the key value.
 *
 * Iterates over the account keys. @iter must be an iterator previously
 * initialized with ag_account_settings_iter_init().
 *
 * Returns: %TRUE if @key and @value have been set, %FALSE if we there are no
 * more account settings to iterate over.
 *
 * Since: 1.4
 */
gboolean
ag_account_settings_iter_get_next (AgAccountSettingIter *iter,
                                   const gchar **key, GVariant **value)
{
    RealIter *ri = (RealIter *)iter;
    AgServiceSettings *ss;
    AgAccountPrivate *priv;
    gint prefix_length;

    g_return_val_if_fail (iter != NULL, FALSE);
    g_return_val_if_fail (AG_IS_ACCOUNT (iter->account), FALSE);
    g_return_val_if_fail (key != NULL && value != NULL, FALSE);
    priv = iter->account->priv;

    prefix_length = ri->key_prefix ? strlen(ri->key_prefix) : 0;

    if (ri->stage == AG_ITER_STAGE_ACCOUNT)
    {
        while (g_hash_table_iter_next (&ri->iter,
                                       (gpointer *)key, (gpointer *)value))
        {
            if (ri->key_prefix && !g_str_has_prefix (*key, ri->key_prefix))
                continue;

            *key = *key + prefix_length;
            return TRUE;
        }
        ri->stage = AG_ITER_STAGE_UNSET;
    }

    if (ri->stage == AG_ITER_STAGE_UNSET)
    {
        GHashTable *settings = NULL;

        if (priv->service != NULL)
        {
            settings = _ag_service_load_default_settings (priv->service);
        }
        else if (ensure_has_provider (priv))
        {
            settings = _ag_provider_load_default_settings (priv->provider);
        }

        if (!settings) goto finish;

        g_hash_table_iter_init (&ri->iter, settings);
        ri->stage = AG_ITER_STAGE_SERVICE;
    }

    ss = get_service_settings (priv, priv->service, FALSE);
    while (g_hash_table_iter_next (&ri->iter,
                                   (gpointer *)key, (gpointer *)value))
    {
        if (ri->key_prefix && !g_str_has_prefix (*key, ri->key_prefix))
            continue;

        /* if the setting is also on the account, it is overriden and we must
         * not return it here */
        if (ss && g_hash_table_lookup (ss->settings, *key) != NULL)
            continue;

        *key = *key + prefix_length;
        return TRUE;
    }

finish:
    *key = NULL;
    *value = NULL;
    return FALSE;
}

/**
 * AgAccountNotifyCb:
 * @account: the #AgAccount.
 * @key: the name of the key whose value has changed.
 * @user_data: the user data that was passed when installing this callback.
 *
 * This callback is invoked when the value of an account configuration setting
 * changes. If the callback was installed with ag_account_watch_key() then @key
 * is the name of the configuration setting which changed; if it was installed
 * with ag_account_watch_dir() then @key is the same key prefix that was used
 * when installing this callback.
 */

/**
 * ag_account_watch_key:
 * @account: the #AgAccount.
 * @key: the name of the key to watch.
 * @callback: (scope async): a #AgAccountNotifyCb callback to be called.
 * @user_data: pointer to user data, to be passed to @callback.
 *
 * Installs a watch on @key: @callback will be invoked whenever the value of
 * @key changes (or the key is removed).
 *
 * Returns: (transfer none): a #AgAccountWatch, which can then be used to
 * remove this watch.
 */
AgAccountWatch
ag_account_watch_key (AgAccount *account, const gchar *key,
                      AgAccountNotifyCb callback, gpointer user_data)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    g_return_val_if_fail (key != NULL, NULL);
    g_return_val_if_fail (callback != NULL, NULL);

    return ag_account_watch_int (account, g_strdup (key), NULL,
                                 callback, user_data);
}

/**
 * ag_account_watch_dir:
 * @account: the #AgAccount.
 * @key_prefix: the prefix of the keys to watch.
 * @callback: (scope async): a #AgAccountNotifyCb callback to be called.
 * @user_data: pointer to user data, to be passed to @callback.
 *
 * Installs a watch on all the keys under @key_prefix: @callback will be
 * invoked whenever the value of any of these keys changes (or a key is
 * removed).
 *
 * Returns: (transfer none): a #AgAccountWatch, which can then be used to
 * remove this watch.
 */
AgAccountWatch
ag_account_watch_dir (AgAccount *account, const gchar *key_prefix,
                      AgAccountNotifyCb callback, gpointer user_data)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), NULL);
    g_return_val_if_fail (key_prefix != NULL, NULL);
    g_return_val_if_fail (callback != NULL, NULL);

    return ag_account_watch_int (account, NULL, g_strdup (key_prefix),
                                 callback, user_data);
}

/**
 * ag_account_remove_watch:
 * @account: the #AgAccount.
 * @watch: the watch to remove.
 *
 * Removes the notification callback identified by @watch.
 */
void
ag_account_remove_watch (AgAccount *account, AgAccountWatch watch)
{
    AgAccountPrivate *priv;
    GHashTable *service_watches;

    g_return_if_fail (AG_IS_ACCOUNT (account));
    g_return_if_fail (watch != NULL);
    priv = account->priv;

    if (G_LIKELY (priv->watches))
    {
        service_watches = g_hash_table_lookup (priv->watches, watch->service);
        if (G_LIKELY (service_watches &&
                      g_hash_table_remove (service_watches, watch)))
            return; /* success */
    }

    g_warning ("Watch %p not found", watch);
}

/**
 * AgAccountStoreCb:
 * @account: the #AgAccount.
 * @error: a #GError, or %NULL.
 * @user_data: the user data that was passed to ag_account_store().
 *
 * This callback is invoked when storing the account settings is completed. If
 * @error is not %NULL, then some error occurred and the data has most likely
 * not been written.
 */

/**
 * ag_account_store:
 * @account: the #AgAccount.
 * @callback: (scope async): function to be called when the settings have been
 * written.
 * @user_data: pointer to user data, to be passed to @callback.
 *
 * Commit the changed account settings to the account database, and invoke
 * @callback when the operation has been completed.
 *
 * Deprecated: 1.4: Use ag_account_store_async() instead.
 */
void
ag_account_store (AgAccount *account, AgAccountStoreCb callback,
                  gpointer user_data)
{
    AsyncReadyCbWrapperData *cb_data;

    g_return_if_fail (AG_IS_ACCOUNT (account));

    cb_data = g_slice_new (AsyncReadyCbWrapperData);
    cb_data->callback = callback;
    cb_data->user_data = user_data;
    ag_account_store_async (account, NULL, async_ready_cb_wrapper, cb_data);
}

/**
 * ag_account_store_async:
 * @account: the #AgAccount.
 * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
 * @callback: (scope async): function to be called when the settings have been
 * written.
 * @user_data: pointer to user data, to be passed to @callback.
 *
 * Commit the changed account settings to the account database, and invoke
 * @callback when the operation has been completed.
 *
 * Since: 1.4
 */
void
ag_account_store_async (AgAccount *account, GCancellable *cancellable,
                        GAsyncReadyCallback callback, gpointer user_data)
{
    AgAccountPrivate *priv;

    g_return_if_fail (AG_IS_ACCOUNT (account));
    priv = account->priv;

    if (G_UNLIKELY (priv->store_task != NULL))
    {
        g_critical ("ag_account_store_async called again before completion");
        g_task_report_new_error (account,
                                 callback, user_data,
                                 ag_account_store_async,
                                 AG_ACCOUNTS_ERROR,
                                 AG_ACCOUNTS_ERROR_STORE_IN_PROGRESS,
                                 "Store operation already in progress");
        return;
    }

    priv->store_task =
        g_task_new (account, cancellable, callback, user_data);
    g_object_add_weak_pointer ((GObject *)priv->store_task,
                               (gpointer *)&priv->store_task);

    if (G_UNLIKELY (priv->changes == NULL))
    {
        /* Nothing to do: invoke the callback immediately */
        g_task_return_boolean (priv->store_task, TRUE);
        g_clear_object (&priv->store_task);
        return;
    }

    _ag_manager_store_async (priv->manager, account, priv->store_task);
}

/**
 * ag_account_store_finish:
 * @account: the #AgAccount.
 * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to
 * ag_account_store_async().
 * @error: return location for error, or %NULL.
 *
 * Finishes the store operation started by ag_account_store_async().
 *
 * Returns: %TRUE on success, %FALSE otherwise.
 *
 * Since: 1.4
 */
gboolean
ag_account_store_finish (AgAccount *account, GAsyncResult *res,
                         GError **error)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), FALSE);

    return g_task_propagate_boolean (G_TASK (res), error);
}

/**
 * ag_account_store_blocking:
 * @account: the #AgAccount.
 * @error: pointer to receive the #GError, or %NULL.
 *
 * Commit the changed account settings to the account database, and invoke
 * @callback when the operation has been completed.
 *
 * Returns: %TRUE on success, %FALSE on failure.
 */
gboolean
ag_account_store_blocking (AgAccount *account, GError **error)
{
    AgAccountPrivate *priv;

    g_return_val_if_fail (AG_IS_ACCOUNT (account), FALSE);
    priv = account->priv;

    if (G_UNLIKELY (priv->changes == NULL))
    {
        /* Nothing to do: return immediately */
        return TRUE;
    }

    return _ag_manager_store_sync (priv->manager, account, error);
}

/**
 * ag_account_sign:
 * @account: the #AgAccount.
 * @key: the name of the key or prefix of the keys to be signed.
 * @token: a signing token (%NULL-terminated string) for creating the
 * signature. The application must possess (request) the token.
 *
 * Creates signature of the @key with given @token. The account must be
 * stored prior to calling this function.
 */
void
ag_account_sign (G_GNUC_UNUSED AgAccount *account,
                 G_GNUC_UNUSED const gchar *key,
                 G_GNUC_UNUSED const gchar *token)
{
    g_warning ("ag_account_sign: no encryptor supported.");
}

/**
 * ag_account_verify:
 * @account: the #AgAccount.
 * @key: the name of the key or prefix of the keys to be verified.
 * @token: location to receive the pointer to aegis token.
 *
 * Verify if the key is signed and the signature matches the value
 * and provides the aegis token which was used for signing the @key.
 *
 * Returns: %TRUE if the key is signed and the signature matches the value,
 * %FALSE otherwise.
 */
gboolean
ag_account_verify (G_GNUC_UNUSED AgAccount *account,
                   G_GNUC_UNUSED const gchar *key,
                   G_GNUC_UNUSED const gchar **token)
{
    g_warning ("ag_account_verify: no encryptor supported.");
    return FALSE;
}

/**
 * ag_account_verify_with_tokens:
 * @account: the #AgAccount.
 * @key: the name of the key or prefix of the keys to be verified.
 * @tokens: array of aegis tokens.
 *
 * Verify if the @key is signed with any of the tokens from the @tokens
 * and the signature is valid.
 *
 * Returns: %TRUE if the key is signed with any of the given tokens and the
 * signature is valid, %FALSE otherwise.
 */
gboolean
ag_account_verify_with_tokens (AgAccount *account, const gchar *key, const gchar **tokens)
{
    g_return_val_if_fail (AG_IS_ACCOUNT (account), FALSE);

    const gchar *tmp_token = NULL;

    g_return_val_if_fail (tokens != NULL, FALSE);

    if (ag_account_verify (account, key, &tmp_token))
    {
        g_return_val_if_fail (tmp_token != NULL, FALSE);

        while (*tokens != NULL)
        {
            if (strcmp (tmp_token, *tokens) == 0)
            {
                return TRUE;
            }
            tokens++;
        }
    }

    return FALSE;
}
07070100000029000081A4000003E800000064000000015BDC2B8B00001E6D000000000000000000000000000000000000003400000000libaccounts-glib-1.24/libaccounts-glib/ag-account.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_ACCOUNT_H_
#define _AG_ACCOUNT_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <gio/gio.h>
#include <glib-object.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS

#define AG_TYPE_ACCOUNT             (ag_account_get_type ())
#define AG_ACCOUNT(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), AG_TYPE_ACCOUNT, AgAccount))
#define AG_ACCOUNT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), AG_TYPE_ACCOUNT, AgAccountClass))
#define AG_IS_ACCOUNT(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), AG_TYPE_ACCOUNT))
#define AG_IS_ACCOUNT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), AG_TYPE_ACCOUNT))
#define AG_ACCOUNT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), AG_TYPE_ACCOUNT, AgAccountClass))

typedef struct _AgAccountClass AgAccountClass;
typedef struct _AgAccountPrivate AgAccountPrivate;

/**
 * AgAccountClass:
 *
 * Use the accessor functions below.
 */
struct _AgAccountClass
{
    GObjectClass parent_class;
    void (*_ag_reserved1) (void);
    void (*_ag_reserved2) (void);
    void (*_ag_reserved3) (void);
    void (*_ag_reserved4) (void);
    void (*_ag_reserved5) (void);
    void (*_ag_reserved6) (void);
    void (*_ag_reserved7) (void);
};

struct _AgAccount
{
    GObject parent_instance;
    AgAccountId id;

    /*< private >*/
    AgAccountPrivate *priv;
};

GType ag_account_get_type (void) G_GNUC_CONST;

gboolean ag_account_supports_service (AgAccount *account,
                                      const gchar *service_type);
GList *ag_account_list_services (AgAccount *account);
GList *ag_account_list_services_by_type (AgAccount *account,
                                         const gchar *service_type);
GList *ag_account_list_enabled_services (AgAccount *account);

AgManager *ag_account_get_manager (AgAccount *account);

const gchar *ag_account_get_provider_name (AgAccount *account);

const gchar *ag_account_get_display_name (AgAccount *account);
void ag_account_set_display_name (AgAccount *account,
                                  const gchar *display_name);

/* Account configuration */
void ag_account_select_service (AgAccount *account, AgService *service);
AgService *ag_account_get_selected_service (AgAccount *account);

gboolean ag_account_get_enabled (AgAccount *account);
void ag_account_set_enabled (AgAccount *account, gboolean enabled);

void ag_account_delete (AgAccount *account);

/**
 * AgSettingSource:
 * @AG_SETTING_SOURCE_NONE: the setting is not present
 * @AG_SETTING_SOURCE_ACCOUNT: the setting comes from the current account
 * configuration
 * @AG_SETTING_SOURCE_PROFILE: the setting comes from the predefined profile
 *
 * The source of a setting on a #AgAccount.
 */
typedef enum {
    AG_SETTING_SOURCE_NONE = 0,
    AG_SETTING_SOURCE_ACCOUNT,
    AG_SETTING_SOURCE_PROFILE,
} AgSettingSource;

#ifndef AG_DISABLE_DEPRECATED
AG_DEPRECATED_FOR(ag_account_get_variant)
AgSettingSource ag_account_get_value (AgAccount *account, const gchar *key,
                                      GValue *value);
AG_DEPRECATED_FOR(ag_account_set_variant)
void ag_account_set_value (AgAccount *account, const gchar *key,
                           const GValue *value);
#endif

GVariant *ag_account_get_variant (AgAccount *account, const gchar *key,
                                  AgSettingSource *source);
void ag_account_set_variant (AgAccount *account, const gchar *key,
                             GVariant *value);


typedef struct _AgAccountSettingIter AgAccountSettingIter;

/**
 * AgAccountSettingIter:
 * @account: the AgAccount to iterate over
 *
 * Iterator for account settings.
 */
struct _AgAccountSettingIter {
    AgAccount *account;
    /*< private >*/
    GHashTableIter iter1;
    gpointer ptr1;
    gpointer ptr2;
    gint idx1;
    gint idx2;
};

GType ag_account_settings_iter_get_type (void) G_GNUC_CONST;

void ag_account_settings_iter_free (AgAccountSettingIter *iter);

void ag_account_settings_iter_init (AgAccount *account,
                                    AgAccountSettingIter *iter,
                                    const gchar *key_prefix);
#ifndef AG_DISABLE_DEPRECATED
AG_DEPRECATED_FOR(ag_account_settings_iter_get_next)
gboolean ag_account_settings_iter_next (AgAccountSettingIter *iter,
                                        const gchar **key,
                                        const GValue **value);
#endif
gboolean ag_account_settings_iter_get_next (AgAccountSettingIter *iter,
                                            const gchar **key,
                                            GVariant **value);

AgAccountSettingIter *ag_account_get_settings_iter (AgAccount *account,
                                                    const gchar *key_prefix);

/**
 * AgAccountWatch:
 *
 * An opaque struct returned from ag_account_watch_dir() and
 * ag_account_watch_key().
 */
typedef struct _AgAccountWatch *AgAccountWatch;

typedef void (*AgAccountNotifyCb) (AgAccount *account, const gchar *key,
                                   gpointer user_data);
AgAccountWatch ag_account_watch_key (AgAccount *account,
                                     const gchar *key,
                                     AgAccountNotifyCb callback,
                                     gpointer user_data);
AgAccountWatch ag_account_watch_dir (AgAccount *account,
                                     const gchar *key_prefix,
                                     AgAccountNotifyCb callback,
                                     gpointer user_data);
void ag_account_remove_watch (AgAccount *account, AgAccountWatch watch);

#ifndef AG_DISABLE_DEPRECATED
typedef void (*AgAccountStoreCb) (AgAccount *account, const GError *error,
                                  gpointer user_data);
AG_DEPRECATED_FOR(ag_account_store_async)
void ag_account_store (AgAccount *account, AgAccountStoreCb callback,
                       gpointer user_data);
#endif
void ag_account_store_async (AgAccount *account,
                             GCancellable *cancellable,
                             GAsyncReadyCallback callback,
                             gpointer user_data);
gboolean ag_account_store_finish (AgAccount *account,
                                  GAsyncResult *res,
                                  GError **error);

gboolean ag_account_store_blocking (AgAccount *account, GError **error);

void ag_account_sign (AgAccount *account, const gchar *key, const gchar *token);

gboolean ag_account_verify (AgAccount *account, const gchar *key, const gchar **token);

gboolean ag_account_verify_with_tokens (AgAccount *account, const gchar *key, const gchar **tokens);

/* Signon */
/* TODO: depends on signon-glib */

G_END_DECLS

#endif /* _AG_ACCOUNT_H_ */
0707010000002A000081A4000003E800000064000000015BDC2B8B00003F7A000000000000000000000000000000000000003800000000libaccounts-glib-1.24/libaccounts-glib/ag-application.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-application
 * @short_description: information on the client applications of libaccounts.
 * @include: libaccounts-glib/ag-application.h
 *
 * The #AgApplication structure holds information on the client applications
 * registered with libaccounts.
 * It is instantiated by #AgManager with ag_manager_get_application() and
 * ag_manager_list_applications_by_service(), and destroyed with
 * ag_application_unref().
 *
 * <example>
 * <title>Querying application names for an
 * <structname>AgService</structname></title>
 * <programlisting>
 * AgManager *manager;
 * GList *services, *applications;
 * AgService *service;
 *
 * manager = ag_manager_new ();
 * services = ag_manager_list_services (manager);
 * g_assert (services != NULL);
 * service = (AgService *) services->data;
 * applications = ag_manager_list_applications_by_service (manager, service);
 *
 * g_print ("Service type: %s\n", ag_service_get_name (service));
 * for (applications; applications != NULL; applications = applications->next)
 * {
 *     const gchar *application_name = ag_application_get_name ((AgApplication *) applications->data);
 *     g_print ("  Application name: %s\n", application_name);
 * }
 * </programlisting>
 * </example>
 */

#include "ag-application.h"
#include "ag-internals.h"
#include "ag-service.h"
#include "ag-util.h"

#include <libxml/xmlreader.h>
#include <string.h>

struct _AgApplication {
    /*< private >*/
    gint ref_count;

    gchar *name;
    gchar *desktop_entry;
    gchar *description;
    gchar *i18n_domain;

    GDesktopAppInfo *desktop_app_info;
    gboolean desktop_app_info_loaded;

    /* the values of these hash tables are AgApplicationItem elements */
    GHashTable *services;
    GHashTable *service_types;
};

typedef struct {
    gchar *description;
    /* more fields could be added later on (for instance, supported features) */
} AgApplicationItem;

G_DEFINE_BOXED_TYPE (AgApplication, ag_application,
                     (GBoxedCopyFunc)ag_application_ref,
                     (GBoxedFreeFunc)ag_application_unref);

static void
_ag_application_item_free (AgApplicationItem *item)
{
    g_free (item->description);
    g_slice_free (AgApplicationItem, item);
}

static AgApplicationItem *
_ag_application_get_service_item (AgApplication *self, AgService *service)
{
    AgApplicationItem *item = NULL;

    if (self->services != NULL)
        item = g_hash_table_lookup (self->services, service->name);
    if (item == NULL && self->service_types != NULL)
    {
        item = g_hash_table_lookup (self->service_types,
                                    ag_service_get_service_type (service));
    }

    return item;
}

static inline void
_ag_application_ensure_desktop_app_info (AgApplication *self)
{
    if (!self->desktop_app_info_loaded)
    {
        const char *filename = self->desktop_entry != NULL ?
            self->desktop_entry : self->name;
        gchar *filename_tmp = NULL;
        if (!g_str_has_suffix (filename, ".desktop"))
        {
            filename_tmp = g_strconcat (filename, ".desktop", NULL);
            filename = filename_tmp;
        }

        self->desktop_app_info = g_desktop_app_info_new (filename);
        self->desktop_app_info_loaded = TRUE;
        g_free (filename_tmp);
    }
}

static gboolean
parse_item (xmlTextReaderPtr reader, GHashTable *hash_table,
            const gchar *item_tag)
{
    AgApplicationItem *item;
    xmlChar *xml_item_id;
    gchar *item_id;
    int ret, type;

    xml_item_id = xmlTextReaderGetAttribute (reader,
                                             (xmlChar *)"id");
    if (G_UNLIKELY (xml_item_id == NULL))
    {
        g_warning ("Found element %s with no \"id\" attribute",
                   item_tag);
        return FALSE;
    }
    item_id = g_strdup ((const gchar*)xml_item_id);
    xmlFree (xml_item_id);

    item = g_slice_new0 (AgApplicationItem);
    g_hash_table_insert (hash_table, item_id, item);

    if (xmlTextReaderIsEmptyElement (reader)) return TRUE;

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        const gchar *name = (const gchar *)xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!name)) return FALSE;

        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_END_ELEMENT &&
            strcmp (name, item_tag) == 0)
            break;

        if (type == XML_READER_TYPE_ELEMENT)
        {
            gboolean ok;

            if (strcmp (name, "description") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &item->description);
            }
            else
                ok = TRUE;

            if (G_UNLIKELY (!ok)) return FALSE;
        }

        ret = xmlTextReaderNext (reader);
    }
    return TRUE;
}

static gboolean
parse_items (xmlTextReaderPtr reader,
             GHashTable **hash_table,
             const gchar *item_tag)
{
    const gchar *name;

    if (*hash_table == NULL)
    {
        *hash_table =
            g_hash_table_new_full (g_str_hash, g_str_equal,
                                   g_free,
                                   (GDestroyNotify)_ag_application_item_free);
    }

    int ret, type;

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = (const gchar *)xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!name)) return FALSE;

        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_END_ELEMENT)
            break;

        if (type == XML_READER_TYPE_ELEMENT)
        {
            gboolean ok;

            if (strcmp (name, item_tag) == 0)
            {
                ok = parse_item (reader, *hash_table, item_tag);
            }
            else
            {
                /* ignore unrecognized elements */
                ok = TRUE;
            }

            if (G_UNLIKELY (!ok)) return FALSE;
        }

        ret = xmlTextReaderNext (reader);
    }
    return TRUE;
}

static gboolean
parse_application (xmlTextReaderPtr reader, AgApplication *application)
{
    const gchar *name;
    int ret, type;

    if (!application->name)
    {
        xmlChar *_name = xmlTextReaderGetAttribute (reader,
                                                    (xmlChar *) "id");
        application->name = g_strdup ((const gchar *)_name);
        if (_name) xmlFree(_name);
    }

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = (const gchar *)xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!name)) return FALSE;

        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_END_ELEMENT &&
            strcmp (name, "application") == 0)
            break;

        if (type == XML_READER_TYPE_ELEMENT)
        {
            gboolean ok;

            if (strcmp (name, "desktop-entry") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &application->desktop_entry);
            }
            else if (strcmp (name, "description") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &application->description);
            }
            else if (strcmp (name, "translations") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &application->i18n_domain);
            }
            else if (strcmp (name, "services") == 0)
            {
                ok = parse_items (reader, &application->services,
                                  "service");
            }
            else if (strcmp (name, "service-types") == 0)
            {
                ok = parse_items (reader, &application->service_types,
                                  "service-type");
            }
            else
                ok = TRUE;

            if (G_UNLIKELY (!ok)) return FALSE;
        }

        ret = xmlTextReaderNext (reader);
    }
    return TRUE;
}

static gboolean
read_application_file (xmlTextReaderPtr reader, AgApplication *application)
{
    const xmlChar *name;
    int ret;

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = xmlTextReaderConstName (reader);
        if (G_LIKELY (name &&
                      strcmp ((const gchar *)name, "application") == 0))
        {
            return parse_application (reader, application);
        }

        ret = xmlTextReaderNext (reader);
    }
    return FALSE;
}

static gboolean
_ag_application_load_from_file (AgApplication *application)
{
    xmlTextReaderPtr reader;
    gchar *filepath;
    gboolean ret = FALSE;
    GError *error = NULL;
    gchar *file_data;
    gsize file_data_len;

    g_return_val_if_fail (application->name != NULL, FALSE);

    DEBUG_REFS ("Loading application %s", application->name);
    filepath = _ag_find_libaccounts_file (application->name,
                                          ".application",
                                          "AG_APPLICATIONS",
                                          APPLICATION_FILES_DIR);
    if (G_UNLIKELY (!filepath)) return FALSE;

    g_file_get_contents (filepath, &file_data, &file_data_len, &error);
    if (G_UNLIKELY (error))
    {
        g_warning ("Error reading %s: %s", filepath, error->message);
        g_error_free (error);
        g_free (filepath);
        return FALSE;
    }

    reader = xmlReaderForMemory (file_data, file_data_len, filepath, NULL, 0);
    g_free (filepath);
    if (G_UNLIKELY (reader == NULL))
        goto err_reader;

    ret = read_application_file (reader, application);

    xmlFreeTextReader (reader);
err_reader:
    g_free (file_data);
    return ret;
}

static gint compare_service_name (gconstpointer a, gconstpointer b)
{
    AgService *service = (AgService *)a;
    const gchar *name = b;
    return g_strcmp0 (ag_service_get_name (service), name);
}

AgApplication *
_ag_application_new_from_file (const gchar *application_name)
{
    AgApplication *application;

    application = g_slice_new0 (AgApplication);
    application->ref_count = 1;
    application->name = g_strdup (application_name);

    if (!_ag_application_load_from_file (application))
    {
        ag_application_unref (application);
        application = NULL;
    }

    return application;
}

GList *
_ag_application_list_supported_services (AgApplication *self, AgManager *manager)
{
    GHashTableIter iter;
    GList *ret = NULL;
    gchar *key;
    AgService *service;

    g_return_val_if_fail (self != NULL, NULL);

    if (self->service_types)
    {
        g_hash_table_iter_init (&iter, self->service_types);
        while (g_hash_table_iter_next (&iter, (gpointer)&key, NULL))
        {
            GList *services = ag_manager_list_services_by_type (manager, key);
            ret = g_list_concat (ret, services);
        }
    }

    if (self->services)
    {
        g_hash_table_iter_init (&iter, self->services);
        while (g_hash_table_iter_next (&iter, (gpointer)&key, NULL))
        {
            if (g_list_find_custom (ret, key, compare_service_name)) continue;

            service = ag_manager_get_service (manager, key);
            if (service)
            {
                ret = g_list_prepend (ret, service);
            }
        }
    }

    return ret;
}

/**
 * ag_application_get_name:
 * @self: the #AgApplication.
 *
 * Get the name of the #AgApplication.
 *
 * Returns: the name of @self.
 */
const gchar *
ag_application_get_name (AgApplication *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    return self->name;
}

/**
 * ag_application_get_description:
 * @self: the #AgApplication.
 *
 * Get the description of the #AgApplication.
 *
 * Returns: the description of @self.
 */
const gchar *
ag_application_get_description (AgApplication *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    if (self->description == NULL)
    {
        _ag_application_ensure_desktop_app_info (self);
        if (self->desktop_app_info != NULL)
        {
            return g_app_info_get_description (G_APP_INFO
                                               (self->desktop_app_info));
        }
    }

    return self->description;
}

/**
 * ag_application_get_i18n_domain:
 * @self: the #AgApplication.
 *
 * Get the translation domain of the #AgApplication.
 *
 * Returns: the translation domain.
 */
const gchar *
ag_application_get_i18n_domain (AgApplication *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    return self->i18n_domain;
}

/**
 * ag_application_get_desktop_app_info:
 * @self: the #AgApplication.
 *
 * Get the #GDesktopAppInfo of the application.
 *
 * Returns: (transfer full): the #GDesktopAppInfo for @self, or %NULL if
 * failed.
 */
GDesktopAppInfo *
ag_application_get_desktop_app_info (AgApplication *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    _ag_application_ensure_desktop_app_info (self);
    return self->desktop_app_info != NULL ?
        g_object_ref (self->desktop_app_info) : NULL;
}

/**
 * ag_application_supports_service:
 * @self: the #AgApplication.
 * @service: an #AgService.
 *
 * Check whether the application supports the given service.
 *
 * Returns: %TRUE if @service is supported, %FALSE otherwise.
 */
gboolean
ag_application_supports_service (AgApplication *self, AgService *service)
{
    AgApplicationItem *item;

    g_return_val_if_fail (self != NULL, FALSE);
    g_return_val_if_fail (service != NULL, FALSE);

    item = _ag_application_get_service_item (self, service);

    return item != NULL;
}

/**
 * ag_application_get_service_usage:
 * @self: the #AgApplication.
 * @service: an #AgService.
 *
 * Get the description from the application XML file, for the specified
 * service; if not found, get the service-type description instead.
 *
 * Returns: usage description of the service.
 */
const gchar *
ag_application_get_service_usage(AgApplication *self, AgService *service)
{
    AgApplicationItem *item;

    g_return_val_if_fail (self != NULL, NULL);
    g_return_val_if_fail (service != NULL, NULL);

    item = _ag_application_get_service_item (self, service);

    return (item != NULL) ? item->description : NULL;
}

/**
 * ag_application_ref:
 * @self: the #AgApplication.
 *
 * Increment the reference count of @self.
 *
 * Returns: @self.
 */
AgApplication *
ag_application_ref (AgApplication *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    g_atomic_int_inc (&self->ref_count);
    return self;
}

/**
 * ag_application_unref:
 * @self: the #AgApplication.
 *
 * Decrements the reference count of @self. The item is destroyed when the
 * count gets to 0.
 */
void
ag_application_unref (AgApplication *self)
{
    g_return_if_fail (self != NULL);
    if (g_atomic_int_dec_and_test (&self->ref_count))
    {
        g_free (self->name);
        g_free (self->desktop_entry);
        g_free (self->description);
        g_free (self->i18n_domain);

        if (self->desktop_app_info != NULL)
            g_object_unref (self->desktop_app_info);
        if (self->services != NULL)
            g_hash_table_unref (self->services);
        if (self->service_types != NULL)
            g_hash_table_unref (self->service_types);

        g_slice_free (AgApplication, self);
    }
}

0707010000002B000081A4000003E800000064000000015BDC2B8B000007CE000000000000000000000000000000000000003800000000libaccounts-glib-1.24/libaccounts-glib/ag-application.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_APPLICATION_H_
#define _AG_APPLICATION_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <gio/gdesktopappinfo.h>
#include <glib-object.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS

GType ag_application_get_type (void) G_GNUC_CONST;

const gchar *ag_application_get_name (AgApplication *self);
const gchar *ag_application_get_description (AgApplication *self);
const gchar *ag_application_get_i18n_domain (AgApplication *self);

GDesktopAppInfo *ag_application_get_desktop_app_info (AgApplication *self);

gboolean ag_application_supports_service (AgApplication *self,
                                          AgService *service);
const gchar *ag_application_get_service_usage(AgApplication *self,
                                              AgService *service);

AgApplication *ag_application_ref (AgApplication *self);
void ag_application_unref (AgApplication *self);

G_END_DECLS

#endif /* _AG_APPLICATION_H_ */
0707010000002C000081A4000003E800000064000000015BDC2B8B00002AAB000000000000000000000000000000000000003600000000libaccounts-glib-1.24/libaccounts-glib/ag-auth-data.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-auth-data
 * @short_description: information for account authentication.
 * @include: libaccounts-glib/ag-auth-data.h
 *
 * The #AgAuthData structure holds information on the authentication
 * parameters used by an account. It is created by
 * ag_account_service_get_auth_data(), and can be destroyed with
 * ag_auth_data_unref().
 */

#define AG_DISABLE_DEPRECATION_WARNINGS

#include "ag-auth-data.h"
#include "ag-internals.h"
#include "ag-util.h"

struct _AgAuthData {
    /*< private >*/
    gint ref_count;
    guint credentials_id;
    gchar *method;
    gchar *mechanism;
    GHashTable *parameters;
    /* For compatibility with the deprecated API */
    GHashTable *parameters_compat;
};

G_DEFINE_BOXED_TYPE (AgAuthData, ag_auth_data,
                     (GBoxedCopyFunc)ag_auth_data_ref,
                     (GBoxedFreeFunc)ag_auth_data_unref);

static GVariant *
get_value_with_fallback (AgAccount *account, AgService *service,
                         const gchar *key)
{
    GVariant *value;
    ag_account_select_service (account, service);
    value = ag_account_get_variant (account, key, NULL);
    if (value == NULL && service != NULL)
    {
        /* fallback to the global account */
        ag_account_select_service (account, NULL);
        value = ag_account_get_variant (account, key, NULL);
    }

    return value;
}

static gchar *
get_string_with_fallback (AgAccount *account, AgService *service,
                          const gchar *key)
{
    GVariant *value;

    value = get_value_with_fallback (account, service, key);
    if (value == NULL)
        return NULL;

    return g_variant_dup_string (value, NULL);
}

static guint32
get_uint_with_fallback (AgAccount *account, AgService *service,
                        const gchar *key)
{
    GVariant *value;

    value = get_value_with_fallback (account, service, key);
    if (value == NULL)
        return 0;

    return g_variant_get_uint32 (value);
}

static void
read_auth_settings (AgAccount *account, const gchar *key_prefix,
                    GHashTable *out)
{
    AgAccountSettingIter iter;
    const gchar *key;
    GVariant *value;

    ag_account_settings_iter_init (account, &iter, key_prefix);
    while (ag_account_settings_iter_get_next (&iter, &key, &value))
    {
        g_hash_table_insert (out, g_strdup (key), g_variant_ref (value));
    }
}

AgAuthData *
_ag_auth_data_new (AgAccount *account, AgService *service)
{
    guint32 credentials_id;
    gchar *method, *mechanism;
    gchar *key_prefix;
    GHashTable *parameters;
    AgAuthData *data = NULL;

    g_return_val_if_fail (account != NULL, NULL);

    credentials_id = get_uint_with_fallback (account, service, "CredentialsId");

    method = get_string_with_fallback (account, service, "auth/method");
    mechanism = get_string_with_fallback (account, service, "auth/mechanism");

    parameters = g_hash_table_new_full (g_str_hash, g_str_equal,
                                        g_free,
                                        (GDestroyNotify)g_variant_unref);
    key_prefix = g_strdup_printf ("auth/%s/%s/", method, mechanism);

    /* first, take the values from the global account */
    ag_account_select_service (account, NULL);
    read_auth_settings (account, key_prefix, parameters);

    /* next, the service-specific authentication settings */
    if (service != NULL)
    {
        ag_account_select_service (account, service);
        read_auth_settings (account, key_prefix, parameters);
    }

    g_free (key_prefix);

    data = g_slice_new (AgAuthData);
    data->ref_count = 1;
    data->credentials_id = credentials_id;
    data->method = method;
    data->mechanism = mechanism;
    data->parameters = parameters;
    data->parameters_compat = NULL;
    return data;
}

/**
 * ag_auth_data_ref:
 * @self: the #AgAuthData.
 *
 * Increment the reference count of @self.
 *
 * Returns: @self.
 */
AgAuthData *
ag_auth_data_ref (AgAuthData *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    g_atomic_int_inc (&self->ref_count);
    return self;
}

/**
 * ag_auth_data_unref:
 * @self: the #AgAuthData.
 *
 * Decrements the reference count of @self. The item is destroyed when the
 * count gets to 0.
 */
void
ag_auth_data_unref (AgAuthData *self)
{
    g_return_if_fail (self != NULL);
    if (g_atomic_int_dec_and_test (&self->ref_count))
    {
        g_free (self->method);
        g_free (self->mechanism);
        g_hash_table_unref (self->parameters);
        if (self->parameters_compat != NULL)
            g_hash_table_unref (self->parameters_compat);
        g_slice_free (AgAuthData, self);
    }
}

/**
 * ag_auth_data_get_credentials_id:
 * @self: the #AgAuthData.
 *
 * Gets the ID of the credentials associated with this account.
 *
 * Returns: the credentials ID.
 *
 * Since: 1.1
 */
guint
ag_auth_data_get_credentials_id (AgAuthData *self)
{
    g_return_val_if_fail (self != NULL, 0);
    return self->credentials_id;
}

/**
 * ag_auth_data_get_method:
 * @self: the #AgAuthData.
 *
 * Gets the authentication method.
 *
 * Returns: the authentication method.
 */
const gchar *
ag_auth_data_get_method (AgAuthData *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    return self->method;
}

/**
 * ag_auth_data_get_mechanism:
 * @self: the #AgAuthData.
 *
 * Gets the authentication mechanism.
 *
 * Returns: the authentication mechanism.
 */
const gchar *
ag_auth_data_get_mechanism (AgAuthData *self)
{
    g_return_val_if_fail (self != NULL, NULL);
    return self->mechanism;
}

/**
 * ag_auth_data_get_parameters:
 * @self: the #AgAuthData.
 *
 * Gets the authentication parameters.
 *
 * Returns: (transfer none) (element-type utf8 GValue): a #GHashTable
 * containing all the authentication parameters.
 *
 * Deprecated: 1.4: use ag_auth_data_get_login_parameters() instead.
 */
GHashTable *
ag_auth_data_get_parameters (AgAuthData *self)
{
    g_return_val_if_fail (self != NULL, NULL);

    if (self->parameters_compat == NULL)
    {
        GHashTableIter iter;
        const gchar *key;
        GVariant *variant;

        self->parameters_compat =
            g_hash_table_new_full (g_str_hash, g_str_equal,
                                   g_free,
                                   (GDestroyNotify)_ag_value_slice_free);

        /* Convert the GVariants into GValues */
        g_hash_table_iter_init (&iter, self->parameters);
        while (g_hash_table_iter_next (&iter,
                                       (gpointer)&key, (gpointer)&variant))
        {
            GValue *value = g_slice_new0 (GValue);
            _ag_value_from_variant (value, variant);
            g_hash_table_insert (self->parameters_compat,
                                 g_strdup (key), value);
        }

    }

    return self->parameters_compat;
}

/**
 * ag_auth_data_insert_parameters:
 * @self: the #AgAuthData.
 * @parameters: (transfer none) (element-type utf8 GValue): a #GHashTable
 * containing the authentication parameters to be added.
 *
 * Insert the given authentication parameters into the authentication data. If
 * some parameters were already present, the parameters passed with this method
 * take precedence.
 *
 * Deprecated: 1.4: use ag_auth_data_get_login_parameters() instead.
 */
void
ag_auth_data_insert_parameters (AgAuthData *self, GHashTable *parameters)
{
    GHashTable *self_parameters;
    GHashTableIter iter;
    const gchar *key;
    const GValue *value;

    g_return_if_fail (self != NULL);
    g_return_if_fail (parameters != NULL);

    self_parameters = ag_auth_data_get_parameters (self);
    g_hash_table_iter_init (&iter, parameters);
    while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&value))
    {
        g_hash_table_insert (self_parameters,
                             g_strdup (key),
                             _ag_value_slice_dup (value));
    }
}

/**
 * ag_auth_data_get_login_parameters:
 * @self: the #AgAuthData.
 * @extra_parameters: (transfer floating) (allow-none): a #GVariant containing
 * client-specific authentication parameters to be added to the returned
 * dictionary.
 *
 * Gets the authentication parameters.
 *
 * Returns: (transfer none): a floating #GVariant of type
 * %G_VARIANT_TYPE_VARDICT containing all the authentication parameters.
 *
 * Since: 1.4
 */
GVariant *
ag_auth_data_get_login_parameters (AgAuthData *self, GVariant *extra_parameters)
{
    GVariantBuilder builder;
    GHashTableIter iter;
    gchar *key;
    GVariant *value;
    GSList *skip_keys = NULL;

    g_return_val_if_fail (self != NULL, NULL);

    g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);

    /* Put the parameters from the client. */
    if (extra_parameters != NULL)
    {
        GVariantIter i_extra;

        g_variant_ref_sink (extra_parameters);
        g_variant_iter_init (&i_extra, extra_parameters);
        while (g_variant_iter_next (&i_extra, "{&sv}", &key, &value))
        {
            g_variant_builder_add (&builder, "{sv}", key, value);
            g_variant_unref (value);

            /* Make sure we are not going to add the same key later */
            if (g_hash_table_lookup (self->parameters, key))
                skip_keys = g_slist_prepend (skip_keys, g_strdup (key));
        }
        g_variant_unref (extra_parameters);
    }

    /* Put the parameters from the account first. */
    g_hash_table_iter_init (&iter, self->parameters);
    while (g_hash_table_iter_next (&iter,
                                   (gpointer)&key, (gpointer)&value))
    {
        /* If the key is also present in extra_parameters, then don't add it */
        if (!g_slist_find_custom (skip_keys, key, (GCompareFunc)g_strcmp0))
            g_variant_builder_add (&builder, "{sv}", key, value);
    }

    /* Free the skip_keys list */
    while (skip_keys != NULL)
    {
        g_free (skip_keys->data);
        skip_keys = g_slist_delete_link (skip_keys, skip_keys);
    }

    return g_variant_builder_end (&builder);
}
0707010000002D000081A4000003E800000064000000015BDC2B8B000007DA000000000000000000000000000000000000003600000000libaccounts-glib-1.24/libaccounts-glib/ag-auth-data.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_AUTH_DATA_H_
#define _AG_AUTH_DATA_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib-object.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS

GType ag_auth_data_get_type (void) G_GNUC_CONST;

AgAuthData *ag_auth_data_ref (AgAuthData *self);
void ag_auth_data_unref (AgAuthData *self);

guint ag_auth_data_get_credentials_id (AgAuthData *self);
const gchar *ag_auth_data_get_method (AgAuthData *self);
const gchar *ag_auth_data_get_mechanism (AgAuthData *self);

#ifndef AG_DISABLE_DEPRECATED
AG_DEPRECATED_FOR(ag_auth_data_get_login_parameters)
GHashTable *ag_auth_data_get_parameters (AgAuthData *self);

AG_DEPRECATED_FOR(ag_auth_data_get_login_parameters)
void ag_auth_data_insert_parameters (AgAuthData *self, GHashTable *parameters);
#endif

GVariant *ag_auth_data_get_login_parameters (AgAuthData *self,
                                             GVariant *extra_parameters);

G_END_DECLS

#endif /* _AG_AUTH_DATA_H_ */
0707010000002E000081A4000003E800000064000000015BDC2B8B00000684000000000000000000000000000000000000003200000000libaccounts-glib-1.24/libaccounts-glib/ag-debug.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include "ag-debug.h"

static const GDebugKey debug_keys[] = {
    { "time", AG_DEBUG_TIME },
    { "refs", AG_DEBUG_REFS },
    { "locks", AG_DEBUG_LOCKS },
    { "queries", AG_DEBUG_QUERIES },
    { "info", AG_DEBUG_INFO },
};

static AgDebugLevel debug_level = AG_DEBUG_LOCKS;

void
_ag_debug_init (void)
{
    const gchar *env;
    static gboolean initialized = FALSE;

    if (initialized) return;

    initialized = TRUE;
    env = g_getenv ("AG_DEBUG");
    if (env)
    {
        debug_level = g_parse_debug_string (env, debug_keys,
                                            G_N_ELEMENTS(debug_keys));
    }
}

AgDebugLevel
_ag_debug_get_level (void)
{
    return debug_level;
}

0707010000002F000081A4000003E800000064000000015BDC2B8B00000B0C000000000000000000000000000000000000003200000000libaccounts-glib-1.24/libaccounts-glib/ag-debug.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_DEBUG_H_
#define _AG_DEBUG_H_

#include <glib.h>
#include <time.h>

G_BEGIN_DECLS

G_GNUC_INTERNAL
void _ag_debug_init (void);

typedef enum {
    AG_DEBUG_TIME = 1 << 0,
    AG_DEBUG_REFS = 1 << 1,
    AG_DEBUG_LOCKS = 1 << 2,
    AG_DEBUG_QUERIES = 1 << 3,
    AG_DEBUG_INFO = 1 << 4,
    AG_DEBUG_ALL = 0xffffffff
} AgDebugLevel;

G_GNUC_INTERNAL
AgDebugLevel _ag_debug_get_level (void);

#ifdef ENABLE_DEBUG

#define DEBUG(level, format, ...) G_STMT_START {                   \
    if (_ag_debug_get_level() & level)                         \
        g_debug("%s: " format, G_STRFUNC, ##__VA_ARGS__);   \
} G_STMT_END

/* Macros for profiling */
#define TIME_START() \
    struct timespec tm0, tm1; \
    struct timespec tt0, tt1; \
    long ms_mdiff; \
    long ms_tdiff; \
    clock_gettime(CLOCK_MONOTONIC, &tm0); \
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tt0)

#define TIME_STOP() \
    clock_gettime(CLOCK_MONOTONIC, &tm1); \
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tt1); \
    ms_mdiff = (tm1.tv_sec - tm0.tv_sec) * 1000 + \
               (tm1.tv_nsec - tm0.tv_nsec) / 1000000; \
    ms_tdiff = (tt1.tv_sec - tt0.tv_sec) * 1000 + \
               (tt1.tv_nsec - tt0.tv_nsec) / 1000000; \
    DEBUG_TIME("%s, total %ld ms, thread %ld ms", G_STRLOC, ms_mdiff, ms_tdiff)

#else /* !ENABLE_DEBUG */

#define DEBUG(level, format, ...)
#define TIME_START()
#define TIME_STOP()

#endif

#define DEBUG_TIME(format, ...) DEBUG(AG_DEBUG_TIME, format, ##__VA_ARGS__)
#define DEBUG_REFS(format, ...) DEBUG(AG_DEBUG_REFS, format, ##__VA_ARGS__)
#define DEBUG_LOCKS(format, ...) DEBUG(AG_DEBUG_LOCKS, format, ##__VA_ARGS__)
#define DEBUG_QUERIES(format, ...) \
    DEBUG(AG_DEBUG_QUERIES, format, ##__VA_ARGS__)
#define DEBUG_INFO(format, ...) DEBUG(AG_DEBUG_INFO, format, ##__VA_ARGS__)

G_END_DECLS

#endif /* _AG_DEBUG_H_ */
07070100000030000081A4000003E800000064000000015BDC2B8B00000ADC000000000000000000000000000000000000003300000000libaccounts-glib-1.24/libaccounts-glib/ag-errors.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_ERRORS_H_
#define _AG_ERRORS_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib.h>

G_BEGIN_DECLS

GQuark ag_errors_quark (void);
GQuark ag_accounts_error_quark (void);

#define AG_ERRORS   ag_errors_quark ()

/**
 * AG_ACCOUNTS_ERROR:
 *
 * Error domain for libaccounts-glib errors. Errors in this domain will be from
 * the AgAccountsError enumeration.
 */
#define AG_ACCOUNTS_ERROR AG_ERRORS

typedef enum {
    AG_ERROR_DB,
    AG_ERROR_DISPOSED,
    AG_ERROR_DELETED,
    AG_ERROR_DB_LOCKED,
    AG_ERROR_ACCOUNT_NOT_FOUND,
} AgError;

/**
 * AgAccountsError:
 * @AG_ACCOUNTS_ERROR_DB: there was an error accessing the accounts database
 * @AG_ACCOUNTS_ERROR_DISPOSED: the account was in the process of being
 * disposed
 * @AG_ACCOUNTS_ERROR_DELETED: the account was in the process of being deleted
 * @AG_ACCOUNTS_ERROR_DB_LOCKED: the database was locked
 * @AG_ACCOUNTS_ERROR_ACCOUNT_NOT_FOUND: the requested account was not found
 * @AG_ACCOUNTS_ERROR_STORE_IN_PROGRESS: an asynchronous store operation is
 * already in progress. Since 1.4
 * @AG_ACCOUNTS_ERROR_READONLY: the accounts DB is in read-only mode. Since 1.4
 *
 * These identify the various errors that can occur with methods in
 * libaccounts-glib that return a #GError.
 */
typedef enum {
    AG_ACCOUNTS_ERROR_DB = AG_ERROR_DB,
    AG_ACCOUNTS_ERROR_DISPOSED = AG_ERROR_DISPOSED,
    AG_ACCOUNTS_ERROR_DELETED = AG_ERROR_DELETED,
    AG_ACCOUNTS_ERROR_DB_LOCKED = AG_ERROR_DB_LOCKED,
    AG_ACCOUNTS_ERROR_ACCOUNT_NOT_FOUND = AG_ERROR_ACCOUNT_NOT_FOUND,
    AG_ACCOUNTS_ERROR_STORE_IN_PROGRESS,
    AG_ACCOUNTS_ERROR_READONLY,
} AgAccountsError;

G_END_DECLS

#endif /* _AG_ERRORS_H_ */
07070100000031000081A4000003E800000064000000015BDC2B8B00001D8A000000000000000000000000000000000000003600000000libaccounts-glib-1.24/libaccounts-glib/ag-internals.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_INTERNALS_H_
#define _AG_INTERNALS_H_

#include "ag-account.h"
#include "ag-auth-data.h"
#include "ag-debug.h"
#include "ag-manager.h"
#include <sqlite3.h>
#include <time.h>

G_BEGIN_DECLS

#define AG_DBUS_PATH_SERVICE "/ServiceType"
#define AG_DBUS_IFACE "com.google.code.AccountsSSO.Accounts"
#define AG_DBUS_SIG_CHANGED "AccountChanged"

#define SERVICE_GLOBAL_TYPE "global"
#define AG_DBUS_PATH_SERVICE_GLOBAL \
    AG_DBUS_PATH_SERVICE "/" SERVICE_GLOBAL_TYPE

#define MAX_SQLITE_BUSY_LOOP_TIME 5
#define MAX_SQLITE_BUSY_LOOP_TIME_MS (MAX_SQLITE_BUSY_LOOP_TIME * 1000)

#if GLIB_CHECK_VERSION (2, 30, 0)
#else
#define G_VALUE_INIT { 0, { { 0 } } }
#endif

typedef struct _AgAccountChanges AgAccountChanges;

struct _AgAccountChanges {
    gboolean deleted;
    gboolean created;

    /* The keys of the table are service names, and the values are
     * AgServiceChanges structures */
    GHashTable *services;
};

G_GNUC_INTERNAL
void _ag_account_store_completed (AgAccount *account,
                                  AgAccountChanges *changes);

G_GNUC_INTERNAL
void _ag_account_done_changes (AgAccount *account, AgAccountChanges *changes);

G_GNUC_INTERNAL
GVariant *_ag_account_build_dbus_changes (AgAccount *account,
                                          AgAccountChanges *changes,
                                          const struct timespec *ts);
G_GNUC_INTERNAL
AgAccountChanges *_ag_account_changes_from_dbus (AgManager *manager,
                                                 GVariant *v_services,
                                                 gboolean created,
                                                 gboolean deleted);

G_GNUC_INTERNAL
gchar *_ag_account_get_store_sql (AgAccount *account, GError **error);

G_GNUC_INTERNAL
AgAccountChanges *_ag_account_steal_changes (AgAccount *account);

G_GNUC_INTERNAL
GHashTable *_ag_account_get_service_changes (AgAccount *account,
                                             AgService *service);

G_GNUC_INTERNAL
void _ag_manager_exec_transaction (AgManager *manager, const gchar *sql,
                                   AgAccountChanges *changes,
                                   AgAccount *account,
                                   GTask *task);

typedef gboolean (*AgQueryCallback) (sqlite3_stmt *stmt, gpointer user_data);

G_GNUC_INTERNAL
void _ag_manager_exec_transaction_blocking (AgManager *manager,
                                            const gchar *sql,
                                            AgAccountChanges *changes,
                                            AgAccount *account,
                                            GError **error);
G_GNUC_INTERNAL
gint _ag_manager_exec_query (AgManager *manager,
                             AgQueryCallback callback, gpointer user_data,
                             const gchar *sql);
G_GNUC_INTERNAL
void _ag_manager_take_error (AgManager *manager, GError *error);
G_GNUC_INTERNAL
const GError *_ag_manager_get_last_error (AgManager *manager);

G_GNUC_INTERNAL
AgService *_ag_manager_get_service_lazy (AgManager *manager,
                                         const gchar *service_name,
                                         const gchar *service_type,
                                         const gint service_id);
G_GNUC_INTERNAL
guint _ag_manager_get_service_id (AgManager *manager, AgService *service);

G_GNUC_INTERNAL
void _ag_manager_store_async (AgManager *manager, AgAccount *account,
                              GTask *task);
G_GNUC_INTERNAL
gboolean _ag_manager_store_sync (AgManager *manager, AgAccount *account,
                                 GError **error);

struct _AgService {
    /*< private >*/
    gint ref_count;
    gchar *name;
    gchar *display_name;
    gchar *description;
    gchar *type;
    gchar *provider;
    gchar *icon_name;
    gchar *i18n_domain;
    gchar *file_data;
    gsize type_data_offset;
    gint id;
    GHashTable *default_settings;
    GHashTable *tags;
};

G_GNUC_INTERNAL
AgService *_ag_service_new_from_file (const gchar *service_name);
G_GNUC_INTERNAL
AgService *_ag_service_new_from_memory (const gchar *service_name,
                                        const gchar *service_type,
                                        const gint service_id);

G_GNUC_INTERNAL
GHashTable *_ag_service_load_default_settings (AgService *service);

G_GNUC_INTERNAL
GVariant *_ag_service_get_default_setting (AgService *service,
                                           const gchar *key);

G_GNUC_INTERNAL
AgService *_ag_service_new (void);

struct _AgProvider {
    /*< private >*/
    gint ref_count;
    gchar *i18n_domain;
    gchar *icon_name;
    gchar *name;
    gchar *display_name;
    gchar *description;
    gchar *domains;
    gchar *plugin_name;
    gchar *file_data;
    gboolean single_account;
    GHashTable *default_settings;
};

G_GNUC_INTERNAL
AgProvider *_ag_provider_new_from_file (const gchar *provider_name);

G_GNUC_INTERNAL
GHashTable *_ag_provider_load_default_settings (AgProvider *provider);

G_GNUC_INTERNAL
GVariant *_ag_provider_get_default_setting (AgProvider *provider,
                                            const gchar *key);

G_GNUC_INTERNAL
GPtrArray *_ag_account_changes_get_service_types (AgAccountChanges *changes);

G_GNUC_INTERNAL
gboolean _ag_account_changes_have_service_type (AgAccountChanges *changes,
                                                gchar *service_type);

G_GNUC_INTERNAL
gboolean _ag_account_changes_have_enabled (AgAccountChanges *changes);

G_GNUC_INTERNAL
GList *_ag_manager_list_all (AgManager *manager);

G_GNUC_INTERNAL
void _ag_account_changes_free (AgAccountChanges *change);

G_GNUC_INTERNAL
void _ag_account_settings_iter_init (AgAccount *account,
                                     AgAccountSettingIter *iter,
                                     const gchar *key_prefix,
                                     gboolean copy_string);

/* Service type functions */
G_GNUC_INTERNAL
AgServiceType *_ag_service_type_new_from_file (const gchar *service_type_name);

/* AgAuthData functions */
G_GNUC_INTERNAL
AgAuthData *_ag_auth_data_new (AgAccount *account, AgService *service);

/* Application functions */
G_GNUC_INTERNAL
AgApplication *_ag_application_new_from_file (const gchar *application_name);

G_GNUC_INTERNAL
GList *_ag_application_list_supported_services (AgApplication *self,
                                                AgManager *manager);

#endif /* _AG_INTERNALS_H_ */
07070100000032000081A4000003E800000064000000015BDC2B8B0001542E000000000000000000000000000000000000003400000000libaccounts-glib-1.24/libaccounts-glib/ag-manager.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-manager
 * @short_description: The account manager object
 *
 * The #AgManager is the main object in this library. Use it to create an
 * #AgAccount, and to instantiate boxed types such as #AgProvider,
 * #AgApplication and #AgService.
 *
 * #AgManager can be instantiated with a set service type with
 * ag_manager_new_for_service_type(), which restricts some future operations on
 * the manager, such as ag_manager_list() or ag_manager_list_services(), to
 * only affect accounts or services with the set service type.
 *
 * Lists of objects instantiated by the manager can be freed with the
 * corresponding functions, such as ag_manager_list_free() for the #GList of
 * #AgAccountId returned from ag_manager_list(), or ag_service_list_free() for
 * the #GList of #AgService returned from ag_manager_list_services().
 */

#include "ag-manager.h"

#include "ag-account-service.h"
#include "ag-application.h"
#include "ag-errors.h"
#include "ag-internals.h"
#include "ag-service.h"
#include "ag-util.h"
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <sqlite3.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef DATABASE_DIR
#define DATABASE_DIR "libaccounts-glib"
#endif

#ifdef DISABLE_WAL
#define JOURNAL_MODE "TRUNCATE"
#else
#define JOURNAL_MODE "WAL"
#endif

enum
{
    PROP_0,

    PROP_SERVICE_TYPE,
    PROP_DB_TIMEOUT,
    PROP_ABORT_ON_DB_TIMEOUT,
    PROP_USE_DBUS,
    N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES];

enum
{
    ACCOUNT_CREATED,
    ACCOUNT_DELETED,
    ACCOUNT_ENABLED,
    ACCOUNT_UPDATED,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

struct _AgManagerPrivate {
    sqlite3 *db;

    sqlite3_stmt *begin_stmt;
    sqlite3_stmt *commit_stmt;
    sqlite3_stmt *rollback_stmt;

    sqlite3_int64 last_service_id;
    sqlite3_int64 last_account_id;

    GDBusConnection *dbus_conn;

    /* Cache for AgService */
    GHashTable *services;

    /* Weak references to loaded accounts */
    GHashTable *accounts;

    /* list of StoreCbData awaiting for exclusive locks */
    GList *locks;

    /* list of EmittedSignalData for the signals emitted by this instance */
    GList *emitted_signals;

    /* list of ProcessedSignalData, to avoid processing signals twice */
    GList *processed_signals;

    /* D-Bus object paths we are listening to */
    GPtrArray *object_paths;

    /* List of GDBus signal subscriptions */
    GSList *subscription_ids;

    GError *last_error;

    guint db_timeout;

    guint abort_on_db_timeout : 1;
    guint use_dbus : 1;
    guint is_disposed : 1;
    guint is_readonly : 1;

    gchar *service_type;
};

typedef struct {
    AgManager *manager;
    AgAccount *account;
    gchar *sql;
    AgAccountChanges *changes;
    guint id;
    GTask *task;
} StoreCbData;

typedef struct {
    struct timespec ts;
    gboolean must_process;
} EmittedSignalData;

typedef struct {
    struct timespec ts;
} ProcessedSignalData;

static const gchar *key_remote_changes = "ag_remote_changes";

static void ag_manager_initable_iface_init(gpointer g_iface,
                                           gpointer iface_data);

G_DEFINE_TYPE_WITH_CODE (AgManager, ag_manager, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                            ag_manager_initable_iface_init));

#define AG_MANAGER_PRIV(obj) (AG_MANAGER(obj)->priv)

static void store_cb_data_free (StoreCbData *sd);
static void account_weak_notify (gpointer userdata, GObject *dead_account);

typedef gpointer (*AgDataFileLoadFunc) (AgManager *self,
                                        const gchar *base_name);

static void
on_dbus_store_done (GObject *object, GAsyncResult *res,
                    gpointer user_data)
{
    GDBusConnection *conn = G_DBUS_CONNECTION (object);
    GTask *task = user_data;
    GVariant *result;
    GError *error_int = NULL;

    result = g_dbus_connection_call_finish (conn, res, &error_int);
    if (G_UNLIKELY (error_int))
    {
        /* We always report a read-only error here */
        g_task_return_new_error (task,
                                 AG_ACCOUNTS_ERROR,
                                 AG_ACCOUNTS_ERROR_READONLY,
                                 "%s", error_int->message);
        g_error_free (error_int);
    }
    else
    {
        GObject *source = g_task_get_source_object (task);
        AgAccount *account = AG_ACCOUNT (source);
        /* If this was a new account, we must update the local data
         * structure */
        if (account->id == 0 &&
            g_variant_n_children (result) >= 1)
        {
            AgAccountChanges *changes;

            g_variant_get_child (result, 0, "u", &account->id);
            changes = g_object_get_data ((GObject *)task,
                                         key_remote_changes);
            _ag_account_done_changes (account, changes);
        }
        g_variant_unref (result);
        g_task_return_boolean (task, TRUE);
    }

    g_object_unref (task);
}

static void
ag_manager_store_dbus_async (AgManager *manager, AgAccount *account,
                             GTask *task)
{
    AgManagerPrivate *priv = manager->priv;
    AgAccountChanges *changes;
    GVariant *dbus_changes;

    if (G_UNLIKELY (!priv->use_dbus)) {
        g_task_return_new_error (task,
                                 AG_ACCOUNTS_ERROR,
                                 AG_ACCOUNTS_ERROR_READONLY,
                                 "DB read-only and D-Bus disabled");
        g_object_unref (task);
        return;
    }

    changes = _ag_account_steal_changes (account);
    dbus_changes = _ag_account_build_dbus_changes (account, changes, NULL);
    g_object_set_data_full ((GObject *)task,
                            key_remote_changes, changes,
                            (GDestroyNotify) _ag_account_changes_free);

    g_dbus_connection_call (priv->dbus_conn,
                            AG_MANAGER_SERVICE_NAME,
                            AG_MANAGER_OBJECT_PATH,
                            AG_MANAGER_INTERFACE,
                            "store",
                            dbus_changes,
                            NULL,
                            G_DBUS_CALL_FLAGS_NONE,
                            -1,
                            g_task_get_cancellable (task),
                            (GAsyncReadyCallback)on_dbus_store_done,
                            task);
}

static gboolean
ag_manager_store_dbus_sync (AgManager *manager, AgAccount *account,
                            GError **error)
{
    AgManagerPrivate *priv = manager->priv;
    AgAccountChanges *changes;
    GVariant *dbus_changes;
    GVariant *result;
    GError *error_int = NULL;

    if (G_UNLIKELY (!priv->use_dbus)) {
        g_set_error_literal (error,
                             AG_ACCOUNTS_ERROR,
                             AG_ACCOUNTS_ERROR_READONLY,
                             "DB read-only and D-Bus disabled");
        return FALSE;
    }

    changes = _ag_account_steal_changes (account);
    dbus_changes = _ag_account_build_dbus_changes (account, changes, NULL);

    result =
        g_dbus_connection_call_sync (priv->dbus_conn,
                                     AG_MANAGER_SERVICE_NAME,
                                     AG_MANAGER_OBJECT_PATH,
                                     AG_MANAGER_INTERFACE,
                                     "store",
                                     dbus_changes,
                                     NULL,
                                     G_DBUS_CALL_FLAGS_NONE,
                                     -1,
                                     NULL,
                                     &error_int);

    if (G_UNLIKELY (error_int))
    {
        /* We always report a read-only error here */
        g_set_error_literal (error,
                             AG_ACCOUNTS_ERROR,
                             AG_ACCOUNTS_ERROR_READONLY,
                             error_int->message);
        g_error_free (error_int);
        _ag_account_changes_free (changes);
        return FALSE;
    }

    if (account->id == 0 &&
        g_variant_n_children (result) >= 1)
    {
        g_variant_get_child (result, 0, "u", &account->id);
        _ag_account_done_changes (account, changes);
    }

    _ag_account_changes_free (changes);
    return TRUE;
}

static void
add_data_files_from_dir (AgManager *manager, const gchar *dirname,
                         GHashTable *loaded_files, const gchar *suffix,
                         AgDataFileLoadFunc load_file_func)
{
    const gchar *filename;
    gchar *base_name;
    gint suffix_length;
    gpointer loaded_file;
    GDir *dir;

    g_return_if_fail (dirname != NULL);

    dir = g_dir_open (dirname, 0, NULL);
    if (!dir) return;

    suffix_length = strlen (suffix);

    while ((filename = g_dir_read_name (dir)) != NULL)
    {
        if (filename[0] == '.')
            continue;

        if (!g_str_has_suffix (filename, suffix))
            continue;

        base_name = g_strndup (filename, (strlen (filename) - suffix_length));

        /* if there is already a service with the same name in the list, then
         * we skip this one (we process directories in descending order of
         * priority) */
        if (g_hash_table_lookup (loaded_files, base_name) != NULL)
        {
            g_free (base_name);
            continue;
        }

        loaded_file = load_file_func (manager, base_name);
        if (G_UNLIKELY (!loaded_file))
        {
            g_free (base_name);
            continue;
        }

        g_hash_table_insert (loaded_files, base_name, loaded_file);
    }

    g_dir_close (dir);
}

static GList *
list_data_files (AgManager *manager,
                 const gchar *suffix,
                 const gchar *env_var,
                 const gchar *subdir,
                 AgDataFileLoadFunc load_file_func)
{
    GHashTable *loaded_files;
    GList *file_list;
    const gchar * const *dirs;
    const gchar *env_dirname, *datadir;
    gchar *dirname, *desktop_override = NULL;

    loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal,
                                          g_free, NULL);

    env_dirname = g_getenv (env_var);
    if (env_dirname)
    {
        add_data_files_from_dir (manager, env_dirname, loaded_files, suffix,
                                 load_file_func);
        /* If the environment variable is set, don't look in other places */
        goto finish;
    }

    datadir = g_get_user_data_dir ();
    if (G_LIKELY (datadir))
    {
        dirname = g_build_filename (datadir, subdir, NULL);
        add_data_files_from_dir (manager, dirname, loaded_files, suffix,
                                 load_file_func);
        g_free (dirname);
    }

    /* Check what desktop is this running on */
    env_dirname = g_getenv ("XDG_CURRENT_DESKTOP");
    if (env_dirname)
        desktop_override = g_ascii_strdown (env_dirname, -1);

    dirs = g_get_system_data_dirs ();
    for (datadir = *dirs; datadir != NULL; dirs++, datadir = *dirs)
    {
        /* Check first if desktop override files exist and if yes, load them first */
        if (desktop_override)
        {
            dirname = g_build_filename (datadir, subdir, desktop_override, NULL);
            add_data_files_from_dir (manager, dirname, loaded_files, suffix,
                                     load_file_func);
            g_free (dirname);
        }

        dirname = g_build_filename (datadir, subdir, NULL);
        add_data_files_from_dir (manager, dirname, loaded_files, suffix,
                                 load_file_func);
        g_free (dirname);
    }

finish:
    file_list = g_hash_table_get_values (loaded_files);
    g_hash_table_unref (loaded_files);
    g_free (desktop_override);

    return file_list;
}

/**
 * ag_manager_get_application:
 * @self: an #AgManager
 * @application_name: the name of an application to search for
 *
 * Search for @application_name in the list of applications, and return a new
 * #AgApplication if a matching application was found.
 *
 * Returns: a new #AgApplication if one was found, %NULL otherwise
 */
AgApplication *
ag_manager_get_application (AgManager *self, const gchar *application_name)
{
    g_return_val_if_fail (AG_IS_MANAGER (self), NULL);

    return _ag_application_new_from_file (application_name);
}

static inline GList *
_ag_applications_list (AgManager *self)
{
    return list_data_files (self, ".application",
                            "AG_APPLICATIONS", APPLICATION_FILES_DIR,
                            (AgDataFileLoadFunc)ag_manager_get_application);
}

static inline GList *
_ag_providers_list (AgManager *self)
{
    return list_data_files (self, ".provider",
                            "AG_PROVIDERS", PROVIDER_FILES_DIR,
                            (AgDataFileLoadFunc)ag_manager_get_provider);
}

static inline GList *
_ag_services_list (AgManager *self)
{
    return list_data_files (self, ".service",
                            "AG_SERVICES", SERVICE_FILES_DIR,
                            (AgDataFileLoadFunc)ag_manager_get_service);
}

static inline GList *
_ag_service_types_list (AgManager *self)
{
    return list_data_files (self, ".service-type",
                           "AG_SERVICE_TYPES", SERVICE_TYPE_FILES_DIR,
                           (AgDataFileLoadFunc)ag_manager_load_service_type);
}

static GList *
get_account_services_from_accounts (AgManager *manager,
                                    GList *account_ids,
                                    gboolean enabled_only)
{
    GList *ret = NULL, *account_list;

    for (account_list = account_ids;
         account_list != NULL;
         account_list = account_list->next)
    {
        AgAccount *account;
        GList *service_ids, *service_elem;

        account = ag_manager_get_account (manager,
                                          (AgAccountId)
                                          GPOINTER_TO_UINT(account_list->data));
        if (G_UNLIKELY (account == NULL))
            continue;

        service_ids = enabled_only ?
            ag_account_list_enabled_services (account) :
            ag_account_list_services (account);
        for (service_elem = service_ids;
             service_elem != NULL;
             service_elem = service_elem->next)
        {
            AgService *service = (AgService *) service_elem->data;
            AgAccountService *account_service;

            account_service = ag_account_service_new (account, service);
            if (G_UNLIKELY (account_service == NULL))
                continue;

            ret = g_list_prepend (ret, account_service);
        }

        ag_service_list_free (service_ids);
        g_object_unref (account);
    }

    return ret;
}

static void
set_error_from_db (AgManager *manager)
{
    AgManagerPrivate *priv = manager->priv;
    AgAccountsError code;
    GError *error;

    switch (sqlite3_errcode (priv->db))
    {
    case SQLITE_DONE:
    case SQLITE_OK:
        _ag_manager_take_error (manager, NULL);
        return;
    case SQLITE_BUSY:
        code = AG_ACCOUNTS_ERROR_DB_LOCKED;
        if (priv->abort_on_db_timeout)
            g_error ("Accounts DB timeout: causing application to abort.");
        break;
    default:
        code = AG_ACCOUNTS_ERROR_DB;
        break;
    }

    error = g_error_new (AG_ACCOUNTS_ERROR, code, "SQLite error %d: %s",
                         sqlite3_errcode (priv->db),
                         sqlite3_errmsg (priv->db));
    _ag_manager_take_error (manager, error);
}

static gboolean
timed_unref_account (gpointer account)
{
    DEBUG_REFS ("Releasing temporary reference on account %u",
                AG_ACCOUNT (account)->id);
    g_object_unref (account);
    return FALSE;
}

static gboolean
ag_manager_must_emit_updated (AgManager *manager, AgAccountChanges *changes)
{
    AgManagerPrivate *priv = manager->priv;

    /* Don't emit the "updated" signal along with "created" or "deleted" */
    if (changes->created || changes->deleted)
        return FALSE;

    /* The update-event is emitted whenever any value has been changed on
     * particular service of account.
     */
    return (priv->service_type != NULL) ?
        _ag_account_changes_have_service_type (changes, priv->service_type) : FALSE;
}

static gboolean
ag_manager_must_emit_enabled (AgManager *manager, AgAccountChanges *changes)
{
    AgManagerPrivate *priv = manager->priv;

    /* TODO: the enabled-event is emitted whenever enabled status has changed on
     * any service or account. This has some possibility for optimization.
     */
    return (priv->service_type != NULL) ?
        _ag_account_changes_have_enabled (changes) : FALSE;
}

static void
ag_manager_emit_signals (AgManager *manager, AgAccountId account_id,
                         gboolean updated,
                         gboolean enabled,
                         gboolean created,
                         gboolean deleted)
{
    if (updated)
        g_signal_emit_by_name (manager, "account-updated", account_id);

    if (enabled)
        g_signal_emit_by_name (manager, "enabled-event", account_id);

    if (deleted)
        g_signal_emit_by_name (manager, "account-deleted", account_id);

    if (created)
        g_signal_emit_by_name (manager, "account-created", account_id);
}

static gboolean
check_signal_processed (AgManagerPrivate *priv, struct timespec *ts)
{
    ProcessedSignalData *psd;
    GList *list;

    for (list = priv->processed_signals; list != NULL; list = list->next)
    {
        psd = list->data;

        if (psd->ts.tv_sec == ts->tv_sec &&
            psd->ts.tv_nsec == ts->tv_nsec)
        {
            DEBUG_INFO ("Signal already processed: %lu-%lu",
                        ts->tv_sec, ts->tv_nsec);
            return TRUE;
        }
    }

    /* Add the signal to the list of processed ones; this is necessary if the
     * manager was created for a specific service type, because in that case
     * we are subscribing for DBus signals on two different object paths (the
     * one for our service type, and one for the global settings), so we might
     * get notified about the same signal twice.
     */

    /* Don't keep more than a very few elements in the list */
    for (list = g_list_nth (priv->processed_signals, 2);
         list != NULL;
         list = g_list_nth (priv->processed_signals, 2))
    {
        g_slice_free (ProcessedSignalData, list->data);
        priv->processed_signals = g_list_delete_link (priv->processed_signals,
                                                      list);
    }

    psd = g_slice_new (ProcessedSignalData);
    psd->ts = *ts;
    priv->processed_signals = g_list_prepend (priv->processed_signals, psd);

    return FALSE;
}

/*
 * checks whether the sender of the message is listed in the object_paths array
 */
static gboolean
object_path_is_interesting (const gchar *msg_object_path,
                            GPtrArray *object_paths)
{
    guint i;

    /* If the object_paths array is empty, it means that we are
     * interested in all service types. */
    if (object_paths->len == 0)
        return TRUE;

    if (G_UNLIKELY (msg_object_path == NULL))
        return FALSE;

    for (i = 0; i < object_paths->len; i++)
    {
        const gchar *object_path = g_ptr_array_index (object_paths, i);
        if (strcmp (msg_object_path, object_path) == 0)
            return TRUE;
    }
    return FALSE;
}

static void
dbus_filter_callback (G_GNUC_UNUSED GDBusConnection *dbus_conn,
                      G_GNUC_UNUSED const gchar *sender_name,
                      const gchar *object_path,
                      G_GNUC_UNUSED const gchar *interface_name,
                      G_GNUC_UNUSED const gchar *signal_name,
                      GVariant *msg,
                      gpointer user_data)
{
    AgManager *manager = AG_MANAGER (user_data);
    AgManagerPrivate *priv = manager->priv;
    const gchar *provider_name = NULL;
    AgAccountId account_id = 0;
    AgAccount *account;
    AgAccountChanges *changes;
    struct timespec ts;
    gboolean deleted, created;
    gboolean ours = FALSE;
    gboolean updated = FALSE;
    gboolean enabled = FALSE;
    gboolean must_instantiate = TRUE;
    GVariant *v_services;
    GList *list, *node;

    if (!object_path_is_interesting (object_path, priv->object_paths))
        return;

    guint32 sec, nsec;
    g_variant_get (msg,
                   "(uuubb&s@*)",
                   &sec,
                   &nsec,
                   &account_id,
                   &created,
                   &deleted,
                   &provider_name,
                   &v_services);
    ts.tv_sec = sec;
    ts.tv_nsec = nsec;

    DEBUG_INFO ("path = %s, time = %lu-%lu (%p)",
                object_path, ts.tv_sec, ts.tv_nsec,
                manager);

    /* Do not process the same signal more than once. */
    if (check_signal_processed (priv, &ts))
        goto skip_processing;

    list = priv->emitted_signals;
    while (list != NULL)
    {
        EmittedSignalData *esd = list->data;
        node = list;
        list = list->next;

        if (esd->ts.tv_sec == ts.tv_sec &&
            esd->ts.tv_nsec == ts.tv_nsec)
        {
            gboolean must_process = esd->must_process;
            /* message is ours: we can ignore it, as the changes
             * were already processed when the DB transaction succeeded. */
            ours = TRUE;

            DEBUG_INFO ("Signal is ours, must_process = %d", esd->must_process);
            g_slice_free (EmittedSignalData, esd);
            priv->emitted_signals = g_list_delete_link (
                                                    priv->emitted_signals,
                                                    node);
            if (!must_process)
                goto skip_processing;
        }
    }

    /* we must mark our emitted signals for reprocessing, because the current
     * signal might modify some of the fields that were previously modified by
     * us.
     * This ensures that changes coming from different account manager
     * instances are processed in the right order. */
    for (list = priv->emitted_signals; list != NULL; list = list->next)
    {
        EmittedSignalData *esd = list->data;
        DEBUG_INFO ("Marking pending signal for processing");
        esd->must_process = TRUE;
    }

    changes = _ag_account_changes_from_dbus (manager, v_services,
                                             created, deleted);

    /* check if the account is loaded */
    account = g_hash_table_lookup (priv->accounts,
                                   GUINT_TO_POINTER (account_id));

    if (!account && !created && !deleted)
        must_instantiate = FALSE;

    if (ours && (deleted || created))
        must_instantiate = FALSE;

    if (!account && must_instantiate)
    {
        /* because of the checks above, this can happen if this is an account
         * created or deleted from another instance.
         * We must emit the signals, and cache the newly created account for a
         * while, because the application is likely to inspect it */
        account = g_initable_new (AG_TYPE_ACCOUNT, NULL, NULL,
                                  "manager", manager,
                                  "provider", provider_name,
                                  "id", account_id,
                                  "foreign", created,
                                  NULL);
        g_return_if_fail (AG_IS_ACCOUNT (account));

        g_object_weak_ref (G_OBJECT (account), account_weak_notify, manager);
        g_hash_table_insert (priv->accounts, GUINT_TO_POINTER (account_id),
                             account);
        g_timeout_add_seconds (2, timed_unref_account, account);
    }

    if (changes)
    {
        updated = ag_manager_must_emit_updated (manager, changes);
        enabled = ag_manager_must_emit_enabled (manager, changes);
        if (account)
            _ag_account_done_changes (account, changes);

        _ag_account_changes_free (changes);
    }

    ag_manager_emit_signals (manager, account_id,
                             updated,
                             enabled,
                             created,
                             deleted);

skip_processing:
    g_variant_unref (v_services);
}

static void
signal_account_changes_on_service_types (AgManager *manager,
                                         AgAccountChanges *changes,
                                         GVariant *msg)
{
    GPtrArray *service_types;
    guint i;

    /* Add a temporary reference to the message parameters, to make
     * sure that g_dbus_connection_emit_signal() won't steal our
     * variant. */
    g_variant_ref (msg);
    service_types = _ag_account_changes_get_service_types (changes);
    for (i = 0; i < service_types->len; i++)
    {
        const gchar *service_type;
        gchar path[256];
        gchar *escaped_type;
        gboolean ret;

        service_type = g_ptr_array_index(service_types, i);
        escaped_type = _ag_dbus_escape_as_identifier (service_type);
        g_snprintf (path, sizeof (path), "%s/%s",
                    AG_DBUS_PATH_SERVICE, escaped_type);
        g_free (escaped_type);

        ret = g_dbus_connection_emit_signal (manager->priv->dbus_conn,
                                             NULL,
                                             path,
                                             AG_DBUS_IFACE,
                                             AG_DBUS_SIG_CHANGED,
                                             msg,
                                             NULL);
        if (G_UNLIKELY (!ret))
            g_warning ("Emission of DBus signal failed");
    }
    g_ptr_array_free (service_types, TRUE);
    g_variant_unref (msg);
}

static void
signal_account_changes (AgManager *manager, AgAccount *account,
                        AgAccountChanges *changes)
{
    AgManagerPrivate *priv = manager->priv;
    GVariant *msg;
    EmittedSignalData eds;

    clock_gettime(CLOCK_MONOTONIC, &eds.ts);

    msg = _ag_account_build_dbus_changes (account, changes, &eds.ts);
    if (G_UNLIKELY (!msg))
    {
        g_warning ("Creation of D-Bus signal failed");
        return;
    }

    g_variant_ref_sink (msg);

    /* emit the signal on all service-types */
    signal_account_changes_on_service_types(manager, changes, msg);

    g_dbus_connection_flush_sync (priv->dbus_conn, NULL, NULL);
    DEBUG_INFO ("Emitted signal, time: %lu-%lu", eds.ts.tv_sec, eds.ts.tv_nsec);

    eds.must_process = FALSE;
    priv->emitted_signals =
        g_list_prepend (priv->emitted_signals,
                        g_slice_dup (EmittedSignalData, &eds));

    g_variant_unref (msg);
}

static gboolean
got_service (sqlite3_stmt *stmt, AgService **p_service)
{
    AgService *service;

    g_assert (p_service != NULL);

    service = _ag_service_new ();
    service->id = sqlite3_column_int (stmt, 0);
    service->display_name = g_strdup ((gchar *)sqlite3_column_text (stmt, 1));
    service->provider = g_strdup ((gchar *)sqlite3_column_text (stmt, 2));
    service->type = g_strdup ((gchar *)sqlite3_column_text (stmt, 3));

    *p_service = service;
    return TRUE;
}

static gboolean
got_service_id (sqlite3_stmt *stmt, AgService *service)
{
    g_assert (service != NULL);

    service->id = sqlite3_column_int (stmt, 0);
    return TRUE;
}

static gboolean
add_service_to_db (AgManager *manager, AgService *service)
{
    gchar *sql;

    /* Add the service to the DB */
    sql = sqlite3_mprintf ("INSERT INTO Services "
                           "(name, display, provider, type) "
                           "VALUES (%Q, %Q, %Q, %Q);",
                           service->name,
                           service->display_name,
                           service->provider,
                           service->type);
    _ag_manager_exec_query (manager, NULL, NULL, sql);
    sqlite3_free (sql);

    /* The insert statement above might fail in the unlikely case
     * that in the meantime the same service was inserted by some other
     * process; so, instead of calling sqlite3_last_insert_rowid(), we
     * just get the ID with another query. */
    sql = sqlite3_mprintf ("SELECT id FROM Services WHERE name = %Q",
                           service->name);
    _ag_manager_exec_query (manager, (AgQueryCallback)got_service_id,
                            service, sql);
    sqlite3_free (sql);

    return service->id != 0;
}

static gboolean
add_id_to_list (sqlite3_stmt *stmt, GList **plist)
{
    gint id;

    id = sqlite3_column_int (stmt, 0);
    *plist = g_list_prepend (*plist, GINT_TO_POINTER (id));
    return TRUE;
}

static void
account_weak_notify (gpointer userdata, GObject *dead_account)
{
    AgManagerPrivate *priv = AG_MANAGER_PRIV (userdata);
    GHashTableIter iter;
    GObject *account;

    DEBUG_REFS ("called for %p", dead_account);
    g_hash_table_iter_init (&iter, priv->accounts);
    while (g_hash_table_iter_next (&iter, NULL, (gpointer)&account))
    {
        if (account == dead_account)
        {
            g_hash_table_iter_steal (&iter);
            break;
        }
    }
}

static void
account_weak_unref (GObject *account)
{
    g_object_weak_unref (account, account_weak_notify,
                         ag_account_get_manager (AG_ACCOUNT (account)));
}

/*
 * exec_transaction:
 *
 * Executes a transaction, assuming that the exclusive lock has been obtained.
 */
static void
exec_transaction (AgManager *manager, AgAccount *account,
                  const gchar *sql, AgAccountChanges *changes,
                  GError **error)
{
    AgManagerPrivate *priv;
    gchar *err_msg = NULL;
    int ret;
    gboolean updated, enabled;

    DEBUG_LOCKS ("Accounts DB is now locked");
    DEBUG_QUERIES ("called: %s", sql);
    g_return_if_fail (AG_IS_MANAGER (manager));
    priv = manager->priv;
    g_return_if_fail (AG_IS_ACCOUNT (account));
    g_return_if_fail (sql != NULL);
    g_return_if_fail (priv->db != NULL);

    ret = sqlite3_exec (priv->db, sql, NULL, NULL, &err_msg);
    if (G_UNLIKELY (ret != SQLITE_OK))
    {
        *error = g_error_new (AG_ACCOUNTS_ERROR, AG_ACCOUNTS_ERROR_DB, "%s",
                              err_msg);
        if (err_msg)
            sqlite3_free (err_msg);

        ret = sqlite3_step (priv->rollback_stmt);
        if (G_UNLIKELY (ret != SQLITE_OK))
            g_warning ("Rollback failed");
        sqlite3_reset (priv->rollback_stmt);
        DEBUG_LOCKS ("Accounts DB is now unlocked");
        return;
    }

    ret = sqlite3_step (priv->commit_stmt);
    if (G_UNLIKELY (ret != SQLITE_DONE))
    {
        *error = g_error_new_literal (AG_ACCOUNTS_ERROR, AG_ACCOUNTS_ERROR_DB,
                                      sqlite3_errmsg (priv->db));
        sqlite3_reset (priv->commit_stmt);
        return;
    }
    sqlite3_reset (priv->commit_stmt);

    DEBUG_LOCKS ("Accounts DB is now unlocked");

    /* everything went well; if this was a new account, we must update the
     * local data structure */
    if (account->id == 0)
    {
        account->id = priv->last_account_id;

        /* insert the account into our cache */
        g_object_weak_ref (G_OBJECT (account), account_weak_notify, manager);
        g_hash_table_insert (priv->accounts, GUINT_TO_POINTER (account->id),
                             account);
    }

    if (G_LIKELY (priv->use_dbus))
    {
        /* emit DBus signals to notify other processes */
        signal_account_changes (manager, account, changes);
    }

    updated = ag_manager_must_emit_updated(manager, changes);

    enabled = ag_manager_must_emit_enabled(manager, changes);
    _ag_account_done_changes (account, changes);

    ag_manager_emit_signals (manager, account->id,
                             updated,
                             enabled,
                             changes->created,
                             changes->deleted);
}

static void
store_cb_data_free (StoreCbData *sd)
{
    if (sd->id)
        g_source_remove (sd->id);
    g_free (sd->sql);
    g_slice_free (StoreCbData, sd);
}

static gboolean
exec_transaction_idle (StoreCbData *sd)
{
    AgManager *manager = sd->manager;
    AgAccount *account = sd->account;
    AgManagerPrivate *priv;
    GError *error = NULL;
    int ret;

    g_return_val_if_fail (AG_IS_MANAGER (manager), FALSE);
    priv = manager->priv;

    g_object_ref (manager);
    g_object_ref (account);

    /* If the operation was cancelled, abort it. */
    if (g_task_return_error_if_cancelled (sd->task))
    {
        goto finish;
    }

    g_return_val_if_fail (priv->begin_stmt != NULL, FALSE);
    ret = sqlite3_step (priv->begin_stmt);
    if (ret == SQLITE_BUSY)
    {
        sched_yield ();
        g_object_unref (account);
        g_object_unref (manager);
        return TRUE; /* call this callback again */
    }

    if (ret == SQLITE_DONE)
    {
        exec_transaction (manager, account, sd->sql, sd->changes, &error);
    }
    else
    {
        error = g_error_new_literal (AG_ACCOUNTS_ERROR, AG_ACCOUNTS_ERROR_DB,
                                     "Generic error");
    }

finish:
    if (error != NULL)
    {
        g_task_return_error (sd->task, error);
    }
    else
    {
        g_task_return_boolean (sd->task, TRUE);
    }

    _ag_account_store_completed (account, sd->changes);

    priv->locks = g_list_remove (priv->locks, sd);
    sd->id = 0;
    store_cb_data_free (sd);
    g_object_unref (account);
    g_object_unref (manager);
    return FALSE;
}

static int
prepare_transaction_statements (AgManagerPrivate *priv)
{
    int ret;

    if (G_UNLIKELY (!priv->begin_stmt))
    {
        ret = sqlite3_prepare_v2 (priv->db, "BEGIN EXCLUSIVE;", -1,
                                  &priv->begin_stmt, NULL);
        if (ret != SQLITE_OK) return ret;
    }
    else
        sqlite3_reset (priv->begin_stmt);

    if (G_UNLIKELY (!priv->commit_stmt))
    {
        ret = sqlite3_prepare_v2 (priv->db, "COMMIT;", -1,
                                  &priv->commit_stmt, NULL);
        if (ret != SQLITE_OK) return ret;
    }
    else
        sqlite3_reset (priv->commit_stmt);

    if (G_UNLIKELY (!priv->rollback_stmt))
    {
        ret = sqlite3_prepare_v2 (priv->db, "ROLLBACK;", -1,
                                  &priv->rollback_stmt, NULL);
        if (ret != SQLITE_OK) return ret;
    }
    else
        sqlite3_reset (priv->rollback_stmt);

    return SQLITE_OK;
}

static void
set_last_rowid_as_account_id (sqlite3_context *ctx,
                              G_GNUC_UNUSED int argc,
                              G_GNUC_UNUSED sqlite3_value **argv)
{
    AgManagerPrivate *priv;

    priv = sqlite3_user_data (ctx);
    priv->last_account_id = sqlite3_last_insert_rowid (priv->db);
    sqlite3_result_null (ctx);
}

static void
get_account_id (sqlite3_context *ctx,
                G_GNUC_UNUSED int argc,
                G_GNUC_UNUSED sqlite3_value **argv)
{
    AgManagerPrivate *priv;

    priv = sqlite3_user_data (ctx);
    sqlite3_result_int64 (ctx, priv->last_account_id);
}

static void
create_functions (AgManagerPrivate *priv)
{
    sqlite3_create_function (priv->db, "set_last_rowid_as_account_id", 0,
                             SQLITE_ANY, priv,
                             set_last_rowid_as_account_id, NULL, NULL);
    sqlite3_create_function (priv->db, "account_id", 0,
                             SQLITE_ANY, priv,
                             get_account_id, NULL, NULL);
}

static void
setup_db_options (sqlite3 *db)
{
    gchar *error;
    int ret;

    error = NULL;
    ret = sqlite3_exec (db, "PRAGMA synchronous = 1", NULL, NULL, &error);
    if (ret != SQLITE_OK)
    {
        g_warning ("%s: couldn't set synchronous mode (%s)",
                   G_STRFUNC, error);
        sqlite3_free (error);
    }

    error = NULL;
    ret = sqlite3_exec (db, "PRAGMA journal_mode = " JOURNAL_MODE, NULL, NULL,
                        &error);
    if (ret != SQLITE_OK)
    {
        g_warning ("%s: couldn't set journal mode to " JOURNAL_MODE " (%s)",
                   G_STRFUNC, error);
        sqlite3_free (error);
    }
}

static gint
get_db_version (sqlite3 *db)
{
    sqlite3_stmt *stmt;
    gint version = 0, ret;

    ret = sqlite3_prepare(db, "PRAGMA user_version", -1, &stmt, NULL);
    if (G_UNLIKELY(ret != SQLITE_OK)) return 0;

    ret = sqlite3_step(stmt);
    if (G_LIKELY(ret == SQLITE_ROW))
        version = sqlite3_column_int(stmt, 0);

    sqlite3_finalize(stmt);
    return version;
}

static gboolean
create_db (sqlite3 *db)
{
    const gchar *sql;
    gchar *error;
    int ret;

    sql = ""
        "CREATE TABLE IF NOT EXISTS Accounts ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
            "name TEXT,"
            "provider TEXT,"
            "enabled INTEGER);"

        "CREATE TABLE IF NOT EXISTS Services ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
            "name TEXT NOT NULL UNIQUE,"
            "display TEXT NOT NULL,"
            /* following fields are included for performance reasons */
            "provider TEXT,"
            "type TEXT);"
        "CREATE INDEX IF NOT EXISTS idx_service ON Services(name);"

        "CREATE TABLE IF NOT EXISTS Settings ("
            "account INTEGER NOT NULL,"
            "service INTEGER,"
            "key TEXT NOT NULL,"
            "type TEXT NOT NULL,"
            "value BLOB);"
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_setting ON Settings "
            "(account, service, key);"

        "CREATE TRIGGER IF NOT EXISTS tg_delete_account "
            "BEFORE DELETE ON Accounts FOR EACH ROW BEGIN "
                "DELETE FROM Settings WHERE account = OLD.id; "
            "END;"

        "CREATE TABLE IF NOT EXISTS Signatures ("
            "account INTEGER NOT NULL,"
            "service INTEGER,"
            "key TEXT NOT NULL,"
            "signature TEXT NOT NULL,"
            "token TEXT NOT NULL);"
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_signatures ON Signatures "
           "(account, service, key);"

        "PRAGMA user_version = 1;";

    error = NULL;
    ret = sqlite3_exec (db, sql, NULL, NULL, &error);
    if (ret == SQLITE_BUSY)
    {
        guint t;
        for (t = 5; t < MAX_SQLITE_BUSY_LOOP_TIME_MS; t *= 2)
        {
            DEBUG_LOCKS ("Database locked, retrying...");
            sched_yield ();
            g_assert(error != NULL);
            sqlite3_free (error);
            ret = sqlite3_exec (db, sql, NULL, NULL, &error);
            if (ret != SQLITE_BUSY) break;
            usleep(t * 1000);
        }
    }

    if (ret != SQLITE_OK)
    {
        g_warning ("Error initializing DB: %s", error);
        sqlite3_free (error);
        return FALSE;
    }

    return TRUE;
}

static inline gboolean
file_is_read_only (const gchar *filename)
{
    int fd;

    /* FIXME: Here we could just use access(); however, because of bug
     * https://bugs.launchpad.net/bugs/1220713, if the access is blocked by
     * apparmor the access() call would still report that the file is
     * writeable.
     */
    fd = open (filename, O_RDWR);
    if (fd == -1)
    {
        if (errno == EACCES || errno == EROFS)
            return TRUE;
    }
    else
    {
        close (fd);
    }

    return FALSE;
}

static gboolean
open_db (AgManager *manager)
{
    AgManagerPrivate *priv = manager->priv;
    const gchar *basedir;
    gchar *filename, *pathname;
    gint version;
    gboolean ok = TRUE;
    int ret, flags;

    basedir = g_getenv ("ACCOUNTS");
    if (G_LIKELY (!basedir))
    {
        basedir = g_get_user_config_dir ();
        pathname = g_build_path (G_DIR_SEPARATOR_S, basedir,
            DATABASE_DIR, NULL);
        if (G_UNLIKELY (g_mkdir_with_parents(pathname, 0755)))
            g_warning ("Cannot create directory: %s", pathname);
        filename = g_build_filename (pathname, "accounts.db", NULL);
        g_free (pathname);
    }
    else
    {
        filename = g_build_filename (basedir, "accounts.db", NULL);
    }

    /* First, check if the file exists and is writeable */
    if (file_is_read_only (filename))
    {
        DEBUG_INFO ("Opening DB in read-only mode");
        flags = SQLITE_OPEN_READONLY;
        priv->is_readonly = TRUE;
    }
    else
    {
        flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
        priv->is_readonly = FALSE;
    }
    ret = sqlite3_open_v2 (filename, &priv->db, flags, NULL);
    g_free (filename);

    if (ret != SQLITE_OK)
    {
        if (priv->db)
        {
            g_warning ("Error opening accounts DB: %s",
                       sqlite3_errmsg (priv->db));
            sqlite3_close (priv->db);
            priv->db = NULL;
        }
        return FALSE;
    }

    /* TODO: busy handler */

    version = get_db_version(priv->db);
    DEBUG_INFO ("DB version: %d", version);
    if (version < 1)
        ok = create_db(priv->db);
    /* insert here code to upgrade the DB from older versions... */

    if (G_UNLIKELY (!ok))
    {
        sqlite3_close (priv->db);
        priv->db = NULL;
        return FALSE;
    }

    setup_db_options (priv->db);
    create_functions (priv);

    return TRUE;
}

static inline void
add_matches (AgManager *manager)
{
    AgManagerPrivate *priv = manager->priv;
    guint i;

    for (i = 0; i < priv->object_paths->len; i++)
    {
        const gchar *path = g_ptr_array_index(priv->object_paths, i);
        guint id;

        id = g_dbus_connection_signal_subscribe (priv->dbus_conn,
                                                 NULL,
                                                 AG_DBUS_IFACE,
                                                 AG_DBUS_SIG_CHANGED,
                                                 path,
                                                 NULL,
                                                 G_DBUS_SIGNAL_FLAGS_NONE,
                                                 dbus_filter_callback,
                                                 manager,
                                                 NULL);
        priv->subscription_ids =
            g_slist_prepend (priv->subscription_ids, GUINT_TO_POINTER (id));
    }
}

static inline void
add_typeless_match (AgManager *manager)
{
    AgManagerPrivate *priv = manager->priv;
    guint id;

    id = g_dbus_connection_signal_subscribe (priv->dbus_conn,
                                             NULL,
                                             AG_DBUS_IFACE,
                                             AG_DBUS_SIG_CHANGED,
                                             NULL,
                                             NULL,
                                             G_DBUS_SIGNAL_FLAGS_NONE,
                                             dbus_filter_callback,
                                             manager,
                                             NULL);
    priv->subscription_ids =
        g_slist_prepend (priv->subscription_ids, GUINT_TO_POINTER (id));
}

static gboolean
setup_dbus (AgManager *manager, GError **error)
{
    AgManagerPrivate *priv = manager->priv;
    GError *error_int = NULL;

    priv->dbus_conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error_int);
    if (G_UNLIKELY (error_int != NULL))
    {
        g_warning ("Failed to get D-Bus connection (%s)", error_int->message);
        g_propagate_error (error, error_int);
        return FALSE;
    }

    if (priv->service_type == NULL)
    {
        /* listen to all changes */
        add_typeless_match (manager);
    }
    else
    {
        gchar *escaped_type, *path;

        /* listen for changes on our service type only */
        escaped_type = _ag_dbus_escape_as_identifier (priv->service_type);
        path = g_strdup_printf (AG_DBUS_PATH_SERVICE "/%s", escaped_type);
        g_free (escaped_type);
        g_ptr_array_add (priv->object_paths, path);

        /* add also the global service type */
        g_ptr_array_add (priv->object_paths,
                         g_strdup (AG_DBUS_PATH_SERVICE_GLOBAL));

        add_matches (manager);
    }

    return TRUE;
}

static void
ag_manager_init (AgManager *manager)
{
    AgManagerPrivate *priv;

    manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, AG_TYPE_MANAGER,
                                                 AgManagerPrivate);
    priv = manager->priv;

    priv->services =
        g_hash_table_new_full (g_str_hash, g_str_equal,
                               NULL, (GDestroyNotify)ag_service_unref);
    priv->accounts =
        g_hash_table_new_full (NULL, NULL,
                               NULL, (GDestroyNotify)account_weak_unref);

    priv->db_timeout = MAX_SQLITE_BUSY_LOOP_TIME_MS; /* 5 seconds */
    priv->use_dbus = TRUE;

    priv->object_paths = g_ptr_array_new_with_free_func (g_free);
}

static void
ag_manager_get_property (GObject *object, guint property_id,
                         GValue *value, GParamSpec *pspec)
{
    AgManager *manager = AG_MANAGER (object);
    AgManagerPrivate *priv = manager->priv;

    switch (property_id)
    {
    case PROP_SERVICE_TYPE:
        g_value_set_string (value, priv->service_type);
        break;
    case PROP_DB_TIMEOUT:
        g_value_set_uint (value, priv->db_timeout);
        break;
    case PROP_ABORT_ON_DB_TIMEOUT:
        g_value_set_boolean (value, priv->abort_on_db_timeout);
        break;
    case PROP_USE_DBUS:
        g_value_set_boolean (value, priv->use_dbus);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
ag_manager_set_property (GObject *object, guint property_id,
                         const GValue *value, GParamSpec *pspec)
{
    AgManager *manager = AG_MANAGER (object);
    AgManagerPrivate *priv = manager->priv;

    switch (property_id)
    {
    case PROP_SERVICE_TYPE:
        g_assert (priv->service_type == NULL);
        priv->service_type = g_value_dup_string (value);
        break;
    case PROP_DB_TIMEOUT:
        priv->db_timeout = g_value_get_uint (value);
        break;
    case PROP_ABORT_ON_DB_TIMEOUT:
        priv->abort_on_db_timeout = g_value_get_boolean (value);
        break;
    case PROP_USE_DBUS:
        priv->use_dbus = g_value_get_boolean (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
ag_manager_dispose (GObject *object)
{
    AgManagerPrivate *priv = AG_MANAGER_PRIV (object);

    if (priv->is_disposed) return;
    priv->is_disposed = TRUE;

    DEBUG_REFS ("Disposing manager %p", object);

    while (priv->locks)
    {
        store_cb_data_free (priv->locks->data);
        priv->locks = g_list_delete_link (priv->locks, priv->locks);
    }

    if (priv->dbus_conn)
    {
        while (priv->subscription_ids)
        {
            guint id = GPOINTER_TO_UINT (priv->subscription_ids->data);
            g_dbus_connection_signal_unsubscribe (priv->dbus_conn, id);
            priv->subscription_ids =
                g_slist_delete_link (priv->subscription_ids,
                                     priv->subscription_ids);
        }

        g_object_unref (priv->dbus_conn);
        priv->dbus_conn = NULL;
    }

    G_OBJECT_CLASS (ag_manager_parent_class)->finalize (object);
}

static void
ag_manager_finalize (GObject *object)
{
    AgManagerPrivate *priv = AG_MANAGER_PRIV (object);

    g_ptr_array_free (priv->object_paths, TRUE);

    while (priv->emitted_signals)
    {
        g_slice_free (EmittedSignalData, priv->emitted_signals->data);
        priv->emitted_signals = g_list_delete_link (priv->emitted_signals,
                                                    priv->emitted_signals);
    }

    while (priv->processed_signals)
    {
        g_slice_free (ProcessedSignalData, priv->processed_signals->data);
        priv->processed_signals = g_list_delete_link (priv->processed_signals,
                                                      priv->processed_signals);
    }

    if (priv->begin_stmt)
        sqlite3_finalize (priv->begin_stmt);
    if (priv->commit_stmt)
        sqlite3_finalize (priv->commit_stmt);
    if (priv->rollback_stmt)
        sqlite3_finalize (priv->rollback_stmt);

    if (priv->services)
        g_hash_table_unref (priv->services);

    if (priv->accounts)
        g_hash_table_unref (priv->accounts);

    if (priv->db)
    {
        if (sqlite3_close (priv->db) != SQLITE_OK)
            g_warning ("Failed to close database: %s",
                       sqlite3_errmsg (priv->db));
        priv->db = NULL;
    }
    g_free (priv->service_type);

    if (priv->last_error)
        g_error_free (priv->last_error);

    G_OBJECT_CLASS (ag_manager_parent_class)->finalize (object);
}

static gboolean
ag_manager_initable_init (GInitable *initable,
                          G_GNUC_UNUSED GCancellable *cancellable,
                          GError **error)
{
    AgManager *manager = AG_MANAGER (initable);

    if (G_UNLIKELY (!open_db (manager)))
    {
        g_set_error_literal (error, AG_ACCOUNTS_ERROR, AG_ACCOUNTS_ERROR_DB,
                             "Could not open accounts DB file");
        return FALSE;
    }

    if (G_UNLIKELY (manager->priv->use_dbus && !setup_dbus (manager, error)))
    {
        return FALSE;
    }

    return TRUE;
}

static void
ag_manager_initable_iface_init (gpointer g_iface,
                                G_GNUC_UNUSED gpointer iface_data)
{
    GInitableIface *iface = (GInitableIface *)g_iface;
    iface->init = ag_manager_initable_init;
}

static void
ag_manager_account_deleted (AgManager *manager, AgAccountId id)
{
    g_return_if_fail (AG_IS_MANAGER (manager));

    /* The weak reference is removed automatically when the account is removed
     * from the hash table */
    g_hash_table_remove (manager->priv->accounts, GUINT_TO_POINTER (id));
}

static void
ag_manager_class_init (AgManagerClass *klass)
{
    GObjectClass* object_class = G_OBJECT_CLASS (klass);

    g_type_class_add_private (object_class, sizeof (AgManagerPrivate));

    klass->account_deleted = ag_manager_account_deleted;
    object_class->dispose = ag_manager_dispose;
    object_class->get_property = ag_manager_get_property;
    object_class->set_property = ag_manager_set_property;
    object_class->finalize = ag_manager_finalize;

    /**
     * AgManager:service-type:
     *
     * If the service type is set, certain operations on the #AgManager, such
     * as ag_manager_list() and ag_manager_list_services(), will be restricted
     * to only affect accounts or services with that service type.
     */
    properties[PROP_SERVICE_TYPE] =
        g_param_spec_string ("service-type", "service type", "Set service type",
                             NULL,
                             G_PARAM_STATIC_STRINGS |
                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

    /**
     * AgManager:db-timeout:
     *
     * Timeout for database operations, in milliseconds.
     */
    properties[PROP_DB_TIMEOUT] =
        g_param_spec_uint ("db-timeout", "DB timeout",
                           "Timeout for DB operations (ms)",
                           0, G_MAXUINT, MAX_SQLITE_BUSY_LOOP_TIME_MS,
                           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);

    /**
     * AgManager:abort-on-db-timeout:
     *
     * Whether to abort the application when a database timeout occurs.
     */
    properties[PROP_ABORT_ON_DB_TIMEOUT] =
        g_param_spec_boolean ("abort-on-db-timeout", "Abort on DB timeout",
                              "Whether to abort the application on DB timeout",
                              FALSE,
                              G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);

    /**
     * AgManager:use-dbus:
     *
     * Whether to use D-Bus for inter-process change notification. Setting this
     * property to %FALSE causes libaccounts not to emit the change
     * notification signals, and also not react to changes made by other
     * processes. Disabling D-Bus is only meant to be used for specific cases,
     * such as maintenance programs.
     */
    properties[PROP_USE_DBUS] =
        g_param_spec_boolean ("use-dbus", "Use D-Bus",
                              "Whether to use D-Bus for IPC",
                              TRUE,
                              G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE |
                              G_PARAM_CONSTRUCT_ONLY);

    g_object_class_install_properties (object_class,
                                       N_PROPERTIES,
                                       properties);

    /**
     * AgManager::account-created:
     * @manager: the #AgManager.
     * @account_id: the #AgAccountId of the account that has been created.
     *
     * Emitted when a new account has been created; note that the account must
     * have been stored in the database: the signal is not emitted just in
     * response to ag_manager_create_account().
     */
    signals[ACCOUNT_CREATED] = g_signal_new ("account-created",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        g_cclosure_marshal_VOID__UINT,
        G_TYPE_NONE,
        1, G_TYPE_UINT);

    /**
     * AgManager::enabled-event:
     * @manager: the #AgManager.
     * @account_id: the #AgAccountId of the account that has been enabled.
     *
     * If the manager has been created with ag_manager_new_for_service_type(),
     * this signal will be emitted when an account (identified by @account_id)
     * has been modified in such a way that the application might be interested
     * to start or stop using it: the "enabled" flag on the account or in some
     * service supported by the account and matching the
     * #AgManager:service-type have changed.
     * In practice, this signal might be emitted more often than when strictly
     * needed; applications must call ag_account_list_enabled_services() or
     * ag_manager_list_enabled() to get the current state.
     */
    signals[ACCOUNT_ENABLED] = g_signal_new ("enabled-event",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        0,
        NULL, NULL,
        g_cclosure_marshal_VOID__UINT,
        G_TYPE_NONE,
        1, G_TYPE_UINT);

    /**
     * AgManager::account-deleted:
     * @manager: the #AgManager.
     * @account_id: the #AgAccountId of the account that has been deleted.
     *
     * Emitted when an account has been deleted.
     * This signal is redundant with #AgAccount::deleted, but it is convenient
     * to provide full change notification with #AgManager.
     */
    signals[ACCOUNT_DELETED] = g_signal_new ("account-deleted",
        G_TYPE_FROM_CLASS (klass),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (AgManagerClass, account_deleted),
        NULL, NULL,
        g_cclosure_marshal_VOID__UINT,
        G_TYPE_NONE,
        1, G_TYPE_UINT);

    /**
     * AgManager::account-updated:
     * @manager: the #AgManager.
     * @account_id: the #AgAccountId of the account that has been update.
     *
     * Emitted when particular service of an account has been updated.
     * This signal is redundant with #AgAccount::deleted, but it is convenient
     * to provide full change notification with #AgManager.
     */
    signals[ACCOUNT_UPDATED] = g_signal_new ("account-updated",
         G_TYPE_FROM_CLASS (klass),
         G_SIGNAL_RUN_LAST,
         0,
         NULL, NULL,
         g_cclosure_marshal_VOID__UINT,
         G_TYPE_NONE,
         1, G_TYPE_UINT);

    _ag_debug_init();
}

/**
 * ag_manager_new:
 *
 * Create a new #AgManager.
 *
 * Returns: an instance of an #AgManager.
 */
AgManager *
ag_manager_new ()
{
    return g_initable_new (AG_TYPE_MANAGER, NULL, NULL,
                           NULL);
}

GList *
_ag_manager_list_all (AgManager *manager)
{
    GList *list = NULL;
    const gchar *sql;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    sql = "SELECT id FROM Accounts;";
    _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
                            &list, sql);
    return list;
}

void
_ag_manager_take_error (AgManager *manager, GError *error)
{
    AgManagerPrivate *priv;

    g_return_if_fail (AG_IS_MANAGER (manager));
    priv = manager->priv;

    if (priv->last_error)
        g_error_free (priv->last_error);
    priv->last_error = error;
}

const GError *
_ag_manager_get_last_error (AgManager *manager)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);

    return manager->priv->last_error;
}

/**
 * ag_manager_list:
 * @manager: the #AgManager.
 *
 * Lists the accounts. If the #AgManager is created with a specified
 * #AgManager:service-type, it will return only the accounts supporting this
 * service type.
 *
 * Returns: (transfer full) (element-type AgAccountId): a #GList of
 * #AgAccountId representing the accounts. Must be free'd with
 * ag_manager_list_free() when no longer required.
 */
GList *
ag_manager_list (AgManager *manager)
{
    AgManagerPrivate *priv;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    priv = manager->priv;

    if (priv->service_type)
        return ag_manager_list_by_service_type (manager, priv->service_type);

    return _ag_manager_list_all (manager);
}

/**
 * ag_manager_list_by_service_type:
 * @manager: the #AgManager.
 * @service_type: the name of the service type to check for.
 *
 * Lists the accounts supporting the given service type.
 *
 * Returns: (transfer full) (element-type AgAccountId): a #GList of
 * #AgAccountId representing the accounts. Must be free'd with
 * ag_manager_list_free() when no longer required.
 */
GList *
ag_manager_list_by_service_type (AgManager *manager,
                                 const gchar *service_type)
{
    GList *list = NULL;
    char sql[512];

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    sqlite3_snprintf (sizeof (sql), sql,
                      "SELECT id FROM Accounts WHERE provider IN ("
                      "SELECT provider FROM Services WHERE type = %Q);",
                      service_type);
    _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
                            &list, sql);
    return list;
}

/**
 * ag_manager_list_enabled:
 * @manager: the #AgManager.
 *
 * Lists the enabled accounts.
 *
 * Returns: (transfer full) (element-type AgAccountId): a #GList of the enabled
 * #AgAccountId representing the accounts. Must be free'd with
 * ag_manager_list_free() when no longer required.
 */
GList *
ag_manager_list_enabled (AgManager *manager)
{
    GList *list = NULL;
    char sql[512];
    AgManagerPrivate *priv;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    priv = manager->priv;

    if (priv->service_type == NULL)
    {
        sqlite3_snprintf (sizeof (sql), sql,
                          "SELECT id FROM Accounts WHERE enabled=1;");
        _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
                                &list, sql);
    }
    else
    {
        list = ag_manager_list_enabled_by_service_type(manager, priv->service_type);
    }
    return list;
}

/**
 * ag_manager_list_enabled_by_service_type:
 * @manager: the #AgManager.
 * @service_type: the name of the service type to check for.
 *
 * Lists the enabled accounts supporting the given service type.
 *
 * Returns: (transfer full) (element-type AgAccountId): a #GList of the enabled
 * #AgAccountId representing the accounts. Must be free'd with
 * ag_manager_list_free() when no longer required.
 */
GList *
ag_manager_list_enabled_by_service_type (AgManager *manager,
                                         const gchar *service_type)
{
    GList *list = NULL;
    char sql[512];

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    g_return_val_if_fail (service_type != NULL, NULL);
    sqlite3_snprintf (sizeof (sql), sql,
                      "SELECT Settings.account FROM Settings "
                      "INNER JOIN Services ON Settings.service = Services.id "
                      "WHERE Settings.key='enabled' AND Settings.value='true' "
                      "AND Services.type = %Q AND Settings.account IN "
                      "(SELECT id FROM Accounts WHERE enabled=1);",
                      service_type);
    _ag_manager_exec_query (manager, (AgQueryCallback)add_id_to_list,
                            &list, sql);
    return list;
}

/**
 * ag_manager_list_free:
 * @list: (element-type AgAccountId): a #GList returned from a #AgManager
 * method which returns account IDs.
 *
 * Frees the memory taken by a #GList of #AgAccountId allocated by #AgManager,
 * such as by ag_manager_list(), ag_manager_list_enabled() or
 * ag_manager_list_enabled_by_service_type().
 */
void
ag_manager_list_free (GList *list)
{
    g_list_free (list);
}

/**
 * ag_manager_get_enabled_account_services:
 * @manager: the #AgManager.
 *
 * Gets all the enabled account services. If the @manager was created for a
 * specific service type, only services with that type will be returned.
 * <note>
 *   <para>
 *   This method causes the loading of all the service settings for all the
 *   returned accounts (unless they have been loaded previously). If you are
 *   interested in a specific account/service, consider using
 *   ag_manager_load_account() to first load the the account, and then create
 *   the AgAccountService for that account only.
 *   </para>
 * </note>
 *
 * Returns: (transfer full) (element-type AgAccountService): a list of
 * #AgAccountService objects. When done with it, call g_object_unref() on the
 * list elements, and g_list_free() on the container.
 */
GList *
ag_manager_get_enabled_account_services (AgManager *manager)
{
    GList *account_ids, *account_services;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);

    account_ids = ag_manager_list_enabled (manager);
    account_services = get_account_services_from_accounts (manager,
                                                           account_ids,
                                                           TRUE);
    ag_manager_list_free (account_ids);
    return account_services;
}

/**
 * ag_manager_get_account_services:
 * @manager: the #AgManager.
 *
 * Gets all the account services. If the @manager was created for a
 * specific service type, only services with that type will be returned.
 * <note>
 *   <para>
 *   This method causes the loading of all the service settings for all the
 *   returned accounts (unless they have been loaded previously). If you are
 *   interested in a specific account/service, consider using
 *   ag_manager_load_account() to first load the the account, and then create
 *   the AgAccountService for that account only.
 *   </para>
 * </note>
 *
 * Returns: (transfer full) (element-type AgAccountService): a list of
 * #AgAccountService objects. When done with it, call g_object_unref() on the
 * list elements, and g_list_free() on the container.
 */
GList *
ag_manager_get_account_services (AgManager *manager)
{
    GList *account_ids, *account_services;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);

    account_ids = ag_manager_list (manager);
    account_services = get_account_services_from_accounts (manager,
                                                           account_ids,
                                                           FALSE);
    ag_manager_list_free (account_ids);
    return account_services;
}

/**
 * ag_manager_get_account:
 * @manager: the #AgManager.
 * @account_id: the #AgAccountId of the account.
 *
 * Instantiates the object representing the account identified by
 * @account_id.
 *
 * Returns: (transfer full): an #AgAccount, on which the client must call
 * g_object_unref() when it is no longer required, or %NULL if an error occurs.
 */
AgAccount *
ag_manager_get_account (AgManager *manager, AgAccountId account_id)
{
    return ag_manager_load_account (manager, account_id, NULL);
}

/**
 * ag_manager_load_account:
 * @manager: the #AgManager.
 * @account_id: the #AgAccountId of the account.
 * @error: pointer to a #GError, or %NULL.
 *
 * Instantiates the object representing the account identified by
 * @account_id.
 *
 * Returns: (transfer full): an #AgAccount, on which the client must call
 * g_object_unref() when it is no longer required, or %NULL if an error occurs.
 */
AgAccount *
ag_manager_load_account (AgManager *manager, AgAccountId account_id,
                         GError **error)
{
    AgManagerPrivate *priv;
    AgAccount *account;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    g_return_val_if_fail (account_id != 0, NULL);
    priv = manager->priv;

    account = g_hash_table_lookup (priv->accounts,
                                   GUINT_TO_POINTER (account_id));
    if (account)
        return g_object_ref (account);

    /* the account is not loaded; do it now */
    account = g_initable_new (AG_TYPE_ACCOUNT, NULL, error,
                              "manager", manager,
                              "id", account_id,
                              NULL);
    if (G_LIKELY (account))
    {
        g_object_weak_ref (G_OBJECT (account), account_weak_notify, manager);
        g_hash_table_insert (priv->accounts, GUINT_TO_POINTER (account_id),
                             account);
    }

    return account;
}

/**
 * ag_manager_create_account:
 * @manager: the #AgManager.
 * @provider_name: name of the provider of the account.
 *
 * Create a new account. The account is not stored in the database until
 * ag_account_store() has successfully returned; the @id field in the
 * #AgAccount structure is also not meant to be valid until the account has
 * been stored.
 *
 * Returns: (transfer full): a new #AgAccount, or %NULL.
 */
AgAccount *
ag_manager_create_account (AgManager *manager, const gchar *provider_name)
{
    AgAccount *account;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);

    account = g_initable_new (AG_TYPE_ACCOUNT, NULL, NULL,
                              "manager", manager,
                              "provider", provider_name,
                              NULL);
    return account;
}

/* This is called when creating AgService objects from inside the DBus
 * handler: we don't want to access the Db from there */
AgService *
_ag_manager_get_service_lazy (AgManager *manager, const gchar *service_name,
                              const gchar *service_type, const gint service_id)
{
    AgManagerPrivate *priv;
    AgService *service;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    g_return_val_if_fail (service_name != NULL, NULL);
    priv = manager->priv;

    service = g_hash_table_lookup (priv->services, service_name);
    if (service)
    {
        if (service->id == 0)
            service->id = service_id;
        return ag_service_ref (service);
    }

    service = _ag_service_new_from_memory (service_name, service_type, service_id);

    g_hash_table_insert (priv->services, service->name, service);
    return ag_service_ref (service);
}

/**
 * ag_manager_get_service:
 * @manager: the #AgManager.
 * @service_name: the name of the service.
 *
 * Loads the service identified by @service_name.
 *
 * Returns: an #AgService, which must be free'd with ag_service_unref() when no
 * longer required.
 */
AgService *
ag_manager_get_service (AgManager *manager, const gchar *service_name)
{
    AgManagerPrivate *priv;
    AgService *service;
    gchar *sql;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    g_return_val_if_fail (service_name != NULL, NULL);
    priv = manager->priv;

    service = g_hash_table_lookup (priv->services, service_name);
    if (service)
        return ag_service_ref (service);

    /* First, check if the service is in the DB */
    sql = sqlite3_mprintf ("SELECT id, display, provider, type "
                           "FROM Services WHERE name = %Q", service_name);
    _ag_manager_exec_query (manager, (AgQueryCallback)got_service,
                            &service, sql);
    sqlite3_free (sql);

    if (service)
    {
        /* the basic server data have been loaded from the DB; the service name
         * is still missing, though */
        service->name = g_strdup (service_name);
    }
    else
    {
        /* The service is not in the DB: it must be loaded */
        service = _ag_service_new_from_file (service_name);

        if (service && !add_service_to_db (manager, service))
        {
            g_warning ("Error in adding service %s to DB!", service_name);
            ag_service_unref (service);
            service = NULL;
        }
    }

    if (G_UNLIKELY (!service)) return NULL;

    g_hash_table_insert (priv->services, service->name, service);
    return ag_service_ref (service);
}

guint
_ag_manager_get_service_id (AgManager *manager, AgService *service)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), 0);

    if (service == NULL) return 0; /* global service */

    if (service->id == 0)
    {
        gchar *sql;
        gint rows;

        /* We got this service name from another process; load the id from the
         * DB - it must already exist */
        sql = sqlite3_mprintf ("SELECT id FROM Services WHERE name = %Q",
                               service->name);
        rows = _ag_manager_exec_query (manager, (AgQueryCallback)got_service_id,
                                       service, sql);
        sqlite3_free (sql);
        if (G_UNLIKELY (rows != 1))
        {
            g_warning ("%s: got %d rows when asking for service %s",
                       G_STRFUNC, rows, service->name);
        }
    }

    return service->id;
}

/**
 * ag_manager_list_services:
 * @manager: the #AgManager.
 *
 * Gets a list of all the installed services.
 * If the #AgManager was created with a specified #AgManager:service_type
 * it will return only the installed services supporting that service type.
 *
 * Returns: (transfer full) (element-type AgService): a list of #AgService,
 * which must be free'd with ag_service_list_free() when no longer required.
 */
GList *
ag_manager_list_services (AgManager *manager)
{
    AgManagerPrivate *priv;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    priv = manager->priv;

    if (priv->service_type)
        return ag_manager_list_services_by_type (manager, priv->service_type);

    return _ag_services_list (manager);
}

/**
 * ag_manager_list_services_by_type:
 * @manager: the #AgManager.
 * @service_type: the type of the service.
 *
 * Gets a list of all the installed services where the service type name is
 * @service_type.
 *
 * Returns: (transfer full) (element-type AgService): a list of #AgService,
 * which must be free'd with ag_service_list_free() when no longer required.
 */
GList *
ag_manager_list_services_by_type (AgManager *manager, const gchar *service_type)
{
    GList *all_services, *list;
    GList *services = NULL;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    g_return_val_if_fail (service_type != NULL, NULL);

    /* if we kept the DB Service table always up-to-date with all known
     * services, then we could just run a query over it. But while we are not,
     * it's simpler to implement the function by reusing the output from
     * _ag_services_list(manager). */
    all_services = _ag_services_list (manager);
    for (list = all_services; list != NULL; list = list->next)
    {
        AgService *service = list->data;
        const gchar *serviceType = ag_service_get_service_type (service);
        if (serviceType && strcmp (serviceType, service_type) == 0)
        {
            services = g_list_prepend (services, service);
        }
        else
            ag_service_unref (service);
    }
    g_list_free (all_services);

    return services;
}

static GError *
sqlite_error_to_gerror (int db_error, sqlite3 *db)
{
    AgAccountsError code = (db_error == SQLITE_READONLY) ?
        AG_ACCOUNTS_ERROR_READONLY : AG_ACCOUNTS_ERROR_DB;
    return g_error_new (AG_ACCOUNTS_ERROR, code,
                        "Got error: %s (%d)",
                        sqlite3_errmsg (db), db_error);
}

void
_ag_manager_exec_transaction (AgManager *manager, const gchar *sql,
                              AgAccountChanges *changes, AgAccount *account,
                              GTask *task)
{
    AgManagerPrivate *priv = manager->priv;
    GError *error = NULL;
    int ret;

    ret = prepare_transaction_statements (priv);
    if (G_UNLIKELY (ret != SQLITE_OK))
    {
        error = sqlite_error_to_gerror (ret, priv->db);
        goto finish;
    }

    ret = sqlite3_step (priv->begin_stmt);
    if (ret == SQLITE_BUSY)
    {
        StoreCbData *sd;

        sd = g_slice_new (StoreCbData);
        sd->manager = manager;
        sd->account = account;
        sd->changes = changes;
        sd->task = task;
        sd->sql = g_strdup (sql);
        sd->id = g_idle_add ((GSourceFunc)exec_transaction_idle, sd);
        priv->locks = g_list_prepend (priv->locks, sd);
        return;
    }

    if (ret != SQLITE_DONE)
    {
        error = sqlite_error_to_gerror (ret, priv->db);
        goto finish;
    }

    exec_transaction (manager, account, sql, changes, &error);

finish:
    if (error != NULL)
    {
        g_task_return_error (task, error);
    }
    else
    {
        g_task_return_boolean (task, TRUE);
    }

    _ag_account_store_completed (account, changes);
}

void
_ag_manager_exec_transaction_blocking (AgManager *manager, const gchar *sql,
                                       AgAccountChanges *changes,
                                       AgAccount *account,
                                       GError **error)
{
    AgManagerPrivate *priv = manager->priv;
    gint sleep_ms = 200;
    int ret;

    ret = prepare_transaction_statements (priv);
    if (G_UNLIKELY (ret != SQLITE_OK))
    {
        *error = sqlite_error_to_gerror (ret, priv->db);
        return;
    }

    ret = sqlite3_step (priv->begin_stmt);
    while (ret == SQLITE_BUSY)
    {
        /* TODO: instead of this loop, use a semaphore or some other non
         * polling mechanism */
        if (sleep_ms > 30000)
        {
            DEBUG_LOCKS ("Database locked for more than 30 seconds; "
                         "giving up!");
            break;
        }
        DEBUG_LOCKS ("Database locked, sleeping for %ums", sleep_ms);
        g_usleep (sleep_ms * 1000);
        sleep_ms *= 2;
        ret = sqlite3_step (priv->begin_stmt);
    }

    if (ret != SQLITE_DONE)
    {
        *error = sqlite_error_to_gerror (ret, priv->db);
        return;
    }

    exec_transaction (manager, account, sql, changes, error);
}

static void
ag_manager_store_local_async (AgManager *manager, AgAccount *account,
                              GTask *task)
{
    AgAccountChanges *changes;
    GError *error = NULL;
    gchar *sql;

    sql = _ag_account_get_store_sql (account, &error);
    if (G_UNLIKELY (error))
    {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    changes = _ag_account_steal_changes (account);

    _ag_manager_exec_transaction (manager, sql, changes, account, task);
    g_free (sql);
}

static gboolean
ag_manager_store_local_sync (AgManager *manager, AgAccount *account,
                             GError **error)
{
    AgAccountChanges *changes;
    GError *error_int = NULL;
    gchar *sql;

    sql = _ag_account_get_store_sql (account, &error_int);
    if (G_UNLIKELY (error_int))
    {
        g_warning ("%s: %s", G_STRFUNC, error_int->message);
        g_propagate_error (error, error_int);
        return FALSE;
    }

    changes = _ag_account_steal_changes (account);

    _ag_manager_exec_transaction_blocking (manager, sql,
                                           changes, account,
                                           &error_int);
    g_free (sql);
    _ag_account_changes_free (changes);

    if (G_UNLIKELY (error_int))
    {
        g_warning ("%s: %s", G_STRFUNC, error_int->message);
        g_propagate_error (error, error_int);
        return FALSE;
    }

    return TRUE;
}

void
_ag_manager_store_async (AgManager *manager, AgAccount *account,
                         GTask *task)
{
    if (manager->priv->is_readonly)
    {
        ag_manager_store_dbus_async (manager, account, task);
    }
    else
    {
        ag_manager_store_local_async (manager, account, task);
    }
}

gboolean
_ag_manager_store_sync (AgManager *manager, AgAccount *account,
                        GError **error)
{
    if (manager->priv->is_readonly)
    {
        return ag_manager_store_dbus_sync (manager, account, error);
    }
    else
    {
        return ag_manager_store_local_sync (manager, account, error);
    }
}

static guint
timespec_diff_ms(struct timespec *ts1, struct timespec *ts0)
{
    return (ts1->tv_sec - ts0->tv_sec) * 1000 +
        (ts1->tv_nsec - ts0->tv_nsec) / 1000000;
}

/* Executes an SQL statement, and optionally calls
 * the callback for every row of the result.
 * Returns the number of rows fetched.
 */
gint
_ag_manager_exec_query (AgManager *manager,
                        AgQueryCallback callback, gpointer user_data,
                        const gchar *sql)
{
    sqlite3 *db;
    int ret;
    sqlite3_stmt *stmt;
    struct timespec ts0, ts1;
    gint rows = 0;

    g_return_val_if_fail (AG_IS_MANAGER (manager), 0);
    db = manager->priv->db;

    g_return_val_if_fail (db != NULL, 0);

    ret = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);
    if (ret != SQLITE_OK)
    {
        g_warning ("%s: can't compile SQL statement \"%s\": %s", G_STRFUNC, sql,
                   sqlite3_errmsg (db));
        return 0;
    }

    DEBUG_QUERIES ("about to run:\n%s", sql);

    /* get the current time, to abort the operation in case the DB is locked
     * for longer than db_timeout. */
    clock_gettime(CLOCK_MONOTONIC, &ts0);

    do
    {
        ret = sqlite3_step (stmt);

        switch (ret)
        {
            case SQLITE_DONE:
                break;

            case SQLITE_ROW:
                if (callback == NULL || callback (stmt, user_data))
                {
                    rows++;
                }
                break;

            case SQLITE_BUSY:
                clock_gettime(CLOCK_MONOTONIC, &ts1);
                if (timespec_diff_ms(&ts1, &ts0) < manager->priv->db_timeout)
                {
                    /* If timeout was specified and table is locked,
                     * wait instead of executing default runtime
                     * error action. Otherwise, fall through to it. */
                    sched_yield ();
                    break;
                }

            default:
                set_error_from_db (manager);
                g_warning ("%s: runtime error while executing \"%s\": %s",
                           G_STRFUNC, sql, sqlite3_errmsg (db));
                sqlite3_finalize (stmt);
                return rows;
        }
    } while (ret != SQLITE_DONE);

    sqlite3_finalize (stmt);

    return rows;
}

/**
 * ag_manager_get_provider:
 * @manager: the #AgManager.
 * @provider_name: the name of the provider.
 *
 * Loads the provider identified by @provider_name.
 *
 * Returns: an #AgProvider, which must be free'd with ag_provider_unref() when
 * no longer required.
 */
AgProvider *
ag_manager_get_provider (AgManager *manager, const gchar *provider_name)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    g_return_val_if_fail (provider_name != NULL, NULL);

    /* We don't implement any caching mechanism for AgProvider structures: they
     * shouldn't be loaded that often. */
    return _ag_provider_new_from_file (provider_name);
}

/**
 * ag_manager_list_providers:
 * @manager: the #AgManager.
 *
 * Gets a list of all the installed providers.
 *
 * Returns: (transfer full) (element-type AgProvider): a list of #AgProvider,
 * which must be then free'd with ag_provider_list_free().
 */
GList *
ag_manager_list_providers (AgManager *manager)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);

    return _ag_providers_list (manager);
}

/**
 * ag_manager_new_for_service_type:
 * @service_type: the name of a service type
 *
 * Create a new #AgManager with the service type with the name @service_type.
 *
 * Returns: an #AgManager instance with the specified service type.
 */
AgManager *
ag_manager_new_for_service_type (const gchar *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);

    return g_initable_new (AG_TYPE_MANAGER, NULL, NULL,
                           "service-type", service_type,
                           NULL);
}

/**
 * ag_manager_get_service_type:
 * @manager: the #AgManager.
 *
 * Get the service type for @manager.
 *
 * Returns: the name of the service type for the supplied @manager.
 */
const gchar *
ag_manager_get_service_type (AgManager *manager)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);

    return manager->priv->service_type;
}

/**
 * ag_manager_set_db_timeout:
 * @manager: the #AgManager.
 * @timeout_ms: the new timeout, in milliseconds.
 *
 * Sets the timeout for database operations. This tells the library how long
 * it is allowed to block while waiting for a locked DB to become accessible.
 * Higher values mean a higher chance of successful reads, but also mean that
 * the execution might be blocked for a longer time.
 * The default is 5 seconds.
 */
void
ag_manager_set_db_timeout (AgManager *manager, guint timeout_ms)
{
    g_return_if_fail (AG_IS_MANAGER (manager));
    manager->priv->db_timeout = timeout_ms;
}

/**
 * ag_manager_get_db_timeout:
 * @manager: the #AgManager.
 *
 * Get the timeout of database operations for @manager, in milliseconds.
 *
 * Returns: the timeout (in milliseconds) for database operations.
 */
guint
ag_manager_get_db_timeout (AgManager *manager)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), 0);
    return manager->priv->db_timeout;
}

/**
 * ag_manager_set_abort_on_db_timeout:
 * @manager: the #AgManager.
 * @abort: whether to abort when a DB timeout occurs.
 *
 * Tells libaccounts whether it should make the client application abort when
 * a timeout error occurs. The default is %FALSE.
 */
void
ag_manager_set_abort_on_db_timeout (AgManager *manager, gboolean abort)
{
    g_return_if_fail (AG_IS_MANAGER (manager));
    manager->priv->abort_on_db_timeout = abort;
}

/**
 * ag_manager_get_abort_on_db_timeout:
 * @manager: the #AgManager.
 *
 * Get whether the library will abort when a timeout error occurs.
 *
 * Returns: %TRUE is the library will abort when a timeout error occurs, %FALSE
 * otherwise.
 */
gboolean
ag_manager_get_abort_on_db_timeout (AgManager *manager)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), FALSE);
    return manager->priv->abort_on_db_timeout;
}

/**
 * ag_manager_list_service_types:
 * @manager: the #AgManager.
 *
 * Gets a list of all the installed service types.
 *
 * Returns: (transfer full) (element-type AgServiceType): a list of
 * #AgServiceType, which must be free'd with ag_service_type_list_free() when
 * no longer required.
 */
GList *
ag_manager_list_service_types (AgManager *manager)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    return _ag_service_types_list (manager);
}

/**
 * ag_manager_load_service_type:
 * @manager: the #AgManager.
 * @service_type: the name of the service type.
 *
 * Instantiate the service type with the name @service_type.
 *
 * Returns: (transfer full): an #AgServiceType, which must be free'd with
 * ag_service_type_unref() when no longer required.
 */
AgServiceType *
ag_manager_load_service_type (AgManager *manager, const gchar *service_type)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);

    /* Given the small size of the service type file, and the unlikely need to
     * load them more than once, we don't cache them in the manager. But this
     * might change in the future.
     */
    return _ag_service_type_new_from_file (service_type);
}

/**
 * ag_manager_list_applications_by_service:
 * @manager: the #AgManager.
 * @service: the #AgService for which we want to get the applications list.
 *
 * Lists the registered applications which support the given service.
 *
 * Returns: (transfer full) (element-type AgApplication): a #GList of all the
 * applications which have declared support for the given service or for its
 * service type.
 */
GList *
ag_manager_list_applications_by_service (AgManager *manager,
                                         AgService *service)
{
    GList *applications = NULL;
    GList *all_applications, *list;

    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    g_return_val_if_fail (service != NULL, NULL);

    all_applications = _ag_applications_list (manager);
    for (list = all_applications; list != NULL; list = list->next)
    {
        AgApplication *application = list->data;
        if (ag_application_supports_service (application, service))
        {
            applications = g_list_prepend (applications, application);
        }
        else
        {
            ag_application_unref (application);
        }
    }
    g_list_free (all_applications);

    return applications;
}

/**
 * ag_manager_list_services_by_application:
 * @manager: the #AgManager.
 * @application: a #AgApplication.
 *
 * Get the list of services that are supported by @application.
 *
 * Returns: (transfer full) (element-type AgService): a #GList of #AgService
 * items representing all the services which are supported. Must be free'd with
 * ag_service_list_free().
 */
GList *
ag_manager_list_services_by_application (AgManager *manager,
                                         AgApplication *application)
{
    g_return_val_if_fail (AG_IS_MANAGER (manager), NULL);
    return _ag_application_list_supported_services (application, manager);
}
07070100000033000081A4000003E800000064000000015BDC2B8B000013E4000000000000000000000000000000000000003400000000libaccounts-glib-1.24/libaccounts-glib/ag-manager.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_MANAGER_H_
#define _AG_MANAGER_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib-object.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS

#define AG_TYPE_MANAGER             (ag_manager_get_type ())
#define AG_MANAGER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), AG_TYPE_MANAGER, AgManager))
#define AG_MANAGER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), AG_TYPE_MANAGER, AgManagerClass))
#define AG_IS_MANAGER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), AG_TYPE_MANAGER))
#define AG_IS_MANAGER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), AG_TYPE_MANAGER))
#define AG_MANAGER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), AG_TYPE_MANAGER, AgManagerClass))

typedef struct _AgManagerClass AgManagerClass;
typedef struct _AgManagerPrivate AgManagerPrivate;

/**
 * AgManagerClass:
 *
 * Use the accessor functions below.
 */
struct _AgManagerClass
{
    GObjectClass parent_class;
    void (*account_deleted) (AgManager *manager, AgAccountId id);
    void (*_ag_reserved2) (void);
    void (*_ag_reserved3) (void);
    void (*_ag_reserved4) (void);
    void (*_ag_reserved5) (void);
    void (*_ag_reserved6) (void);
    void (*_ag_reserved7) (void);
};

struct _AgManager
{
    GObject parent_instance;

    /*< private >*/
    AgManagerPrivate *priv;
};

GType ag_manager_get_type (void) G_GNUC_CONST;

AgManager *ag_manager_new (void);

AgManager *ag_manager_new_for_service_type (const gchar *service_type);

GList *ag_manager_list (AgManager *manager);
GList *ag_manager_list_by_service_type (AgManager *manager,
                                        const gchar *service_type);
void ag_manager_list_free (GList *list);

GList *ag_manager_get_account_services (AgManager *manager);
GList *ag_manager_get_enabled_account_services (AgManager *manager);

AgAccount *ag_manager_get_account (AgManager *manager,
                                   AgAccountId account_id);
AgAccount *ag_manager_load_account (AgManager *manager,
                                    AgAccountId account_id,
                                    GError **error);
AgAccount *ag_manager_create_account (AgManager *manager,
                                      const gchar *provider_name);

AgService *ag_manager_get_service (AgManager *manager,
                                   const gchar *service_name);
GList *ag_manager_list_services (AgManager *manager);
GList *ag_manager_list_services_by_type (AgManager *manager,
                                         const gchar *service_type);
GList *ag_manager_list_services_by_application (AgManager *manager,
                                                AgApplication *application);
GList *ag_manager_list_enabled (AgManager *manager);
GList *ag_manager_list_enabled_by_service_type (AgManager *manager,
                                                const gchar *service_type);
const gchar *ag_manager_get_service_type (AgManager *manager);

AgProvider *ag_manager_get_provider (AgManager *manager,
                                     const gchar *provider_name);
GList *ag_manager_list_providers (AgManager *manager);

void ag_manager_set_db_timeout (AgManager *manager, guint timeout_ms);
guint ag_manager_get_db_timeout (AgManager *manager);
void ag_manager_set_abort_on_db_timeout (AgManager *manager, gboolean abort);
gboolean ag_manager_get_abort_on_db_timeout (AgManager *manager);

GList *ag_manager_list_service_types (AgManager *manager);
AgServiceType *ag_manager_load_service_type (AgManager *manager,
                                             const gchar *service_type);

AgApplication *ag_manager_get_application (AgManager *self,
                                           const gchar *application_name);
GList *ag_manager_list_applications_by_service (AgManager *manager,
                                                AgService *service);

G_END_DECLS

#endif /* _AG_MANAGER_H_ */
07070100000034000081A4000003E800000064000000015BDC2B8B00000015000000000000000000000000000000000000003700000000libaccounts-glib-1.24/libaccounts-glib/ag-marshal.listVOID:STRING,BOOLEAN

07070100000035000081A4000003E800000064000000015BDC2B8B000039DE000000000000000000000000000000000000003500000000libaccounts-glib-1.24/libaccounts-glib/ag-provider.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-provider
 * @short_description: A representation of a provider.
 * @include: libaccounts-glib/ag-provider.h
 *
 * The #AgProvider structure represents an account provider. The structure is
 * not directly exposed to applications, but its fields are accessible via
 * getter methods. It can be instantiated by #AgManager with
 * ag_manager_get_provider() or ag_manager_list_providers().
 * The structure is reference counted. One must use ag_provider_unref() when
 * done with it.
 *
 * See the <link linkend="example-create-new-AgAccount">example of creating a
 * new <structname>AgAccount</structname></link> to see how #AgProvider can be
 * used.
 */

#include "ag-provider.h"

#include "ag-internals.h"
#include "ag-util.h"
#include <libxml/xmlreader.h>
#include <string.h>

G_DEFINE_BOXED_TYPE (AgProvider, ag_provider,
                     (GBoxedCopyFunc)ag_provider_ref,
                     (GBoxedFreeFunc)ag_provider_unref);

static gboolean
parse_template (xmlTextReaderPtr reader, AgProvider *provider)
{
    GHashTable *settings;
    gboolean ok;

    g_return_val_if_fail (provider->default_settings == NULL, FALSE);

    settings =
        g_hash_table_new_full (g_str_hash, g_str_equal,
                               g_free, (GDestroyNotify)g_variant_unref);

    ok = _ag_xml_parse_settings (reader, "", settings);
    if (G_UNLIKELY (!ok))
    {
        g_hash_table_destroy (settings);
        return FALSE;
    }

    provider->default_settings = settings;
    return TRUE;
}

static gboolean
parse_provider (xmlTextReaderPtr reader, AgProvider *provider)
{
    const gchar *name;
    int ret, type;

    if (!provider->name)
    {
        xmlChar *_name = xmlTextReaderGetAttribute (reader,
                                                    (xmlChar *) "id");
        provider->name = g_strdup((const gchar *)_name);
        if (_name) xmlFree(_name);
    }

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = (const gchar *)xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!name)) return FALSE;

        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_END_ELEMENT &&
            strcmp (name, "provider") == 0)
            break;

        if (type == XML_READER_TYPE_ELEMENT)
        {
            gboolean ok;

            if (strcmp (name, "name") == 0 && !provider->display_name)
            {
                ok = _ag_xml_dup_element_data (reader, &provider->display_name);
                /* that's the only thing we are interested of: we can stop the
                 * parsing now. */
            }
            else if (strcmp (name, "description") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &provider->description);
            }
            else if (strcmp (name, "translations") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &provider->i18n_domain);
            }
            else if (strcmp (name, "icon") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &provider->icon_name);
            }
            else if (strcmp (name, "domains") == 0)
            {
                ok = _ag_xml_dup_element_data (reader, &provider->domains);
            }
            else if (strcmp (name, "plugin") == 0)
            {
                ok = _ag_xml_dup_element_data (reader, &provider->plugin_name);
            }
            else if (strcmp (name, "single-account") == 0)
            {
                ok = _ag_xml_get_boolean (reader, &provider->single_account);
            }
            else if (strcmp (name, "template") == 0)
            {
                ok = parse_template (reader, provider);
            }
            else
                ok = TRUE;

            if (G_UNLIKELY (!ok)) return FALSE;
        }

        ret = xmlTextReaderNext (reader);
    }
    return TRUE;
}

static gboolean
read_provider_file (xmlTextReaderPtr reader, AgProvider *provider)
{
    const xmlChar *name;
    int ret, type;

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_ELEMENT)
        {
            name = xmlTextReaderConstName (reader);
            if (G_LIKELY (name &&
                          strcmp ((const gchar *)name, "provider") == 0))
            {
                return parse_provider (reader, provider);
            }
        }

        ret = xmlTextReaderNext (reader);
    }
    return FALSE;
}

static AgProvider *
_ag_provider_new (void)
{
    AgProvider *provider;

    provider = g_slice_new0 (AgProvider);
    provider->ref_count = 1;

    return provider;
}

static gboolean
_ag_provider_load_from_file (AgProvider *provider)
{
    xmlTextReaderPtr reader;
    gchar *filepath;
    gboolean ret;
    GError *error = NULL;
    gsize len;

    g_return_val_if_fail (provider->name != NULL, FALSE);

    DEBUG_REFS ("Loading provider %s", provider->name);
    filepath = _ag_find_libaccounts_file (provider->name,
                                          ".provider",
                                          "AG_PROVIDERS",
                                          PROVIDER_FILES_DIR);
    if (G_UNLIKELY (!filepath)) return FALSE;

    g_file_get_contents (filepath, &provider->file_data,
                         &len, &error);
    if (G_UNLIKELY (error))
    {
        g_warning ("Error reading %s: %s", filepath, error->message);
        g_error_free (error);
        g_free (filepath);
        return FALSE;
    }

    g_free (filepath);

    /* TODO: cache the xmlReader */
    reader = xmlReaderForMemory (provider->file_data, len,
                                 NULL, NULL, 0);
    if (G_UNLIKELY (reader == NULL))
        return FALSE;

    ret = read_provider_file (reader, provider);

    xmlFreeTextReader (reader);
    return ret;
}

AgProvider *
_ag_provider_new_from_file (const gchar *provider_name)
{
    AgProvider *provider;

    provider = _ag_provider_new ();
    provider->name = g_strdup (provider_name);
    if (!_ag_provider_load_from_file (provider))
    {
        ag_provider_unref (provider);
        provider = NULL;
    }

    return provider;
}

GHashTable *
_ag_provider_load_default_settings (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);

    if (!provider->default_settings)
    {
        /* This can happen if the provider was created by the AccountManager by
         * loading the record from the DB.
         * Now we must reload the provider from its XML file.
         */
        if (!_ag_provider_load_from_file (provider))
        {
            g_warning ("Loading provider %s file failed", provider->name);
            return NULL;
        }
    }

    return provider->default_settings;
}

GVariant *
_ag_provider_get_default_setting (AgProvider *provider, const gchar *key)
{
    GHashTable *settings;

    g_return_val_if_fail (key != NULL, NULL);

    settings = _ag_provider_load_default_settings (provider);
    if (G_UNLIKELY (!settings))
        return NULL;

    return g_hash_table_lookup (settings, key);
}

/**
 * ag_provider_get_name:
 * @provider: the #AgProvider.
 *
 * Get the name of the #AgProvider.
 *
 * Returns: the name of @provider.
 */
const gchar *
ag_provider_get_name (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    return provider->name;
}

/**
 * ag_provider_get_i18n_domain:
 * @provider: the #AgProvider.
 *
 * Get the translation domain of the #AgProvider.
 *
 * Returns: the translation domain.
 */
const gchar *
ag_provider_get_i18n_domain (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    return provider->i18n_domain;
}

/**
 * ag_provider_get_icon_name:
 * @provider: the #AgProvider.
 *
 * Get the icon name of the #AgProvider.
 *
 * Returns: the icon_name.
 */
const gchar *
ag_provider_get_icon_name (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    return provider->icon_name;
}

/**
 * ag_provider_get_display_name:
 * @provider: the #AgProvider.
 *
 * Get the display name of the #AgProvider.
 *
 * Returns: the display name of @provider.
 */
const gchar *
ag_provider_get_display_name (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    return provider->display_name;
}

/**
 * ag_provider_get_description:
 * @provider: the #AgProvider.
 *
 * Get the description of the #AgProvider.
 *
 * Returns: the description of @provider, or %NULL upon failure.
 *
 * Since: 1.2
 */
const gchar *
ag_provider_get_description (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    return provider->description;
}

/**
 * ag_provider_get_domains_regex:
 * @provider: the #AgProvider.
 *
 * Get a regular expression matching all domains where this provider's accounts
 * can be used.
 *
 * Returns: a regular expression matching the domain names.
 *
 * Since: 1.1
 */
const gchar *
ag_provider_get_domains_regex (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    return provider->domains;
}

/**
 * ag_provider_match_domain:
 * @provider: the #AgProvider.
 * @domain: a domain name.
 *
 * Check whether @domain is supported by this provider, by matching it with the
 * regex returned by ag_provider_get_domains_regex().
 * If the provider does not define a regular expression to match the supported
 * domains, this function will return %FALSE.
 *
 * Returns: %TRUE if the given domain is supported, %FALSE otherwise.
 *
 * Since: 1.2
 */
gboolean
ag_provider_match_domain (AgProvider *provider, const gchar *domain)
{
    g_return_val_if_fail (provider != NULL, FALSE);
    g_return_val_if_fail (domain != NULL, FALSE);

    if (provider->domains == NULL)
        return FALSE;

    return g_regex_match_simple (provider->domains, domain, 0, 0);
}

/**
 * ag_provider_get_plugin_name:
 * @provider: the #AgProvider.
 *
 * Get the name of the account plugin which manages all accounts created from
 * this #AgProvider.
 * Some platforms might find it useful to store plugin names in the provider
 * XML files, especially when the same plugin can work for different providers.
 *
 * Returns: the plugin name for @provider, or %NULL if a plugin name is not
 * defined.
 *
 * Since: 1.5
 */
const gchar *
ag_provider_get_plugin_name (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    return provider->plugin_name;
}

/**
 * ag_provider_get_single_account:
 * @provider: the #AgProvider.
 *
 * Tell whether the provider doesn't support creating more than one account.
 * Note that libaccounts itself does not enforce preventing the creation of
 * multiple accounts when this flag is set: the flag is only informative, and
 * its implementation is left to the client.
 *
 * Returns: %FALSE if multiple accounts can be created from this provider,
 * %TRUE otherwise.
 *
 * Since: 1.14
 */
gboolean
ag_provider_get_single_account (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, FALSE);
    return provider->single_account;
}

/**
 * ag_provider_get_file_contents:
 * @provider: the #AgProvider.
 * @contents: location to receive the pointer to the file contents.
 *
 * Gets the contents of the XML provider file.  The buffer returned in @contents
 * should not be modified or freed, and is guaranteed to be valid as long as
 * @provider is referenced.
 * If some error occurs, @contents is set to %NULL.
 */
void
ag_provider_get_file_contents (AgProvider *provider,
                              const gchar **contents)
{
    g_return_if_fail (provider != NULL);
    g_return_if_fail (contents != NULL);

    if (provider->file_data == NULL)
    {
        /* This can happen if the provider was created by the AccountManager by
         * loading the record from the DB.
         * Now we must reload the provider from its XML file.
         */
        if (!_ag_provider_load_from_file (provider))
            g_warning ("Loading provider %s file failed", provider->name);
    }

    *contents = provider->file_data;
}

/**
 * ag_provider_ref:
 * @provider: the #AgProvider.
 *
 * Adds a reference to @provider.
 *
 * Returns: @provider.
 */
AgProvider *
ag_provider_ref (AgProvider *provider)
{
    g_return_val_if_fail (provider != NULL, NULL);
    g_return_val_if_fail (provider->ref_count > 0, NULL);

    DEBUG_REFS ("Referencing provider %s (%d)",
                provider->name, provider->ref_count);
    provider->ref_count++;
    return provider;
}

/**
 * ag_provider_unref:
 * @provider: the #AgProvider.
 *
 * Used to unreference the #AgProvider structure.
 */
void
ag_provider_unref (AgProvider *provider)
{
    g_return_if_fail (provider != NULL);
    g_return_if_fail (provider->ref_count > 0);

    DEBUG_REFS ("Unreferencing provider %s (%d)",
                provider->name, provider->ref_count);
    provider->ref_count--;
    if (provider->ref_count == 0)
    {
        g_free (provider->name);
        g_free (provider->i18n_domain);
        g_free (provider->icon_name);
        g_free (provider->description);
        g_free (provider->display_name);
        g_free (provider->domains);
        g_free (provider->plugin_name);
        g_free (provider->file_data);
        if (provider->default_settings != NULL)
            g_hash_table_unref (provider->default_settings);
        g_slice_free (AgProvider, provider);
    }
}

/**
 * ag_provider_list_free:
 * @list: (element-type AgProvider): a #GList of providers returned by some
 * function of this library.
 *
 * Frees the list @list.
 */
void
ag_provider_list_free (GList *list)
{
    g_list_foreach (list, (GFunc)ag_provider_unref, NULL);
    g_list_free (list);
}

07070100000036000081A4000003E800000064000000015BDC2B8B000008D9000000000000000000000000000000000000003500000000libaccounts-glib-1.24/libaccounts-glib/ag-provider.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_PROVIDER_H_
#define _AG_PROVIDER_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib.h>
#include <glib-object.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS

GType ag_provider_get_type (void) G_GNUC_CONST;

const gchar *ag_provider_get_name (AgProvider *provider);
const gchar *ag_provider_get_display_name (AgProvider *provider);
const gchar *ag_provider_get_description (AgProvider *provider);
const gchar *ag_provider_get_i18n_domain (AgProvider *provider);
const gchar *ag_provider_get_icon_name (AgProvider *provider);
const gchar *ag_provider_get_domains_regex (AgProvider *provider);
gboolean ag_provider_match_domain (AgProvider *provider,
                                   const gchar *domain);
const gchar *ag_provider_get_plugin_name (AgProvider *provider);
gboolean ag_provider_get_single_account (AgProvider *provider);
void ag_provider_get_file_contents (AgProvider *provider,
                                    const gchar **contents);
AgProvider *ag_provider_ref (AgProvider *provider);
void ag_provider_unref (AgProvider *provider);
void ag_provider_list_free (GList *list);

G_END_DECLS

#endif /* _AG_PROVIDER_H_ */
07070100000037000081A4000003E800000064000000015BDC2B8B00003024000000000000000000000000000000000000003900000000libaccounts-glib-1.24/libaccounts-glib/ag-service-type.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-service-type
 * @short_description: A description of a service type.
 *
 * The #AgServiceType structure represents a service type. The structure is
 * not directly exposed to applications, but its fields are accessible via
 * getter methods.
 * It is instantiated by #AgManager with ag_manager_list_service_types() or
 * ag_manager_load_service_type(). Additonally, #AgManager can be instantiated
 * with a set service type with ag_manager_new_for_service_type(), which
 * restricts some future operations on the manager, such as ag_manager_list()
 * or ag_manager_list_services(), to only affect accounts or services with the
 * set service type.
 * The structure is reference counted. One must use ag_service_type_unref()
 * when done with it.
 */

#include "ag-service-type.h"

#include "ag-internals.h"
#include "ag-util.h"
#include <libxml/xmlreader.h>
#include <string.h>

struct _AgServiceType {
    /*< private >*/
    gint ref_count;
    gchar *name;
    gchar *i18n_domain;
    gchar *display_name;
    gchar *description;
    gchar *icon_name;
    gchar *file_data;
    gsize file_data_len;
    GHashTable *tags;
};

G_DEFINE_BOXED_TYPE (AgServiceType, ag_service_type,
                     (GBoxedCopyFunc)ag_service_type_ref,
                     (GBoxedFreeFunc)ag_service_type_unref);

static gboolean
parse_service_type (xmlTextReaderPtr reader, AgServiceType *service_type)
{
    const gchar *name;
    int ret, type;

    if (!service_type->name)
    {
        xmlChar *_name = xmlTextReaderGetAttribute (reader,
                                                    (xmlChar *) "id");
        service_type->name = g_strdup ((const gchar *)_name);
        if (_name) xmlFree(_name);
    }

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = (const gchar *)xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!name)) return FALSE;

        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_END_ELEMENT &&
            strcmp (name, "service-type") == 0)
            break;

        if (type == XML_READER_TYPE_ELEMENT)
        {
            gboolean ok;

            if (strcmp (name, "name") == 0 && !service_type->display_name)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &service_type->display_name);
            }
            else if (strcmp (name, "description") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &service_type->description);
            }
            else if (strcmp (name, "icon") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &service_type->icon_name);
            }
            else if (strcmp (name, "translations") == 0)
            {
                ok = _ag_xml_dup_element_data (reader,
                                               &service_type->i18n_domain);
            }
            else if (strcmp (name, "tags") == 0)
            {
                ok = _ag_xml_parse_element_list (reader, "tag",
                                                 &service_type->tags);
            }
            else
                ok = TRUE;

            if (G_UNLIKELY (!ok)) return FALSE;
        }

        ret = xmlTextReaderNext (reader);
    }
    return TRUE;
}

static gboolean
read_service_type_file (xmlTextReaderPtr reader, AgServiceType *service_type)
{
    const xmlChar *name;
    int ret;

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = xmlTextReaderConstName (reader);
        if (G_LIKELY (name &&
                      strcmp ((const gchar *)name, "service-type") == 0))
        {
            return parse_service_type (reader, service_type);
        }

        ret = xmlTextReaderNext (reader);
    }
    return FALSE;
}

static AgServiceType *
_ag_service_type_new (void)
{
    AgServiceType *service_type;

    service_type = g_slice_new0 (AgServiceType);
    service_type->ref_count = 1;

    return service_type;
}

static gboolean
_ag_service_type_load_from_file (AgServiceType *service_type)
{
    xmlTextReaderPtr reader;
    gchar *filepath;
    gboolean ret;
    GError *error = NULL;

    g_return_val_if_fail (service_type->name != NULL, FALSE);

    DEBUG_REFS ("Loading service_type %s", service_type->name);
    filepath = _ag_find_libaccounts_file (service_type->name,
                                          ".service-type",
                                          "AG_SERVICE_TYPES",
                                          SERVICE_TYPE_FILES_DIR);
    if (G_UNLIKELY (!filepath)) return FALSE;

    g_file_get_contents (filepath, &service_type->file_data,
                         &service_type->file_data_len, &error);
    if (G_UNLIKELY (error))
    {
        g_warning ("Error reading %s: %s", filepath, error->message);
        g_error_free (error);
        g_free (filepath);
        return FALSE;
    }

    /* TODO: cache the xmlReader */
    reader = xmlReaderForMemory (service_type->file_data,
                                 service_type->file_data_len,
                                 filepath, NULL, 0);
    g_free (filepath);
    if (G_UNLIKELY (reader == NULL))
        return FALSE;

    ret = read_service_type_file (reader, service_type);

    xmlFreeTextReader (reader);
    return ret;
}

AgServiceType *
_ag_service_type_new_from_file (const gchar *service_type_name)
{
    AgServiceType *service_type;

    service_type = _ag_service_type_new ();
    service_type->name = g_strdup (service_type_name);
    if (!_ag_service_type_load_from_file (service_type))
    {
        ag_service_type_unref (service_type);
        service_type = NULL;
    }

    return service_type;
}

/**
 * ag_service_type_get_name:
 * @service_type: the #AgServiceType.
 *
 * Get the name of the #AgServiceType.
 *
 * Returns: the name of @service_type.
 */
const gchar *
ag_service_type_get_name (AgServiceType *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);
    return service_type->name;
}

/**
 * ag_service_type_get_i18n_domain:
 * @service_type: the #AgServiceType.
 *
 * Get the translation domain of the #AgServiceType.
 *
 * Returns: the translation domain.
 */
const gchar *
ag_service_type_get_i18n_domain (AgServiceType *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);
    return service_type->i18n_domain;
}

/**
 * ag_service_type_get_display_name:
 * @service_type: the #AgServiceType.
 *
 * Get the display name of the #AgServiceType.
 *
 * Returns: the display name of @service_type.
 */
const gchar *
ag_service_type_get_display_name (AgServiceType *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);
    return service_type->display_name;
}

/**
 * ag_service_type_get_description:
 * @service_type: the #AgServiceType.
 *
 * Get the description of the #AgServiceType.
 *
 * Returns: the description of @service_type, or %NULL upon failure.
 *
 * Since: 1.2
 */
const gchar *
ag_service_type_get_description (AgServiceType *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);
    return service_type->description;
}

/**
 * ag_service_type_get_icon_name:
 * @service_type: the #AgServiceType.
 *
 * Get the icon name of the #AgServiceType.
 *
 * Returns: the name of the icon of @service_type.
 */
const gchar *
ag_service_type_get_icon_name (AgServiceType *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);
    return service_type->icon_name;
}

/**
 * ag_service_type_has_tag:
 * @service_type: the #AgServiceType.
 * @tag: the tag to check for.
 *
 * Check if the #AgServiceType has the requested tag.
 *
 * Returns: TRUE if the #AgServiceType has the tag, FALSE otherwise
 */
gboolean ag_service_type_has_tag (AgServiceType *service_type,
                                  const gchar *tag)
{
    g_return_val_if_fail (service_type != NULL, FALSE);
    if (service_type->tags == NULL) return FALSE;
    return g_hash_table_lookup_extended (service_type->tags, tag, NULL, NULL);
}

/**
 * ag_service_type_get_tags:
 * @service_type: the #AgServiceType.
 *
 * Get list of tags specified for the #AgServiceType.
 *
 * Returns: (transfer container) (element-type utf8): #GList of tags for
 * @service_type.
 * The list must be freed with g_list_free(). Entries are owned by the
 * #AgServiceType type and must not be free'd.
 */
GList *ag_service_type_get_tags (AgServiceType *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);
    if (service_type->tags == NULL) return NULL;
    return g_hash_table_get_keys (service_type->tags);
}

/**
 * ag_service_type_get_file_contents:
 * @service_type: the #AgServiceType.
 * @contents: location to receive the pointer to the file contents.
 * @len: location to receive the length of the file, in bytes.
 *
 * Gets the contents of the XML service type file.  The buffer returned in
 * @contents should not be modified or freed, and is guaranteed to be valid as
 * long as @service_type is referenced.
 * If some error occurs, @contents is set to %NULL.
 */
void
ag_service_type_get_file_contents (AgServiceType *service_type,
                                   const gchar **contents,
                                   gsize *len)
{
    g_return_if_fail (service_type != NULL);
    g_return_if_fail (contents != NULL);

    *contents = service_type->file_data;
    if (len)
        *len = service_type->file_data_len;
}

/**
 * ag_service_type_ref:
 * @service_type: the #AgServiceType.
 *
 * Adds a reference to @service_type.
 *
 * Returns: @service_type.
 */
AgServiceType *
ag_service_type_ref (AgServiceType *service_type)
{
    g_return_val_if_fail (service_type != NULL, NULL);
    g_return_val_if_fail (service_type->ref_count > 0, NULL);

    DEBUG_REFS ("Referencing service_type %s (%d)",
                service_type->name, service_type->ref_count);
    service_type->ref_count++;
    return service_type;
}

/**
 * ag_service_type_unref:
 * @service_type: the #AgServiceType.
 *
 * Used to unreference the #AgServiceType structure.
 */
void
ag_service_type_unref (AgServiceType *service_type)
{
    g_return_if_fail (service_type != NULL);
    g_return_if_fail (service_type->ref_count > 0);

    DEBUG_REFS ("Unreferencing service_type %s (%d)",
                service_type->name, service_type->ref_count);
    service_type->ref_count--;
    if (service_type->ref_count == 0)
    {
        g_free (service_type->name);
        g_free (service_type->i18n_domain);
        g_free (service_type->display_name);
        g_free (service_type->description);
        g_free (service_type->icon_name);
        g_free (service_type->file_data);
        if (service_type->tags)
            g_hash_table_destroy (service_type->tags);
        g_slice_free (AgServiceType, service_type);
    }
}

/**
 * ag_service_type_list_free:
 * @list: (element-type AgServiceType): a #GList of service types returned by
 * some function of this library, such as ag_manager_list_service_types().
 *
 * Frees the list @list.
 */
void
ag_service_type_list_free (GList *list)
{
    g_list_foreach (list, (GFunc)ag_service_type_unref, NULL);
    g_list_free (list);
}
07070100000038000081A4000003E800000064000000015BDC2B8B00000957000000000000000000000000000000000000003900000000libaccounts-glib-1.24/libaccounts-glib/ag-service-type.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_SERVICE_TYPE_H_
#define _AG_SERVICE_TYPE_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib.h>
#include <glib-object.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS

GType ag_service_type_get_type (void) G_GNUC_CONST;

const gchar *ag_service_type_get_name (AgServiceType *service_type);
const gchar *ag_service_type_get_i18n_domain (AgServiceType *service_type);
const gchar *ag_service_type_get_display_name (AgServiceType *service_type);
const gchar *ag_service_type_get_description (AgServiceType *service_type);
const gchar *ag_service_type_get_icon_name (AgServiceType *service_type);
gboolean ag_service_type_has_tag (AgServiceType *service_type,
                                  const gchar *tag);
GList *ag_service_type_get_tags (AgServiceType *service_type);
void ag_service_type_get_file_contents (AgServiceType *service_type,
                                        const gchar **contents,
                                        gsize *len);
AgServiceType *ag_service_type_ref (AgServiceType *service_type);
void ag_service_type_unref (AgServiceType *service_type);
void ag_service_type_list_free (GList *list);

G_END_DECLS

#endif /* _AG_SERVICE_TYPE_H_ */
07070100000039000081A4000003E800000064000000015BDC2B8B000041CA000000000000000000000000000000000000003400000000libaccounts-glib-1.24/libaccounts-glib/ag-service.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * SECTION:ag-service
 * @short_description: A representation of a service.
 * @include: libaccounts-glib/ag-service.h
 *
 * The #AgService structure represents a service. The structure is not directly
 * exposed to applications, but its fields are accessible via getter methods.
 * It is instantiated by #AgManager, with ag_manager_get_service(),
 * ag_manager_list_services() or ag_manager_list_services_by_type().
 * The structure is reference counted. One must use ag_service_unref() when
 * done with it.
 */

#include "ag-service.h"

#include "ag-service-type.h"
#include "ag-internals.h"
#include "ag-util.h"
#include <libxml/xmlreader.h>
#include <string.h>

G_DEFINE_BOXED_TYPE (AgService, ag_service,
                     (GBoxedCopyFunc)ag_service_ref,
                     (GBoxedFreeFunc)ag_service_unref);

static gboolean
parse_template (xmlTextReaderPtr reader, AgService *service)
{
    GHashTable *settings;
    gboolean ok;

    g_return_val_if_fail (service->default_settings == NULL, FALSE);

    settings =
        g_hash_table_new_full (g_str_hash, g_str_equal,
                               g_free, (GDestroyNotify)g_variant_unref);

    ok = _ag_xml_parse_settings (reader, "", settings);
    if (G_UNLIKELY (!ok))
    {
        g_hash_table_destroy (settings);
        return FALSE;
    }

    service->default_settings = settings;
    return TRUE;
}

static gboolean
parse_preview (G_GNUC_UNUSED xmlTextReaderPtr reader,
               G_GNUC_UNUSED AgService *service)
{
    /* TODO: implement */
    return TRUE;
}

static gboolean
parse_service (xmlTextReaderPtr reader, AgService *service)
{
    const gchar *name;
    int ret, type;

    if (!service->name)
    {
        xmlChar *_name = xmlTextReaderGetAttribute (reader,
                                                    (xmlChar *) "id");
        service->name = g_strdup ((const gchar *)_name);
        if (_name) xmlFree(_name);
    }

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = (const gchar *)xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!name)) return FALSE;

        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_END_ELEMENT &&
            strcmp (name, "service") == 0)
            break;

        if (type == XML_READER_TYPE_ELEMENT)
        {
            gboolean ok;

            if (strcmp (name, "type") == 0 && !service->type)
            {
                ok = _ag_xml_dup_element_data (reader, &service->type);
            }
            else if (strcmp (name, "name") == 0 && !service->display_name)
            {
                ok = _ag_xml_dup_element_data (reader, &service->display_name);
            }
            else if (strcmp (name, "description") == 0)
            {
                ok = _ag_xml_dup_element_data (reader, &service->description);
            }
            else if (strcmp (name, "provider") == 0 && !service->provider)
            {
                ok = _ag_xml_dup_element_data (reader, &service->provider);
            }
            else if (strcmp (name, "icon") == 0)
            {
                ok = _ag_xml_dup_element_data (reader, &service->icon_name);
            }
            else if (strcmp (name, "translations") == 0)
            {
                ok = _ag_xml_dup_element_data (reader, &service->i18n_domain);
            }

            else if (strcmp (name, "template") == 0)
            {
                ok = parse_template (reader, service);
            }
            else if (strcmp (name, "preview") == 0)
            {
                ok = parse_preview (reader, service);
            }
            else if (strcmp (name, "type_data") == 0)
            {
                static const gchar element[] = "<type_data";
                gsize offset;

                /* find the offset in the file where this element begins */
                offset = xmlTextReaderByteConsumed(reader);
                while (offset > 0)
                {
                    if (strncmp (service->file_data + offset, element,
                                 sizeof (element)) == 0)
                    {
                        service->type_data_offset = offset;
                        break;
                    }
                    offset--;
                }

                /* this element is placed after all the elements we are
                 * interested in: we can stop the parsing now */
                return TRUE;
            }
            else if (strcmp (name, "tags") == 0)
            {
                ok = _ag_xml_parse_element_list (reader, "tag",
                                                 &service->tags);
            }
            else
                ok = TRUE;

            if (G_UNLIKELY (!ok)) return FALSE;
        }

        ret = xmlTextReaderNext (reader);
    }
    return TRUE;
}

static gboolean
read_service_file (xmlTextReaderPtr reader, AgService *service)
{
    const xmlChar *name;
    int ret;

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = xmlTextReaderConstName (reader);
        if (G_LIKELY (name &&
                      strcmp ((const gchar *)name, "service") == 0))
        {
            return parse_service (reader, service);
        }

        ret = xmlTextReaderNext (reader);
    }
    return FALSE;
}

static void
copy_tags_from_type (AgService *service)
{
    AgServiceType *type;
    GList *type_tags, *tag_list;

    service->tags = g_hash_table_new_full (g_str_hash, g_str_equal,
                                           g_free, NULL);
    type = _ag_service_type_new_from_file (service->type);
    if (G_UNLIKELY (type == NULL)) return;

    type_tags = ag_service_type_get_tags (type);
    for (tag_list = type_tags; tag_list != NULL; tag_list = tag_list->next)
        g_hash_table_insert (service->tags,
                             g_strdup (tag_list->data), NULL);
    g_list_free (type_tags);
    ag_service_type_unref (type);
}

AgService *
_ag_service_new (void)
{
    AgService *service;

    service = g_slice_new0 (AgService);
    service->ref_count = 1;

    return service;
}

static gboolean
_ag_service_load_from_file (AgService *service)
{
    xmlTextReaderPtr reader;
    gchar *filepath;
    gboolean ret;
    GError *error = NULL;
    gsize len;

    g_return_val_if_fail (service->name != NULL, FALSE);

    DEBUG_REFS ("Loading service %s", service->name);
    filepath = _ag_find_libaccounts_file (service->name,
                                          ".service",
                                          "AG_SERVICES",
                                          SERVICE_FILES_DIR);
    if (G_UNLIKELY (!filepath)) return FALSE;

    g_file_get_contents (filepath, &service->file_data,
                         &len, &error);
    if (G_UNLIKELY (error))
    {
        g_warning ("Error reading %s: %s", filepath, error->message);
        g_error_free (error);
        g_free (filepath);
        return FALSE;
    }

    /* TODO: cache the xmlReader */
    reader = xmlReaderForMemory (service->file_data, len,
                                 filepath, NULL, 0);
    g_free (filepath);
    if (G_UNLIKELY (reader == NULL))
        return FALSE;

    ret = read_service_file (reader, service);

    xmlFreeTextReader (reader);
    return ret;
}

AgService *
_ag_service_new_from_file (const gchar *service_name)
{
    AgService *service;

    service = _ag_service_new ();
    service->name = g_strdup (service_name);
    if (!_ag_service_load_from_file (service))
    {
        ag_service_unref (service);
        service = NULL;
    }

    return service;
}

AgService *
_ag_service_new_from_memory (const gchar *service_name, const gchar *service_type,
                             const gint service_id)
{
    AgService *service;

    service = _ag_service_new ();
    service->name = g_strdup (service_name);
    service->type = g_strdup (service_type);
    service->id = service_id;

    return service;
}

GHashTable *
_ag_service_load_default_settings (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);

    if (!service->default_settings)
    {
        /* This can happen if the service was created by the AccountManager by
         * loading the record from the DB.
         * Now we must reload the service from its XML file.
         */
        if (!_ag_service_load_from_file (service))
        {
            g_warning ("Loading service %s file failed", service->name);
            return NULL;
        }
    }

    return service->default_settings;
}

GVariant *
_ag_service_get_default_setting (AgService *service, const gchar *key)
{
    GHashTable *settings;

    g_return_val_if_fail (key != NULL, NULL);

    settings = _ag_service_load_default_settings (service);
    if (G_UNLIKELY (!settings))
        return NULL;

    return g_hash_table_lookup (settings, key);
}

/**
 * ag_service_get_name:
 * @service: the #AgService.
 *
 * Gets the name of the #AgService.
 *
 * Returns: the name of @service.
 */
const gchar *
ag_service_get_name (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);
    return service->name;
}

/**
 * ag_service_get_display_name:
 * @service: the #AgService.
 *
 * Gets the display name of the #AgService.
 *
 * Returns: the display name of @service.
 */
const gchar *
ag_service_get_display_name (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);
    if (service->display_name == NULL && !service->file_data)
        _ag_service_load_from_file (service);
    return service->display_name;
}

/**
 * ag_service_get_description:
 * @service: the #AgService.
 *
 * Gets the description of the #AgService.
 *
 * Returns: the description of @service, or %NULL upon failure.
 *
 * Since: 1.2
 */
const gchar *
ag_service_get_description (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);
    if (service->description == NULL && !service->file_data)
        _ag_service_load_from_file (service);
    return service->description;
}

/**
 * ag_service_get_service_type:
 * @service: the #AgService.
 *
 * Gets the service type of the #AgService.
 *
 * Returns: the type of @service.
 */
const gchar *
ag_service_get_service_type (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);
    if (service->type == NULL && !service->file_data)
        _ag_service_load_from_file (service);
    return service->type;
}

/**
 * ag_service_get_provider:
 * @service: the #AgService.
 *
 * Gets the provider name of the #AgService.
 *
 * Returns: the name of the provider of @service.
 */
const gchar *
ag_service_get_provider (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);
    if (service->provider == NULL && !service->file_data)
        _ag_service_load_from_file (service);
    return service->provider;
}

/**
 * ag_service_get_icon_name:
 * @service: the #AgService.
 *
 * Gets the icon name of the #AgService.
 *
 * Returns: the name of the icon of @service.
 */
const gchar *
ag_service_get_icon_name (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);

    if (!service->file_data)
        _ag_service_load_from_file (service);

    return service->icon_name;
}

/**
 * ag_service_get_i18n_domain:
 * @service: the #AgService.
 *
 * Gets the translation domain of the #AgService.
 *
 * Returns: the name of the translation catalog.
 */
const gchar *
ag_service_get_i18n_domain (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);

    if (!service->file_data)
        _ag_service_load_from_file (service);

    return service->i18n_domain;
}

/**
 * ag_service_has_tag:
 * @service: the #AgService.
 * @tag: tag to check for.
 *
 * Checks if the #AgService has the requested tag.
 *
 * Returns: TRUE if #AgService has the tag, FALSE otherwise
 */
gboolean ag_service_has_tag (AgService *service, const gchar *tag)
{
    g_return_val_if_fail (service != NULL, FALSE);

    if (!service->file_data)
        _ag_service_load_from_file (service);

    if (service->tags == NULL)
        copy_tags_from_type (service);

    return g_hash_table_lookup_extended (service->tags, tag, NULL, NULL);
}

/**
 * ag_service_get_tags:
 * @service: the #AgService.
 *
 * Get list of tags specified for the #AgService. If the service has not
 * defined tags, tags from the service type will be returned.
 *
 * Returns: (transfer container) (element-type utf8): #GList of tags for
 * @service. The list must be freed with g_list_free(). Entries are owned by
 * the #AgService type and must not be free'd.
 */
GList *ag_service_get_tags (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);

    if (!service->file_data)
        _ag_service_load_from_file (service);

    if (service->tags == NULL)
        copy_tags_from_type (service);

    return g_hash_table_get_keys (service->tags);
}

/**
 * ag_service_get_file_contents:
 * @service: the #AgService.
 * @contents: location to receive the pointer to the file contents.
 * @data_offset: pointer to receive the offset of the type data.
 *
 * Gets the contents of the XML service file.  The buffer returned in @contents
 * should not be modified or freed, and is guaranteed to be valid as long as
 * @service is referenced. If @data_offset is not %NULL, it is set to the
 * offset where the &lt;type_data&gt; element can be found.
 * If some error occurs, @contents is set to %NULL.
 */
void
ag_service_get_file_contents (AgService *service,
                              const gchar **contents,
                              gsize *data_offset)
{
    g_return_if_fail (service != NULL);
    g_return_if_fail (contents != NULL);

    if (service->file_data == NULL)
    {
        /* This can happen if the service was created by the AccountManager by
         * loading the record from the DB.
         * Now we must reload the service from its XML file.
         */
        if (!_ag_service_load_from_file (service))
            g_warning ("Loading service %s file failed", service->name);
    }

    *contents = service->file_data;

    if (data_offset)
        *data_offset = service->type_data_offset;
}

/**
 * ag_service_ref:
 * @service: the #AgService.
 *
 * Adds a reference to @service.
 *
 * Returns: @service.
 */
AgService *
ag_service_ref (AgService *service)
{
    g_return_val_if_fail (service != NULL, NULL);
    g_return_val_if_fail (service->ref_count > 0, NULL);

    DEBUG_REFS ("Referencing service %s (%d)",
                service->name, service->ref_count);
    service->ref_count++;
    return service;
}

/**
 * ag_service_unref:
 * @service: the #AgService.
 *
 * Used to unreference the #AgService structure.
 */
void
ag_service_unref (AgService *service)
{
    g_return_if_fail (service != NULL);
    g_return_if_fail (service->ref_count > 0);

    DEBUG_REFS ("Unreferencing service %s (%d)",
                service->name, service->ref_count);
    service->ref_count--;
    if (service->ref_count == 0)
    {
        g_free (service->name);
        g_free (service->display_name);
        g_free (service->description);
        g_free (service->icon_name);
        g_free (service->i18n_domain);
        g_free (service->type);
        g_free (service->provider);
        g_free (service->file_data);
        if (service->default_settings)
            g_hash_table_unref (service->default_settings);
        if (service->tags)
            g_hash_table_destroy (service->tags);
        g_slice_free (AgService, service);
    }
}

/**
 * ag_service_list_free:
 * @list: (element-type AgService): a #GList of services returned by some
 * function of this library.
 *
 * Frees the list @list.
 */
void
ag_service_list_free (GList *list)
{
    g_list_foreach (list, (GFunc)ag_service_unref, NULL);
    g_list_free (list);
}

0707010000003A000081A4000003E800000064000000015BDC2B8B0000090B000000000000000000000000000000000000003400000000libaccounts-glib-1.24/libaccounts-glib/ag-service.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_SERVICE_H_
#define _AG_SERVICE_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib.h>
#include <glib-object.h>
#include <libaccounts-glib/ag-types.h>

G_BEGIN_DECLS


GType ag_service_get_type (void) G_GNUC_CONST;

const gchar *ag_service_get_name (AgService *service);
const gchar *ag_service_get_display_name (AgService *service);
const gchar *ag_service_get_description (AgService *service);
const gchar *ag_service_get_service_type (AgService *service);
const gchar *ag_service_get_provider (AgService *service);
const gchar *ag_service_get_icon_name (AgService *service);
const gchar *ag_service_get_i18n_domain (AgService *service);
gboolean ag_service_has_tag (AgService *service, const gchar *tag);
GList *ag_service_get_tags (AgService *service);
void ag_service_get_file_contents (AgService *service,
                                   const gchar **contents,
                                   gsize *data_offset);
AgService *ag_service_ref (AgService *service);
void ag_service_unref (AgService *service);
void ag_service_list_free (GList *list);


G_END_DECLS

#endif /* _AG_SERVICE_H_ */
0707010000003B000081A4000003E800000064000000015BDC2B8B00000BED000000000000000000000000000000000000003200000000libaccounts-glib-1.24/libaccounts-glib/ag-types.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_TYPES_H_
#define _AG_TYPES_H_

#if !defined (__ACCOUNTS_GLIB_H_INSIDE__) && !defined (ACCOUNTS_GLIB_COMPILATION)
#warning "Only <libaccounts-glib.h> should be included directly."
#endif

#include <glib.h>

G_BEGIN_DECLS

/**
 * AgAccount:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgAccount AgAccount;
/**
 * AgManager:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgManager AgManager;
/**
 * AgService:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgService AgService;
/**
 * AgAccountService:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgAccountService AgAccountService;
/**
 * AgProvider:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgProvider AgProvider;
/**
 * AgAuthData:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgAuthData AgAuthData;
/**
 * AgServiceType:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgServiceType AgServiceType;
/**
 * AgApplication:
 *
 * Opaque structure. Use related accessor functions.
 */
typedef struct _AgApplication AgApplication;

/**
 * AgAccountId:
 *
 * ID of an account. Often used when retrieving lists of accounts from
 * #AgManager.
 */
typedef guint AgAccountId;

/* guards to avoid bumping up the GLib dependency */
#ifndef G_DEPRECATED
#define G_DEPRECATED            G_GNUC_DEPRECATED
#define G_DEPRECATED_FOR(x)     G_GNUC_DEPRECATED_FOR(x)
#endif

#ifdef AG_DISABLE_DEPRECATION_WARNINGS
#define AG_DEPRECATED
#define AG_DEPRECATED_FOR(x)
#else
#define AG_DEPRECATED           G_DEPRECATED
#define AG_DEPRECATED_FOR(x)    G_DEPRECATED_FOR(x)
#endif

/* Expected address of the D-Bus account manager service */
#define AG_MANAGER_SERVICE_NAME "com.google.code.AccountsSSO.Accounts.Manager"
#define AG_MANAGER_OBJECT_PATH "/com/google/code/AccountsSSO/Accounts/Manager"
#define AG_MANAGER_INTERFACE "com.google.code.AccountsSSO.Accounts.Manager"

G_END_DECLS

#endif /* _AG_TYPES_H_ */
0707010000003C000081A4000003E800000064000000015BDC2B8B000041FB000000000000000000000000000000000000003100000000libaccounts-glib-1.24/libaccounts-glib/ag-util.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include "ag-util.h"
#include "ag-debug.h"
#include "ag-errors.h"

#include <gio/gio.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

GString *
_ag_string_append_printf (GString *string, const gchar *format, ...)
{
    va_list ap;
    char *sql;

    va_start (ap, format);
    sql = sqlite3_vmprintf (format, ap);
    va_end (ap);

    if (sql)
    {
        g_string_append (string, sql);
        sqlite3_free (sql);
    }

    return string;
}

GValue *
_ag_value_slice_dup (const GValue *value)
{
    GValue *copy;

    if (!value) return NULL;
    copy = g_slice_new0 (GValue);
    g_value_init (copy, G_VALUE_TYPE (value));
    g_value_copy (value, copy);
    return copy;
}

void
_ag_value_slice_free (GValue *value)
{
    if (!value) return;
    g_value_unset (value);
    g_slice_free (GValue, value);
}

GVariant *
_ag_value_to_variant (const GValue *in_value)
{
    const GVariantType *type;
    GValue transformed_value = G_VALUE_INIT;
    const GValue *value;

    g_return_val_if_fail (in_value != NULL, NULL);

    /* transform some GValues which g_dbus_gvalue_to_gvariant() cannot handle */
    if (G_VALUE_TYPE (in_value) == G_TYPE_CHAR)
    {
        g_value_init (&transformed_value, G_TYPE_INT);
        if (G_UNLIKELY (!g_value_transform (in_value, &transformed_value)))
        {
            g_warning ("%s: could not transform %s to %s", G_STRFUNC,
                       G_VALUE_TYPE_NAME (in_value),
                       G_VALUE_TYPE_NAME (&transformed_value));
            return NULL;
        }

        value = &transformed_value;
    }
    else
    {
        value = in_value;
    }

    type = _ag_type_from_g_type (G_VALUE_TYPE (value));
    return g_dbus_gvalue_to_gvariant (value, type);
}

gchar *
_ag_value_to_db (GVariant *value, gboolean type_annotate)
{
    return g_variant_print (value, type_annotate);
}

const GVariantType *
_ag_type_from_g_type (GType type)
{
    switch (type)
    {
    case G_TYPE_STRING:
        return G_VARIANT_TYPE_STRING;
    case G_TYPE_INT:
    case G_TYPE_CHAR:
        return G_VARIANT_TYPE_INT32;
    case G_TYPE_UINT:
        return G_VARIANT_TYPE_UINT32;
    case G_TYPE_BOOLEAN:
        return G_VARIANT_TYPE_BOOLEAN;
    case G_TYPE_UCHAR:
        return G_VARIANT_TYPE_BYTE;
    case G_TYPE_INT64:
        return G_VARIANT_TYPE_INT64;
    case G_TYPE_UINT64:
        return G_VARIANT_TYPE_UINT64;
    default:
        /* handle dynamic types here */
        if (type == G_TYPE_STRV)
            return G_VARIANT_TYPE_STRING_ARRAY;

        g_warning ("%s: unsupported type ``%s''", G_STRFUNC,
                   g_type_name (type));
        return NULL;
    }
}

void
_ag_value_from_variant (GValue *value, GVariant *variant)
{
    g_dbus_gvariant_to_gvalue (variant, value);
}

static GVariant *
_ag_value_from_string (const gchar *type, const gchar *string)
{
    GVariant *variant;
    GError *error = NULL;

    if (G_UNLIKELY (!string)) return NULL;

    /* g_variant_parse() expects all strings to be enclosed in quotes, which we
     * wouldn't like to enforce in the XML files. So, if we know that we are
     * reading a string, just build the GValue right away */
    if (type != NULL && type[0] == 's' && type[1] == '\0' &&
        string[0] != '"' && string[0] != '\'')
    {
        return g_variant_new_string (string);
    }

    variant = g_variant_parse ((GVariantType *)type, string,
                               NULL, NULL, &error);
    if (error != 0)
    {
        g_warning ("%s: error parsing type \"%s\" ``%s'': %s",
                   G_STRFUNC, type, string, error->message);
        g_error_free (error);
        return NULL;
    }

    return variant;
}

GVariant *
_ag_value_from_db (sqlite3_stmt *stmt, gint col_type, gint col_value)
{
    gchar *string_value;
    gchar *type;

    type = (gchar *)sqlite3_column_text (stmt, col_type);
    string_value = (gchar *)sqlite3_column_text (stmt, col_value);

    return _ag_value_from_string (type, string_value);
}

/**
 * ag_errors_quark:
 *
 * Return the libaccounts-glib error domain.
 *
 * Returns: the libaccounts-glib error domain.
 */
GQuark
ag_errors_quark (void)
{
    static gsize quark = 0;

    if (g_once_init_enter (&quark))
    {
        GQuark domain = g_quark_from_static_string ("ag_errors");

        g_assert (sizeof (GQuark) <= sizeof (gsize));

        g_once_init_leave (&quark, domain);
    }

    return (GQuark) quark;
}

/**
 * ag_accounts_error_quark:
 *
 * Return the libaccounts-glib error domain.
 *
 * Returns: the libaccounts-glib error domain.
 */
GQuark
ag_accounts_error_quark (void)
{
    return ag_errors_quark ();
}

gboolean
_ag_xml_get_element_data (xmlTextReaderPtr reader, const gchar **dest_ptr)
{
    gint node_type;

    if (dest_ptr) *dest_ptr = NULL;

    if (xmlTextReaderIsEmptyElement (reader))
        return TRUE;

    if (xmlTextReaderRead (reader) != 1)
        return FALSE;

    node_type = xmlTextReaderNodeType (reader);
    if (node_type != XML_READER_TYPE_TEXT)
        return (node_type == XML_READER_TYPE_END_ELEMENT) ? TRUE : FALSE;

    if (dest_ptr)
        *dest_ptr = (const gchar *)xmlTextReaderConstValue (reader);

    return TRUE;
}

static gboolean
close_element (xmlTextReaderPtr reader)
{
    if (xmlTextReaderRead (reader) != 1 ||
        xmlTextReaderNodeType (reader) != XML_READER_TYPE_END_ELEMENT)
        return FALSE;

    return TRUE;
}

gboolean
_ag_xml_dup_element_data (xmlTextReaderPtr reader, gchar **dest_ptr)
{
    const gchar *data;
    gboolean ret;

    ret = _ag_xml_get_element_data (reader, &data);
    if (dest_ptr)
        *dest_ptr = g_strdup (data);

    close_element (reader);
    return ret;
}

gboolean
_ag_xml_get_boolean (xmlTextReaderPtr reader, gboolean *dest_boolean)
{
    GVariant *variant;
    const gchar *data;
    gboolean ok;

    ok = _ag_xml_get_element_data (reader, &data);
    if (G_UNLIKELY (!ok)) return FALSE;

    variant = _ag_value_from_string ("b", data);
    if (G_UNLIKELY (variant == NULL)) return FALSE;

    *dest_boolean = g_variant_get_boolean (variant);
    g_variant_unref (variant);

    ok = close_element (reader);

    return ok;
}

static gboolean
parse_param (xmlTextReaderPtr reader, GVariant **value)
{
    const gchar *str_value;
    xmlChar *str_type = NULL;
    gboolean ok;
    const gchar *type;

    str_type = xmlTextReaderGetAttribute (reader,
                                          (xmlChar *) "type");
    if (!str_type)
        type = "s";
    else
    {
        type = (const gchar*)str_type;
    }

    ok = _ag_xml_get_element_data (reader, &str_value);
    if (G_UNLIKELY (!ok)) goto error;

    /* Empty value is not an error, but simply ignored */
    if (G_UNLIKELY (!str_value)) goto finish;

    *value = _ag_value_from_string (type, str_value);

    ok = close_element (reader);
    if (G_UNLIKELY (!ok))
    {
        g_variant_unref (*value);
        *value = NULL;
        goto error;
    }

finish:
    ok = TRUE;
error:
    if (str_type != NULL)
        xmlFree(str_type);
    return ok;
}

gboolean
_ag_xml_parse_settings (xmlTextReaderPtr reader, const gchar *group,
                        GHashTable *settings)
{
    const gchar *name;
    int ret, type;

    ret = xmlTextReaderRead (reader);
    while (ret == 1)
    {
        name = (const gchar *)xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!name)) return FALSE;

        type = xmlTextReaderNodeType (reader);
        if (type == XML_READER_TYPE_END_ELEMENT)
            break;

        if (type == XML_READER_TYPE_ELEMENT)
        {
            gboolean ok;

            DEBUG_INFO ("found name %s", name);
            if (strcmp (name, "setting") == 0)
            {
                GVariant *value = NULL;
                xmlChar *key_name;
                gchar *key;

                key_name = xmlTextReaderGetAttribute (reader, (xmlChar *)"name");
                key = g_strdup_printf ("%s%s", group, (const gchar*)key_name);

                if (key_name) xmlFree (key_name);

                ok = parse_param (reader, &value);
                if (ok && value != NULL)
                {
                    g_variant_take_ref (value);
                    g_hash_table_insert (settings, key, value);
                }
                else
                {
                    if (value != NULL) g_variant_unref (value);
                    g_free (key);
                }
            }
            else if (strcmp (name, "group") == 0 &&
                     xmlTextReaderHasAttributes (reader))
            {
                /* it's a subgroup */
                if (!xmlTextReaderIsEmptyElement (reader))
                {
                    xmlChar *group_name;
                    gchar *subgroup;

                    group_name = xmlTextReaderGetAttribute (reader,
                                                            (xmlChar *)"name");
                    subgroup = g_strdup_printf ("%s%s/", group,
                                                (const gchar *)group_name);
                    if (group_name) xmlFree (group_name);

                    ok = _ag_xml_parse_settings (reader, subgroup, settings);
                    g_free (subgroup);
                }
                else
                    ok = TRUE;
            }
            else
            {
                g_warning ("%s: using wrong XML for groups; "
                           "please change to <group name=\"%s\">",
                           xmlTextReaderConstBaseUri (reader), name);
                /* it's a subgroup */
                if (!xmlTextReaderIsEmptyElement (reader))
                {
                    gchar *subgroup;

                    subgroup = g_strdup_printf ("%s%s/", group, name);
                    ok = _ag_xml_parse_settings (reader, subgroup, settings);
                    g_free (subgroup);
                }
                else
                    ok = TRUE;
            }

            if (G_UNLIKELY (!ok)) return FALSE;
        }

        ret = xmlTextReaderNext (reader);
    }
    return TRUE;
}

gboolean _ag_xml_parse_element_list (xmlTextReaderPtr reader, const gchar *match,
                                     GHashTable **list)
{
    gboolean ok = FALSE;
    const gchar *ename;
    gchar *data;
    int res, etype;

    *list = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

    res = xmlTextReaderRead (reader);
    while (res == 1)
    {
        ename = (const gchar *) xmlTextReaderConstName (reader);
        if (G_UNLIKELY (!ename)) return FALSE;

        etype = xmlTextReaderNodeType (reader);
        if (etype == XML_READER_TYPE_END_ELEMENT)
            break;

        if (etype == XML_READER_TYPE_ELEMENT)
        {
            if (strcmp (ename, match) == 0)
            {
                if (_ag_xml_dup_element_data (reader, &data))
                {
                    g_hash_table_insert (*list, data, NULL);
                    ok = TRUE;
                }
                else return FALSE;
            }
        }

        res = xmlTextReaderNext (reader);
    }
    return ok;
}

static inline gboolean
_esc_ident_bad (gchar c, gboolean is_first)
{
  return ((c < 'a' || c > 'z') &&
          (c < 'A' || c > 'Z') &&
          (c < '0' || c > '9' || is_first));
}

/**
 * _ag_dbus_escape_as_identifier:
 * @name: The string to be escaped
 *
 * Taken from telepathy-glib's tp_escape_as_identifier().
 *
 * Escape an arbitrary string so it follows the rules for a C identifier,
 * and hence an object path component, interface element component,
 * bus name component or member name in D-Bus.
 *
 * Unlike g_strcanon this is a reversible encoding, so it preserves
 * distinctness.
 *
 * The escaping consists of replacing all non-alphanumerics, and the first
 * character if it's a digit, with an underscore and two lower-case hex
 * digits:
 *
 *    "0123abc_xyz\x01\xff" -> _30123abc_5fxyz_01_ff
 *
 * i.e. similar to URI encoding, but with _ taking the role of %, and a
 * smaller allowed set. As a special case, "" is escaped to "_" (just for
 * completeness, really).
 *
 * Returns: the escaped string, which must be freed by the caller with #g_free
 */
gchar *
_ag_dbus_escape_as_identifier (const gchar *name)
{
    gboolean bad = FALSE;
    size_t len = 0;
    GString *op;
    const gchar *ptr, *first_ok;

    g_return_val_if_fail (name != NULL, NULL);

    /* fast path for empty name */
    if (name[0] == '\0')
        return g_strdup ("_");

    for (ptr = name; *ptr; ptr++)
    {
        if (_esc_ident_bad (*ptr, ptr == name))
        {
            bad = TRUE;
            len += 3;
        }
        else
            len++;
    }

    /* fast path if it's clean */
    if (!bad)
        return g_strdup (name);

    /* If strictly less than ptr, first_ok is the first uncopied safe
     * character. */
    first_ok = name;
    op = g_string_sized_new (len);
    for (ptr = name; *ptr; ptr++)
    {
        if (_esc_ident_bad (*ptr, ptr == name))
        {
            /* copy preceding safe characters if any */
            if (first_ok < ptr)
            {
                g_string_append_len (op, first_ok, ptr - first_ok);
            }
            /* escape the unsafe character */
            g_string_append_printf (op, "_%02x", (unsigned char)(*ptr));
            /* restart after it */
            first_ok = ptr + 1;
        }
    }
    /* copy trailing safe characters if any */
    if (first_ok < ptr)
    {
        g_string_append_len (op, first_ok, ptr - first_ok);
    }
    return g_string_free (op, FALSE);
}

/**
 * _ag_find_libaccounts_file:
 * @file_id: the base name of the file, without suffix.
 * @suffix: the file suffix.
 * @env_var: name of the environment variable which could specify an override
 * path.
 * @subdir: file will be searched in $XDG_DATA_DIRS/<subdir>/
 *
 * Search for the libaccounts file @file_id.
 *
 * Returns: the path of the file, if found, %NULL otherwise.
 */
gchar *
_ag_find_libaccounts_file (const gchar *file_id,
                           const gchar *suffix,
                           const gchar *env_var,
                           const gchar *subdir)
{
    const gchar * const *dirs;
    const gchar *dirname;
    const gchar *env_dirname;
    gchar *filename, *filepath, *desktop_override = NULL;

    filename = g_strconcat (file_id, suffix, NULL);
    env_dirname = g_getenv (env_var);
    if (env_dirname)
    {
        filepath = g_build_filename (env_dirname, filename, NULL);
        if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
            goto found;
        g_free (filepath);
    }

    dirname = g_get_user_data_dir ();
    if (G_LIKELY (dirname))
    {
        filepath = g_build_filename (dirname, subdir, filename, NULL);
        if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
            goto found;
        g_free (filepath);
    }

    /* Check what desktop is this running on */
    env_dirname = g_getenv ("XDG_CURRENT_DESKTOP");
    if (env_dirname)
        desktop_override = g_ascii_strdown (env_dirname, -1);

    dirs = g_get_system_data_dirs ();
    for (dirname = *dirs; dirname != NULL; dirs++, dirname = *dirs)
    {
        /* Check first if desktop override files exist and if yes, load them first */
        if (desktop_override)
        {
            filepath = g_build_filename (dirname, subdir, desktop_override, filename, NULL);
            if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
                goto found;
            g_free (filepath);
        }
        filepath = g_build_filename (dirname, subdir, filename, NULL);
        if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
            goto found;
        g_free (filepath);
    }

    filepath = NULL;
found:
    g_free (desktop_override);
    g_free (filename);
    return filepath;
}

0707010000003D000081A4000003E800000064000000015BDC2B8B00000B65000000000000000000000000000000000000003100000000libaccounts-glib-1.24/libaccounts-glib/ag-util.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _AG_UTIL_H_
#define _AG_UTIL_H_

#include <glib.h>
#include <glib-object.h>
#include <libxml/xmlreader.h>
#include <sqlite3.h>

G_BEGIN_DECLS

GString *_ag_string_append_printf (GString *string,
                                   const gchar *format,
                                   ...) G_GNUC_INTERNAL;

G_GNUC_INTERNAL
GValue *_ag_value_slice_dup (const GValue *value);

G_GNUC_INTERNAL
void _ag_value_slice_free (GValue *value);

G_GNUC_INTERNAL
GVariant *_ag_value_to_variant (const GValue *value);
G_GNUC_INTERNAL
void _ag_value_from_variant (GValue *value, GVariant *variant);

G_GNUC_INTERNAL
gchar *_ag_value_to_db (GVariant *value, gboolean type_annotate);

G_GNUC_INTERNAL
GVariant *_ag_value_from_db (sqlite3_stmt *stmt, gint col_type, gint col_value);

G_GNUC_INTERNAL
const GVariantType *_ag_type_from_g_type (GType type);

G_GNUC_INTERNAL
gboolean _ag_xml_get_boolean (xmlTextReaderPtr reader, gboolean *dest_boolean);

G_GNUC_INTERNAL
gboolean _ag_xml_get_element_data (xmlTextReaderPtr reader,
                                   const gchar **dest_ptr);

G_GNUC_INTERNAL
gboolean _ag_xml_dup_element_data (xmlTextReaderPtr reader, gchar **dest_ptr);

G_GNUC_INTERNAL
gboolean _ag_xml_parse_settings (xmlTextReaderPtr reader, const gchar *group,
                                 GHashTable *settings);

G_GNUC_INTERNAL
gboolean _ag_xml_parse_element_list (xmlTextReaderPtr reader, const gchar *match,
                                     GHashTable **list);

G_GNUC_INTERNAL
gchar *_ag_dbus_escape_as_identifier (const gchar *name);

G_GNUC_INTERNAL
gchar *_ag_find_libaccounts_file (const gchar *file_id,
                                  const gchar *suffix,
                                  const gchar *env_var,
                                  const gchar *subdir);

G_END_DECLS

#endif /* _AG_UTIL_H_ */
0707010000003E000081A4000003E800000064000000015BDC2B8B00000881000000000000000000000000000000000000005800000000libaccounts-glib-1.24/libaccounts-glib/com.google.code.AccountsSSO.Accounts.Manager.xml<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">

  <!--
    com.google.code.AccountsSSO.Accounts.Manager
    @short_description: Account Manager interface

    This interface is unstable, subject to change and should not be
    used by client applications, which should use libaccounts-glib
    instead.

    When libaccounts-glib finds that the accounts DB is read-only (this can
    happen in Ubuntu because of the application confinement setup with
    AppArmor) any call to the ag_account_store* family of functions will result
    in a D-Bus call being posted for the object
    /com/google/code/AccountsSSO/Accounts/Manager in the D-Bus service
    com.google.code.AccountsSSO.Accounts.Manager.

    By implementing this interface, a service can capture the write requests to
    the accounts DB and execute them (if it itself has read-write access to the
    DB). If such a service doesn't exist, or calling it results in some error,
    the client will receive an error message indicating that the DB is
    read-only.

    Again, note that this functionality is experimental.
  -->
  <interface name="com.google.code.AccountsSSO.Accounts.Manager">
    <!--
      store:
      @short_description: Request to write account changes to the DB
      @id: the ID of the account (0 if it's a new account)
      @created: whether the account is being created
      @deleted: whether the account is being deleted
      @provider: ID of the account provider

      Request performing these changes on the given account.
    -->
    <method name="store">
      <annotation name="org.qtproject.QtDBus.QtTypeName.In4" value="QList&lt;ServiceChanges>"/>
      <arg name="id" type="u" direction="in"/>
      <arg name="created" type="b" direction="in"/>
      <arg name="deleted" type="b" direction="in"/>
      <arg name="provider" type="s" direction="in"/>
      <arg name="settings" type="a(ssua{sv}as)" direction="in"/>
      <arg name="accountId" type="u" direction="out"/>
    </method>
  </interface>
</node>
0707010000003F000081A4000003E800000064000000015BDC2B8B0000000D000000000000000000000000000000000000003D00000000libaccounts-glib-1.24/libaccounts-glib/libaccounts-glib.depsgio-unix-2.0
07070100000040000081A4000003E800000064000000015BDC2B8B0000050D000000000000000000000000000000000000003A00000000libaccounts-glib-1.24/libaccounts-glib/libaccounts-glib.h/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2011-2016 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef _ACCOUNTS_GLIB_H_
#define _ACCOUNTS_GLIB_H_

#define __ACCOUNTS_GLIB_H_INSIDE__

#include <ag-account.h>
#include <ag-account-service.h>
#include <ag-application.h>
#include <ag-auth-data.h>
#include <ag-errors.h>
#include <ag-manager.h>
#include <ag-provider.h>
#include <ag-service.h>
#include <ag-service-type.h>

#endif /* _ACCOUNTS_GLIB_H_ */
07070100000041000081A4000003E800000064000000015BDC2B8B00000232000000000000000000000000000000000000003E00000000libaccounts-glib-1.24/libaccounts-glib/libaccounts-glib.pc.inprefix=@prefix@
libdir=@libdir@
includedir=@includedir@
applicationfilesdir=${prefix}/share/@APPLICATION_FILES_DIR@
servicefilesdir=${prefix}/share/@SERVICE_FILES_DIR@
servicetypefilesdir=${prefix}/share/@SERVICE_TYPE_FILES_DIR@
providerfilesdir=${prefix}/share/@PROVIDER_FILES_DIR@

Name: libaccounts-glib
Description: A low-level library for managing accounts settings.
Requires: glib-2.0 gobject-2.0 gio-unix-2.0
Require.private: libxml-2.0 sqlite3
Version: @VERSION@
Libs: -L${libdir} -laccounts-glib
Cflags: -I${includedir} -I${includedir}/libaccounts-glib
07070100000042000081A4000003E800000064000000015BDC2B8B00000BCD000000000000000000000000000000000000003300000000libaccounts-glib-1.24/libaccounts-glib/meson.buildpublic_headers = files(
    'libaccounts-glib.h',
    'ag-types.h',
    'ag-account.h',
    'ag-account-service.h',
    'ag-application.h',
    'ag-auth-data.h',
    'ag-errors.h',
    'ag-manager.h',
    'ag-provider.h',
    'ag-service.h',
    'ag-service-type.h'
)

private_headers = files(
    'ag-debug.h',
    'ag-internals.h',
    'ag-util.h'
)

c_files = files(
    'ag-account.c',
    'ag-account-service.c',
    'ag-application.c',
    'ag-auth-data.c',
    'ag-debug.c',
    'ag-manager.c',
    'ag-provider.c',
    'ag-service.c',
    'ag-service-type.c',
    'ag-util.c'
)

marshal_files = gnome.genmarshal('ag-marshal',
    sources: 'ag-marshal.list',
    prefix: 'ag_marshal'
)

private_headers += marshal_files[1]
c_files += marshal_files[0]

ag_library = shared_library('accounts-glib',
    public_headers + private_headers + c_files,
    c_args: '-DACCOUNTS_GLIB_COMPILATION=1',
    soversion: version_major,
    dependencies: accounts_glib_library_deps,
    include_directories: root_dir,
    install: true
)

install_headers(public_headers + files('accounts-glib.h'),
    subdir: 'libaccounts-glib'
)

# PkgConfig creation
accounts_glib_config = configuration_data()
accounts_glib_config.set('prefix', get_option('prefix'))
accounts_glib_config.set('libdir', join_paths('${prefix}', get_option('libdir')))
accounts_glib_config.set('includedir', join_paths('${prefix}', get_option('includedir')))
accounts_glib_config.set('APPLICATION_FILES_DIR', application_files_dir)
accounts_glib_config.set('SERVICE_FILES_DIR', service_files_dir)
accounts_glib_config.set('SERVICE_TYPE_FILES_DIR', service_type_files_dir)
accounts_glib_config.set('PROVIDER_FILES_DIR', provider_files_dir)
accounts_glib_config.set('VERSION', full_version)

configure_file(
    input: 'libaccounts-glib.pc.in',
    output: 'libaccounts-glib.pc',
    configuration: accounts_glib_config,
    install_dir: join_paths(get_option('libdir'), 'pkgconfig')
)

# Creation of the dependency to use it in tools and tests
accounts_glib_dep = declare_dependency(
    link_with: ag_library,
    dependencies: accounts_glib_library_deps,
    include_directories: [ include_directories('.'), root_dir ]
)

# GObject introspection
gir_targets = gnome.generate_gir(ag_library,
    sources: public_headers + c_files,
    nsversion: api_version,
    namespace: 'Accounts',
    identifier_prefix: 'Ag',
    symbol_prefix: 'ag',
    includes: ['GObject-2.0', 'Gio-2.0'],
    dependencies: accounts_glib_library_deps,
    extra_args: ['--c-include=libaccounts-glib.h', '-DACCOUNTS_GLIB_COMPILATION=1'],
    install: true
)

# Vala .vapi generation
gnome.generate_vapi('libaccounts-glib',
    sources: gir_targets[0],
    packages: ['glib-2.0', 'gio-unix-2.0'],
    install: true
)

install_data('libaccounts-glib.deps',
    install_dir: join_paths(get_option('datadir'), 'vala', 'vapi')
)

install_data('com.google.code.AccountsSSO.Accounts.Manager.xml',
    install_dir: join_paths(get_option('datadir'), 'dbus-1', 'interfaces')
)

subdir('pygobject')

07070100000043000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000003100000000libaccounts-glib-1.24/libaccounts-glib/pygobject07070100000044000081A4000003E800000064000000015BDC2B8B00000AA5000000000000000000000000000000000000003D00000000libaccounts-glib-1.24/libaccounts-glib/pygobject/Accounts.pyfrom ..overrides import override
from ..importer import modules
from gi.repository import GObject

Accounts = modules['Accounts']._introspection_module

__all__ = []

def _get_string(self, key, default_value=None):
    value = GObject.Value()
    value.init(GObject.TYPE_STRING)
    if self.get_value(key, value) != Accounts.SettingSource.NONE:
        return value.get_string()
    else:
        return default_value

def _get_int(self, key, default_value=None):
    value = GObject.Value()
    value.init(GObject.TYPE_INT64)
    if self.get_value(key, value) != Accounts.SettingSource.NONE:
        return value.get_int64()
    else:
        return default_value

def _get_bool(self, key, default_value=None):
    value = GObject.Value()
    value.init(GObject.TYPE_BOOLEAN)
    if self.get_value(key, value) != Accounts.SettingSource.NONE:
        return value.get_boolean()
    else:
        return default_value

class Manager(Accounts.Manager):
    def __new__(cls):
        # Since AgManager implements GInitable, g_object_new() doesn't
        # initialize it properly
        # See also: https://bugzilla.gnome.org/show_bug.cgi?id=724275
        return Accounts.Manager.new()

Manager = override(Manager)
__all__.append('Manager')

class Account(Accounts.Account):
    get_string = _get_string
    get_int = _get_int
    get_bool = _get_bool

    def get_settings_iter(self, prefix=''):
        return super(Account, self).get_settings_iter(prefix)

    def get_settings(self, prefix=''):
        itr = self.get_settings_iter(prefix)
        while True:
            success, key, value = itr.next()
            if success:
                yield (key, value)
            else:
                break

    def get_settings_dict(self, prefix=''):
        return dict(self.get_settings(prefix))

    def __eq__(self, other):
        return self.id == other.id
    def __ne__(self, other):
        return self.id != other.id

Account = override(Account)
__all__.append('Account')

class Service(Accounts.Service):
    def __eq__(self, other):
        return self.get_name() == other.get_name()
    def __ne__(self, other):
        return self.get_name() != other.get_name()

Service = override(Service)
__all__.append('Service')

class AccountService(Accounts.AccountService):
    get_string = _get_string
    get_int = _get_int
    get_bool = _get_bool

    def __eq__(self, other):
        return self.get_account() == other.get_account() and \
            self.get_service() == other.get_service()
    def __ne__(self, other):
        return self.get_account() != other.get_account() or \
            self.get_service() != other.get_service()

AccountService = override(AccountService)
__all__.append('AccountService')
07070100000045000081A4000003E800000064000000015BDC2B8B00000197000000000000000000000000000000000000003D00000000libaccounts-glib-1.24/libaccounts-glib/pygobject/meson.buildpython3 = import('python3')
python_exec = python3.find_python()
python_exec_result = run_command(python_exec, ['-c', 'import gi; from os.path import abspath; print(abspath(gi._overridesdir))'])

if python_exec_result.returncode() != 0
    error('Failed to retreive the python GObject override directory')
endif

install_data('Accounts.py',
    install_dir: join_paths(python_exec_result.stdout().strip())
)
07070100000046000081A4000003E800000064000000015BDC2B8B000005A8000000000000000000000000000000000000002200000000libaccounts-glib-1.24/meson.buildproject('libaccounts-glib', 'c')

version_major = 1
version_minor = 24
full_version = '@0@.@1@'.format(version_major, version_minor)
api_version = '@0@.0'.format(version_major)

gnome = import('gnome')

glib_dep = dependency('glib-2.0', version : '>=2.26')
gio_dep = dependency('gio-2.0', version : '>=2.26')
gio_unix_dep = dependency('gio-unix-2.0')
gobject_dep = dependency('gobject-2.0', version : '>=2.35.1')
libxml_dep = dependency('libxml-2.0')
sqlite_dep = dependency('sqlite3', version : '>=3.7.0')

application_files_dir = 'accounts/applications'
provider_files_dir = 'accounts/providers'
service_files_dir = 'accounts/services'
service_type_files_dir = 'accounts/service_types'
database_dir = 'libaccounts-glib'

add_global_arguments('-DAPPLICATION_FILES_DIR="'+ application_files_dir + '"', language : 'c')
add_global_arguments('-DPROVIDER_FILES_DIR="'+ provider_files_dir + '"', language : 'c')
add_global_arguments('-DSERVICE_FILES_DIR="'+ service_files_dir + '"', language : 'c')
add_global_arguments('-DSERVICE_TYPE_FILES_DIR="'+ service_type_files_dir + '"', language : 'c')
add_global_arguments('-DDATABASE_DIR="'+ database_dir + '"', language : 'c')

accounts_glib_library_deps = [glib_dep, gio_dep, gio_unix_dep, gobject_dep, libxml_dep, sqlite_dep]

xmllint = find_program('xmllint', required: false)

root_dir = include_directories('.')

subdir('libaccounts-glib')
subdir('tools')
subdir('data')
subdir('docs')
subdir('tests')
07070100000047000041ED000003E800000064000000035BDC2B8B00000000000000000000000000000000000000000000001C00000000libaccounts-glib-1.24/tests07070100000048000081A4000003E800000064000000015BDC2B8B0002110C000000000000000000000000000000000000002700000000libaccounts-glib-1.24/tests/check_ag.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012-2016 Canonical Ltd.
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/**
 * @example check_ag.c
 * Shows how to initialize the framework.
 */

#define AG_DISABLE_DEPRECATION_WARNINGS

#define MAX_SQLITE_BUSY_LOOP_TIME 5
#define MAX_SQLITE_BUSY_LOOP_TIME_MS (MAX_SQLITE_BUSY_LOOP_TIME * 1000)

#include "test-manager.h"

#include <libaccounts-glib.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <check.h>
#include <sched.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define PROVIDER    "dummyprovider"
#define TEST_STRING "Hey dude!"
#define TEST_SERVICE_VALUE  "calendar"

static gchar *db_filename;
static GMainLoop *main_loop = NULL;
gboolean lock_released = FALSE;
static AgAccount *account = NULL;
static AgManager *manager = NULL;
static AgService *service = NULL;
static gboolean data_stored = FALSE;
static guint source_id = 0;
static guint idle_finish = 0;

typedef struct {
    gboolean called;
    gchar *service;
    gboolean enabled_check;
} EnabledCbData;

typedef struct {
    gboolean called;
    guint account_id;
    gboolean created;
    gboolean deleted;
    gchar *provider;
    GVariant *settings;
} StoreCbData;

static void
store_cb_data_unset (StoreCbData *data)
{
    g_free (data->provider);
    g_variant_unref (data->settings);
    memset (data, 0, sizeof (*data));
}

static void
set_read_only()
{
    gchar *filename;

    /* This is just to ensure that the DB exists */
    manager = ag_manager_new ();
    g_object_unref (manager);

    chmod (db_filename, S_IRUSR | S_IRGRP | S_IROTH);

    filename = g_strconcat (db_filename, "-shm", NULL);
    chmod (filename, S_IRUSR | S_IRGRP | S_IROTH);
    g_free (filename);

    filename = g_strconcat (db_filename, "-wal", NULL);
    unlink (filename);
    g_free (filename);
}

static void
delete_db()
{
    gchar *filename;

    unlink (db_filename);

    filename = g_strconcat (db_filename, "-shm", NULL);
    unlink (filename);
    g_free (filename);

    filename = g_strconcat (db_filename, "-wal", NULL);
    unlink (filename);
    g_free (filename);
}

static void
on_enabled (AgAccount *account, const gchar *service, gboolean enabled,
            EnabledCbData *ecd)
{
    ecd->called = TRUE;
    ecd->service = g_strdup (service);
    ecd->enabled_check = (ag_account_get_enabled (account) == enabled);
}

static gboolean
quit_loop (gpointer user_data)
{
    GMainLoop *loop = user_data;
    g_main_loop_quit (loop);
    return FALSE;
}

static void
run_main_loop_for_n_seconds(guint seconds)
{
    GMainLoop *loop = g_main_loop_new (NULL, FALSE);
    g_timeout_add_seconds (seconds, quit_loop, loop);
    g_main_loop_run (loop);
    g_main_loop_unref (loop);
}

static gboolean
test_strv_equal (const gchar **s1, const gchar **s2)
{
    gint i;

    if (s1 == NULL) return s2 == NULL;
    if (s1 != NULL && s2 == NULL) return FALSE;

    for (i = 0; s1[i] != NULL; i++)
        if (strcmp(s1[i], s2[i]) != 0) {
            g_debug ("s1: %s, s2: %s", s1[i], s2[i]);
            return FALSE;
        }
    if (s2[i] != NULL) return FALSE;

    return TRUE;
}

static guint
time_diff(struct timespec *start_time, struct timespec *end_time)
{
    struct timespec diff_time;
    diff_time.tv_sec = end_time->tv_sec - start_time->tv_sec;
    diff_time.tv_nsec = end_time->tv_nsec - start_time->tv_nsec;
    return diff_time.tv_sec * 1000 + diff_time.tv_nsec / 1000000;
}

static void
end_test ()
{
    if (account)
    {
        g_object_unref (account);
        account = NULL;
    }
    if (manager)
    {
        g_object_unref (manager);
        manager = NULL;
    }
    if (service)
    {
        ag_service_unref (service);
        service = NULL;
    }

    if (main_loop)
    {
        g_main_loop_quit (main_loop);
        g_main_loop_unref (main_loop);
        main_loop = NULL;
    }

    data_stored = FALSE;
}

START_TEST(test_init)
{
    manager = ag_manager_new ();

    fail_unless (AG_IS_MANAGER (manager),
                 "Failed to initialize the AgManager.");

    end_test ();
}
END_TEST

START_TEST(test_timeout_properties)
{
    gboolean abort_on_db_timeout;
    guint db_timeout;

    manager = ag_manager_new ();
    ck_assert (AG_IS_MANAGER (manager));

    g_object_get (manager,
                  "db-timeout", &db_timeout,
                  "abort-on-db-timeout", &abort_on_db_timeout,
                  NULL);

    ck_assert (!abort_on_db_timeout);
    ck_assert (!ag_manager_get_abort_on_db_timeout (manager));
    ck_assert_uint_eq (db_timeout, ag_manager_get_db_timeout (manager));

    g_object_set (manager,
                  "db-timeout", 120,
                  "abort_on_db_timeout", TRUE,
                  NULL);
    ck_assert (ag_manager_get_abort_on_db_timeout (manager));
    ck_assert_uint_eq (ag_manager_get_db_timeout (manager), 120);

    end_test ();
}
END_TEST

START_TEST(test_object)
{
    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, NULL);
    fail_unless (AG_IS_ACCOUNT (account),
                 "Failed to create the AgAccount.");

    end_test ();
}
END_TEST

START_TEST(test_read_only)
{
    GError *error = NULL;
    gboolean ok;

    set_read_only ();

    manager = ag_manager_new ();
    fail_unless (manager != NULL);

    /* create an account, and expect a failure */
    account = ag_manager_create_account (manager, "bisbone");
    fail_unless (AG_IS_ACCOUNT (account),
                 "Failed to create the AgAccount.");

    ok = ag_account_store_blocking (account, &error);
    fail_unless (!ok);
    fail_unless (error->code == AG_ACCOUNTS_ERROR_READONLY);
    g_debug ("Error message: %s", error->message);
    g_error_free (error);

    /* delete the DB */
    g_object_unref (account);
    account = NULL;
    g_object_unref (manager);
    manager = NULL;

    delete_db ();

    g_debug("Ending read-only test");

    end_test ();
}
END_TEST

START_TEST(test_provider)
{
    const gchar *provider_name, *display_name;
    const gchar *description;
    const gchar *domains;
    const gchar *plugin_name;
    AgProvider *provider;
    GList *providers, *list;
    gboolean single_account;
    gboolean found;

    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, PROVIDER);
    fail_unless (AG_IS_ACCOUNT (account),
                 "Failed to create the AgAccount.");

    provider_name = ag_account_get_provider_name (account);
    fail_if (g_strcmp0 (provider_name, PROVIDER) != 0);

    /* Test provider XML file loading */
    provider = ag_manager_get_provider (manager, "MyProvider");
    fail_unless (provider != NULL);

    ck_assert_str_eq (ag_provider_get_name (provider), "MyProvider");
    ck_assert_str_eq (ag_provider_get_i18n_domain (provider),
                      "provider_i18n");
    ck_assert_str_eq (ag_provider_get_icon_name (provider),
                      "general_myprovider");

    display_name = ag_provider_get_display_name (provider);
    fail_unless (g_strcmp0 (display_name, "My Provider") == 0);

    description = ag_provider_get_description (provider);
    fail_unless (g_strcmp0 (description, "My Provider Description") == 0);

    single_account = ag_provider_get_single_account (provider);
    fail_unless (single_account);

    /* The next couple of lines serve only to add coverage for
     * ag_provider_ref() */
    ag_provider_ref (provider);
    ag_provider_unref (provider);

    ag_provider_unref (provider);

    provider = ag_manager_get_provider (manager, "maemo");
    fail_unless (provider != NULL);

    single_account = ag_provider_get_single_account (provider);
    fail_unless (!single_account);

    ag_provider_unref (provider);

    /* Test provider enumeration */
    providers = ag_manager_list_providers (manager);
    fail_unless (providers != NULL);
    fail_unless (g_list_length (providers) == 2);

    found = FALSE;
    for (list = providers; list != NULL; list = list->next)
    {
        provider = list->data;
        display_name = ag_provider_get_display_name (provider);
        if (g_strcmp0 (display_name, "My Provider") != 0) continue;

        found = TRUE;
        domains = ag_provider_get_domains_regex (provider);
        fail_unless (g_strcmp0 (domains, ".*provider\\.com") == 0);

        fail_unless (ag_provider_match_domain (provider, "www.provider.com"));

        plugin_name = ag_provider_get_plugin_name (provider);
        fail_unless (g_strcmp0 (plugin_name, "oauth2") == 0);
    }

    fail_unless (found);

    ag_provider_list_free (providers);

    end_test ();
}
END_TEST

START_TEST(test_provider_settings)
{
    AgSettingSource source;
    GVariant *variant;

    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, "MyProvider");
    fail_unless (AG_IS_ACCOUNT (account),
                 "Failed to create the AgAccount.");

    /* Test provider default settings */
    source = AG_SETTING_SOURCE_NONE;
    variant = ag_account_get_variant (account, "login/server", &source);
    fail_unless (source == AG_SETTING_SOURCE_PROFILE);
    fail_unless (variant != NULL);
    fail_unless (g_strcmp0 (g_variant_get_string (variant, NULL),
                            "login.example.com") == 0);

    source = AG_SETTING_SOURCE_NONE;
    variant = ag_account_get_variant (account, "login/remember-me", &source);
    fail_unless (source == AG_SETTING_SOURCE_PROFILE);
    fail_unless (variant != NULL);
    fail_unless (g_variant_get_boolean (variant) == TRUE);

    end_test ();
}
END_TEST

START_TEST(test_provider_directories)
{
    AgProvider *provider;
    gchar *ag_providers_env;
    gchar *xdg_data_home_env;

    /* Unset the AG_PROVIDERS environment variable, just for this test, as
     * that disables the fallback mechanism which we now want to test. */
    ag_providers_env = g_strdup (g_getenv ("AG_PROVIDERS"));
    g_unsetenv ("AG_PROVIDERS");
    /* Disable also XDG_DATA_HOME, but reuse its value for XDG_DATA_DIRS
     * which is where the fallback mechanism is implemented. */
    xdg_data_home_env = g_strdup (g_getenv ("XDG_DATA_HOME"));
    g_unsetenv ("XDG_DATA_HOME");
    g_setenv ("XDG_DATA_DIRS", xdg_data_home_env, TRUE);

    /* check that the expected MyProvider file is loaded */
    g_unsetenv ("XDG_CURRENT_DESKTOP");
    manager = ag_manager_new ();

    provider = ag_manager_get_provider (manager, "MyProvider");
    fail_unless (provider != NULL);
    ck_assert_str_eq (ag_provider_get_name (provider), "MyProvider");
    ck_assert_str_eq (ag_provider_get_display_name (provider), "My Provider");

    ag_provider_unref (provider);
    g_object_unref (manager);

    /* Now check a desktop-specific override */
    g_setenv ("XDG_CURRENT_DESKTOP", "Fake-OS", TRUE);
    manager = ag_manager_new ();

    provider = ag_manager_get_provider (manager, "MyProvider");
    fail_unless (provider != NULL);
    ck_assert_str_eq (ag_provider_get_name (provider), "MyProvider");
    ck_assert_str_eq (ag_provider_get_display_name (provider), "FakeOs Provider");

    ag_provider_unref (provider);
    g_object_unref (manager);

    g_unsetenv ("XDG_DATA_DIRS");
    g_setenv ("XDG_DATA_HOME", xdg_data_home_env, TRUE);
    g_free (xdg_data_home_env);
    g_setenv ("AG_PROVIDERS", ag_providers_env, TRUE);
    g_free (ag_providers_env);
    manager = NULL;
    end_test ();
}
END_TEST

void account_store_cb (AgAccount *account, const GError *error,
                       gpointer user_data)
{
    const gchar *string = user_data;

    fail_unless (AG_IS_ACCOUNT (account), "Account got disposed?");
    if (error)
        fail("Got error: %s", error->message);
    fail_unless (g_strcmp0 (string, TEST_STRING) == 0, "Got wrong string");

    end_test ();
}

START_TEST(test_store)
{
    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, PROVIDER);

    main_loop = g_main_loop_new (NULL, FALSE);
    ag_account_store (account, account_store_cb, TEST_STRING);
    if (main_loop)
    {
        g_debug ("Running loop");
        g_main_loop_run (main_loop);
    }
    else
        end_test ();
}
END_TEST

void account_store_locked_cb (AgAccount *account, const GError *error,
                              gpointer user_data)
{
    const gchar *string = user_data;

    g_debug ("%s called", G_STRFUNC);
    fail_unless (AG_IS_ACCOUNT (account), "Account got disposed?");
    if (error)
        fail("Got error: %s", error->message);
    fail_unless (g_strcmp0 (string, TEST_STRING) == 0, "Got wrong string");

    fail_unless (lock_released, "Data stored while DB locked!");

    end_test ();
}

gboolean
release_lock (sqlite3 *db)
{
    g_debug ("releasing lock");
    sqlite3_exec (db, "COMMIT;", NULL, NULL, NULL);
    lock_released = TRUE;
    return FALSE;
}

START_TEST(test_store_locked)
{
    sqlite3 *db;

    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, PROVIDER);

    /* get an exclusive lock on the DB */
    sqlite3_open (db_filename, &db);
    sqlite3_exec (db, "BEGIN EXCLUSIVE", NULL, NULL, NULL);

    main_loop = g_main_loop_new (NULL, FALSE);
    ag_account_store (account, account_store_locked_cb, TEST_STRING);
    g_timeout_add (100, (GSourceFunc)release_lock, db);
    fail_unless (main_loop != NULL, "Callback invoked too early");
    g_debug ("Running loop");
    g_main_loop_run (main_loop);
    sqlite3_close (db);
}
END_TEST

static void
account_store_locked_cancel_cb (GObject *object, GAsyncResult *res,
                               gpointer user_data)
{
    gboolean *called = user_data;
    GError *error = NULL;

    g_debug ("%s called", G_STRFUNC);

    ag_account_store_finish (AG_ACCOUNT (object), res, &error);
    fail_unless (error != NULL, "Account disposed but no error set!");
    fail_unless (error->domain == G_IO_ERROR, "Wrong error domain");
    fail_unless (error->code == G_IO_ERROR_CANCELLED,
                 "Got a different error code");
    g_error_free (error);
    *called = TRUE;
}

static gboolean
release_lock_cancel (sqlite3 *db)
{
    g_debug ("releasing lock");
    sqlite3_exec (db, "COMMIT;", NULL, NULL, NULL);

    end_test ();
    return FALSE;
}

static gboolean
cancel_store (gpointer user_data)
{
    GCancellable *cancellable = user_data;

    g_debug ("Cancelling %p", cancellable);
    g_cancellable_cancel (cancellable);
    return FALSE;
}

START_TEST(test_store_locked_cancel)
{
    sqlite3 *db;
    GCancellable *cancellable;
    gboolean cb_called = FALSE;

    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, PROVIDER);

    /* get an exclusive lock on the DB */
    sqlite3_open (db_filename, &db);
    sqlite3_exec (db, "BEGIN EXCLUSIVE", NULL, NULL, NULL);

    main_loop = g_main_loop_new (NULL, FALSE);
    cancellable = g_cancellable_new ();
    ag_account_store_async (account, cancellable, account_store_locked_cancel_cb, &cb_called);
    g_timeout_add (100, (GSourceFunc)cancel_store, cancellable);
    g_timeout_add (200, (GSourceFunc)release_lock_cancel, db);
    fail_unless (main_loop != NULL, "Callback invoked too early");
    g_debug ("Running loop");
    g_main_loop_run (main_loop);
    fail_unless (cb_called, "Callback not invoked");
    sqlite3_close (db);
    g_object_unref (cancellable);
}
END_TEST

static gboolean
test_store_read_only_handle_store_cb (TestManager *test_manager,
                                      GDBusMethodInvocation *invocation,
                                      guint account_id,
                                      gboolean created,
                                      gboolean deleted,
                                      const gchar *provider,
                                      GVariant *settings,
                                      StoreCbData *store_data)
{
    g_debug ("%s called", G_STRFUNC);
    guint result_id = store_data->account_id;

    store_data->called = TRUE;
    store_data->account_id = account_id;
    store_data->created = created;
    store_data->deleted = deleted;
    store_data->provider = g_strdup (provider);
    store_data->settings = g_variant_ref (settings);
    test_manager_complete_store (test_manager, invocation, result_id);
    return TRUE;
}

static void
test_store_read_only_store_cb (GObject *object, GAsyncResult *res,
                               gpointer user_data)
{
    GMainLoop *loop = user_data;
    GError *error = NULL;

    g_debug ("%s called", G_STRFUNC);

    ag_account_store_finish (AG_ACCOUNT (object), res, &error);
    ck_assert (error == NULL);
    g_main_loop_quit (loop);
}

START_TEST(test_store_read_only)
{
    TestManager *test_manager;
    TestObjectSkeleton *test_object;
    StoreCbData store_data = { 0 };
    GDBusObjectManagerServer *object_manager;
    GDBusConnection *conn;
    const gchar *display_name = "My readonly account";
    const AgAccountId expected_id = 4;
    GError *error = NULL;
    guint reg_id;

    set_read_only ();

    main_loop = g_main_loop_new (NULL, FALSE);

    /* Register the D-Bus account manager service */
    conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
    ck_assert (error == NULL);
    ck_assert (conn != NULL);

    reg_id = g_bus_own_name_on_connection (conn,
                                           AG_MANAGER_SERVICE_NAME,
                                           G_BUS_NAME_OWNER_FLAGS_REPLACE,
                                           NULL, NULL, NULL, NULL);

    test_manager = test_manager_skeleton_new ();
    g_signal_connect (test_manager, "handle-store",
                      G_CALLBACK (test_store_read_only_handle_store_cb),
                      &store_data);

    test_object = test_object_skeleton_new (AG_MANAGER_OBJECT_PATH);
    test_object_skeleton_set_manager (test_object, test_manager);

    object_manager =
        g_dbus_object_manager_server_new ("/com/google/code/AccountsSSO");
    g_dbus_object_manager_server_export (object_manager,
                                         G_DBUS_OBJECT_SKELETON (test_object));
    g_dbus_object_manager_server_set_connection (object_manager, conn);

    manager = ag_manager_new ();
    ck_assert (manager != NULL);

    /* create an account, write its display name */
    account = ag_manager_create_account (manager, "fakebook");
    fail_unless (AG_IS_ACCOUNT (account),
                 "Failed to create the AgAccount.");
    ag_account_set_display_name (account, display_name);

    /* We want to verify that the local account will be updated with this
     * ID */
    store_data.account_id = expected_id;
    ag_account_store_async (account, NULL,
                            test_store_read_only_store_cb,
                            main_loop);
    g_main_loop_run (main_loop);

    ck_assert (store_data.called);
    ck_assert (store_data.created);
    ck_assert (!store_data.deleted);
    ck_assert_uint_eq (store_data.account_id, 0);
    ck_assert_str_eq (store_data.provider, "fakebook");
    store_cb_data_unset (&store_data);

    ck_assert_uint_eq (account->id, expected_id);
    const char *name = ag_account_get_display_name (account);
    ck_assert (name != NULL);
    ck_assert_str_eq (name, display_name);

    /* cleaning up */
    g_object_unref (object_manager);
    g_object_unref (test_object);
    g_object_unref (test_manager);
    g_bus_unown_name (reg_id);
    g_object_unref (conn);

    /* delete the DB */
    g_object_unref (account);
    account = NULL;
    g_object_unref (manager);
    manager = NULL;

    delete_db ();

    end_test ();
}
END_TEST

void account_store_now_cb (AgAccount *account, const GError *error,
                           gpointer user_data)
{
    const gchar *string = user_data;

    fail_unless (AG_IS_ACCOUNT (account), "Account got disposed?");
    if (error)
        fail("Got error: %s", error->message);
    fail_unless (g_strcmp0 (string, TEST_STRING) == 0, "Got wrong string");

    data_stored = TRUE;
}

START_TEST(test_account_service)
{
    GValue value = { 0 };
    const gchar *description = "This is really a beautiful account";
    const gchar *display_name = "My test account";
    AgSettingSource source;
    AgAccountService *account_service;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, description);
    ag_account_set_value (account, "description", &value);
    g_value_unset (&value);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    ag_account_set_enabled (account, FALSE);
    ag_account_set_display_name (account, display_name);

    account_service = ag_account_service_new (account, service);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service),
                 "Failed to create AccountService");

    /* test the readable properties */
    {
        AgAccount *account_prop = NULL;
        AgService *service_prop = NULL;

        g_object_get (account_service,
                      "account", &account_prop,
                      "service", &service_prop,
                      NULL);
        fail_unless (account_prop == account);
        fail_unless (service_prop == service);
        g_object_unref (account_prop);
        ag_service_unref (service_prop);
    }

    /* test getting default setting from template */
    g_value_init (&value, G_TYPE_INT);
    source = ag_account_service_get_value (account_service, "parameters/port", &value);
    fail_unless (source == AG_SETTING_SOURCE_PROFILE,
                 "Cannot get port from profile");
    fail_unless (g_value_get_int (&value) == 5223,
                 "Wrong port number: %d", g_value_get_int (&value));

    g_value_unset (&value);

    /* test getters for account and service */
    fail_unless (ag_account_service_get_service (account_service) == service);
    fail_unless (ag_account_service_get_account (account_service) == account);

    g_object_unref (account_service);

    /* Test account service for global settings */
    account_service = ag_account_service_new (account, NULL);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service),
                 "Failed to create AccountService for global settings");

    g_value_init (&value, G_TYPE_STRING);
    source = ag_account_service_get_value (account_service, "description", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT);
    fail_unless (g_strcmp0 (g_value_get_string (&value), description) == 0);
    g_value_unset (&value);

    g_object_unref (account_service);

    end_test ();
}
END_TEST

static void
on_account_service_enabled (AgAccountService *account_service,
                            gboolean enabled,
                            gboolean *enabled_value)
{
    fail_unless (ag_account_service_get_enabled (account_service) == enabled);
    *enabled_value = enabled;
}

START_TEST(test_account_service_enabledness)
{
    AgAccountId account_id;
    AgAccountService *account_service;
    gboolean service_enabled = FALSE;
    GError *error = NULL;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    ag_account_set_enabled (account, FALSE);

    account_service = ag_account_service_new (account, service);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service),
                 "Failed to create AccountService");

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;
    account_id = account->id;

    g_signal_connect (account_service, "enabled",
                      G_CALLBACK (on_account_service_enabled),
                      &service_enabled);

    /* enable the service */
    ag_account_select_service (account, service);
    ag_account_set_enabled (account, TRUE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* Still disabled, because the account is disabled */
    fail_unless (service_enabled == FALSE);
    service_enabled = TRUE;
    g_object_get (account_service, "enabled", &service_enabled, NULL);
    fail_unless (service_enabled == FALSE);

    /* enable the account */
    ag_account_select_service (account, NULL);
    ag_account_set_enabled (account, TRUE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (service_enabled == TRUE);
    service_enabled = FALSE;
    g_object_get (account_service, "enabled", &service_enabled, NULL);
    fail_unless (service_enabled == TRUE);

    g_object_unref (account_service);

    ag_service_unref (service);
    g_object_unref (account);
    g_object_unref (manager);

    manager = ag_manager_new ();

    /* reload the account and see that it's enabled */
    account = ag_manager_load_account (manager, account_id, &error);
    fail_unless (AG_IS_ACCOUNT (account),
                 "Couldn't load account %u", account_id);
    fail_unless (error == NULL, "Error is not NULL");

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    /* load the global account, and check that it's enabled */
    account_service = ag_account_service_new (account, NULL);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service));

    fail_unless (ag_account_service_get_enabled (account_service) == TRUE);
    g_object_unref (account_service);

    /* load the service, and check that it's enabled */
    account_service = ag_account_service_new (account, service);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service),
                 "Failed to create AccountService");

    g_signal_connect (account_service, "enabled",
                      G_CALLBACK (on_account_service_enabled),
                      &service_enabled);

    fail_unless (ag_account_service_get_enabled (account_service) == TRUE);

    /* disable the service */
    ag_account_select_service (account, service);
    ag_account_set_enabled (account, FALSE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (service_enabled == FALSE);

    g_object_unref (account_service);
    end_test ();
}
END_TEST

static void
on_account_service_changed (AgAccountService *account_service,
                            gchar ***fields)
{
    *fields = ag_account_service_get_changed_fields (account_service);
}

static gboolean
string_in_array (gchar **array, const gchar *string)
{
    gint i;
    gboolean found = FALSE;

    for (i = 0; array[i] != NULL; i++)
        if (g_strcmp0 (string, array[i]) == 0)
        {
            found = TRUE;
            break;
        }

    return found;
}

START_TEST(test_account_service_settings)
{
    AgAccountSettingIter iter, *dyn_iter;
    GValue value = { 0 };
    GVariant *variant;
    const gchar *username = "me@myhome.com";
    const gboolean check_automatically = TRUE;
    const gchar *display_name = "My test account";
    const gchar *key;
    AgAccountService *account_service;
    AgSettingSource source;
    gchar **changed_fields = NULL;
    gint known_keys_count, total_keys_count;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    ag_account_set_enabled (account, FALSE);
    ag_account_set_display_name (account, display_name);

    account_service = ag_account_service_new (account, service);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service),
                 "Failed to create AccountService");

    g_signal_connect (account_service, "changed",
                      G_CALLBACK (on_account_service_changed),
                      &changed_fields);

    /* enable the service */
    ag_account_set_enabled (account, TRUE);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, username);
    ag_account_service_set_value (account_service, "username", &value);
    g_value_unset (&value);

    variant = g_variant_new_boolean (check_automatically);
    ag_account_service_set_variant (account_service, "check_automatically",
                                    variant);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* The callback for the "changed" signal should have been emitted.
     * Let's check what changed fields were reported, and what their value
     * is now.
     */
    fail_unless (changed_fields != NULL);
    fail_unless (string_in_array (changed_fields, "username"));
    g_value_init (&value, G_TYPE_STRING);
    source = ag_account_service_get_value (account_service, "username", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT);
    fail_unless (strcmp (g_value_get_string (&value), username) == 0);
    g_value_unset (&value);

    fail_unless (string_in_array (changed_fields, "check_automatically"));
    g_strfreev (changed_fields);

    /* Let's repeat the test, now that the settings are stored in the DB */
    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, check_automatically);
    ag_account_service_set_value (account_service, "check_automatically", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "Wednesday");
    ag_account_service_set_value (account_service, "day", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, TRUE);
    ag_account_service_set_value (account_service, "ForReal", &value);
    g_value_unset (&value);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* The callback for the "changed" signal should have been emitted.
     * Let's check what changed fields were reported, and what their value
     * is now.
     */
    fail_unless (string_in_array (changed_fields, "check_automatically"));
    variant = ag_account_service_get_variant (account_service,
                                              "check_automatically", &source);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT);
    fail_unless (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN));
    ck_assert_int_eq (g_variant_get_boolean (variant), check_automatically);

    fail_unless (string_in_array (changed_fields, "day"));
    fail_unless (string_in_array (changed_fields, "ForReal"));
    g_strfreev (changed_fields);

    /* Enumerate the account service settings */
    known_keys_count = 0;
    total_keys_count = 0;
    ag_account_service_settings_iter_init (account_service, &iter, NULL);
    while (ag_account_settings_iter_get_next (&iter, &key, &variant))
    {
        fail_unless (key != NULL);
        fail_unless (variant != NULL);

        total_keys_count++;

        if (g_strcmp0 (key, "check_automatically") == 0)
        {
            known_keys_count++;
            fail_unless (g_variant_is_of_type (variant,
                                               G_VARIANT_TYPE_BOOLEAN));
            ck_assert_int_eq (g_variant_get_boolean (variant),
                              check_automatically);
        }
        else if (g_strcmp0 (key, "username") == 0)
        {
            known_keys_count++;
            fail_unless (g_variant_is_of_type (variant,
                                               G_VARIANT_TYPE_STRING));
            ck_assert_str_eq (g_variant_get_string (variant, NULL),
                              username);
        }
        else if (g_strcmp0 (key, "day") == 0)
        {
            known_keys_count++;
            fail_unless (g_variant_is_of_type (variant,
                                               G_VARIANT_TYPE_STRING));
            ck_assert_str_eq (g_variant_get_string (variant, NULL),
                              "Wednesday");
        }
        else if (g_strcmp0 (key, "ForReal") == 0)
        {
            known_keys_count++;
            fail_unless (g_variant_is_of_type (variant,
                                               G_VARIANT_TYPE_BOOLEAN));
            ck_assert_int_eq (g_variant_get_boolean (variant), TRUE);
        }
    }

    ck_assert_int_eq (known_keys_count, 4);

    /* Now try the same with the dynamically allocated iterator; let's just
     * check that it returns the same number of keys. */
    dyn_iter = ag_account_service_get_settings_iter (account_service, NULL);
    fail_unless (dyn_iter != NULL);

    while (ag_account_settings_iter_get_next (dyn_iter, &key, &variant))
    {
        total_keys_count--;
    }
    ck_assert_int_eq (total_keys_count, 0);

    g_boxed_free (ag_account_settings_iter_get_type (), dyn_iter);

    g_object_unref (account_service);
    end_test ();
}
END_TEST

static gboolean
account_service_in_list(GList *list, AgAccountId id, const gchar *service_name)
{
    while (list != NULL) {
        AgAccountService *account_service = AG_ACCOUNT_SERVICE(list->data);
        AgAccount *account;
        AgService *service;

        account = ag_account_service_get_account (account_service);
        service = ag_account_service_get_service (account_service);
        if (account->id == id &&
            g_strcmp0(ag_service_get_name (service), service_name) == 0)
            return TRUE;
        list = list->next;
    }

    return FALSE;
}

START_TEST(test_account_service_list)
{
    const gchar *display_name = "My test account";
#define N_ACCOUNTS  3
    AgAccountId account_id[N_ACCOUNTS];
    AgService *my_service, *my_service2;
    GList *list;
    gint i;

    /* delete the database */
    g_unlink (db_filename);

    manager = ag_manager_new ();

    /* create a few accounts */
    for (i = 0; i < N_ACCOUNTS; i++)
    {
        account = ag_manager_create_account (manager, "maemo");
        ag_account_set_enabled (account, TRUE);
        ag_account_set_display_name (account, display_name);
        ag_account_store (account, account_store_now_cb, TEST_STRING);
        run_main_loop_for_n_seconds(0);
        fail_unless (data_stored, "Callback not invoked immediately");
        data_stored = FALSE;
        account_id[i] = account->id;
        g_object_unref (account);
        account = NULL;
    }

    list = ag_manager_get_enabled_account_services (manager);
    fail_unless (list == NULL);

    list = ag_manager_get_account_services (manager);
    for (i = 0; i < N_ACCOUNTS; i++) {
        fail_unless (account_service_in_list (list,
                                              account_id[i], "MyService"));
        fail_unless (account_service_in_list (list,
                                              account_id[i], "MyService2"));
    }
    fail_unless (g_list_length (list) == N_ACCOUNTS * 2,
                 "Got list length %d, expecting %d",
                 g_list_length (list), N_ACCOUNTS * 2);
    g_list_foreach (list, (GFunc)g_object_unref, NULL);
    g_list_free (list);


    /* Now add a few services, and play with the enabled flags */
    my_service = ag_manager_get_service (manager, "MyService");
    fail_unless (my_service != NULL);
    my_service2 = ag_manager_get_service (manager, "MyService2");
    fail_unless (my_service2 != NULL);

    account = ag_manager_get_account (manager, account_id[0]);
    fail_unless (AG_IS_ACCOUNT(account));
    ag_account_select_service (account, my_service);
    ag_account_set_enabled (account, TRUE);
    ag_account_select_service (account, my_service2);
    ag_account_set_enabled (account, FALSE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;
    g_object_unref (account);

    account = ag_manager_get_account (manager, account_id[1]);
    fail_unless (AG_IS_ACCOUNT(account));
    ag_account_set_enabled (account, FALSE);
    ag_account_select_service (account, my_service);
    ag_account_set_enabled (account, TRUE);
    ag_account_select_service (account, my_service2);
    ag_account_set_enabled (account, FALSE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;
    g_object_unref (account);

    account = ag_manager_get_account (manager, account_id[2]);
    fail_unless (AG_IS_ACCOUNT(account));
    ag_account_select_service (account, my_service);
    ag_account_set_enabled (account, FALSE);
    ag_account_select_service (account, my_service2);
    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    g_object_unref (manager);

    /* Now check if the list functions return the expected results */
    manager = ag_manager_new ();

    list = ag_manager_get_account_services (manager);
    for (i = 0; i < N_ACCOUNTS; i++) {
        fail_unless (account_service_in_list (list,
                                              account_id[i], "MyService"));
        fail_unless (account_service_in_list (list,
                                              account_id[i], "MyService2"));
    }
    fail_unless (g_list_length (list) == N_ACCOUNTS * 2,
                 "Got list length %d, expecting %d",
                 g_list_length (list), N_ACCOUNTS * 2);
    g_list_foreach (list, (GFunc)g_object_unref, NULL);
    g_list_free (list);

    list = ag_manager_get_enabled_account_services (manager);
    fail_unless (account_service_in_list (list, account_id[0], "MyService"));
    fail_unless (account_service_in_list (list, account_id[2], "MyService2"));
    fail_unless (g_list_length (list) == 2,
                 "Got list length %d, expecting %d",
                 g_list_length (list), 2);
    g_list_foreach (list, (GFunc)g_object_unref, NULL);
    g_list_free (list);

    g_object_unref (manager);

    /* Now try with a manager created for a specific service type */
    manager = ag_manager_new_for_service_type ("e-mail");

    list = ag_manager_get_account_services (manager);
    for (i = 0; i < N_ACCOUNTS; i++) {
        fail_unless (account_service_in_list (list,
                                              account_id[i], "MyService"));
    }
    fail_unless (g_list_length (list) == N_ACCOUNTS,
                 "Got list length %d, expecting %d",
                 g_list_length (list), N_ACCOUNTS);
    g_list_foreach (list, (GFunc)g_object_unref, NULL);
    g_list_free (list);

    list = ag_manager_get_enabled_account_services (manager);
    fail_unless (account_service_in_list (list, account_id[0], "MyService"));
    fail_unless (g_list_length (list) == 1,
                 "Got list length %d, expecting %d",
                 g_list_length (list), 1);
    g_list_foreach (list, (GFunc)g_object_unref, NULL);
    g_list_free (list);

    ag_service_unref (my_service);
    ag_service_unref (my_service2);

    end_test ();
}
END_TEST

static void
write_strings_to_account (AgAccount *account, const gchar *key_prefix,
                          const gchar **strings)
{
    GValue value = { 0, };
    gint i;

    /* first string is the key, second the value */
    for (i = 0; strings[i] != NULL; i += 2)
    {
        gchar *key = g_strdup_printf ("%s/%s", key_prefix, strings[i]);
        g_value_init (&value, G_TYPE_STRING);
        g_value_set_static_string (&value, strings[i + 1]);
        ag_account_set_value (account, key, &value);
        g_value_unset (&value);
        g_free (key);
    }
}

static void
check_string_in_params (GHashTable *params,
                        const gchar *key, const gchar *expected)
{
    GValue *value;
    const gchar *actual;
    gboolean equal;

    value = g_hash_table_lookup (params, key);
    if (value == NULL)
    {
        if (expected == NULL) return;
        fail ("Key %s is missing", key);
    }

    actual = g_value_get_string (value);
    equal = (g_strcmp0 (actual, expected) == 0);
    if (!equal)
    {
        g_warning ("Values differ! Expected %s, actual %s", expected, actual);
    }

    fail_unless (equal);
}

START_TEST(test_auth_data)
{
    AgAccountId account_id;
    AgAccountService *account_service;
    AgService *my_service;
    const guint credentials_id = 0xdeadbeef;
    const gchar *method = "dummy-method";
    const gchar *mechanism = "dummy-mechanism";
    const gchar *global_params[] = {
        "id", "123",
        "service", "contacts",
        NULL
    };
    const gchar *service_params[] = {
        "display", "mobile",
        "service", TEST_SERVICE_VALUE,
        NULL
    };
    gchar *key_prefix;
    AgAuthData *data;
    GHashTable *params;
    GValue value = { 0, };

    /* delete the database */
    g_unlink (db_filename);

    manager = ag_manager_new ();

    key_prefix = g_strdup_printf ("auth/%s/%s", method, mechanism);

    /* create a new account */
    account = ag_manager_create_account (manager, "maemo");
    ag_account_set_enabled (account, TRUE);
    write_strings_to_account (account, key_prefix, global_params);

    my_service = ag_manager_get_service (manager, "MyService");
    fail_unless (my_service != NULL);
    ag_account_select_service (account, my_service);
    ag_account_set_enabled (account, TRUE);
    write_strings_to_account (account, key_prefix, service_params);
    g_free (key_prefix);

    g_value_init (&value, G_TYPE_UINT);
    g_value_set_uint (&value, credentials_id);
    ag_account_set_value (account, "CredentialsId", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, method);
    ag_account_set_value (account, "auth/method", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, mechanism);
    ag_account_set_value (account, "auth/mechanism", &value);
    g_value_unset (&value);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;
    account_id = account->id;
    g_object_unref (account);
    account = NULL;

    /* reload the account and get the AccountService */
    account = ag_manager_get_account (manager, account_id);
    fail_unless (AG_IS_ACCOUNT (account));
    account_service = ag_account_service_new (account, my_service);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service));

    /* get the auth data and check its contents */
    data = ag_account_service_get_auth_data (account_service);
    fail_unless (data != NULL);
    fail_unless (ag_auth_data_get_credentials_id (data) == credentials_id);
    fail_unless (strcmp (ag_auth_data_get_method (data), method) == 0);
    fail_unless (strcmp (ag_auth_data_get_mechanism (data), mechanism) == 0);
    params = ag_auth_data_get_parameters (data);
    fail_unless (params != NULL);

    check_string_in_params (params, "id", "123");
    check_string_in_params (params, "display", "mobile");
    check_string_in_params (params, "service", TEST_SERVICE_VALUE);
    check_string_in_params (params, "from-provider", "yes");

    ag_auth_data_unref (data);
    g_object_unref (account_service);
    ag_service_unref (my_service);

    end_test ();
}
END_TEST

static void
check_variant_in_dict (GVariant *dict, const gchar *key,
                       GVariant *expected)
{
    GVariant *actual;

    actual = g_variant_lookup_value (dict, key, NULL);
    if (actual == NULL)
    {
        if (expected == NULL) return;
        fail ("Key %s is missing", key);
    }

    if (!g_variant_equal(actual, expected))
    {
        fail ("Values differ for key %s! Expected %s, actual %s", key,
              g_variant_print (expected, TRUE),
              g_variant_print (actual, TRUE));
    }

    g_variant_ref_sink (expected);
    g_variant_unref (expected);
    g_variant_unref (actual);
}

START_TEST(test_auth_data_get_login_parameters)
{
    GList *account_services;
    AgAccountService *account_service;
    AgAuthData *data;
    GVariant *params, *variant;
    GVariantBuilder builder;
    const gchar *display = "desktop";
    const gchar *animal = "cat";

    manager = ag_manager_new_for_service_type ("e-mail");

    /* first, check the default parameters on a non-stored account */
    account = ag_manager_create_account (manager, "maemo");
    account_service = ag_account_service_new (account, NULL);
    data = ag_account_service_get_auth_data (account_service);
    fail_unless (data != NULL);

    params = ag_auth_data_get_login_parameters (data, NULL);
    fail_unless (params != NULL);

    check_variant_in_dict (params, "id", g_variant_new_string ("879"));
    check_variant_in_dict (params, "display",
                           g_variant_new_string ("desktop"));
    check_variant_in_dict (params, "from-provider",
                           g_variant_new_string ("yes"));
    g_variant_unref (params);
    ag_auth_data_unref (data);
    data = NULL;
    g_clear_object (&account);
    g_clear_object (&account_service);

    /* reload the account and get the AccountService */
    account_services = ag_manager_get_account_services (manager);
    fail_unless (g_list_length(account_services) == 1);
    account_service = AG_ACCOUNT_SERVICE (account_services->data);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service));
    g_list_free (account_services);

    /* get the auth data */
    data = ag_account_service_get_auth_data (account_service);
    fail_unless (data != NULL);

    /* add an application setting */
    params = ag_auth_data_get_login_parameters (data, NULL);
    fail_unless (params != NULL);

    check_variant_in_dict (params, "id", g_variant_new_string ("123"));
    check_variant_in_dict (params, "display",
                           g_variant_new_string ("mobile"));
    check_variant_in_dict (params, "service",
                           g_variant_new_string (TEST_SERVICE_VALUE));
    g_variant_unref (params);

    /* Try adding some client parameters */
    g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
    g_variant_builder_add (&builder, "{sv}",
                           "display", g_variant_new_string (display));
    g_variant_builder_add (&builder, "{sv}",
                           "animal", g_variant_new_string (animal));

    variant = g_variant_builder_end (&builder);
    params = ag_auth_data_get_login_parameters (data, variant);
    check_variant_in_dict (params, "id", g_variant_new_string ("123"));
    check_variant_in_dict (params, "display",
                           g_variant_new_string (display));
    check_variant_in_dict (params, "service",
                           g_variant_new_string (TEST_SERVICE_VALUE));
    check_variant_in_dict (params, "animal",
                           g_variant_new_string (animal));
    g_variant_unref (params);

    ag_auth_data_unref (data);
    g_object_unref (account_service);

    end_test ();
}
END_TEST
START_TEST(test_auth_data_insert_parameters)
{
    GList *account_services;
    AgAccountService *account_service;
    AgAuthData *data;
    GHashTable *params;
    GValue v_display = { 0, };
    GValue v_animal = { 0, };
    const gchar *display = "desktop";
    const gchar *animal = "cat";

    manager = ag_manager_new_for_service_type ("e-mail");

    /* reload the account and get the AccountService */
    account_services = ag_manager_get_account_services (manager);
    fail_unless (g_list_length(account_services) == 1);
    account_service = AG_ACCOUNT_SERVICE (account_services->data);
    fail_unless (AG_IS_ACCOUNT_SERVICE (account_service));
    g_list_free (account_services);

    /* get the auth data */
    data = ag_account_service_get_auth_data (account_service);
    fail_unless (data != NULL);

    /* add an application setting */
    params = g_hash_table_new (g_str_hash, g_str_equal);

    g_value_init (&v_display, G_TYPE_STRING);
    g_value_set_static_string (&v_display, display);
    g_hash_table_insert (params, "display", &v_display);

    g_value_init (&v_animal, G_TYPE_STRING);
    g_value_set_static_string (&v_animal, animal);
    g_hash_table_insert (params, "animal", &v_animal);

    ag_auth_data_insert_parameters (data, params);
    g_hash_table_unref (params);

    /* now check that the values are what we expect them to be */
    params = ag_auth_data_get_parameters (data);
    fail_unless (params != NULL);

    check_string_in_params (params, "animal", animal);
    check_string_in_params (params, "display", display);
    /* check the the other values are retained */
    check_string_in_params (params, "service", TEST_SERVICE_VALUE);

    ag_auth_data_unref (data);
    g_object_unref (account_service);

    end_test ();
}
END_TEST

START_TEST(test_application)
{
    AgService *email_service, *sharing_service;
    AgApplication *application;
    GDesktopAppInfo *app_info;
    GList *list;

    manager = ag_manager_new ();

    application = ag_manager_get_application (manager, "Mailer");
    fail_unless (application != NULL);
    ag_application_unref (application);

    email_service = ag_manager_get_service (manager, "MyService");
    fail_unless (email_service != NULL);

    sharing_service = ag_manager_get_service (manager, "OtherService");
    fail_unless (email_service != NULL);

    list = ag_manager_list_applications_by_service (manager, email_service);
    fail_unless (list != NULL);
    fail_unless (g_list_length(list) == 1,
                 "Got %d applications, expecting 1", g_list_length(list));

    application = list->data;
    fail_unless (g_strcmp0 (ag_application_get_name (application),
                            "Mailer") == 0);
    fail_unless (g_strcmp0 (ag_application_get_i18n_domain (application),
                            "mailer-catalog") == 0);
    fail_unless (g_strcmp0 (ag_application_get_description (application),
                            "Mailer application") == 0);
    ck_assert (ag_application_supports_service (application, email_service));
    ck_assert (!ag_application_supports_service (application, sharing_service));
    fail_unless (g_strcmp0 (ag_application_get_service_usage (application,
                                                              email_service),
                            "Mailer can retrieve your e-mails") == 0);
    app_info = ag_application_get_desktop_app_info (application);
    fail_unless (G_IS_DESKTOP_APP_INFO (app_info));
    fail_unless (g_strcmp0 (g_app_info_get_display_name (G_APP_INFO (app_info)),
                            "Easy Mailer") == 0);
    g_object_unref (app_info);

    ag_application_unref (application);
    g_list_free (list);

    list = ag_manager_list_applications_by_service (manager, sharing_service);
    fail_unless (list != NULL);
    fail_unless (g_list_length(list) == 1,
                 "Got %d applications, expecting 1", g_list_length(list));

    application = list->data;
    fail_unless (g_strcmp0 (ag_application_get_name (application),
                            "Gallery") == 0);
    fail_unless (g_strcmp0 (ag_application_get_description (application),
                            "Image gallery") == 0);
    ck_assert (!ag_application_supports_service (application, email_service));
    ck_assert (ag_application_supports_service (application, sharing_service));
    fail_unless (g_strcmp0 (ag_application_get_service_usage (application,
                                                              sharing_service),
                            "Publish images on OtherService") == 0);
    ag_application_unref (application);
    g_list_free (list);

    ag_service_unref (email_service);
    ag_service_unref (sharing_service);

    end_test ();
}
END_TEST

START_TEST(test_application_supported_services)
{
    AgService *email_service;
    AgApplication *application;
    GList *list;

    manager = ag_manager_new ();

    application = ag_manager_get_application (manager, "Mailer");
    ck_assert (application != NULL);

    list = ag_manager_list_services_by_application (manager, application);
    ck_assert (list != NULL);
    ck_assert_int_eq (g_list_length (list), 1);

    email_service = list->data;
    ck_assert (email_service != NULL);
    ck_assert_str_eq (ag_service_get_name (email_service), "MyService");

    ag_application_unref (application);
    ag_service_list_free (list);

    application = ag_manager_get_application (manager, "Gallery");
    ck_assert (application != NULL);

    list = ag_manager_list_services_by_application (manager, application);
    ck_assert (list != NULL);
    ck_assert_int_eq (g_list_length (list), 1);

    email_service = list->data;
    ck_assert_str_eq (ag_service_get_name (email_service), "OtherService");

    ag_application_unref (application);
    ag_service_list_free (list);

    end_test ();
}
END_TEST

START_TEST(test_service)
{
    GValue value = { 0 };
    AgService *service2;
    GList *tag_list, *list;
    AgAccountId account_id;
    const gchar *provider_name, *service_type, *service_name,
                *service_description, *icon_name;
    const gchar *description = "This is really a beautiful account";
    const gchar *username = "me@myhome.com";
    const gint interval = 30;
    const gboolean check_automatically = TRUE;
    const gchar *display_name = "My test account";
    const gchar **string_list;
    const gchar *capabilities[] = {
        "chat",
        "file",
        "smileys",
        NULL
    };
    const gchar *animals[] = {
        "cat",
        "dog",
        "monkey",
        "snake",
        NULL
    };

    AgSettingSource source;
    GError *error = NULL;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    fail_unless (ag_account_get_selected_service (account) == NULL);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, description);
    ag_account_set_value (account, "description", &value);
    g_value_unset (&value);

    service = ag_manager_get_service (manager, "MyUnexistingService");
    fail_unless (service == NULL);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    service_type = ag_service_get_service_type (service);
    fail_unless (g_strcmp0 (service_type, "e-mail") == 0,
                 "Wrong service type: %s", service_type);

    service_name = ag_service_get_name (service);
    fail_unless (g_strcmp0 (service_name, "MyService") == 0,
                 "Wrong service name: %s", service_name);

    service_name = ag_service_get_display_name (service);
    fail_unless (g_strcmp0 (service_name, "My Service") == 0,
                 "Wrong service display name: %s", service_name);

    service_description = ag_service_get_description (service);
    fail_unless (g_strcmp0 (service_description,
			    "My Service Description") == 0,
                 "Wrong service description: %s", service_description);

    icon_name = ag_service_get_icon_name (service);
    fail_unless (g_strcmp0 (icon_name, "general_myservice") == 0,
                 "Wrong service icon name: %s", icon_name);

    ck_assert_str_eq (ag_service_get_i18n_domain (service), "myservice_i18n");

    tag_list = ag_service_get_tags (service);
    fail_unless (tag_list != NULL);
    for (list = tag_list; list != NULL; list = list->next)
    {
        const gchar *tag = list->data;
        g_debug(" Service tag: %s", tag);
        fail_unless (g_strcmp0 (tag, "e-mail") == 0 ||
                     g_strcmp0 (tag, "messaging") == 0,
                     "Wrong service tag: %s", tag);
    }
    g_list_free (tag_list);
    fail_unless (ag_service_has_tag (service, "e-mail"),
                 "Missing service tag");

    ag_account_set_enabled (account, FALSE);
    ag_account_set_display_name (account, display_name);

    ag_account_select_service (account, service);
    ck_assert_ptr_eq (ag_account_get_selected_service (account), service);

    /* test getting default setting from template */
    g_value_init (&value, G_TYPE_INT);
    source = ag_account_get_value (account, "parameters/port", &value);
    fail_unless (source == AG_SETTING_SOURCE_PROFILE,
                 "Cannot get port from profile");
    fail_unless (g_value_get_int (&value) == 5223,
                 "Wrong port number: %d", g_value_get_int (&value));
    g_value_unset (&value);

    /* test getting a string list */
    g_value_init (&value, G_TYPE_STRV);
    source = ag_account_get_value (account, "parameters/capabilities", &value);
    fail_unless (source == AG_SETTING_SOURCE_PROFILE,
                 "Cannot get capabilities from profile");
    string_list = g_value_get_boxed (&value);
    fail_unless (test_strv_equal (capabilities, string_list),
                 "Wrong capabilties");
    g_value_unset (&value);

    /* enable the service */
    ag_account_set_enabled (account, TRUE);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, username);
    ag_account_set_value (account, "username", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, check_automatically);
    ag_account_set_value (account, "check_automatically", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, interval);
    ag_account_set_value (account, "interval", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRV);
    g_value_set_boxed (&value, animals);
    ag_account_set_value (account, "pets", &value);
    g_value_unset (&value);

    service2 = ag_manager_get_service (manager, "OtherService");

    tag_list = ag_service_get_tags (service2);
    fail_unless (tag_list != NULL);
    for (list = tag_list; list != NULL; list = list->next)
    {
        const gchar *tag = list->data;
        g_debug(" Service tag: %s", tag);
        fail_unless (g_strcmp0 (tag, "video") == 0 ||
                     g_strcmp0 (tag, "sharing") == 0,
                     "Wrong service tag: %s", tag);
    }
    g_list_free (tag_list);
    fail_unless (ag_service_has_tag (service2, "sharing"),
                 "Missing service tag");
    
    ag_account_select_service (account, service2);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "Wednesday");
    ag_account_set_value (account, "day", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, TRUE);
    ag_account_set_value (account, "ForReal", &value);
    g_value_unset (&value);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    g_debug ("Account id: %d", account->id);
    account_id = account->id;

    ag_service_unref (service2);
    g_object_unref (account);
    g_object_unref (manager);

    manager = ag_manager_new ();

    /* first, try to load an unexisting account */
    account = ag_manager_load_account (manager, account_id + 2, &error);
    fail_unless (account == NULL, "Loading a non-existing account!");
    fail_unless (error != NULL, "Error is NULL");
    g_clear_error (&error);

    account = ag_manager_load_account (manager, account_id, &error);
    fail_unless (AG_IS_ACCOUNT (account),
                 "Couldn't load account %u", account_id);
    fail_unless (error == NULL, "Error is not NULL");

    provider_name = ag_account_get_provider_name (account);
    fail_unless (g_strcmp0 (provider_name, PROVIDER) == 0,
                 "Got provider %s, expecting %s", provider_name, PROVIDER);

    /* check that the values are retained */
    fail_unless (ag_account_get_enabled (account) == FALSE,
                 "Account enabled!");
    fail_unless (g_strcmp0 (ag_account_get_display_name (account),
                            display_name) == 0,
                 "Display name not retained!");

    g_value_init (&value, G_TYPE_STRING);
    source = ag_account_get_value (account, "description", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    fail_unless (g_strcmp0(g_value_get_string (&value), description) == 0,
                 "Wrong value");
    g_value_unset (&value);

    ag_account_select_service (account, service);

    /* we enabled the service before: check that it's still enabled */
    fail_unless (ag_account_get_enabled (account) == TRUE,
                 "Account service not enabled!");

    g_value_init (&value, G_TYPE_STRING);
    source = ag_account_get_value (account, "username", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    fail_unless (g_strcmp0(g_value_get_string (&value), username) == 0,
                 "Wrong value");
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    source = ag_account_get_value (account, "check_automatically", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    fail_unless (g_value_get_boolean (&value) == check_automatically,
                 "Wrong value");
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_INT);
    source = ag_account_get_value (account, "interval", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    fail_unless (g_value_get_int (&value) == interval, "Wrong value");
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRV);
    source = ag_account_get_value (account, "pets", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    string_list = g_value_get_boxed (&value);
    fail_unless (test_strv_equal (string_list, animals),
                 "Wrong animals :-)");
    g_value_unset (&value);

    /* check also value conversion */
    g_value_init (&value, G_TYPE_CHAR);
    source = ag_account_get_value (account, "interval", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
#if GLIB_CHECK_VERSION(2,32,0)
    fail_unless (g_value_get_schar (&value) == interval, "Wrong value");
#else
    fail_unless (g_value_get_char (&value) == interval, "Wrong value");
#endif
    g_value_unset (&value);

    /* change a value */
    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "Friday");
    ag_account_set_value (account, "day", &value);
    g_value_unset (&value);

    /* change global enabledness */
    ag_account_select_service (account, NULL);
    ag_account_set_enabled (account, TRUE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (ag_account_get_enabled (account) == TRUE,
                 "Account still disabled!");
    end_test ();
}
END_TEST

static gboolean
service_in_list(GList *list, const gchar *service_name)
{
    while (list != NULL) {
        AgService *service = list->data;

        if (g_strcmp0(ag_service_get_name (service), service_name) == 0)
            return TRUE;
        list = list->next;
    }

    return FALSE;
}

START_TEST(test_account_services)
{
    GList *services;

    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, "maemo");
    fail_unless (AG_IS_ACCOUNT (account),
                 "Failed to create the AgAccount.");

    services = ag_account_list_services (account);
    fail_unless (g_list_length (services) == 2);

    /* These should be MyService and Myservice2; the order is random */
    fail_unless (service_in_list(services, "MyService"));
    fail_unless (service_in_list(services, "MyService2"));

    ag_service_list_free (services);

    /* check that MyService is returned as a service supporting e-mail for
     * this account */
    services = ag_account_list_services_by_type (account, "e-mail");
    fail_unless (g_list_length (services) == 1);

    fail_unless (service_in_list(services, "MyService"));

    ag_service_list_free (services);

    /* check that the account supports the "e-mail" type (it's the type of
     * MyService */
    fail_unless (ag_account_supports_service (account, "e-mail") == TRUE);
    /* and doesn't support "sharing" */
    fail_unless (ag_account_supports_service (account, "sharing") == FALSE);

    end_test ();
}
END_TEST

static void
set_boolean_variable (gboolean *flag)
{
    *flag = TRUE;
}

START_TEST(test_signals)
{
    const gchar *display_name = "My lovely account";
    gboolean enabled_called = FALSE;
    gboolean display_name_called = FALSE;
    gboolean notify_enabled_called = FALSE;
    gboolean notify_display_name_called = FALSE;
    gboolean enabled = FALSE;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    g_signal_connect_swapped (account, "enabled",
                              G_CALLBACK (set_boolean_variable),
                              &enabled_called);
    g_signal_connect_swapped (account, "display-name-changed",
                              G_CALLBACK (set_boolean_variable),
                              &display_name_called);
    g_signal_connect_swapped (account, "notify::enabled",
                              G_CALLBACK (set_boolean_variable),
                              &notify_enabled_called);
    g_signal_connect_swapped (account, "notify::display-name",
                              G_CALLBACK (set_boolean_variable),
                              &notify_display_name_called);

    ag_account_set_enabled (account, TRUE);
    ag_account_set_display_name (account, display_name);


    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");

    fail_unless (enabled_called, "Enabled signal not emitted!");
    fail_unless (display_name_called, "DisplayName signal not emitted!");
    fail_unless (notify_enabled_called, "Enabled property not notified!");
    g_object_get (account, "enabled", &enabled, NULL);
    fail_unless (enabled == TRUE, "Account not enabled!");
    fail_unless (notify_display_name_called,
                 "DisplayName property not notified!");

    end_test ();
}
END_TEST

START_TEST(test_signals_other_manager)
{
    AgAccountId account_id;
    AgManager *manager2;
    AgAccount *account2;
    EnabledCbData ecd;
    GError *error = NULL;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    ag_account_set_enabled (account, FALSE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;
    account_id = account->id;

    manager2 = ag_manager_new ();

    /* reload the account and see that it's enabled */
    account2 = ag_manager_load_account (manager2, account_id, &error);
    fail_unless (AG_IS_ACCOUNT (account2),
                 "Couldn't load account %u", account_id);
    fail_unless (error == NULL, "Error is not NULL");

    memset(&ecd, 0, sizeof(ecd));
    g_signal_connect (account2, "enabled",
                      G_CALLBACK (on_enabled),
                      &ecd);

    /* enable the service */
    ag_account_select_service (account, service);
    ag_account_set_enabled (account, TRUE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    main_loop = g_main_loop_new (NULL, FALSE);
    g_timeout_add_seconds (2, quit_loop, main_loop);
    g_signal_connect_swapped (account2, "enabled",
                              G_CALLBACK (quit_loop), main_loop);
    g_main_loop_run (main_loop);
    g_main_loop_unref (main_loop);
    main_loop = NULL;

    fail_unless (ecd.called);
    fail_unless (g_strcmp0 (ecd.service, "MyService") == 0);
    g_free (ecd.service);

    ag_service_unref (service);
    service = NULL;
    g_object_unref (account2);
    g_object_unref (manager2);

    end_test ();
}
END_TEST

START_TEST(test_list)
{
    const gchar *display_name = "New account";
    const gchar *provider_name = "other_provider";
    const gchar *my_service_name = "MyService";
    const gchar *service_name = "OtherService";
    const gchar *service_type;
    GList *list;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, provider_name);

    ag_account_set_enabled (account, TRUE);
    ag_account_set_display_name (account, display_name);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (account->id != 0, "Account ID is still 0!");

    /* Test the account readable properties */
    {
        AgAccountId id_prop = 0;
        AgManager *manager_prop = NULL;
        gchar *provider_prop = NULL;

        g_object_get (account,
                      "id", &id_prop,
                      "manager", &manager_prop,
                      "provider", &provider_prop,
                      NULL);
        fail_unless (id_prop == account->id);
        fail_unless (manager_prop == manager);
        fail_unless (g_strcmp0 (provider_prop, provider_name) == 0);
        g_object_unref (manager);
        g_free (provider_prop);
    }

    list = ag_manager_list (manager);
    fail_unless (list != NULL, "Empty list");
    fail_unless (g_list_find (list, GUINT_TO_POINTER (account->id)) != NULL,
                 "Created account not found in list");
    g_list_free (list);

    /* check that it doesn't support the service type provided by MyService */
    service = ag_manager_get_service (manager, my_service_name);
    service_type = ag_service_get_service_type (service);
    fail_unless (service_type != NULL,
                 "Service %s has no type", my_service_name);

    list = ag_manager_list_by_service_type (manager, service_type);
    fail_unless (g_list_find (list, GUINT_TO_POINTER (account->id)) == NULL,
                 "New account supports %s service type, but shouldn't",
                 service_type);
    g_list_free (list);
    ag_service_unref(service);

    service = ag_manager_get_service (manager, service_name);
    service_type = ag_service_get_service_type (service);
    fail_unless (service_type != NULL,
                 "Service %s has no type", service_name);

    list = ag_manager_list_by_service_type (manager, service_type);
    fail_unless (g_list_find (list, GUINT_TO_POINTER (account->id)) != NULL,
                 "New account doesn't supports %s service type, but should",
                 service_type);
    g_list_free (list);

    end_test ();
}
END_TEST

START_TEST(test_settings_iter_gvalue)
{
    const gchar *keys[] = {
        "param/address",
        "weight",
        "param/city",
        "age",
        "param/country",
        NULL,
    };
    const gchar *values[] = {
        "Helsinginkatu",
        "110",
        "Helsinki",
        "90",
        "Suomi",
        NULL,
    };
    const gchar *service_name = "OtherService";
    GValue value = { 0 };
    AgAccountSettingIter iter;
    const gchar *key;
    const GValue *val;
    gint i, n_values, n_read;
    const gint new_port_value = 432412;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    ag_account_set_enabled (account, TRUE);

    for (i = 0; keys[i] != NULL; i++)
    {
        g_value_init (&value, G_TYPE_STRING);
        g_value_set_static_string (&value, values[i]);
        ag_account_set_value (account, keys[i], &value);
        g_value_unset (&value);
    }
    n_values = i;

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (account->id != 0, "Account ID is still 0!");

    /* iterate the settings */
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, NULL);
    while (ag_account_settings_iter_next (&iter, &key, &val))
    {
        gboolean found = FALSE;
        for (i = 0; keys[i] != NULL; i++)
        {
            if (g_strcmp0 (key, keys[i]) == 0)
            {
                const gchar *text;
                found = TRUE;
                text = g_value_get_string (val);
                fail_unless (g_strcmp0 (values[i], text) == 0,
                             "Got value %s for key %s, expecting %s",
                             text, key, values[i]);
                break;
            }
        }

        fail_unless (found, "Unknown setting %s", key);

        n_read++;
    }

    fail_unless (n_read == n_values,
                 "Not all settings were retrieved (%d out of %d)",
                 n_read, n_values);

    /* iterate settings with prefix */
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, "param/");
    while (ag_account_settings_iter_next (&iter, &key, &val))
    {
        gboolean found = FALSE;
        gchar *full_key;
        fail_unless (strncmp (key, "param/", 6) != 0,
                     "Got key with unstripped prefix (%s)", key);

        full_key = g_strconcat ("param/", key, NULL);
        for (i = 0; keys[i] != NULL; i++)
        {
            if (g_strcmp0 (full_key, keys[i]) == 0)
            {
                const gchar *text;
                found = TRUE;
                text = g_value_get_string (val);
                fail_unless (g_strcmp0 (values[i], text) == 0,
                             "Got value %s for key %s, expecting %s",
                             text, key, values[i]);
                break;
            }
        }
        g_free (full_key);

        fail_unless (found, "Unknown setting %s", key);

        n_read++;
    }

    fail_unless (n_read == 3, "Not all settings were retrieved");

    /* iterate template default settings */
    service = ag_manager_get_service (manager, service_name);
    ag_account_select_service (account, service);
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, NULL);
    while (ag_account_settings_iter_next (&iter, &key, &val))
    {
        g_debug ("Got key %s of type %s", key, G_VALUE_TYPE_NAME (val));

        n_read++;
    }
    fail_unless (n_read == 4, "Not all settings were retrieved");

    /* Add a setting that is also on the template, to check if it will
     * override the one on the template */
    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, new_port_value);
    ag_account_set_value (account, "parameters/port", &value);
    g_value_unset (&value);

    /* Add a setting */
    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "How's life?");
    ag_account_set_value (account, "parameters/message", &value);
    g_value_unset (&value);

    /* save */
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* enumerate the parameters */
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, "parameters/");
    while (ag_account_settings_iter_next (&iter, &key, &val))
    {
        fail_unless (strncmp (key, "parameters/", 6) != 0,
                     "Got key with unstripped prefix (%s)", key);

        g_debug ("Got key %s of type %s", key, G_VALUE_TYPE_NAME (val));
        if (g_strcmp0 (key, "port") == 0)
        {
            gint port;

            port = g_value_get_int (val);
            fail_unless (port == new_port_value,
                         "Got value %d for key %s, expecting %d",
                         port, key, new_port_value);
        }

        n_read++;
    }

    fail_unless (n_read == 5, "Not all settings were retrieved");


    end_test ();
}
END_TEST

START_TEST(test_settings_iter)
{
    const gchar *keys[] = {
        "param/address",
        "weight",
        "param/city",
        "age",
        "param/country",
        NULL,
    };
    const gchar *values[] = {
        "Helsinginkatu",
        "110",
        "Helsinki",
        "90",
        "Suomi",
        NULL,
    };
    const gchar *service_name = "OtherService";
    AgAccountSettingIter iter;
    const gchar *key;
    GVariant *val;
    gint i, n_values, n_read;
    const gint new_port_value = 32412;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    ag_account_set_enabled (account, TRUE);

    for (i = 0; keys[i] != NULL; i++)
    {
        ag_account_set_variant (account, keys[i],
                                g_variant_new_string (values[i]));
    }
    n_values = i;

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (account->id != 0, "Account ID is still 0!");

    /* iterate the settings */
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, NULL);
    while (ag_account_settings_iter_get_next (&iter, &key, &val))
    {
        gboolean found = FALSE;
        for (i = 0; keys[i] != NULL; i++)
        {
            if (g_strcmp0 (key, keys[i]) == 0)
            {
                const gchar *text;
                found = TRUE;
                text = g_variant_get_string (val, NULL);
                fail_unless (g_strcmp0 (values[i], text) == 0,
                             "Got value %s for key %s, expecting %s",
                             text, key, values[i]);
                break;
            }
        }

        fail_unless (found, "Unknown setting %s", key);

        n_read++;
    }

    fail_unless (n_read == n_values,
                 "Not all settings were retrieved (%d out of %d)",
                 n_read, n_values);

    /* iterate settings with prefix */
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, "param/");
    while (ag_account_settings_iter_get_next (&iter, &key, &val))
    {
        gboolean found = FALSE;
        gchar *full_key;
        fail_unless (strncmp (key, "param/", 6) != 0,
                     "Got key with unstripped prefix (%s)", key);

        full_key = g_strconcat ("param/", key, NULL);
        for (i = 0; keys[i] != NULL; i++)
        {
            if (g_strcmp0 (full_key, keys[i]) == 0)
            {
                const gchar *text;
                found = TRUE;
                text = g_variant_get_string (val, NULL);
                fail_unless (g_strcmp0 (values[i], text) == 0,
                             "Got value %s for key %s, expecting %s",
                             text, key, values[i]);
                break;
            }
        }
        g_free (full_key);

        fail_unless (found, "Unknown setting %s", key);

        n_read++;
    }

    fail_unless (n_read == 3, "Not all settings were retrieved");

    /* iterate template default settings */
    service = ag_manager_get_service (manager, service_name);
    ag_account_select_service (account, service);
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, NULL);
    while (ag_account_settings_iter_get_next (&iter, &key, &val))
    {
        g_debug ("Got key %s of type %s",
                 key, g_variant_get_type_string (val));

        n_read++;
    }
    fail_unless (n_read == 4, "Not all settings were retrieved");

    /* Add a setting that is also on the template, to check if it will
     * override the one on the template */
    ag_account_set_variant (account, "parameters/port",
                            g_variant_new_int16 (new_port_value));

    /* Add a setting */
    ag_account_set_variant (account, "parameters/message",
                            g_variant_new_string ("How's life?"));

    /* save */
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* enumerate the parameters */
    n_read = 0;
    ag_account_settings_iter_init (account, &iter, "parameters/");
    while (ag_account_settings_iter_get_next (&iter, &key, &val))
    {
        fail_unless (strncmp (key, "parameters/", 6) != 0,
                     "Got key with unstripped prefix (%s)", key);

        g_debug ("Got key %s of type %s",
                 key, g_variant_get_type_string (val));
        if (g_strcmp0 (key, "port") == 0)
        {
            gint port;

            port = g_variant_get_int16 (val);
            fail_unless (port == new_port_value,
                         "Got value %d for key %s, expecting %d",
                         port, key, new_port_value);
        }

        n_read++;
    }

    fail_unless (n_read == 5, "Not all settings were retrieved");


    end_test ();
}
END_TEST

START_TEST(test_list_services)
{
    GList *services, *list;
    gint n_services;
    AgService *service;
    const gchar *name;

    manager = ag_manager_new ();

    /* get all services */
    services = ag_manager_list_services (manager);

    n_services = g_list_length (services);
    fail_unless (n_services == 3, "Got %d services, expecting 3", n_services);

    for (list = services; list != NULL; list = list->next)
    {
        service = list->data;

        name = ag_service_get_name (service);
        g_debug ("Service name: %s", name);
        fail_unless (g_strcmp0 (name, "MyService") == 0 ||
                     g_strcmp0 (name, "MyService2") == 0 ||
                     g_strcmp0 (name, "OtherService") == 0,
                     "Got unexpected service `%s'", name);
    }
    ag_service_list_free (services);

    /* get services by type */
    services = ag_manager_list_services_by_type (manager, "sharing");

    n_services = g_list_length (services);
    fail_unless (n_services == 1, "Got %d services, expecting 1", n_services);

    list = services;
    service = list->data;
    name = ag_service_get_name (service);
    fail_unless (g_strcmp0 (name, "OtherService") == 0,
                 "Got unexpected service `%s'", name);
    ag_service_list_free (services);

    end_test ();
}
END_TEST

START_TEST(test_list_service_types)
{
    GList *service_types, *list, *tags, *tag_list;
    gint n_service_types;
    AgServiceType *service_type;
    const gchar *name, *tag;

    manager = ag_manager_new ();

    service_types = ag_manager_list_service_types (manager);

    n_service_types = g_list_length (service_types);
    fail_unless (n_service_types == 1,
                 "Got %d service types, expecting 1",
                 n_service_types);

    for (list = service_types; list != NULL; list = list->next)
    {
        service_type = list->data;

        name = ag_service_type_get_name (service_type);
        g_debug ("Service type name: %s", name);
        fail_unless (g_strcmp0 (name, "e-mail") == 0,
                     "Got unexpected service type `%s'", name);
        
        tags = ag_service_type_get_tags (service_type);
        for (tag_list = tags; tag_list != NULL; tag_list = tag_list->next)
        {
            tag = (gchar *) tag_list->data;
            g_debug (" Service type tag: %s", tag);
            fail_unless ((g_strcmp0 (tag, "e-mail") == 0 ||
                          g_strcmp0 (tag, "messaging") == 0),
                         "Got unexpected service type tag `%s'", tag);
        }
        g_list_free (tags);
        fail_unless (ag_service_type_has_tag (service_type, "messaging"),
                     "Missing service type tag");
    }
    ag_service_type_list_free (service_types);

    end_test ();
}
END_TEST


START_TEST(test_delete)
{
    AgAccountId id;
    gboolean enabled_called, deleted_called;

    manager = ag_manager_new ();

    /* create an account */
    account = ag_manager_create_account (manager, PROVIDER);
    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (account->id != 0, "Account ID is still 0!");
    id = account->id;

    /* monitor the account status */
    g_signal_connect_swapped (account, "enabled",
                              G_CALLBACK (set_boolean_variable),
                              &enabled_called);
    g_signal_connect_swapped (account, "deleted",
                              G_CALLBACK (set_boolean_variable),
                              &deleted_called);
    enabled_called = deleted_called = FALSE;

    /* delete it */
    ag_account_delete (account);

    /* until ag_account_store() is called, the signals should not have been
     * emitted */
    fail_unless (enabled_called == FALSE, "Accound disabled too early!");
    fail_unless (deleted_called == FALSE, "Accound deleted too early!");

    /* really delete the account */
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* check that the signals are emitted */
    fail_unless (enabled_called, "Accound enabled signal not emitted");
    fail_unless (deleted_called, "Accound deleted signal not emitted");

    g_object_unref (account);

    /* load the account again: this must fail */
    account = ag_manager_get_account (manager, id);
    fail_unless (account == NULL, "The account still exists");

    end_test ();
}
END_TEST

static void
key_changed_cb (AgAccount *account, const gchar *key, gboolean *invoked)
{
    fail_unless (invoked != NULL);
    fail_unless (*invoked == FALSE, "Callback invoked twice!");

    fail_unless (key != NULL);
    fail_unless (g_strcmp0 (key, "parameters/server") == 0 ||
                 g_strcmp0 (key, "parameters/port") == 0,
                 "Callback invoked for wrong key %s", key);
    *invoked = TRUE;
}

static void
dir_changed_cb (AgAccount *account, const gchar *key, gboolean *invoked)
{
    fail_unless (invoked != NULL);
    fail_unless (*invoked == FALSE, "Callback invoked twice!");

    fail_unless (key != NULL);
    fail_unless (g_strcmp0 (key, "parameters/") == 0,
                 "Callback invoked for wrong dir %s", key);
    *invoked = TRUE;
}

START_TEST(test_watches)
{
    gboolean server_changed = FALSE;
    gboolean port_changed = FALSE;
    gboolean dir_changed = FALSE;
    AgAccountWatch w_server, w_port, w_dir;
    GValue value = { 0 };

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    ag_account_select_service (account, service);

    /* install some watches */
    w_server = ag_account_watch_key (account, "parameters/server",
                                     (AgAccountNotifyCb)key_changed_cb,
                                     &server_changed);
    fail_unless (w_server != NULL);

    w_port = ag_account_watch_key (account, "parameters/port",
                                   (AgAccountNotifyCb)key_changed_cb,
                                   &port_changed);
    fail_unless (w_port != NULL);

    w_dir = ag_account_watch_dir (account, "parameters/",
                                  (AgAccountNotifyCb)dir_changed_cb,
                                  &dir_changed);
    fail_unless (w_dir != NULL);

    /* change the port */
    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, 22);
    ag_account_set_value (account, "parameters/port", &value);
    g_value_unset (&value);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* if we didn't change the server, make sure the callback is not
     * invoked */
    fail_unless (server_changed == FALSE, "Callback for 'server' invoked");

    /* make sure the port callback was called */
    fail_unless (port_changed == TRUE, "Callback for 'port' not invoked");

    /* make sure the dir callback was called */
    fail_unless (dir_changed == TRUE, "Callback for 'parameters/' not invoked");


    /* remove the watch for the port */
    ag_account_remove_watch (account, w_port);

    /* change two settings */
    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, 25);
    ag_account_set_value (account, "parameters/port", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "warez.maemo.org");
    ag_account_set_value (account, "parameters/server", &value);
    g_value_unset (&value);

    server_changed = FALSE;
    port_changed = FALSE;
    dir_changed = FALSE;
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* make sure the callback for the server is invoked */
    fail_unless (server_changed == TRUE, "Callback for 'server' not invoked");

    /* make sure the port callback was not called (we removed the watch) */
    fail_unless (port_changed == FALSE, "Callback for 'port' invoked");

    /* make sure the dir callback was called */
    fail_unless (dir_changed == TRUE, "Callback for 'parameters/' not invoked");

    end_test ();
}
END_TEST

START_TEST(test_no_dbus)
{
    gchar *bus_address;
    gboolean use_dbus = TRUE;

    /* Unset the DBUS_SESSION_BUS_ADDRESS variable, so that the connection
     * to D-Bus will fail.
     */
    bus_address = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
    g_unsetenv("DBUS_SESSION_BUS_ADDRESS");

    manager = g_initable_new (AG_TYPE_MANAGER, NULL, NULL,
                              "use-dbus", FALSE,
                              NULL);
    ck_assert_msg (manager != NULL, "AgManager creation failed even "
                   "with use-dbus set to FALSE");

    g_object_get (manager, "use-dbus", &use_dbus, NULL);
    ck_assert (!use_dbus);

    /* Test creating an account */
    account = ag_manager_create_account (manager, PROVIDER);
    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    ck_assert_msg (data_stored, "Callback not invoked immediately");
    ck_assert_msg (account->id != 0, "Account ID is still 0!");

    /* Restore the initial value */
    g_setenv ("DBUS_SESSION_BUS_ADDRESS", bus_address, TRUE);

    g_free (bus_address);

    end_test ();
}
END_TEST

static void
on_account_created (AgManager *manager, AgAccountId account_id,
                    AgAccountId *id)
{
    g_debug ("%s called (%u)", G_STRFUNC, account_id);

    *id = account_id;
    g_main_loop_quit (main_loop);
}

static void
on_account_deleted (AgManager *manager, AgAccountId account_id,
                    AgAccountId *id)
{
    g_debug ("%s called (%u)", G_STRFUNC, account_id);

    fail_unless (account_id == *id, "Deletion of unexpected account");
    *id = 0;
    g_main_loop_quit (main_loop);
}

static void
changed_cb (AgAccount *account, const gchar *key, gboolean *invoked)
{
    fail_unless (invoked != NULL);
    fail_unless (*invoked == FALSE, "Callback invoked twice!");

    fail_unless (key != NULL);
    *invoked = TRUE;
    if (idle_finish == 0)
        idle_finish = g_idle_add ((GSourceFunc)g_main_loop_quit, main_loop);
}

static gboolean
concurrency_test_failed (gpointer userdata)
{
    g_debug ("Timeout");
    source_id = 0;
    g_main_loop_quit (main_loop);
    return FALSE;
}

START_TEST(test_concurrency)
{
    AgAccountId account_id;
    const gchar *provider_name, *display_name;
    gchar command[512];
    GValue value = { 0 };
    gboolean character_changed, string_changed, boolean_changed;
    gboolean unsigned_changed;
    AgSettingSource source;
    EnabledCbData ecd;
    gint ret;
    const gchar **string_list;
    const gchar *numbers[] = {
        "one",
        "two",
        "three",
        NULL
    };

    manager = ag_manager_new ();

    g_signal_connect (manager, "account-created",
                      G_CALLBACK (on_account_created), &account_id);

    account_id = 0;
    ret = system ("test-process create myprovider MyAccountName");
    fail_unless (ret != -1);

    main_loop = g_main_loop_new (NULL, FALSE);
    source_id = g_timeout_add_seconds (2, concurrency_test_failed, NULL);
    g_debug ("Running loop");
    g_main_loop_run (main_loop);

    fail_unless (source_id != 0, "Timeout happened");
    g_source_remove (source_id);

    fail_unless (account_id != 0, "Account ID still 0");

    account = ag_manager_get_account (manager, account_id);
    fail_unless (AG_IS_ACCOUNT (account), "Got invalid account");

    provider_name = ag_account_get_provider_name (account);
    fail_unless (g_strcmp0 (provider_name, "myprovider") == 0,
                 "Wrong provider name '%s'", provider_name);

    display_name = ag_account_get_display_name (account);
    fail_unless (g_strcmp0 (display_name, "MyAccountName") == 0,
                 "Wrong display name '%s'", display_name);

    {
        gchar *allocated_display_name = NULL;
        g_object_get (account,
                      "display-name", &allocated_display_name,
                      NULL);
        fail_unless (g_strcmp0 (allocated_display_name, "MyAccountName") == 0,
                     "Wrong display name '%s'", allocated_display_name);
        g_free (allocated_display_name);
    }

    /* check deletion */
    g_signal_connect (manager, "account-deleted",
                      G_CALLBACK (on_account_deleted), &account_id);
    sprintf (command, "test-process delete %d", account_id);
    ret = system (command);
    fail_unless (ret != -1);

    source_id = g_timeout_add_seconds (2, concurrency_test_failed, NULL);
    g_main_loop_run (main_loop);
    fail_unless (source_id != 0, "Timeout happened");
    g_source_remove (source_id);
    g_object_unref (account);

    fail_unless (account_id == 0, "Account still alive");

    /* check a more complex creation */
    ret = system ("test-process create2 myprovider MyAccountName");
    fail_unless (ret != -1);

    source_id = g_timeout_add_seconds (2, concurrency_test_failed, NULL);
    g_main_loop_run (main_loop);
    fail_unless (source_id != 0, "Timeout happened");
    g_source_remove (source_id);

    fail_unless (account_id != 0, "Account ID still 0");

    account = ag_manager_get_account (manager, account_id);
    fail_unless (AG_IS_ACCOUNT (account), "Got invalid account");

    fail_unless (ag_account_get_enabled (account) == TRUE);

    g_value_init (&value, G_TYPE_INT);
    ag_account_get_value (account, "integer", &value);
    fail_unless (g_value_get_int (&value) == -12345);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRING);
    ag_account_get_value (account, "string", &value);
    fail_unless (g_strcmp0 (g_value_get_string (&value), "a string") == 0);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRV);
    source = ag_account_get_value (account, "numbers", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    string_list = g_value_get_boxed (&value);
    fail_unless (test_strv_equal (string_list, numbers),
                 "Wrong numbers");
    g_value_unset (&value);

    /* we expect more keys in MyService */
    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL, "Cannot get service");

    ag_account_select_service (account, service);

    g_value_init (&value, G_TYPE_UINT);
    ag_account_get_value (account, "unsigned", &value);
    fail_unless (g_value_get_uint (&value) == 54321);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_CHAR);
    ag_account_get_value (account, "character", &value);
#if GLIB_CHECK_VERSION(2,32,0)
    fail_unless (g_value_get_schar (&value) == 'z');
#else
    fail_unless (g_value_get_char (&value) == 'z');
#endif
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    ag_account_get_value (account, "boolean", &value);
    fail_unless (g_value_get_boolean (&value) == TRUE);
    g_value_unset (&value);

    fail_unless (ag_account_get_enabled (account) == FALSE);

    /* watch some key changes/deletions */
    ag_account_watch_key (account, "character",
                          (AgAccountNotifyCb)changed_cb,
                          &character_changed);

    ag_account_watch_key (account, "boolean",
                          (AgAccountNotifyCb)changed_cb,
                          &boolean_changed);

    ag_account_watch_key (account, "unsigned",
                          (AgAccountNotifyCb)changed_cb,
                          &unsigned_changed);

    ag_account_select_service (account, NULL);
    ag_account_watch_key (account, "string",
                          (AgAccountNotifyCb)changed_cb,
                          &string_changed);
    /* watch account enabledness */
    g_signal_connect (account, "enabled",
                      G_CALLBACK (on_enabled), &ecd);

    character_changed = boolean_changed = string_changed =
        unsigned_changed = FALSE;
    memset (&ecd, 0, sizeof (ecd));

    /* make changes remotely */
    sprintf (command, "test-process change %d", account_id);
    ret = system (command);
    fail_unless (ret != -1);

    source_id = g_timeout_add_seconds (2, concurrency_test_failed, NULL);
    g_main_loop_run (main_loop);
    fail_unless (source_id != 0, "Timeout happened");
    g_source_remove (source_id);

    fail_unless (character_changed == TRUE);
    fail_unless (boolean_changed == TRUE);
    fail_unless (string_changed == TRUE);
    fail_unless (unsigned_changed == FALSE);

    g_value_init (&value, G_TYPE_STRING);
    ag_account_get_value (account, "string", &value);
    fail_unless (g_strcmp0 (g_value_get_string (&value),
                            "another string") == 0);
    g_value_unset (&value);

    ag_account_select_service (account, service);

    g_value_init (&value, G_TYPE_CHAR);
    source = ag_account_get_value (account, "character", &value);
    fail_unless (source == AG_SETTING_SOURCE_NONE);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    ag_account_get_value (account, "boolean", &value);
    fail_unless (g_value_get_boolean (&value) == FALSE);
    g_value_unset (&value);

    fail_unless (ag_account_get_enabled (account) == TRUE);

    /* verify that the signal has been emitted correctly */
    fail_unless (ecd.called == TRUE);
    fail_unless (ecd.enabled_check == TRUE);
    fail_unless (g_strcmp0 (ecd.service, "MyService") == 0);
    g_free (ecd.service);

    end_test ();
}
END_TEST

START_TEST(test_service_regression)
{
    GValue value = { 0 };
    AgAccountId account_id;
    const gchar *provider_name;
    const gchar *username = "me@myhome.com";
    const gint interval = 30;
    const gboolean check_automatically = TRUE;
    const gchar *display_name = "My test account";
    AgSettingSource source;

    /* This test is to catch a bug that happened when creating a new
     * account and settings some service values before setting the display
     * name. The store operation would fail with error "column name is not
     * unique" because for some reason the same service ended up being
     * written twice into the DB.
     */

    /* delete the database: this is essential because the bug was
     * reproducible only when the service was not yet in DB */
    g_unlink (db_filename);

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);

    ag_account_select_service (account, service);

    /* enable the service */
    ag_account_set_enabled (account, TRUE);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, username);
    ag_account_set_value (account, "username", &value);
    g_value_unset (&value);

    /* Change the display name (this is on the base account) */
    ag_account_set_display_name (account, display_name);

    /* and some more service settings */
    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, check_automatically);
    ag_account_set_value (account, "check_automatically", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, interval);
    ag_account_set_value (account, "interval", &value);
    g_value_unset (&value);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    g_debug ("Account id: %d", account->id);
    account_id = account->id;

    g_object_unref (account);
    g_object_unref (manager);

    manager = ag_manager_new ();
    account = ag_manager_get_account (manager, account_id);
    fail_unless (AG_IS_ACCOUNT (account),
                 "Couldn't load account %u", account_id);

    provider_name = ag_account_get_provider_name (account);
    fail_unless (g_strcmp0 (provider_name, PROVIDER) == 0,
                 "Got provider %s, expecting %s", provider_name, PROVIDER);

    /* check that the values are retained */
    fail_unless (g_strcmp0 (ag_account_get_display_name (account),
                         display_name) == 0,
                 "Display name not retained!");

    ag_account_select_service (account, service);

    /* we enabled the service before: check that it's still enabled */
    fail_unless (ag_account_get_enabled (account) == TRUE,
                 "Account service not enabled!");

    g_value_init (&value, G_TYPE_STRING);
    source = ag_account_get_value (account, "username", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    fail_unless (g_strcmp0(g_value_get_string (&value), username) == 0,
                 "Wrong value");
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    source = ag_account_get_value (account, "check_automatically", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    fail_unless (g_value_get_boolean (&value) == check_automatically,
                 "Wrong value");
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_INT);
    source = ag_account_get_value (account, "interval", &value);
    fail_unless (source == AG_SETTING_SOURCE_ACCOUNT, "Wrong source");
    fail_unless (g_value_get_int (&value) == interval, "Wrong value");
    g_value_unset (&value);

    end_test ();
}
END_TEST

START_TEST(test_blocking)
{
    const gchar *display_name, *lock_filename;
    gchar command[512];
    gint timeout_ms, block_ms;
    GError *error = NULL;
    gboolean ok;
    struct timespec start_time, end_time;
    gint fd;
    gint ret;

    /* create an account */
    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);
    fail_unless (account != NULL);
    ag_account_set_display_name (account, "Blocked account");
    ok = ag_account_store_blocking (account, &error);
    fail_unless (ok, "Got error %s", error ? error->message : "No error set");
    fail_unless (account->id != 0);

    display_name = ag_account_get_display_name (account);
    fail_unless (g_strcmp0 (display_name, "Blocked account") == 0,
                 "Wrong display name '%s'", display_name);

    /* Now change the display name and make sure it's not updated
     * without storing :-) */
    ag_account_set_display_name (account, "Want to change");
    display_name = ag_account_get_display_name (account);
    fail_unless (g_strcmp0 (display_name, "Blocked account") == 0);


    /* Now start a process in the background to lock the DB for some time */

    /* first, create a lock file to synchronize the test */
    lock_filename = "/tmp/check_ag.lock";
    fd = open (lock_filename, O_CREAT | O_RDWR, 0666);

    timeout_ms = MAX_SQLITE_BUSY_LOOP_TIME_MS;

    sprintf (command, "test-process lock_db %d %s &",
             timeout_ms, lock_filename);
    ret = system (command);
    fail_unless (ret != -1);

    /* wait till the file is locked */
    while (lockf(fd, F_TEST, 0) == 0)
        sched_yield();

    clock_gettime (CLOCK_MONOTONIC, &start_time);
    ok = ag_account_store_blocking (account, &error);
    clock_gettime (CLOCK_MONOTONIC, &end_time);

    /* the operation completed successfully */
    fail_unless (ok, "Got error %s", error ? error->message : "No error set");

    /* make sure the display name changed */
    display_name = ag_account_get_display_name (account);
    fail_unless (g_strcmp0 (display_name, "Want to change") == 0);

    /* make sure that we have been waiting for a reasonable time */
    block_ms = time_diff(&start_time, &end_time);
    g_debug ("Been blocking for %u ms", block_ms);

    /* With WAL journaling, the DB might be locked for a much shorter time
     * than what we expect. The following line would fail in that case:
     *
     * fail_unless (block_ms > timeout_ms - 100);
     *
     * Instead, let's just check that we haven't been locking for too long.
     */
    fail_unless (block_ms < timeout_ms + 10000);

    end_test ();
}
END_TEST

START_TEST(test_cache_regression)
{
    AgAccountId account_id;
    const gchar *provider1 = "first_provider";
    const gchar *provider2 = "second_provider";
    const gchar *display_name1 = "first_displayname";
    const gchar *display_name2 = "second_displayname";
    AgAccount *deleted_account;

    /* This test is to catch a bug that happened when deleting the account
     * with the highest ID without letting the object die, and creating a
     * new account: the account manager would still return the old account!
     */

    /* delete the database */
    g_unlink (db_filename);

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, provider1);
    fail_unless (account != NULL);

    ag_account_set_display_name (account, display_name1);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    account_id = account->id;

    /* now remove the account, but don't destroy the object */
    ag_account_delete (account);
    deleted_account = account;

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* after deleting the account, we shouldn't get it anymore, even if we
     * didn't release our reference */
    account = ag_manager_get_account (manager, account_id);
    fail_unless (account == NULL);

    /* create another account */
    account = ag_manager_create_account (manager, provider2);
    fail_unless (account != NULL);

    ag_account_set_display_name (account, display_name2);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* check that the values are the correct ones */
    fail_unless (g_strcmp0 (ag_account_get_display_name (account),
                         display_name2) == 0);

    fail_unless (g_strcmp0 (ag_account_get_provider_name (account),
                         provider2) == 0);

    g_object_unref (deleted_account);

    end_test ();
}
END_TEST

START_TEST(test_serviceid_regression)
{
    AgAccount *account1, *account2;
    AgManager *manager1, *manager2;
    AgService *service1, *service2;
    const gchar *provider = "first_provider";

    /* This test is to catch a bug that happened when creating two accounts
     * having the same service, from two different instances of the
     * manager: the creation of the second account would fail.
     * Precondition: empty DB.
     */

    /* delete the database */
    g_unlink (db_filename);

    manager1 = ag_manager_new ();
    manager2 = ag_manager_new ();

    account1 = ag_manager_create_account (manager1, provider);
    fail_unless (account1 != NULL);
    account2 = ag_manager_create_account (manager2, provider);
    fail_unless (account2 != NULL);

    service1 = ag_manager_get_service (manager1, "MyService");
    fail_unless (service1 != NULL);
    service2 = ag_manager_get_service (manager2, "MyService");
    fail_unless (service2 != NULL);

    ag_account_select_service (account1, service1);
    ag_account_set_enabled (account1, TRUE);
    ag_account_select_service (account2, service2);
    ag_account_set_enabled (account2, FALSE);

    ag_account_store (account1, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    ag_account_store (account2, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (account1->id != 0);
    fail_unless (account2->id != 0);

    /* clear up */
    ag_service_unref (service1);
    ag_service_unref (service2);
    g_object_unref (account1);
    g_object_unref (account2);
    g_object_unref (manager1);
    g_object_unref (manager2);

    end_test ();
}
END_TEST

START_TEST(test_enabled_regression)
{
    EnabledCbData ecd;

    manager = ag_manager_new ();
    account = ag_manager_create_account (manager, PROVIDER);

    fail_unless (account != NULL);

    g_signal_connect (account, "enabled",
                      G_CALLBACK (on_enabled),
                      &ecd);

    memset (&ecd, 0, sizeof (ecd));
    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, NULL, TEST_STRING);

    fail_unless (ecd.called == TRUE);
    fail_unless (ecd.service == NULL);
    fail_unless (ecd.enabled_check == TRUE, "Settings are not updated!");

    memset (&ecd, 0, sizeof (ecd));
    ag_account_set_enabled (account, FALSE);
    ag_account_store (account, NULL, TEST_STRING);

    fail_unless (ecd.called == TRUE);
    fail_unless (ecd.service == NULL);
    fail_unless (ecd.enabled_check == TRUE, "Settings are not updated!");

    end_test ();
}
END_TEST

START_TEST(test_delete_regression)
{
    AgAccountService *account_service;
    gboolean enabled_called, deleted_called;

    manager = ag_manager_new_for_service_type ("e-mail");

    /* create an account */
    account = ag_manager_create_account (manager, PROVIDER);
    ag_account_set_enabled (account, TRUE);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);
    ag_account_select_service (account, service);
    ag_account_set_enabled (account, TRUE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (account->id != 0, "Account ID is still 0!");

    account_service = ag_account_service_new (account, service);

    /* monitor the account status */
    g_signal_connect_swapped (account_service, "enabled",
                              G_CALLBACK (set_boolean_variable),
                              &enabled_called);
    g_signal_connect_swapped (account, "deleted",
                              G_CALLBACK (set_boolean_variable),
                              &deleted_called);
    enabled_called = deleted_called = FALSE;

    /* delete it */
    ag_account_delete (account);

    /* until ag_account_store() is called, the signals should not have been
     * emitted */
    fail_unless (enabled_called == FALSE, "Accound disabled too early!");
    fail_unless (deleted_called == FALSE, "Accound deleted too early!");

    /* really delete the account */
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    /* check that the signals are emitted */
    fail_unless (enabled_called, "Accound enabled signal not emitted");
    fail_unless (deleted_called, "Accound deleted signal not emitted");

    g_object_unref (account_service);

    end_test ();
}
END_TEST

static void
on_account_created_count (AgManager *manager, AgAccountId account_id,
                          gint *counter)
{
    g_debug ("%s called (%u), counter %d", G_STRFUNC, account_id, *counter);

    (*counter)++;
}

START_TEST(test_duplicate_create_regression)
{
    gint create_signal_counter;

    manager = ag_manager_new ();

    g_signal_connect (manager, "account-created",
                      G_CALLBACK (on_account_created_count),
                      &create_signal_counter);

    /* create an account */
    account = ag_manager_create_account (manager, PROVIDER);
    ag_account_set_enabled (account, TRUE);

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);
    ag_account_select_service (account, service);
    ag_account_set_enabled (account, TRUE);
    ag_service_unref(service);

    service = ag_manager_get_service (manager, "MyService2");
    fail_unless (service != NULL);
    ag_account_select_service (account, service);
    ag_account_set_enabled (account, TRUE);

    create_signal_counter = 0;
    ag_account_store_blocking (account, NULL);

    main_loop = g_main_loop_new (NULL, FALSE);
    source_id = g_timeout_add_seconds (2, quit_loop, main_loop);
    g_debug ("Running loop");
    g_main_loop_run (main_loop);

    fail_unless(create_signal_counter == 1,
                "account-created emitted %d times!", create_signal_counter);

    end_test ();
}
END_TEST

START_TEST(test_manager_new_for_service_type)
{
    AgAccount *account1, *account2;
    AgService *service1, *service2;
    const gchar *provider = "first_provider";
    GList *list;

    /* delete the database */
    g_unlink (db_filename);

    manager = ag_manager_new_for_service_type ("e-mail");
    fail_unless (g_strcmp0 (ag_manager_get_service_type (manager),
                         "e-mail") == 0);

    account1 = ag_manager_create_account (manager, provider);
    fail_unless (account1 != NULL);
    account2 = ag_manager_create_account (manager, provider);
    fail_unless (account2 != NULL);

    service1 = ag_manager_get_service (manager, "MyService");
    fail_unless (service1 != NULL);
    service2 = ag_manager_get_service (manager, "OtherService");
    fail_unless (service2 != NULL);

    ag_account_set_enabled (account1, TRUE);
    ag_account_select_service (account1, service1);
    ag_account_set_enabled (account1, TRUE);
    ag_account_set_enabled (account2, TRUE);
    ag_account_select_service (account2, service2);
    ag_account_set_enabled (account2, FALSE);

    ag_account_store (account1, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;
    ag_account_store (account2, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    fail_unless (account1->id != 0);
    fail_unless (account2->id != 0);

    list = ag_manager_list_enabled_by_service_type (manager, "e-mail");
    fail_unless (g_list_length (list) == 1);
    fail_unless (account1->id == GPOINTER_TO_UINT(list->data));

    /* clear up */
    ag_service_unref (service1);
    ag_service_unref (service2);
    g_object_unref (account1);
    g_object_unref (account2);
    ag_manager_list_free (list);

    end_test ();
}
END_TEST

static void
on_enabled_event (AgManager *manager, AgAccountId account_id,
                  AgAccountId *id)
{
    g_debug ("%s called (%u)", G_STRFUNC, account_id);
    AgAccount *acc;
    AgService *service;

    acc = ag_manager_get_account (manager, account_id);
    fail_unless (acc != NULL);
    fail_unless (ag_account_get_enabled (acc));

    service = ag_manager_get_service (manager, "MyService");
    fail_unless (service != NULL);
    ag_account_select_service (acc, service);
    fail_unless (ag_account_get_enabled (acc));
    ag_service_unref (service);

    *id = account_id;

    g_object_unref (acc);
    g_main_loop_quit (main_loop);
}

static gboolean
enabled_event_test_failed (gpointer userdata)
{
    g_debug ("Timeout");
    source_id = 0;
    g_main_loop_quit (main_loop);
    return FALSE;
}

START_TEST(test_manager_enabled_event)
{
    gint ret;

    /* consume any still unprocessed D-Bus signals */
    run_main_loop_for_n_seconds (2);

    /* delete the database */
    g_unlink (db_filename);

    /* watch account enabledness */
    gchar command[512];
    AgAccountId account_id = 0;

    manager = ag_manager_new_for_service_type ("e-mail");
    fail_unless (manager != NULL);
    account = ag_manager_create_account (manager, "maemo");
    fail_unless (account != NULL);

    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);
    run_main_loop_for_n_seconds(0);
    fail_unless (data_stored, "Callback not invoked immediately");
    data_stored = FALSE;

    main_loop = g_main_loop_new (NULL, FALSE);

    g_signal_connect (manager, "enabled-event",
                      G_CALLBACK (on_enabled_event), &account_id);

    /* this command will enable MyService (which is of type e-mail) */
    sprintf (command, "test-process enabled_event %d", account->id);
    ret = system (command);
    fail_unless (ret != -1);

    source_id = g_timeout_add_seconds (2, enabled_event_test_failed, NULL);
    g_main_loop_run (main_loop);
    fail_unless (source_id != 0, "Timeout happened");
    g_source_remove (source_id);

    fail_unless (account_id == account->id);

    account_id = 0;

    /* now disable the account. This also should trigger the enabled-event. */
    sprintf (command, "test-process enabled_event2 %d", account->id);
    ret = system (command);
    fail_unless (ret != -1);

    source_id = g_timeout_add_seconds (2, enabled_event_test_failed, NULL);
    g_main_loop_run (main_loop);
    fail_unless (source_id != 0, "Timeout happened");
    g_source_remove (source_id);

    fail_unless (account_id == account->id);

    end_test ();
}
END_TEST

START_TEST(test_list_enabled_account)
{
    GList *list = NULL;
    AgAccount *account1 = NULL;
    AgAccount *account2 = NULL;
    GList *iter = NULL;
    gboolean found = FALSE;
    AgAccount *account3 = NULL;
    const gchar *name = NULL;

    manager = ag_manager_new ();
    fail_unless (manager != NULL, "Manager should not be NULL");

    account1 = ag_manager_create_account (manager, "MyProvider");
    fail_unless (AG_IS_ACCOUNT (account1),
                 "Failed to create the AgAccount.");
    ag_account_set_display_name (account1, "EnabledAccount");
    ag_account_set_enabled (account1, TRUE);
    ag_account_store (account1, account_store_now_cb, TEST_STRING);

    account2 = ag_manager_create_account (manager, "MyProvider");
    fail_unless (AG_IS_ACCOUNT (account2),
                 "Failed to create the AgAccount.");
    ag_account_set_display_name (account2, "DisabledAccount");
    ag_account_set_enabled (account2, FALSE);
    ag_account_store (account2, account_store_now_cb, TEST_STRING);


    list = ag_manager_list_enabled (manager);
    fail_unless (g_list_length (list) > 0,
                 "No enabled accounts?");

    for (iter = list; iter != NULL; iter = g_list_next (iter))
    {
        account3 = ag_manager_get_account (manager,
                                           GPOINTER_TO_UINT (iter->data));

        name = ag_account_get_display_name (account3);
        if (strcmp (name, "EnabledAccount") == 0)
        {
            found = TRUE;
            break;
        }
        g_object_unref (account3);
        account3 = NULL;
    }

    fail_unless (found == TRUE, "Required account not enabled");

    if (account3)
        g_object_unref (account3);
    if (account2)
        g_object_unref (account2);
    if (account1)
        g_object_unref (account1);

    ag_manager_list_free (list);

    end_test ();
}
END_TEST

START_TEST(test_account_list_enabled_services)
{
    GList *services;
    gint n_services;
    AgService *service1, *service2;

    /*
     * Two additional managers:
     * manager2 : e-mail type
     * manager3 : sharing type
     * */
    AgManager *manager2, *manager3;

    /*
     * Same instances of account:
     * account2: from e-mail type manager
     * account3: from sharing manager
     * */
    AgAccount *account2, *account3;

    /*
     * new account for the same manager
     * */
    AgAccount *account4;

    /* delete the database */
    g_unlink (db_filename);

    manager = ag_manager_new ();
    fail_unless (manager != NULL);

    manager2 = ag_manager_new_for_service_type ("e-mail");
    fail_unless (manager2 != NULL);

    manager3 = ag_manager_new_for_service_type ("sharing");
    fail_unless (manager3 != NULL);

    account = ag_manager_create_account (manager, "maemo");
    fail_unless (account != NULL);

    service1 = ag_manager_get_service (manager, "MyService");
    fail_unless (service1 != NULL);
    service2 = ag_manager_get_service (manager, "OtherService");
    fail_unless (service2 != NULL);

    /* 2 services, 1 enabled  */
    ag_account_select_service (account, service1);
    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);

    ag_account_select_service (account, service2);
    ag_account_set_enabled (account, FALSE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);

    services = ag_account_list_enabled_services (account);
    n_services = g_list_length (services);
    fail_unless (n_services == 1, "Got %d services, expecting 1", n_services);
    ag_service_list_free (services);

    /* 2 services, 2 enabled  */
    ag_account_select_service (account, service2);
    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_now_cb, TEST_STRING);

    services = ag_account_list_enabled_services (account);

    n_services = g_list_length (services);
    fail_unless (n_services == 2, "Got %d services, expecting 2", n_services);
    ag_service_list_free (services);

    account2 = ag_manager_get_account (manager2, account->id);
    fail_unless (account2 != NULL);

    account3 = ag_manager_get_account (manager3, account->id);
    fail_unless (account3 != NULL);

    services = ag_account_list_enabled_services (account2);

    n_services = g_list_length (services);
    fail_unless (n_services == 1, "Got %d services, expecting 1", n_services);
    ag_service_list_free (services);

    services = ag_account_list_enabled_services (account3);

    n_services = g_list_length (services);
    fail_unless (n_services == 1, "Got %d services, expecting 1", n_services);
    ag_service_list_free (services);

    /* 2 services, 0 enabled  */
    account4 = ag_manager_create_account (manager, "maemo");
    fail_unless (account4 != NULL);

    ag_account_select_service (account, service1);
    ag_account_set_enabled (account, FALSE);

    ag_account_select_service (account, service2);
    ag_account_set_enabled (account, FALSE);

    ag_account_store (account, account_store_now_cb, TEST_STRING);

    ag_account_select_service (account4, service2);
    ag_account_set_enabled (account4, TRUE);
    ag_account_store (account4, account_store_now_cb, TEST_STRING);

    services = ag_account_list_enabled_services (account);

    n_services = g_list_length (services);
    fail_unless (n_services == 0, "Got %d services, expecting 0", n_services);
    services = ag_account_list_enabled_services (account);
    /* clear up */
    ag_service_unref (service1);
    ag_service_unref (service2);
    ag_service_list_free (services);

    g_object_unref (account2);
    g_object_unref (account3);
    g_object_unref (account4);
    g_object_unref (manager2);
    g_object_unref (manager3);

    end_test ();
}
END_TEST

START_TEST(test_service_type)
{
    const gchar *string;
    AgServiceType *service_type;

    manager = ag_manager_new ();

    service_type = ag_manager_load_service_type (manager, "I don't exist");
    fail_unless (service_type == NULL);

    service_type = ag_manager_load_service_type (manager, "e-mail");
    fail_unless (service_type != NULL);

    string = ag_service_type_get_name (service_type);
    fail_unless (g_strcmp0 (string, "e-mail") == 0,
                 "Wrong service type name: %s", string);

    string = ag_service_type_get_display_name (service_type);
    fail_unless (g_strcmp0 (string, "Electronic mail") == 0,
                 "Wrong service type display name: %s", string);

    string = ag_service_type_get_description (service_type);
    fail_unless (g_strcmp0 (string, "Electronic mail description") == 0,
                 "Wrong service type description: %s", string);

    string = ag_service_type_get_icon_name (service_type);
    fail_unless (g_strcmp0 (string, "email_icon") == 0,
                 "Wrong service type icon name: %s", string);

    string = ag_service_type_get_i18n_domain (service_type);
    fail_unless (g_strcmp0 (string, "translation_file") == 0,
                 "Wrong service type i18n name: %s", string);

    ag_service_type_unref (service_type);

    end_test ();
}
END_TEST

static void
on_account_created_with_db_locked (AgManager *manager, AgAccountId account_id)
{
    AgAccount *account;
    AgService *service;
    const gchar *name;
    GList *list;

    g_debug ("%s called (%u)", G_STRFUNC, account_id);

    account = ag_manager_get_account (manager, account_id);
    fail_unless (account != NULL);

    g_debug ("account loaded");
    list = ag_account_list_enabled_services (account);
    fail_unless (list != NULL);
    fail_unless (g_list_length (list) == 1);

    service = list->data;
    fail_unless (service != NULL);

    name = ag_service_get_name (service);
    fail_unless (strcmp (name, "MyService") == 0);

    ag_service_list_free (list);
    g_main_loop_quit (main_loop);
}

START_TEST(test_db_access)
{
    const gchar *lock_filename;
    gchar command[512];
    gint timeout_ms;
    gint fd;
    gint ret;

    /* This test is for making sure that no DB accesses occur while certain
     * events occur.
     *
     * Checked scenarios:
     *
     * - when another process creates an account and we get the
     *   account-created signal and call
     *   ag_account_list_enabled_services(), we shouldn't be blocked.
     */

    /* first, create a lock file to synchronize the test */
    lock_filename = "/tmp/check_ag.lock";
    fd = open (lock_filename, O_CREAT | O_RDWR, 0666);

    timeout_ms = 2000; /* two seconds */

    manager = ag_manager_new ();
    ag_manager_set_db_timeout (manager, 0);
    ag_manager_set_abort_on_db_timeout (manager, TRUE);
    g_signal_connect (manager, "account-created",
                      G_CALLBACK (on_account_created_with_db_locked), NULL);

    /* create an account with the e-mail service type enabled */
    ret = system ("test-process create3 myprovider MyAccountName");
    fail_unless (ret != -1);

    /* lock the DB for the specified timeout */
    sprintf (command, "test-process lock_db %d %s &",
             timeout_ms, lock_filename);
    ret = system (command);
    fail_unless (ret != -1);

    /* wait till the file is locked */
    while (lockf (fd, F_TEST, 0) == 0)
        sched_yield ();

    /* now the DB is locked; we iterate the main loop to get the signals
     * about the account creation and do some operations with the account.
     * We expect to never get any error because of the locked DB, as the
     * methods we are calling should not access it */

    main_loop = g_main_loop_new (NULL, FALSE);
    source_id = g_timeout_add_seconds (timeout_ms / 1000,
                                       concurrency_test_failed, NULL);
    g_debug ("Running loop");
    g_main_loop_run (main_loop);

    fail_unless (source_id != 0, "Timeout happened");
    g_source_remove (source_id);

    end_test ();
}
END_TEST

Suite *
ag_suite(const char *test_case)
{
    Suite *s = suite_create ("accounts-glib");

#define IF_TEST_CASE_ENABLED(test_name) \
    if (test_case == NULL || strcmp (test_name, test_case) == 0)

    TCase *tc;

    tc = tcase_create("Core");
    tcase_add_test (tc, test_init);
    tcase_add_test (tc, test_timeout_properties);
    IF_TEST_CASE_ENABLED("Core")
        suite_add_tcase (s, tc);

    tc = tcase_create("Create");
    tcase_add_test (tc, test_object);
    tcase_add_test (tc, test_read_only);
    IF_TEST_CASE_ENABLED("Create")
        suite_add_tcase (s, tc);

    tc = tcase_create("Provider");
    tcase_add_test (tc, test_provider);
    tcase_add_test (tc, test_provider_settings);
    tcase_add_test (tc, test_provider_directories);
    IF_TEST_CASE_ENABLED("Provider")
        suite_add_tcase (s, tc);

    tc = tcase_create("Store");
    tcase_add_test (tc, test_store);
    tcase_add_test (tc, test_store_locked);
    tcase_add_test (tc, test_store_locked_cancel);
    tcase_add_test (tc, test_store_read_only);
    IF_TEST_CASE_ENABLED("Store")
        suite_add_tcase (s, tc);

    tc = tcase_create("Service");
    tcase_add_test (tc, test_service);
    tcase_add_test (tc, test_account_services);
    tcase_add_test (tc, test_settings_iter_gvalue);
    tcase_add_test (tc, test_settings_iter);
    tcase_add_test (tc, test_service_type);
    IF_TEST_CASE_ENABLED("Service")
        suite_add_tcase (s, tc);

    tc = tcase_create("AccountService");
    tcase_add_test (tc, test_account_service);
    tcase_add_test (tc, test_account_service_enabledness);
    tcase_add_test (tc, test_account_service_settings);
    tcase_add_test (tc, test_account_service_list);
    IF_TEST_CASE_ENABLED("AccountService")
        suite_add_tcase (s, tc);

    tc = tcase_create("AuthData");
    tcase_add_test (tc, test_auth_data);
    tcase_add_test (tc, test_auth_data_get_login_parameters);
    tcase_add_test (tc, test_auth_data_insert_parameters);
    IF_TEST_CASE_ENABLED("AuthData")
        suite_add_tcase (s, tc);

    tc = tcase_create("Application");
    tcase_add_test (tc, test_application);
    tcase_add_test (tc, test_application_supported_services);
    IF_TEST_CASE_ENABLED("Application")
        suite_add_tcase (s, tc);

    tc = tcase_create("List");
    tcase_add_test (tc, test_list);
    tcase_add_test (tc, test_list_enabled_account);
    tcase_add_test (tc, test_list_services);
    tcase_add_test (tc, test_account_list_enabled_services);
    tcase_add_test (tc, test_list_service_types);
    IF_TEST_CASE_ENABLED("List")
        suite_add_tcase (s, tc);

    tc = tcase_create("Signalling");
    tcase_add_test (tc, test_signals);
    tcase_add_test (tc, test_signals_other_manager);
    tcase_add_test (tc, test_delete);
    tcase_add_test (tc, test_watches);
    IF_TEST_CASE_ENABLED("Signalling")
        suite_add_tcase (s, tc);

    tc = tcase_create("Concurrency");
    tcase_add_test (tc, test_no_dbus);
    tcase_add_test (tc, test_concurrency);
    tcase_add_test (tc, test_blocking);
    tcase_add_test (tc, test_manager_new_for_service_type);
    tcase_add_test (tc, test_manager_enabled_event);
    /* Tests for ensuring that opening and reading from a locked DB was
     * delayed have been removed since WAL journaling has been introduced:
     * they were failing, because with WAL journaling a writer does not
     * block readers.
     * Should we even need those tests back, they can be found in the git
     * history.
     */
    tcase_set_timeout (tc, 30);
    IF_TEST_CASE_ENABLED("Concurrency")
        suite_add_tcase (s, tc);

    tc = tcase_create("Regression");
    tcase_add_test (tc, test_service_regression);
    tcase_add_test (tc, test_cache_regression);
    tcase_add_test (tc, test_serviceid_regression);
    tcase_add_test (tc, test_enabled_regression);
    tcase_add_test (tc, test_delete_regression);
    tcase_add_test (tc, test_duplicate_create_regression);
    IF_TEST_CASE_ENABLED("Regression")
        suite_add_tcase (s, tc);

    tc = tcase_create("Caching");
    tcase_add_test (tc, test_db_access);
    tcase_set_timeout (tc, 10);
    IF_TEST_CASE_ENABLED("Caching")
        suite_add_tcase (s, tc);

    return s;
}

int main(int argc, char **argv)
{
    int number_failed;
    const char *test_case = NULL;

    if (argc > 1)
        test_case = argv[1];
    else
        test_case = g_getenv ("TEST_CASE");

    Suite * s = ag_suite(test_case);
    SRunner * sr = srunner_create(s);

    db_filename = g_build_filename (g_getenv ("ACCOUNTS"), "accounts.db",
                                    NULL);

    srunner_set_xml(sr, "/tmp/result.xml");
    srunner_run_all(sr, CK_NORMAL);
    number_failed = srunner_ntests_failed(sr);
    srunner_free (sr);

    g_free (db_filename);

    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

/* vim: set ai et tw=75 ts=4 sw=4: */

07070100000049000041ED000003E800000064000000045BDC2B8B00000000000000000000000000000000000000000000002100000000libaccounts-glib-1.24/tests/data0707010000004A000041ED000003E800000064000000065BDC2B8B00000000000000000000000000000000000000000000002A00000000libaccounts-glib-1.24/tests/data/accounts0707010000004B000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000003700000000libaccounts-glib-1.24/tests/data/accounts/applications0707010000004C000081A4000003E800000064000000015BDC2B8B0000019A000000000000000000000000000000000000004B00000000libaccounts-glib-1.24/tests/data/accounts/applications/Gallery.application<?xml version="1.0" encoding="UTF-8" ?>
<application id="Gallery">
  <description>Image gallery</description>

  <services>
		<service id="OtherService">
			<description>Publish images on OtherService</description>
		</service>
  </services>

  <service-types>
		<service-type id="sharing">
			<description>Share your images with your friends</description>
		</service-type>
  </service-types>

</application>
0707010000004D000081A4000003E800000064000000015BDC2B8B000002CF000000000000000000000000000000000000004A00000000libaccounts-glib-1.24/tests/data/accounts/applications/Mailer.application<?xml version="1.0" encoding="UTF-8" ?>
<application id="Mailer">
  <description>Mailer application</description>
  <translations>mailer-catalog</translations>

	<!-- This file refers to "missing-service" and "missing-service-type" which,
			 as their name hints, do not exist. The only valid reference contained in
			 this file is the "e-mail" service type. -->
  <services>
    <service id="missing-service" />
  </services>

  <service-types>
		<service-type id="e-mail">
			<description>Mailer can retrieve your e-mails</description>
		</service-type>
		<service-type id="missing-service-type">
			<description>I wish I could do something with it</description>
		</service-type>
  </service-types>

</application>
0707010000004E000041ED000003E800000064000000035BDC2B8B00000000000000000000000000000000000000000000003400000000libaccounts-glib-1.24/tests/data/accounts/providers0707010000004F000081A4000003E800000064000000015BDC2B8B0000027E000000000000000000000000000000000000004800000000libaccounts-glib-1.24/tests/data/accounts/providers/MyProvider.provider<?xml version="1.0" encoding="UTF-8" ?>
<provider id="MyProvider">
  <name>My Provider</name>
  <description>My Provider Description</description>
  <translations>provider_i18n</translations>
  <icon>general_myprovider</icon>
  <domains>.*provider\.com</domains>
  <plugin>oauth2</plugin>
  <single-account>true</single-account>

  <!-- default settings (account settings have precedence over these) -->
  <template>
    <group name="login">
      <setting name="server">login.example.com</setting>
      <setting name="remember-me" type="b">true</setting>
    </group>
    <setting name="color">green</setting>
  </template>
</provider>
07070100000050000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000003C00000000libaccounts-glib-1.24/tests/data/accounts/providers/fake-os07070100000051000081A4000003E800000064000000015BDC2B8B00000117000000000000000000000000000000000000005000000000libaccounts-glib-1.24/tests/data/accounts/providers/fake-os/MyProvider.provider<?xml version="1.0" encoding="UTF-8" ?>
<provider id="MyProvider">
  <name>FakeOs Provider</name>
  <description>My Provider Description</description>
  <translations>provider_i18n</translations>
  <icon>general_myprovider</icon>
  <domains>.*provider\.com</domains>
</provider>
07070100000052000081A4000003E800000064000000015BDC2B8B0000027A000000000000000000000000000000000000004300000000libaccounts-glib-1.24/tests/data/accounts/providers/maemo.provider<?xml version="1.0" encoding="UTF-8" ?>
<provider id="maemo">
  <name>Example provider</name>
  <description>An example provider</description>
  <icon>example</icon>
  <domains>.*example\.com</domains>
  <!-- default settings (account settings have precedence over these) -->
  <template>
    <setting name="auth/method">dummy-method</setting>
    <setting name="auth/mechanism">dummy-mechanism</setting>
    <group name="auth/dummy-method/dummy-mechanism">
      <setting name="id">879</setting>
      <setting name="display">desktop</setting>
      <setting name="from-provider">yes</setting>
    </group>
  </template>
</provider>
07070100000053000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000003800000000libaccounts-glib-1.24/tests/data/accounts/service-types07070100000054000081A4000003E800000064000000015BDC2B8B0000013B000000000000000000000000000000000000004C00000000libaccounts-glib-1.24/tests/data/accounts/service-types/e-mail.service-type<?xml version="1.0" encoding="UTF-8" ?>
<service-type id="e-mail">
  <name>Electronic mail</name>
  <description>Electronic mail description</description>
  <icon>email_icon</icon>
  <translations>translation_file</translations>
  <tags>
      <tag>e-mail</tag>
      <tag>messaging</tag>
  </tags>
</service-type>
07070100000055000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000003300000000libaccounts-glib-1.24/tests/data/accounts/services07070100000056000081A4000003E800000064000000015BDC2B8B00000328000000000000000000000000000000000000004500000000libaccounts-glib-1.24/tests/data/accounts/services/MyService.service<?xml version="1.0" encoding="UTF-8" ?>
<service id="MyService">
  <type>e-mail</type>
  <name>My Service</name>
  <description>My Service Description</description>
  <icon>general_myservice</icon>
  <provider>maemo</provider>
  <translations>myservice_i18n</translations>
  
  <!-- default settings (account settings have precedence over these) -->
  <template>
    <group name="parameters">
      <setting name="server">talk.google.com</setting>
      <setting name="port" type="i">5223</setting>
      <setting name="old-ssl" type="b">true</setting>
      <setting name="fallback-conference-server">conference.jabber.org</setting>
      <setting name="capabilities" type="as">["chat", "file", "smileys"]</setting>
    </group>
    <setting name="enabled" type="b">true</setting>
  </template>

</service>
07070100000057000081A4000003E800000064000000015BDC2B8B00000186000000000000000000000000000000000000004600000000libaccounts-glib-1.24/tests/data/accounts/services/MyService2.service<?xml version="1.0" encoding="UTF-8" ?>
<service id="MyService2">
  <type>calendar</type>
  <name>My Service #2</name>
  <icon>general_myservice2</icon>
  <provider>maemo</provider>

  <!-- default settings (account settings have precedence over these) -->
  <template>
    <group name="parameters">
      <setting name="server">youtube.com</setting>
    </group>
  </template>

</service>
07070100000058000081A4000003E800000064000000015BDC2B8B00000295000000000000000000000000000000000000004800000000libaccounts-glib-1.24/tests/data/accounts/services/OtherService.service<?xml version="1.0" encoding="UTF-8" ?>
<service id="OtherService">
  <type>sharing</type>
  <name>Other Service</name>
  <icon>general_otherservice</icon>
  <provider>other_provider</provider>
  <tags>
      <tag>video</tag>
      <tag>sharing</tag>
  </tags>
  
  <!-- default settings (account settings have precedence over these) -->
  <template>
    <group name="parameters">
      <setting name="server">talk.google.com</setting>
      <setting name="port" type="i">5223</setting>
      <setting name="old-ssl" type="b">true</setting>
      <setting name="fallback-conference-server">conference.jabber.org</setting>
    </group>
  </template>

</service>
07070100000059000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000002E00000000libaccounts-glib-1.24/tests/data/applications0707010000005A000081A4000003E800000064000000015BDC2B8B000000A3000000000000000000000000000000000000003D00000000libaccounts-glib-1.24/tests/data/applications/Mailer.desktop[Desktop Entry]
Name=Easy Mailer
Comment=Send and receive mail
Exec=/bin/sh
Icon=mailer-icon
Terminal=false
Type=Application
Categories=Application;Network;Email;
0707010000005B000081A4000003E800000064000000015BDC2B8B00000A98000000000000000000000000000000000000002800000000libaccounts-glib-1.24/tests/meson.buildcheck_dep = dependency('check')

test_manager = gnome.gdbus_codegen('test-manager',
    join_paths(meson.source_root (), 'libaccounts-glib', 'com.google.code.AccountsSSO.Accounts.Manager.xml'),
    namespace: 'Test',
    interface_prefix: 'com.google.code.AccountsSSO.Accounts.',
    object_manager: true
)

test_process = executable('test-process',
    'test-process.c',
    dependencies: accounts_glib_dep
)

accounts_glib_testsuite = executable('accounts-glib-testsuite',
    'check_ag.c',
    test_manager,
    include_directories: root_dir,
    dependencies: [accounts_glib_dep, check_dep]
)

test_data_dir = join_paths (meson.current_source_dir (), 'data')
test_environment = environment()
test_environment.set ('AG_APPLICATIONS', join_paths (test_data_dir, 'accounts', 'applications'))
test_environment.set ('AG_SERVICES', join_paths (test_data_dir, 'accounts', 'services'))
test_environment.set ('AG_SERVICE_TYPES', join_paths (test_data_dir, 'accounts', 'service-types'))
test_environment.set ('AG_PROVIDERS', join_paths (test_data_dir, 'accounts', 'providers'))
test_environment.set ('ACCOUNTS', '/tmp')
test_environment.set ('AG_DEBUG', 'all')
test_environment.set ('G_MESSAGES_DEBUG', 'all')
test_environment.set ('G_DEBUG', 'fatal-criticals')
test_environment.set ('XDG_DATA_HOME', test_data_dir)
test_environment.append ('PATH', meson.current_build_dir ())


dbus_test_runner = find_program('dbus-test-runner', required: false)
if dbus_test_runner.found()
    test('accounts-glib-testsuite',
        dbus_test_runner,
        args: ['-m','180','-t',join_paths (meson.current_build_dir (), 'accounts-glib-testsuite')],
        env: test_environment,
        timeout: 180
    )
else
    test('accounts-glib-testsuite',
        accounts_glib_testsuite,
        env: test_environment,
        timeout: 180
    )
endif

if xmllint.found ()
    xml_files = [
        ['accounts-application.dtd', 'applications', ['Gallery.application','Mailer.application']],
        ['accounts-service.dtd', 'services', ['MyService.service','MyService2.service','OtherService.service']],
        ['accounts-provider.dtd', 'providers', ['MyProvider.provider']],
        ['accounts-service-type.dtd', 'service-types', ['e-mail.service-type']]
    ]
    foreach xml_file : xml_files
        dtd_path = join_paths (meson.source_root(), 'data', xml_file[0])
        foreach target_file : xml_file[2]
            xml_path = join_paths (meson.current_source_dir(), 'data', 'accounts', xml_file[1], target_file)
            test('xmllint-@0@-@1@'.format(xml_file[1], target_file),
                xmllint,
                args: ['--noout', '--dtdvalid', dtd_path, xml_path]
            )
        endforeach
    endforeach
endif
0707010000005C000081A4000003E800000064000000015BDC2B8B0000294B000000000000000000000000000000000000002B00000000libaccounts-glib-1.24/tests/test-process.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#define AG_DISABLE_DEPRECATION_WARNINGS

#include <libaccounts-glib.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

static GMainLoop *main_loop = NULL;
static AgAccount *account = NULL;
static AgManager *manager = NULL;
static AgService *service = NULL;
static sqlite3 *sqldb = NULL;
int lock_file = 0;

#define PROVIDER    "dummyprovider"

typedef struct {
    gint argc;
    gchar **argv;
} TestArgs;

static void
lock_db(gboolean lock)
{
    static sqlite3_stmt *begin_stmt = NULL;
    static sqlite3_stmt *commit_stmt = NULL;

    /* this lock is to synchronize with the main test application */
    if (!lock)
    {
        int ret = lockf(lock_file, F_ULOCK, 0);
        g_assert(ret == 0);
    }

    if (!begin_stmt)
    {
        sqlite3_prepare_v2 (sqldb, "BEGIN EXCLUSIVE;", -1, &begin_stmt, NULL);
        sqlite3_prepare_v2 (sqldb, "COMMIT;", -1, &commit_stmt, NULL);
    }
    else
    {
        sqlite3_reset (begin_stmt);
        sqlite3_reset (commit_stmt);
    }

    if (lock)
        sqlite3_step (begin_stmt);
    else
        sqlite3_step (commit_stmt);

    /* this lock is to synchronize with the main test application */
    if (lock)
    {
        int ret = lockf(lock_file, F_LOCK, 0);
        g_assert(ret == 0);
    }
}

static void
end_test ()
{
    if (account)
    {
        g_object_unref (account);
        account = NULL;
    }
    if (manager)
    {
        g_object_unref (manager);
        manager = NULL;
    }
    if (service)
    {
        ag_service_unref (service);
        service = NULL;
    }

    if (main_loop)
    {
        g_main_loop_quit (main_loop);
        g_main_loop_unref (main_loop);
        main_loop = NULL;
    }
}

void account_store_cb (AgAccount *account, const GError *error,
                       gpointer user_data)
{
    if (error)
        g_warning ("Got error: %s", error->message);

    end_test ();
}

gboolean test_create (TestArgs *args)
{
    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, args->argv[0]);

    if (args->argc > 1)
    {
        ag_account_set_display_name (account, args->argv[1]);
    }

    ag_account_store (account, account_store_cb, NULL);

    return FALSE;
}

gboolean test_delete (TestArgs *args)
{
    AgAccountId id;

    manager = ag_manager_new ();
    id = atoi(args->argv[0]);
    account = ag_manager_get_account (manager, id);
    ag_account_delete (account);

    ag_account_store (account, account_store_cb, NULL);

    return FALSE;
}

gboolean test_create2 (TestArgs *args)
{
    GValue value = { 0 };
    const gchar *numbers[] = {
        "one",
        "two",
        "three",
        NULL
    };

    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, args->argv[0]);

    if (args->argc > 1)
    {
        ag_account_set_display_name (account, args->argv[1]);
    }

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, -12345);
    ag_account_set_value (account, "integer", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "a string");
    ag_account_set_value (account, "string", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRV);
    g_value_set_boxed (&value, numbers);
    ag_account_set_value (account, "numbers", &value);
    g_value_unset (&value);

    ag_account_set_enabled (account, TRUE);

    /* also set some keys in one service */
    service = ag_manager_get_service (manager, "MyService");
    ag_account_select_service (account, service);

    g_value_init (&value, G_TYPE_UINT);
    g_value_set_uint (&value, 54321);
    ag_account_set_value (account, "unsigned", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_CHAR);
#if GLIB_CHECK_VERSION(2,32,0)
    g_value_set_schar (&value, 'z');
#else
    g_value_set_char (&value, 'z');
#endif
    ag_account_set_value (account, "character", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, TRUE);
    ag_account_set_value (account, "boolean", &value);
    g_value_unset (&value);

    ag_account_set_enabled (account, FALSE);

    ag_account_store (account, account_store_cb, NULL);

    return FALSE;
}

gboolean test_create3 (TestArgs *args)
{
    GValue value = { 0 };

    manager = ag_manager_new ();

    account = ag_manager_create_account (manager, args->argv[0]);

    if (args->argc > 1)
    {
        ag_account_set_display_name (account, args->argv[1]);
    }

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, -12345);
    ag_account_set_value (account, "integer", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "a string");
    ag_account_set_value (account, "string", &value);
    g_value_unset (&value);

    ag_account_set_enabled (account, TRUE);

    /* also set some keys in one service */
    service = ag_manager_get_service (manager, "MyService");
    ag_account_select_service (account, service);

    g_value_init (&value, G_TYPE_UINT);
    g_value_set_uint (&value, 54321);
    ag_account_set_value (account, "unsigned", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_CHAR);
#if GLIB_CHECK_VERSION(2,32,0)
    g_value_set_schar (&value, 'z');
#else
    g_value_set_char (&value, 'z');
#endif
    ag_account_set_value (account, "character", &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, TRUE);
    ag_account_set_value (account, "boolean", &value);
    g_value_unset (&value);

    ag_account_set_enabled (account, TRUE);

    ag_account_store (account, account_store_cb, NULL);

    return FALSE;
}

gboolean test_change (TestArgs *args)
{
    GValue value = { 0 };

    AgAccountId id;

    manager = ag_manager_new ();
    id = atoi(args->argv[0]);
    account = ag_manager_get_account (manager, id);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_static_string (&value, "another string");
    ag_account_set_value (account, "string", &value);
    g_value_unset (&value);

    service = ag_manager_get_service (manager, "MyService");
    ag_account_select_service (account, service);

    ag_account_set_value (account, "character", NULL);

    g_value_init (&value, G_TYPE_BOOLEAN);
    g_value_set_boolean (&value, FALSE);
    ag_account_set_value (account, "boolean", &value);
    g_value_unset (&value);

    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_cb, NULL);

    return FALSE;
}

gboolean test_enabled_event (TestArgs *args)
{
    AgAccountId id;

    manager = ag_manager_new ();
    id = atoi(args->argv[0]);
    account = ag_manager_get_account (manager, id);
    service = ag_manager_get_service (manager, "MyService");
    ag_account_select_service (account, service);
    ag_account_set_enabled (account, TRUE);
    ag_account_store (account, account_store_cb, NULL);

    return FALSE;
}

gboolean test_enabled_event2 (TestArgs *args)
{
    AgAccountId id;

    manager = ag_manager_new ();
    id = atoi (args->argv[0]);
    account = ag_manager_get_account (manager, id);
    ag_account_select_service (account, NULL);
    ag_account_set_enabled (account, FALSE);
    ag_account_store (account, account_store_cb, NULL);

    return FALSE;
}

gboolean unlock_and_exit()
{
    lock_db(FALSE);
    end_test ();
    return FALSE;
}

gboolean test_lock_db (TestArgs *args)
{
    const gchar *basedir;
    gchar *filename;
    gint ms;

    ms = atoi(args->argv[0]);
    lock_file = open(args->argv[1], O_RDWR | O_APPEND);

    basedir = g_getenv ("ACCOUNTS");
    if (G_LIKELY (!basedir))
        basedir = g_get_home_dir ();
    filename = g_build_filename (basedir, "accounts.db", NULL);
    sqlite3_open (filename, &sqldb);
    g_free (filename);

    lock_db(TRUE);

    g_timeout_add (ms, (GSourceFunc)unlock_and_exit, NULL);

    return FALSE;
}

int main(int argc, char **argv)
{
    TestArgs args;

    if (argc >= 2)
    {
        const gchar *test_name = argv[1];

        argc -= 2;
        argv += 2;
        args.argc = argc;
        args.argv = argv;

        if (strcmp (test_name, "create") == 0)
        {
            g_idle_add ((GSourceFunc)test_create, &args);
        }
        else if (strcmp (test_name, "delete") == 0)
        {
            g_idle_add ((GSourceFunc)test_delete, &args);
        }
        else if (strcmp (test_name, "create2") == 0)
        {
            g_idle_add ((GSourceFunc)test_create2, &args);
        }
        else if (strcmp (test_name, "create3") == 0)
        {
            g_idle_add ((GSourceFunc)test_create3, &args);
        }
        else if (strcmp (test_name, "change") == 0)
        {
            g_idle_add ((GSourceFunc)test_change, &args);
        }
        else if (strcmp (test_name, "lock_db") == 0)
        {
            g_idle_add ((GSourceFunc)test_lock_db, &args);
        }
        else if (strcmp (test_name, "enabled_event") == 0)
        {
            g_idle_add ((GSourceFunc)test_enabled_event, &args);
        }
        else if (strcmp (test_name, "enabled_event2") == 0)
        {
            g_idle_add ((GSourceFunc)test_enabled_event2, &args);
        }

        main_loop = g_main_loop_new (NULL, FALSE);
        g_main_loop_run (main_loop);
        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

/* vim: set ai et tw=75 ts=4 sw=4: */

0707010000005D000041ED000003E800000064000000025BDC2B8B00000000000000000000000000000000000000000000001C00000000libaccounts-glib-1.24/tools0707010000005E000081A4000003E800000064000000015BDC2B8B00000E4E000000000000000000000000000000000000002500000000libaccounts-glib-1.24/tools/backup.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2011 Nokia Corporation.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include <glib.h>
#include <sched.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void
show_help ()
{
    printf ("\nUsage:\n"
            "   %1$s\n"
            "Backups the accounts from ~/.config/libaccounts-glib/accounts.db\n"
            "into ~/.config/libaccounts-glib/accounts.db.bak\n\n",
            g_get_prgname());
}

static gboolean
write_backup (sqlite3 *src, const gchar *filename)
{
    sqlite3_backup *backup;
    sqlite3 *dest;
    gint n_retries;
    int ret;

    ret = sqlite3_open (filename, &dest);
    if (ret != SQLITE_OK) return FALSE;

    backup = sqlite3_backup_init (dest, "main", src, "main");
    if (!backup)
    {
        g_warning ("Couldn't start backup");
        sqlite3_close (dest);
        return FALSE;
    }

    n_retries = 0;
    do
    {
        ret = sqlite3_backup_step (backup, -1);
        if (ret == SQLITE_BUSY || ret == SQLITE_LOCKED)
            sqlite3_sleep(250);
        n_retries++;
    }
    while ((ret == SQLITE_BUSY || ret == SQLITE_LOCKED) && n_retries < 5);

    sqlite3_backup_finish (backup);

    sqlite3_close (dest);
    return ret == SQLITE_OK;
}

static gboolean
backup ()
{
    gchar *filename, *filename_bak;
    sqlite3 *db;
    gint n_retries;
    int ret;
    gboolean success = FALSE;;

    filename = g_build_filename (g_get_user_config_dir (),
                                 DATABASE_DIR,
                                 "accounts.db",
                                 NULL);
    filename_bak = g_strdup_printf ("%s.bak", filename);

    g_debug ("Opening %s", filename);

    n_retries = 0;
    do
    {
        ret = sqlite3_open (filename, &db);
        if (ret == SQLITE_BUSY)
            sched_yield ();
        n_retries++;
    }
    while (ret == SQLITE_BUSY && n_retries < 5);

    if (G_UNLIKELY (ret != SQLITE_OK))
    {
        g_warning ("Couldn't open accounts DB: %s", sqlite3_errmsg (db));
        goto error_open;
    }

    n_retries = 0;
    do
    {
        ret = sqlite3_wal_checkpoint (db, NULL);
        if (ret == SQLITE_BUSY)
            sched_yield ();
        n_retries++;
    }
    while (ret == SQLITE_BUSY && n_retries < 5);

    if (G_UNLIKELY (ret != SQLITE_OK))
        g_warning ("Checkpoint failed: %s", sqlite3_errmsg (db));

    success = write_backup (db, filename_bak);

    sqlite3_close (db);
    success = TRUE;

error_open:
    g_free (filename_bak);
    g_free (filename);
    return success;
}

int
main (int argc, char **argv)
{
    gboolean success;

    g_set_prgname (g_path_get_basename (argv[0]));

    if (argc > 1)
    {
        show_help ();
        return 0;
    }

    success = backup ();

    return success ? EXIT_SUCCESS : EXIT_FAILURE;
}

0707010000005F000081A4000003E800000064000000015BDC2B8B00007AC7000000000000000000000000000000000000002300000000libaccounts-glib-1.24/tools/main.c/* vi: set et sw=4 ts=4 cino=t0,(0: */
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of libaccounts-glib
 *
 * Copyright (C) 2009-2010 Nokia Corporation.
 * Copyright (C) 2012 Canonical Ltd.
 *
 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#define AG_DISABLE_DEPRECATION_WARNINGS

#include <libaccounts-glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if GLIB_CHECK_VERSION (2, 30, 0)
#else
#define G_VALUE_INIT { 0, { { 0 } } }
#endif

static gchar *gl_app_name = NULL;

enum
{
    ERROR_GENERIC,
    INVALID_ACC_ID,
    INVALID_SERVICE_NAME,
    INVALID_INPUT
};

static void
show_error (gint err)
{

    switch (err)
    {
    case ERROR_GENERIC:
        printf ("\nUnable to process the request\n\n");
        break;
    case INVALID_ACC_ID:
        printf ("\nAccount does not exist. Check account ID entered\n\n");
        break;
    case INVALID_SERVICE_NAME:
        printf ("\nService does not exist. Enter valid service name\n\n");
        break;
    case INVALID_INPUT:
        printf ("\nRequest is not processed. Check the command parameters\n\n");
        break;
    default:
        printf ("\nUnknown problem in processing the request\n\n");
        break;
    }
}

static void
show_help ()
{

    printf ("\nOptions:\n"
            "   * Creates an account\n"
            "   %1$s create-account <provider name> [<display name>] [<enable|disable>] \n\n"
            "   * Updates/Adds key to account and sets a value to key\n"
            "   %1$s update-account <account id> (int|uint|bool|string):<key>=<value> \n\n"
            "   * Updates/Adds key to service of an account and sets a value to the key\n"
            "   %1$s update-service <account id> <service name>\n"
            "                       (int|uint|bool|string):<key>=<value> \n\n"
            "   * Enables an account\n"
            "   %1$s enable-account <account id>\n\n"
            "   * Enables service of the account\n"
            "   %1$s enable-service <account id> <service name>\n\n"
            "   * Disables an account\n"
            "   %1$s disable-account <account id>\n\n"
            "   * Disables service of an account\n"
            "   %1$s disable-service <account id> <service name>\n\n"
            "   * Gets the value of a key of an account\n"
            "   %1$s get-account <account id> <(int|uint|bool|string):key>\n\n"
            "   * Gets the value of a key of a service\n"
            "   %1$s get-service <account id> <service name>\n\t\t       <(int|uint|bool|string):<key>=<value>\n\n"
            "   * Deletes all accounts is <all> keyword is used or deletes specified account\n"
            "   %1$s delete-account <account id>/<all>\n\n"
            "   * Lists all providers\n"
            "   %1$s list-providers\n\n"
            "   * Lists all services or services that can be associated with an account\n"
            "   %1$s list-services [<account id>]\n\n"
            "   * Lists all accounts\n"
            "   %1$s list-accounts\n\n"
            "   * List all enabled accounts\n"
            "     If account ID is specified lists services enabled on the given account\n"
            "   %1$s list-enabled [<account id>]\n\n"
            "   * Lists settings associated with account\n"
            "   %1$s list-settings <account id>\n", gl_app_name);

    printf ("\nParameters in square braces '[param]' are optional\n");
}

static void
show_help_text (gchar *command)
{
    /* TODO: Show individal command help text if needed */
    show_help ();
}


static gchar *
get_string_value (const GValue *value)
{
    gchar *str = NULL;

    if (G_VALUE_HOLDS_STRING (value))
    {
        str = g_value_dup_string (value);
    }
    else if (G_VALUE_HOLDS_UINT (value))
    {
        str = g_strdup_printf ("%u", g_value_get_uint (value));
    }
    else if (G_VALUE_HOLDS_INT (value))
    {
        str = g_strdup_printf ("%i", g_value_get_int (value));
    }
    else if (G_VALUE_HOLDS_BOOLEAN (value))
    {
        str = g_strdup (g_value_get_boolean (value) ? "true" : "false");
    }
    else
    {
        str = g_strdup_value_contents (value);
    }
    return str;
}

static void
get_account (gchar **argv)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    GValue value = G_VALUE_INIT;
    GType type = 0;
    gchar *str = NULL;
    gchar **param = NULL;

    if (argv[2] == NULL || argv[3] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    /* param[0] = type, param[1] = key. Both separated by ':' */
    param = g_strsplit (argv[3], ":", 2);
    if (param[0] == NULL || param[1] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        return;
    }

    if (strcmp (param[0], "int") == 0)
    {
        g_value_init (&value, G_TYPE_INT);
        type = G_TYPE_INT;
    }
    else if (strcmp (param[0], "uint") == 0)
    {
        g_value_init (&value, G_TYPE_UINT);
        type = G_TYPE_UINT;
    }
    else if (strcmp (param[0], "bool") == 0 ||
             strcmp (param[0], "boolean") == 0)
    {
        g_value_init (&value, G_TYPE_BOOLEAN);
        type = G_TYPE_BOOLEAN;
    }
    else if (strcmp (param[0], "string") == 0)
    {
        g_value_init (&value, G_TYPE_STRING);
        type = G_TYPE_STRING;
    }
    else
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        g_strfreev (param);
        return;
    }

    account = ag_manager_get_account (manager, atoi (argv[2]));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_strfreev (param);
        g_object_unref (manager);
        return;
    }

    if (ag_account_get_value (account, param[1],
                              &value) == AG_SETTING_SOURCE_NONE)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        g_object_unref (account);
        g_object_unref (manager);
        return;
    }

    switch (type)
    {
    case G_TYPE_INT:
        str = g_strdup_printf ("%i", g_value_get_int (&value));
        break;
    case G_TYPE_UINT:
        str = g_strdup_printf ("%u", g_value_get_uint (&value));
        break;
    case G_TYPE_BOOLEAN:
        str = g_strdup_printf ("%i", g_value_get_boolean (&value));
        break;
    case G_TYPE_STRING:
        str = g_value_dup_string (&value);
        break;
    default:
        break;
    }

    if (G_IS_VALUE (&value))
        g_value_unset (&value);
    g_object_unref (account);
    g_object_unref (manager);

    printf ("%s = %s\n", param[1], str);

    g_strfreev (param);
    g_free(str);
}

static void
list_service_settings (AgAccount *account)
{
    GList *list = NULL;
    GList *tmp = NULL;
    AgAccountSettingIter iter;
    const gchar *key = NULL;
    const GValue *val = NULL;
    gchar *str = NULL;

    list = ag_account_list_services (account);
    if (list == NULL || g_list_length (list) == 0)
    {
        return;
    }

    for (tmp = list; tmp != NULL; tmp = g_list_next (tmp))
    {
        printf ("\t\t%s\n", ag_service_get_name (tmp->data));
        ag_account_select_service (account, (AgService *) tmp->data);

        ag_account_settings_iter_init (account, &iter, NULL);
        while (ag_account_settings_iter_next (&iter, &key, &val))
        {
            str = get_string_value (val);
            printf ("%s = %s\n", key, str);
            g_free (str);
            str = NULL;
        }
    }

    ag_service_list_free (list);
}

static void
list_settings (gchar **argv)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    AgAccountSettingIter iter;
    const gchar *key = NULL;
    const GValue *val = NULL;
    gchar *str = NULL;

    if (argv[2] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    account = ag_manager_get_account (manager, atoi (argv[2]));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_object_unref (manager);
        return;
    }

    ag_account_settings_iter_init (account, &iter, NULL);
    while (ag_account_settings_iter_next (&iter, &key, &val))
    {
        str = get_string_value (val);
        printf ("%s = %s\n", key, str);
        g_free (str);
        str = NULL;
    }

    list_service_settings (account);

    g_object_unref (account);
    g_object_unref (manager);
}

static void
get_service (gchar **argv)
{
    AgManager *manager = NULL;
    AgService *service = NULL;
    AgAccount *account = NULL;
    GValue value = G_VALUE_INIT;
    GType type = 0;
    gchar *str = NULL;
    gchar **param = NULL;

    if (argv[2] == NULL || argv[3] == NULL || argv[4] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    /* argv[4] = type:key */
    param = g_strsplit (argv[4], ":", 2);
    if (param[0] == NULL || param[1] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        return;
    }

    if (strcmp (param[0], "int") == 0)
    {
        g_value_init (&value, G_TYPE_INT);
        type = G_TYPE_INT;
    }
    else if (strcmp (param[0], "uint") == 0)
    {
        g_value_init (&value, G_TYPE_UINT);
        type = G_TYPE_UINT;
    }
    else if (strcmp (param[0], "bool") == 0 ||
             strcmp (param[0], "boolean") == 0)
    {
        g_value_init (&value, G_TYPE_BOOLEAN);
        type = G_TYPE_BOOLEAN;
    }
    else if (strcmp (param[0], "string") == 0)
    {
        g_value_init (&value, G_TYPE_STRING);
        type = G_TYPE_STRING;
    }
    else
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        g_strfreev (param);
        return;
    }

    account = ag_manager_get_account (manager, atoi (argv[2]));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_strfreev (param);
        g_object_unref (manager);
        return;
    }

    service = ag_manager_get_service (manager, argv[3]);
    if (service == NULL)
    {
        show_error (INVALID_SERVICE_NAME);
        g_strfreev (param);
        g_object_unref (account);
        g_object_unref (manager);
        return;
    }

    ag_account_select_service (account, service);
    if (ag_account_get_value (account, param[1],
                              &value) == AG_SETTING_SOURCE_NONE)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        ag_service_unref (service);
        g_object_unref (account);
        g_object_unref (manager);
        return;
    }

    switch (type)
    {
    case G_TYPE_INT:
        str = g_strdup_printf ("%i", g_value_get_int (&value));
        break;
    case G_TYPE_UINT:
        str = g_strdup_printf ("%u", g_value_get_uint (&value));
        break;
    case G_TYPE_BOOLEAN:
        str = g_strdup_printf ("%i", g_value_get_boolean (&value));
        break;
    case G_TYPE_STRING:
        str = g_value_dup_string (&value);
        break;
    default:
        break;
    }

    if (G_IS_VALUE (&value))
        g_value_unset (&value);
    ag_service_unref (service);
    g_object_unref (account);
    g_object_unref (manager);

    printf ("%s = %s\n", param[1], str);

    g_strfreev (param);
    g_free (str);
}

static void
update_service (gchar **argv)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    GValue *gvalue = NULL;
    gchar **param = NULL;
    gchar **keytype = NULL;
    AgService *service = NULL;
    GError *error = NULL;

    if (argv[2] == NULL || argv[3] == NULL || argv[4] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    param = g_strsplit (argv[4], "=", 2);
    if (param[0] == NULL || param[1] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        return;
    }

    keytype = g_strsplit (param[0], ":", 2);
    if (keytype[0] == NULL || keytype[1] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        g_strfreev (keytype);
        return;
    }

    gvalue = g_new0 (GValue, 1);
    if (strcmp (keytype[0], "int") == 0)
    {
        g_value_init (gvalue, G_TYPE_INT);
        g_value_set_int (gvalue, strtol (param[1], NULL, 10));
    }
    else if (strcmp (keytype[0], "uint") == 0)
    {
        g_value_init (gvalue, G_TYPE_UINT);
        g_value_set_uint (gvalue, strtoul (param[1], NULL, 10));
    }
    else if (strcmp (keytype[0], "bool") == 0 || strcmp (keytype[0],
                                                         "boolean") == 0)
    {
        g_value_init (gvalue, G_TYPE_BOOLEAN);
        g_value_set_boolean (gvalue, atoi (param[1]));
    }
    else if (strcmp (keytype[0], "string") == 0)
    {
        g_value_init (gvalue, G_TYPE_STRING);
        g_value_set_string (gvalue, param[1]);
    }
    else
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        g_strfreev (keytype);
        g_value_unset (gvalue);
        g_free (gvalue);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        g_strfreev (param);
        g_strfreev (keytype);
        g_value_unset (gvalue);
        g_free (gvalue);
        return;
    }

    account = ag_manager_get_account (manager, atoi (argv[2]));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_strfreev (param);
        g_strfreev (keytype);
        g_object_unref (manager);
        g_value_unset (gvalue);
        g_free (gvalue);
        return;
    }

    service = ag_manager_get_service (manager, argv[3]);
    if (service == NULL)
    {
        show_error (INVALID_SERVICE_NAME);
        g_strfreev (param);
        g_strfreev (keytype);
        g_object_unref (account);
        g_object_unref (manager);
        g_value_unset (gvalue);
        g_free (gvalue);
        return;
    }

    ag_account_select_service (account, service);
    ag_account_set_value (account, keytype[1], gvalue);
    ag_account_store_blocking (account, &error);
    if (error)
    {
        show_error (ERROR_GENERIC);
        g_error_free (error);
    }

    g_strfreev (param);
    g_strfreev (keytype);
    g_value_unset (gvalue);
    g_free (gvalue);
    ag_service_unref (service);
    g_object_unref (account);
    g_object_unref (manager);

    return;
}

static void
update_account (gchar **argv)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    GValue *gvalue = NULL;
    gchar **param = NULL;
    gchar **keytype = NULL;
    GError *error = NULL;

    /* Input parameter will be argv[2] = <account Id>
     * argv[3] = <keytype:key=value>
     */
    if ((argv[2] == NULL) || (argv[3] == NULL))
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    /* param[0] = <keytype:key>, param[1] = value */
    param = g_strsplit (argv[3], "=", 2);
    if (param[0] == NULL || param[1] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        return;
    }

    /* keytype[0] = type, keytype[1] = key */
    keytype = g_strsplit (param[0], ":", 2);
    if (keytype[0] == NULL || keytype[1] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        g_strfreev (keytype);
        return;
    }

    gvalue = g_new0 (GValue, 1);
    if (strcmp (keytype[0], "int") == 0)
    {
        g_value_init (gvalue, G_TYPE_INT);
        g_value_set_int (gvalue, strtol (param[1], NULL, 10));
    }
    else if (strcmp (keytype[0], "uint") == 0)
    {
        g_value_init (gvalue, G_TYPE_UINT);
        g_value_set_uint (gvalue, strtoul (param[1], NULL, 10));
    }
    else if (strcmp (keytype[0], "bool") == 0 || strcmp (keytype[0],
                                                         "boolean") == 0)
    {
        g_value_init (gvalue, G_TYPE_BOOLEAN);
        g_value_set_boolean (gvalue, atoi (param[1]));
    }
    else if (strcmp (keytype[0], "string") == 0)
    {
        g_value_init (gvalue, G_TYPE_STRING);
        g_value_set_string (gvalue, param[1]);
    }
    else
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        g_strfreev (param);
        g_strfreev (keytype);
        g_free (gvalue);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        g_strfreev (param);
        g_strfreev (keytype);
        g_free (gvalue);
        return;
    }

    account = ag_manager_get_account (manager, atoi (argv[2]));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_strfreev (param);
        g_strfreev (keytype);
        g_value_unset (gvalue);
        g_free (gvalue);
        g_object_unref (manager);
        return;
    }

    ag_account_set_value (account, keytype[1], gvalue);

    ag_account_store_blocking (account, &error);
    if (error)
    {
        show_error (ERROR_GENERIC);
        g_error_free (error);
    }

    g_strfreev (param);
    g_strfreev (keytype);
    g_value_unset (gvalue);
    g_free (gvalue);
    g_object_unref (account);
    g_object_unref (manager);
}

static void
create_account (gchar **argv)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    GError *error = NULL;

    if (argv[2] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    account = ag_manager_create_account (manager, argv[2]);
    if (account == NULL)
    {
        show_error (ERROR_GENERIC);
        g_object_unref (manager);
        return;
    }

    if (argv[3] != NULL)
        ag_account_set_display_name (account, argv[3]);

    if (argv[4] != NULL)
    {
        if (strcmp (argv[4], "enable") == 0)
            ag_account_set_enabled (account, TRUE);
        if (strcmp (argv[4], "disable") == 0)
            ag_account_set_enabled (account, FALSE);
    }

    ag_account_store_blocking (account, &error);
    if (error)
    {
        show_error (ERROR_GENERIC);
        g_error_free (error);
    }

    g_object_unref (account);
    g_object_unref (manager);
}

static void
enable_disable_service (gchar **argv, gboolean enable)
{

    AgManager *manager = NULL;
    AgService *service = NULL;
    AgAccount *account = NULL;
    GError *error = NULL;

    if ((argv[2] == NULL) || (argv[3] == NULL))
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    account = ag_manager_get_account (manager, atoi (argv[2]));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_object_unref (manager);
        return;
    }

    service = ag_manager_get_service (manager, argv[3]);
    if (service == NULL)
    {
        show_error (INVALID_SERVICE_NAME);
        g_object_unref (account);
        g_object_unref (manager);
        return;
    }

    ag_account_select_service (account, service);
    ag_account_set_enabled (account, enable);

    ag_account_store_blocking (account, &error);
    if (error)
    {
        show_error (ERROR_GENERIC);
        g_error_free (error);
    }

    ag_service_unref (service);
    g_object_unref (account);
    g_object_unref (manager);
}

static void
delete_account (gchar **argv)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    gint id = 0;
    GList *list = NULL;
    GList *iter = NULL;
    GError *error = NULL;

    if (argv[2] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    if (strcmp (argv[2], "all") == 0)
        list = ag_manager_list (manager);
    else
        list = g_list_prepend (list, GUINT_TO_POINTER (atoi (argv[2])));

    for (iter = list; iter != NULL; iter = g_list_next (iter))
    {
        id = GPOINTER_TO_UINT (iter->data);
        account = ag_manager_get_account (manager, id);
        if (account == NULL)
        {
            show_error (INVALID_ACC_ID);
            continue;
        }

        ag_account_delete (account);

        ag_account_store_blocking (account, &error);
        if (error)
        {
            show_error (ERROR_GENERIC);
            g_error_free (error);
            error = NULL;
        }

        g_object_unref (account);
        account = NULL;
    }

    g_object_unref (manager);
    ag_manager_list_free (list);
}

static void
list_providers ()
{
    AgManager *manager = NULL;
    GList *list = NULL;
    GList *iter = NULL;
    const gchar *name = NULL;

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    list = ag_manager_list_providers (manager);
    if ((list == NULL) || (g_list_length (list) == 0))
    {
        printf ("No providers are available\n");
        return;
    }

    printf ("\nProvider Name\n-------------\n");
    for (iter = list; iter != NULL; iter = g_list_next (iter))
    {
        name = ag_provider_get_name ((AgProvider *) (iter->data));
        printf ("%s\n", name);
    }

    ag_provider_list_free (list);
    g_object_unref (manager);
}

static void
list_services (gchar **argv)
{
    AgManager *manager = NULL;
    GList *list = NULL;
    GList *iter = NULL;
    const gchar *name = NULL;
    AgAccount *account = NULL;
    const gchar *type = NULL;

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    /* If account Id is not specified, list all services */
    if (argv[2] == NULL)
        list = ag_manager_list_services (manager);
    else
    {
        account = ag_manager_get_account (manager, atoi (argv[2]));
        if (account == NULL)
        {
            show_error (INVALID_ACC_ID);
            g_object_unref (manager);
            return;
        }

        list = ag_account_list_services (account);
    }

    if (list == NULL || g_list_length (list) == 0)
    {
        printf ("No services available\n");

        if (account)
            g_object_unref (account);

        g_object_unref (manager);
        return;
    }

    printf ("%-35s %s\n", "Service type", "Service name");
    printf ("%-35s %s\n", "------------", "------------");

    for (iter = list; iter != NULL; iter = g_list_next (iter))
    {
        name = ag_service_get_name ((AgService *) (iter->data));
        type = ag_service_get_service_type ((AgService *) (iter->data));
        printf ("%-35s %s\n", type, name);
    }

    ag_service_list_free (list);

    if (account)
        g_object_unref (account);

    g_object_unref (manager);
}

static void
list_accounts ()
{
    AgManager *manager = NULL;
    GList *list = NULL;
    GList *iter = NULL;
    const gchar *name = NULL;
    const gchar *provider = NULL;
    AgAccount *account = NULL;

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    list = ag_manager_list (manager);
    if (list == NULL || g_list_length (list) == 0)
    {
        printf ("\nNo accounts configured\n");
        g_object_unref (manager);
        return;
    }

    printf ("%-10s %-30s %s\n", "ID", "Provider", "Name");
    printf ("%-10s %-30s %s\n", "--", "--------", "----");

    for (iter = list; iter != NULL; iter = g_list_next (iter))
    {
        printf ("%-10d ", GPOINTER_TO_UINT (iter->data));

        account = ag_manager_get_account (manager,
                                          GPOINTER_TO_UINT (iter->data));
        if (account == NULL)
        {
            continue;
        }

        provider = ag_account_get_provider_name (account);
        if (provider != NULL)
            printf ("%-30s ", provider);
        else
            printf ("%-30s ", " ");

        name = ag_account_get_display_name (account);
        if (name != NULL)
            printf ("%s\n", name);
        else
            printf ("\n");

        g_object_unref (account);
        account = NULL;
    }

    ag_manager_list_free (list);
    g_object_unref (manager);
}

static void
enable_disable_account (gchar **argv, gboolean enable)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    GError *error = NULL;

    if (argv[2] == NULL)
    {
        show_error (INVALID_INPUT);
        show_help_text (argv[1]);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    account = ag_manager_get_account (manager, atoi (argv[2]));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_object_unref (manager);
        return;
    }

    ag_account_set_enabled (account, enable);
    ag_account_store_blocking (account, &error);
    if (error)
    {
        show_error (ERROR_GENERIC);
        g_error_free (error);
    }

    g_object_unref (account);
    g_object_unref (manager);
}

static void
list_enabled_services (gchar *id)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    GList *list = NULL;
    GList *iter = NULL;
    const gchar *name = NULL;
    const gchar *type = NULL;

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    account = ag_manager_get_account (manager, atoi (id));
    if (account == NULL)
    {
        show_error (INVALID_ACC_ID);
        g_object_unref (manager);
        return;
    }

    list = ag_account_list_enabled_services (account);
    if (list == NULL || g_list_length (list) == 0)
    {
        printf ("No services enabled for account\n");
        g_object_unref (account);
        g_object_unref (manager);
        return;
    }

    printf ("%-35s%s\n", "Type", "Service Name");
    printf ("%-35s%s\n", "----", "------------");
    for (iter = list; iter != NULL; iter = g_list_next (iter))
    {
        name = ag_service_get_name ((AgService *) (iter->data));
        type = ag_service_get_service_type ((AgService *) (iter->data));
        printf ("%-35s", type);
        printf ("%s\n", name);
    }

    ag_service_list_free (list);
    g_object_unref (account);
    g_object_unref (manager);
}

static void
list_enabled (gchar **argv)
{
    AgManager *manager = NULL;
    AgAccount *account = NULL;
    GList *list = NULL;
    GList *iter = NULL;
    const gchar *provider = NULL;
    const gchar *name = NULL;

    if (argv[2] != NULL)
    {
        list_enabled_services (argv[2]);
        return;
    }

    manager = ag_manager_new ();
    if (manager == NULL)
    {
        show_error (ERROR_GENERIC);
        return;
    }

    list = ag_manager_list_enabled (manager);
    if (list == NULL || g_list_length (list) == 0)
    {
        printf ("No accounts enabled\n");
        g_object_unref (manager);
        return;
    }

    printf ("%-10s %-30s %s\n", "ID", "Provider", "Name");
    printf ("%-10s %-30s %s\n", "--", "--------", "----");


    for (iter = list; iter != NULL; iter = g_list_next (iter))
    {
        printf ("%-10d ", (AgAccountId) GPOINTER_TO_UINT (iter->data));

        account = ag_manager_get_account (manager,
                                          GPOINTER_TO_UINT (iter->data));
        if (account == NULL)
        {
            continue;
        }

        provider = ag_account_get_provider_name (account);
        if (provider != NULL)
            printf ("%-30s ", provider);
        else
            printf ("%-30s ", " ");

        name = ag_account_get_display_name (account);
        if (name != NULL)
            printf ("%s\n", name);
        else
            printf ("\n");

        g_object_unref (account);
        account = NULL;

    }
    ag_manager_list_free (list);
    g_object_unref (manager);
}

static int
parse (int argc, char **argv)
{
    if (strcmp (argv[1], "create-account") == 0)
    {
        create_account (argv);
        return 0;
    }
    else if (strcmp (argv[1], "delete-account") == 0)
    {
        delete_account (argv);
        return 0;
    }
    else if (strcmp (argv[1], "list-providers") == 0)
    {
        list_providers ();
        return 0;
    }
    else if (strcmp (argv[1], "list-services") == 0)
    {
        list_services (argv);
        return 0;
    }
    else if (strcmp (argv[1], "list-accounts") == 0)
    {
        list_accounts ();
        return 0;
    }
    else if (strcmp (argv[1], "enable-account") == 0)
    {
        enable_disable_account (argv, TRUE);
        return 0;
    }
    else if (strcmp (argv[1], "disable-account") == 0)
    {
        enable_disable_account (argv, FALSE);
        return 0;
    }
    else if (strcmp (argv[1], "list-enabled") == 0)
    {
        list_enabled (argv);
        return 0;
    }
    else if (strcmp (argv[1], "enable-service") == 0)
    {
        enable_disable_service (argv, TRUE);
        return 0;
    }
    else if (strcmp (argv[1], "disable-service") == 0)
    {
        enable_disable_service (argv, FALSE);
        return 0;
    }
    else if (strcmp (argv[1], "update-account") == 0)
    {
        update_account (argv);
        return 0;
    }
    else if (strcmp (argv[1], "update-service") == 0)
    {
        update_service (argv);
        return 0;
    }
    else if (strcmp (argv[1], "get-service") == 0)
    {
        get_service (argv);
        return 0;
    }
    else if (strcmp (argv[1], "get-account") == 0)
    {
        get_account (argv);
        return 0;
    }
    else if (strcmp (argv[1], "list-settings") == 0)
    {
        list_settings (argv);
        return 0;
    }

    return -1;
}

gint
main (int argc, char **argv)
{
    gl_app_name = g_path_get_basename (argv[0]);

    if (argc < 2)
    {
        show_help ();
        return 0;
    }

    if (parse (argc, argv))
        show_help ();
}
07070100000060000081A4000003E800000064000000015BDC2B8B000000C8000000000000000000000000000000000000002800000000libaccounts-glib-1.24/tools/meson.buildexecutable('ag-backup',
    'backup.c',
    dependencies: accounts_glib_library_deps,
    install: true
)

executable('ag-tool',
    'main.c',
    dependencies: accounts_glib_dep,
    install: true
)
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1240 blocks